diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..630b9d3c --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +# ignore visual studio code directory +.vscode/ +# ignore data directory incase cometa was created using cometa.sh script +data/ +# ignore _template files +*_template diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 6b858bb9..73f3e68f 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -3,6 +3,7 @@ # for every installation / server / client # ######################################## # # Changelog: +# 2022-01-26 RRO Added GITCLIENTID, GITCLIENTSECRET # 2022-01-06 RRO Added gunicorn reload for behave and django # ######################################## # @@ -11,14 +12,14 @@ include: - 'gitlab-ci-yamls/*.yml' -# set GIT_STRATEGY to none to avoid # pulling content accidentally and # losing old changes variables: # CI_DEBUG_TRACE: "true" # show verbose information -# GIT_STRATEGY: none +# GIT_STRATEGY: clone COMETA_EMAIL_ENABLED: 'True' COMETA_REPLACE_FAVICON_IN: "front/src/index.html front/src/manifest.json front/src/welcome.html" + COMETA_TOTAL_BROWSER_VERSION: 10 # number of latest browser version that will be pulled. # set deploy order in which jobs # will be executed @@ -29,27 +30,46 @@ stages: # redundancy. .deployment_script: &deployment_script stage: deploy - script: + script: ######################################################### - # OLD REPOSITORY CODE + # REPLACE ######################################################### - # cd to DEPLOY_BACKEND_FOLDER before doing anything - # - cd $DEPLOY_BACKEND_FOLDER - # stash old changes before pulling new changes - # - sudo git stash - # make sure we are in correct branch - # - sudo git checkout $CI_COMMIT_BRANCH - # pull new changes - # - sudo git pull + # update docker-compose file depending on server variables + - sed -i 's//'$DOCKER_HTTP_PORT'/g;s//'$DOCKER_OPENIDC_CONFIG_EXT'/g' docker-compose.yml + # Replace secrets in openidc + - sed -i 's/@@COMETA_CRYPTO_PASSPHRASE@@/'$COMETA_CRYPTO_PASSPHRASE'/g' front/apache-conf/openidc.conf_${DOCKER_OPENIDC_CONFIG_EXT} + # Replace google client information + - sed -i 's/@@COMETA_GOOGLE_CLIENTID@@/'$COMETA_GOOGLE_CLIENTID'/g' front/apache-conf/metadata/accounts.google.com.client + - sed -i 's/@@COMETA_GOOGLE_SECRETKEY@@/'$COMETA_GOOGLE_SECRETKEY'/g' front/apache-conf/metadata/accounts.google.com.client + # Replace git client secret for access to git.amvara.de + - sed -i 's/@@GITCLIENTID@@/'$GITCLIENTID'/g' front/apache-conf/metadata/git.amvara.de.client + - sed -i 's/@@GITCLIENTSECRET@@/'$GITCLIENTSECRET'/g' front/apache-conf/metadata/git.amvara.de.client + # Replace Stripe client keys + - sed -i 's/@@COMETA_STRIPE_PUBLIC_LIVE_KEY@@/'$COMETA_STRIPE_PUBLIC_LIVE_KEY/'g' front/src/app/deploy-tokens.ts + - sed -i 's/@@COMETA_STRIPE_PUBLIC_TEST_KEY@@/'$COMETA_STRIPE_PUBLIC_TEST_KEY'/g' front/src/app/deploy-tokens.ts + # Replace co.meta favicon depending on the branch + - for FILE in ${COMETA_REPLACE_FAVICON_IN}; do sed -i 's/@@BRANCH@@/'$CI_COMMIT_BRANCH'/g' $FILE; done + + ######################################################### - + # COPY CODE TO DEPLOY_BACKEND_FOLDER + ######################################################### + # make sure the deployment folder exists + - mkdir -p $DEPLOY_BACKEND_FOLDER + # copy all the data from current directory to the deployment directory + - rsync -av --exclude={".git/","*.so"} ./ $DEPLOY_BACKEND_FOLDER/. + # change directory to deployment folder + - cd $DEPLOY_BACKEND_FOLDER + ######################################################### # BACKEND ######################################################### + # spin up dockers if not started yet + # - docker-compose up -d db django behave ws selenoid novnc # save all environment variables that start with COMETA_ to secret_variables.py # these variables come from CI/CD variables from gitlab and variables set inside # gitlab-ci yaml files. - - env | grep "^COMETA_" | sed -E "s/([A-Z_])=(.*)/\1='\2'/" > secret_variables.py + - env | grep "^COMETA_" | sed -E "s/([A-Z_])=(.*)/\1='\2'/" > backend/secret_variables.py # Build Selenoid browsers - echo -e "\e[0Ksection_start:`date +%s`:deploy_selenoid\r\e[0KRunning selenoid deployment script" - bash backend/selenoid/deploy_selenoid.sh @@ -68,33 +88,26 @@ stages: - echo -e "\e[0Ksection_start:`date +%s`:parse_browsers\r\e[0KParsing selenoid browsers to update database" - docker exec cometa_django bash -c "curl http://localhost:8000/parseBrowsers/ --silent --retry 5 --retry-delay 5" - echo -e "\e[0Ksection_end:`date +%s`:parse_browsers\r\e[0K" - # Reload project files in gunicorn - - echo -e "\e[0Ksection_start:`date +%s`:reloading_gunicorn\r\e[0KPreparing to reload project in gunicorn" - - echo -e "Now reloading gunicorn for django" - - docker exec cometa_django fuser -k -HUP 8000/tcp - - echo -e "Now reloading gunicorn for behave" - - docker exec cometa_behave fuser -k -HUP 8001/tcp - - echo -e "\e[0Ksection_end:`date +%s`:reloading_gunicorn\r\e[0K" ######################################################### # FRONTEND ######################################################### - # Replace secrets in openidc - - sed -i 's/@@COMETA_CRYPTO_PASSPHRASE@@/'$COMETA_CRYPTO_PASSPHRASE'/g' front/apache-conf/openidc.conf_${DOCKER_OPENIDC_CONFIG_EXT} - # Replace google client information - - sed -i 's/@@COMETA_GOOGLE_CLIENTID@@/'$COMETA_GOOGLE_CLIENTID'/g' front/apache-conf/metadata/accounts.google.com.client - - sed -i 's/@@COMETA_GOOGLE_SECRETKEY@@/'$COMETA_GOOGLE_SECRETKEY'/g' front/apache-conf/metadata/accounts.google.com.client - # Replace Stripe client keys - - sed -i 's/@@COMETA_STRIPE_PUBLIC_LIVE_KEY@@/'$COMETA_STRIPE_PUBLIC_LIVE_KEY/'g' front/src/app/deploy-tokens.ts - - sed -i 's/@@COMETA_STRIPE_PUBLIC_TEST_KEY@@/'$COMETA_STRIPE_PUBLIC_TEST_KEY'/g' front/src/app/deploy-tokens.ts - # update docker-compose file depending on server variables - - sed -i 's//'$DOCKER_HTTP_PORT'/g;s//'$DOCKER_OPENIDC_CONFIG_EXT'/g' docker-compose.yml - # Replace co.meta favicon depending on the branch - - for FILE in ${COMETA_REPLACE_FAVICON_IN}; do sed -i 's/@@BRANCH@@/'$CI_COMMIT_BRANCH'/g' $FILE; done # start container or if already started then compile else start from zero - echo "Checking if cometa_front is already running else start it..." - echo "If there are changes to docker-compose file it will recreate the container..." - - docker-compose up -d apache 2>&1 | grep "up-to-date" && docker exec cometa_front bash -c "cd /code/; ./start.sh $script_options" || ( timeout 360 docker-compose logs -f apache || true ) + - docker-compose up -d apache 2>&1 | grep -E "up-to-date|cometa_front.*?Running" && docker exec cometa_front bash -c "cd /code/; ./start.sh $script_options" || ( timeout 360 docker-compose logs -f --tail=0 apache || true ) + + ######################################################### + # RELOAD GUNICORNS + ######################################################### + # Reload project files in gunicorn + - echo -e "\e[0Ksection_start:`date +%s`:reloading_gunicorn\r\e[0KPreparing to reload project in gunicorn" + - echo -e "Now reloading gunicorn for django" + - docker exec cometa_django fuser -k -HUP 8000/tcp + - echo -e "Now reloading gunicorn for behave" + - docker exec cometa_behave fuser -k -HUP 8001/tcp + - docker exec cometa_behave bash -c "supervisorctl restart django-rq:" + - echo -e "\e[0Ksection_end:`date +%s`:reloading_gunicorn\r\e[0K" # create a deploy job for amvara2 for branch stage deploy:amvara2.stage: @@ -102,18 +115,23 @@ deploy:amvara2.stage: extends: .amvara2.stage # create a deploy job for amvara3 for branch prod -# deploy:amvara3.prod: -# <<: *deployment_script -# extends: .amvara3.prod +deploy:amvara3.prod: + <<: *deployment_script + extends: .amvara3.prod # create a deploy job for client01 for branch prod -# deploy:client01.int: -# <<: *deployment_script -# extends: .client01.int +deploy:client01.int: + <<: *deployment_script + extends: .client01.int # create a deploy job for client01 for branch prod -# deploy:client01.prod: +deploy:client01.prod: + <<: *deployment_script + extends: .client01.prod + +# create a deploy job for client02 for branch prod +# deploy:client02.prod: # <<: *deployment_script -# extends: .client01.prod +# extends: .client02.prod # DONE \ No newline at end of file diff --git a/README.md b/README.md index 00c56af4..5d1b41e9 100644 --- a/README.md +++ b/README.md @@ -4,12 +4,10 @@

Cometa

-COMETA is a 100% open source software suite for visual and functional regression testing, to help QA Managers, DevOps and Business Owners get rid of repeating manual tests. Learn more +Open source is the future. Co.Meta is an advanced & evolving meta-test product that has been made with ❤️ for DevOps and QA Engineers. Learn more

-Telegram -· -Matrix +Support

@@ -24,11 +22,7 @@ COMETA is a 100% open source software suite for visual and functional regression - [Django](https://www.djangoproject.com/) - [Behave](https://behave.readthedocs.io/en/stable/) - [Selenoid](https://aerokube.com/selenoid/) - -## Requirements - -- Docker -- Docker Compose +- [PostgreSQL](https://www.postgresql.org/) ## Getting started @@ -38,8 +32,9 @@ Here is what you need to be able to run Cometa. - Docker - Docker Compose +- Use Linux as operating system. Have a look at pre-build [Virtual Boxes](https://osboxes.org/) -In any case that you are stuck for more than 5 minutes - please us know. And please give us the oportunity to help you. We want to learn how you are using cometa and what problems you encounter. Contact us via Telegram or WhatsApp as seen on https://cometa.rocks/ . +In any case that you are stuck for more than 5 minutes - please us know. And please give us the oportunity to help you. We want to learn how you are using cometa and what problems you encounter. Contact us. We are happy to help. #### Manual @@ -49,67 +44,109 @@ In any case that you are stuck for more than 5 minutes - please us know. And ple ``` 2. Setup at least 1 authentication provider: + To setup Google: * Go to [Google Developer Console](https://console.cloud.google.com/) * Create an OAuth application * Add your domain to the allowed hosts * Retrieve the `client_id` and `secret_id` and paste them in `./front/apache-conf/metadata/accounts.google.com.client` + * Set `redirect_uri` to `https:///callback` + + To setup Gitlab: + * Goto [git.amvara.de](https://git.amvara.de/) + * Create a new account + * Settings > Application > Add new application + * Add your domain to the allowed hosts + * Retrieve the `client_id` and `secret_id` and paste them in `./front/apache-conf/metadata/accounts/git.amvara.de.client` + + * Set `redirect_uri` to `https:///callback` + + In both cases, the default URL when installing on you Desktop or Laptop, is `localhost`. + + +Very nice: Instead of following the manual setup instructions below, you may execute `./cometa.sh` to bring up a localhost version on your machine. + + +3. Create a crontab file for scheduling your automated tests + + ```sh + mkdir -p backend/behave/schedules && touch backend/behave/schedules/crontab + ``` + +4. Get all Docker containers up: + + In `docker-compose.yml` change to 'local' and to '80' or according to your needs. + + * Change the `` port to `80` or any other port you'd like. Keep in mind that this port should match with what is configured inside the `openidc.conf` + * Change the `` to `local` or your custom configuration file in `front/apache-conf/openidc.conf_` + + -3. Get all Docker containers up: ```sh - docker-compose up -d + docker-compose up -d && docker-compose logs -f --tail=10 ``` Cometa starts on port 443. If that port is used on your machine, change it `docker-compose.yml` to e.g. "8443:443" Cometa also starts on port 80. If that is not available you could change that to 8081 ind `docker-compose.yml` - In case you want to view some logs `docker-compose logs -f --tail=10` + View some logs `docker-compose logs -f --tail=10` of the installation process, to get a understanding, when cometa is ready. -4. Load required database objects - ```bash - docker exec -it cometa_django bash - python manage.py loaddata defaults/*.json + Give cometa some minutes to install python, setup django, download npm and docker files, compile the front end. + Depending on your computer this can take a couple of minutes. + + You want a development server? + + ```sh + docker-compose -f docker-compose-dev.yml up -d ``` -5. (optional) Create superuser for the Backend Admin: +5. **(Optional)** Create superuser for the Backend Admin -Default superuser is created on runtime as `admin:admin` + Default superuser is created on runtime as `admin:admin`. - ```bash + ``` + bash docker exec -it cometa_django bash root@cometa_django:/opt/code# python manage.py createsuperuser - ``` + ``` -6. Run the selenoid setup +6. **(Optional)** Install latest browser versions -`./selenoid/deploy_selenoid.sh`. + `./backend/selenoid/deploy_selenoid.sh -n 3`. -This will configure and pull the Docker images for Selenoid. + This will configure and pull the three newest Docker images with virtual browsers for Selenoid. -Selenoid image are the browser that you will be able use and select in cometa. + Selenoid image are the browser that you will be able use and select in cometa. -Of course there are options to include browserstack, headspin or sourcelabs browsers. But that is a bit something you would not want to configure on your first setup. + Of course there are options to include browserstack, headspin or sourcelabs browsers. But that is a bit you would not want to configure on your first setup. -This step will take some time as all the default browser images are being pulled. + This step will take some time as all the default browser images are being pulled. + + Once cometa is up and running, you can parse the new browser images avaible into Cometa by calling `https://localhost/backend/parseBrowsers/` 7. See cometa rocks in your browser -Test server access `curl -k https://:/` + Test server access `curl -k https://:/` + + Example `curl -k https://localhost:443/` + + You should see something like this: +

The document has moved here.

-Example `curl -k https://localhost:8443/` +8. Import the over 70 pre-defined actions -You should see something like this: -

The document has moved here.

+ On first start you have to manually parse the actions.py file. This enables cometa to use any steps defined in the UI. The user can then choose from the steps in the UI. + `https://localhost/backend/parseActions/` ... as a result cometa will show all actions that have been parsed and are now available for selection in cometa-front. 8. Run your first test -Click on the "+" on the very top. Select Department, Environment and Feature Name + Click on the "+" on the very top. Select Department, Environment and Feature Name -And import this JSON to search for "cometa Rocks" on google + And import this JSON to search for "cometa Rocks" on google -```[{"enabled":true,"screenshot":true,"step_keyword":"Given","compare":false,"step_content":"Goto URL \"https://www.google.de/\"","step_type":"normal","continue_on_failure":false,"timeout":60},{"enabled":true,"screenshot":false,"step_keyword":"Given","compare":false,"step_content":"Maximize the browser","step_type":"normal","continue_on_failure":false,"timeout":60},{"enabled":true,"screenshot":true,"step_keyword":"Given","compare":false,"step_content":"wait until I can see \"google\" on page","step_type":"normal","continue_on_failure":false,"timeout":60},{"enabled":true,"screenshot":true,"step_keyword":"Given","compare":false,"step_content":"I move mouse to \"//button[2]\" and click","step_type":"normal","continue_on_failure":true,"timeout":5},{"enabled":true,"screenshot":true,"step_keyword":"Given","compare":false,"step_content":"I move mouse to \"//input\" and click","step_type":"normal","continue_on_failure":false,"timeout":60},{"enabled":true,"screenshot":true,"step_keyword":"Given","compare":false,"step_content":"Send keys \"cometa rocks\"","step_type":"normal","continue_on_failure":false,"timeout":60},{"enabled":true,"screenshot":true,"step_keyword":"Given","compare":false,"step_content":"Press Enter","step_type":"normal","continue_on_failure":false,"timeout":60},{"enabled":true,"screenshot":true,"step_keyword":"Given","compare":false,"step_content":"wait until I can see \"cometa rocks\" on page","step_type":"normal","continue_on_failure":false,"timeout":60},{"enabled":true,"screenshot":true,"step_keyword":"Given","compare":true,"step_content":"I sleep \"1\" seconds","step_type":"normal","continue_on_failure":false,"timeout":60}]``` + ```[{"enabled":true,"screenshot":true,"step_keyword":"Given","compare":false,"step_content":"Goto URL \"https://www.google.de/\"","step_type":"normal","continue_on_failure":false,"timeout":60},{"enabled":true,"screenshot":false,"step_keyword":"Given","compare":false,"step_content":"Maximize the browser","step_type":"normal","continue_on_failure":false,"timeout":60},{"enabled":true,"screenshot":true,"step_keyword":"Given","compare":false,"step_content":"wait until I can see \"google\" on page","step_type":"normal","continue_on_failure":false,"timeout":60},{"enabled":true,"screenshot":true,"step_keyword":"Given","compare":false,"step_content":"I move mouse to \"//button[2]\" and click","step_type":"normal","continue_on_failure":true,"timeout":5},{"enabled":true,"screenshot":true,"step_keyword":"Given","compare":false,"step_content":"I move mouse to \"//input\" and click","step_type":"normal","continue_on_failure":false,"timeout":60},{"enabled":true,"screenshot":true,"step_keyword":"Given","compare":false,"step_content":"Send keys \"cometa rocks\"","step_type":"normal","continue_on_failure":false,"timeout":60},{"enabled":true,"screenshot":true,"step_keyword":"Given","compare":false,"step_content":"Press Enter","step_type":"normal","continue_on_failure":false,"timeout":60},{"enabled":true,"screenshot":true,"step_keyword":"Given","compare":false,"step_content":"wait until I can see \"cometa rocks\" on page","step_type":"normal","continue_on_failure":false,"timeout":60},{"enabled":true,"screenshot":true,"step_keyword":"Given","compare":true,"step_content":"I sleep \"1\" seconds","step_type":"normal","continue_on_failure":false,"timeout":60}]``` #### Notes @@ -155,10 +192,10 @@ That's all, easy peasy. ## License -Copyright 2021 COMETA ROCKS S.L. +Copyright 2022 COMETA ROCKS S.L. Portions of this software are licensed as follows: -* All content that resides under "ee/" directory of this repository (Enterprise Edition) is licensed under the license defined in "ee/LICENSE". +* All content that resides under "ee/" directory of this repository (Enterprise Edition) is licensed under the license defined in "ee/LICENSE". (Work in progress) * All third party components incorporated into the cometa.rocks Software are licensed under the original license provided by the owner of the applicable component. * Content outside of the above mentioned directories or restrictions above is available under the "AGPLv3" license as defined in `LICENSE` file. diff --git a/backend/.gitignore b/backend/.gitignore index 2e9d5f70..279822b6 100755 --- a/backend/.gitignore +++ b/backend/.gitignore @@ -19,12 +19,13 @@ backups/ migrations/ downloads/ videos/ -schedules/ +pdf/ crontab # department folders department_data/ +uploads/ # logs logs/ @@ -35,4 +36,7 @@ output.txt accounts.google.com.client .vars.sh secret_variables.py -.initiated \ No newline at end of file +.initiated + +# browsers.json from selenoid +browsers.json \ No newline at end of file diff --git a/backend/README.md b/backend/README.md index 2867727b..0fcdc2e6 100755 --- a/backend/README.md +++ b/backend/README.md @@ -9,7 +9,8 @@ ### 1. Build -```bash +``` +bash docker-compose up -d ``` @@ -160,8 +161,4 @@ postgres=# \dt Copyright 2021 COMETA ROCKS S.L. -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file +see [License](/LICENSE) for details \ No newline at end of file diff --git a/backend/behave/behave_django/behave_django/settings.py b/backend/behave/behave_django/behave_django/settings.py index fb7b0bc4..72c9e2ae 100755 --- a/backend/behave/behave_django/behave_django/settings.py +++ b/backend/behave/behave_django/behave_django/settings.py @@ -42,7 +42,7 @@ # See https://docs.djangoproject.com/en/2.0/howto/deployment/checklist/ # SECURITY WARNING: keep the secret key used in production secret! -SECRET_KEY = '@!3#b5y$8o#vdj@-*t+0672eskvxf@5eo&eexasiae#djyat@t' +SECRET_KEY = getattr(secret_variables, 'COMETA_BEHAVE_SECRETKEY', '') # SECURITY WARNING: don't run with debug turned on in production! DEBUG = True @@ -59,6 +59,7 @@ 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', + 'django_rq' ] MIDDLEWARE = [ @@ -137,3 +138,13 @@ STATIC_URL = '/static/' RUNTEST_COMMAND_PATH = '/opt/code/run_remote_from_django.sh' + +# Django RQ Configuration +RQ_QUEUES = { + "default": { + "HOST": "redis", + "PORT": 6379, + "DB": 0, + "DEFAULT_TIMEOUT": 7500 + } +} \ No newline at end of file diff --git a/backend/behave/behave_django/behave_django/urls.py b/backend/behave/behave_django/behave_django/urls.py index 4464b5ba..b562e9b7 100755 --- a/backend/behave/behave_django/behave_django/urls.py +++ b/backend/behave/behave_django/behave_django/urls.py @@ -16,11 +16,15 @@ from django.contrib import admin from django.urls import path, re_path from schedules import views +from django.conf.urls import include urlpatterns = [ path('admin/', admin.site.urls), path('run_test/', views.run_test), re_path('kill_task/(?P[0-9]+)/', views.kill_task), path('set_test_schedule/', views.set_test_schedule), - path('remove_test_schedule/', views.remove_test_schedule) + path('remove_test_schedule/', views.remove_test_schedule), + + # Django RQ URLS + path("django-rq/", include("django_rq.urls")) ] diff --git a/backend/behave/behave_django/db.sqlite3 b/backend/behave/behave_django/db.sqlite3 index e953727e..bb1efcdf 100755 Binary files a/backend/behave/behave_django/db.sqlite3 and b/backend/behave/behave_django/db.sqlite3 differ diff --git a/backend/behave/behave_django/schedules/__init__.py b/backend/behave/behave_django/schedules/__init__.py new file mode 100755 index 00000000..e69de29b diff --git a/backend/behave/behave_django/schedules/admin.py b/backend/behave/behave_django/schedules/admin.py new file mode 100755 index 00000000..8c38f3f3 --- /dev/null +++ b/backend/behave/behave_django/schedules/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/backend/behave/behave_django/schedules/apps.py b/backend/behave/behave_django/schedules/apps.py new file mode 100755 index 00000000..6118efb0 --- /dev/null +++ b/backend/behave/behave_django/schedules/apps.py @@ -0,0 +1,5 @@ +from django.apps import AppConfig + + +class SchedulesConfig(AppConfig): + name = 'schedules' diff --git a/backend/behave/behave_django/schedules/forms.py b/backend/behave/behave_django/schedules/forms.py new file mode 100755 index 00000000..531f9b05 --- /dev/null +++ b/backend/behave/behave_django/schedules/forms.py @@ -0,0 +1,15 @@ +from django import forms + +class RunTestValidationForm(forms.Form): + department = forms.CharField(max_length=100) + app = forms.CharField(max_length=100) + feature = forms.CharField(max_length=100) + environment = forms.CharField(max_length=100) + feature_id = forms.IntegerField() + feature_result_id = forms.IntegerField() + +class SetScheduleValidationForm(forms.Form): + department = forms.CharField(max_length=100) + app = forms.CharField(max_length=100) + feature = forms.CharField(max_length=100) + schedule = forms.CharField(max_length=20) \ No newline at end of file diff --git a/backend/behave/behave_django/schedules/models.py b/backend/behave/behave_django/schedules/models.py new file mode 100755 index 00000000..71a83623 --- /dev/null +++ b/backend/behave/behave_django/schedules/models.py @@ -0,0 +1,3 @@ +from django.db import models + +# Create your models here. diff --git a/backend/behave/behave_django/schedules/tasks/runBrowser.py b/backend/behave/behave_django/schedules/tasks/runBrowser.py new file mode 100644 index 00000000..cda48641 --- /dev/null +++ b/backend/behave/behave_django/schedules/tasks/runBrowser.py @@ -0,0 +1,76 @@ +import logging, subprocess, sys, requests, datetime +from django.conf import settings +from django_rq import job +from rq.timeouts import JobTimeoutException +from rq.command import send_stop_job_command +from rq.job import Job +from rq import get_current_job +import django_rq + +# just to import secrets +sys.path.append("/code") +from secret_variables import * +from src.backend.common import * + +# setup logging +logger = logging.getLogger(__name__) +logger.setLevel(BEHAVE_DEBUG_LEVEL) +# create a formatter for the logger +formatter = logging.Formatter(LOGGER_FORMAT, LOGGER_DATE_FORMAT) +# create a stream logger +streamLogger = logging.StreamHandler() +# set the format of streamLogger to formatter +streamLogger.setFormatter(formatter) +# add the stream handle to logger +logger.addHandler(streamLogger) + +@job +def run_browser(json_path, env, **kwargs): + # Start running feature with current browser + with subprocess.Popen(["bash", settings.RUNTEST_COMMAND_PATH, json_path], env=env, stdout=subprocess.PIPE) as process: + try: + logger.debug(f"Process id: {process.pid}") + # wait for the process to finish + process.wait() + logger.debug(f"Process Return Code: {process.returncode}") + if process.returncode > 0: + out, _ = process.communicate() + out = str(out.decode('utf-8')) + logger.error(f"Error ocurred during the feature execution ... please check the output:\n{out}") + if 'Parser failure' in out: + raise Exception("Parsing error in the feature, please recheck the steps.") + except JobTimeoutException as err: + # job was timed out, kill the process + logger.error("Job timed out.") + logger.exception(err) + with subprocess.Popen(f"ps -o pid --ppid {process.pid} --noheaders | xargs kill -15", shell=True) as p2: + p2.wait() + process.wait() + job: Job = get_current_job() + send_stop_job_command(django_rq.get_connection(), job.id) + raise + # TODO: + # Check if process has been stopped + # Send mail? + except Exception as e: + logger.error("run_browser threw an exception:") + logger.exception(e) + requests.post('http://cometa_socket:3001/feature/%s/error' % kwargs.get('feature_id', None), data={ + "browser_info": kwargs.get('browser', None), + "feature_result_id": kwargs.get('feature_result_id', None), + "run_id": kwargs.get('feature_run', None), + "datetime": datetime.datetime.utcnow().strftime('%Y-%m-%dT%H:%M:%SZ'), + "error": str(e), + "user_id": kwargs.get('user_data', {}).get('user_id', None) + }) + +@job +def run_finished(feature_run, feature_id, user_data): + logger.debug('All browsers of current run completed!') + # Send completed websocket to Front + requests.post('http://cometa_socket:3001/feature/%d/runCompleted' % int(feature_id), json={ + 'type': '[WebSockets] Completed Feature Run', + 'run_id': int(feature_run), + 'datetime': datetime.datetime.utcnow().strftime('%Y-%m-%dT%H:%M:%SZ'), + 'user_id': user_data['user_id'] + }) \ No newline at end of file diff --git a/backend/behave/behave_django/schedules/tests.py b/backend/behave/behave_django/schedules/tests.py new file mode 100755 index 00000000..7ce503c2 --- /dev/null +++ b/backend/behave/behave_django/schedules/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/backend/behave/behave_django/schedules/views.py b/backend/behave/behave_django/schedules/views.py new file mode 100755 index 00000000..f25fc138 --- /dev/null +++ b/backend/behave/behave_django/schedules/views.py @@ -0,0 +1,263 @@ +from django.shortcuts import render + +# Create your views here. +from django.views.decorators.http import require_http_methods +from django.views.decorators.csrf import csrf_exempt +from django.http import JsonResponse +import subprocess, datetime, requests +import os.path +from schedules.forms import RunTestValidationForm +from schedules.forms import SetScheduleValidationForm +from crontab import CronTab, CronSlices +import os, logging, sys +from django.conf import settings +import json, time +import secrets +import urllib.parse +from django.views.decorators.clickjacking import xframe_options_exempt +import random +import django_rq +from pprint import pprint +from sentry_sdk import capture_exception +from schedules.tasks.runBrowser import run_browser, run_finished + +from requests.adapters import HTTPAdapter +from requests.packages.urllib3.util.retry import Retry + +# just to import secrets +sys.path.append("/code") +from secret_variables import * +from src.backend.common import * + +# setup logging +logger = logging.getLogger(__name__) +logger.setLevel(BEHAVE_DEBUG_LEVEL) +# create a formatter for the logger +formatter = logging.Formatter(LOGGER_FORMAT, LOGGER_DATE_FORMAT) +# create a stream logger +streamLogger = logging.StreamHandler() +# set the format of streamLogger to formatter +streamLogger.setFormatter(formatter) +# add the stream handle to logger +logger.addHandler(streamLogger) + +# Configure Retry which can be later used for requests +retry_strategy = Retry( + total=6, # Retry maximum 6 times, combined with backoff_factor will wait a total of 60 seconds + backoff_factor=1, # 0s, 2s, 4s, 8s, 16s, ... + status_forcelist=[429, 500, 502, 503, 504], # Retry request on these status codes + allowed_methods=['DELETE', 'GET', 'HEAD', 'OPTIONS', 'PUT', 'TRACE', 'POST'] # Retry on these methods +) +# The following lines creates a new http instance which will use the above retry strategy +adapter = HTTPAdapter(max_retries=retry_strategy) +requests_retry = requests.Session() +requests_retry.mount("https://", adapter) +requests_retry.mount("http://", adapter) + +@require_http_methods(["POST"]) +@csrf_exempt +@xframe_options_exempt +def run_test(request): + # check if RUNTEST_COMMAND_PATH exists else return error + if os.path.exists(settings.RUNTEST_COMMAND_PATH)==False: + return JsonResponse({'Error':'Command file not exists , path ->' + settings.RUNTEST_COMMAND_PATH}, status = 500) + + logger.info('Running Test') + + # get data sent from django + json_path = request.POST["json_path"] # file path that contains the necessary information about the feature + logger.debug("Feature JSON path: {}".format(json_path)) + feature_run = request.POST['feature_run'] # feature_run id that will contain the feature_results + logger.debug('Feature run id: {}'.format(feature_run)) + X_SERVER = request.POST['HTTP_X_SERVER'] # where the request is coming from + logger.debug('X_SERVER: {}'.format(X_SERVER)) + VARIABLES = request.POST['variables'] # environments variables for feature department just in case feature is using variables in steps + logger.debug('Environment Variables: {}'.format(VARIABLES)) + PROXY_USER = request.POST['HTTP_PROXY_USER'] # user who executed the testcase + logger.debug('Executed By: {}'.format(PROXY_USER)) + executions = json.loads(request.POST['browsers']) # browsers list of the feature + logger.debug('Executions: {}'.format(executions)) + feature_id = request.POST['feature_id'] # id of the feature that is being executed + logger.debug('Feature id: {}'.format(feature_id)) + department = request.POST['department'] # department where the feature belongs, set in request so we can get the department settings + logger.debug('Department the feature belongs to: {}'.format(department)) + PARAMETERS = request.POST['parameters'] # job parameters if the job was scheculed using schedule step + logger.debug('Job Parameters: {}'.format(PARAMETERS)) + + # assign environment variables to share data between files and threads + environment_variables = { + 'feature_run': str(feature_run), + 'X_SERVER': X_SERVER, + 'PROXY_USER': PROXY_USER, + 'VARIABLES': VARIABLES, + 'PARAMETERS': PARAMETERS, + 'department': department, + 'feature_id': feature_id + } + + # Loads user data + user_data = json.loads(PROXY_USER) + + # save all the jobs + jobs = [] + + # create a thread for each browser + for execution in executions: + browser = execution['browser'] + feature_result_id = execution['feature_result_id'] + run_hash = execution['run_hash'] + + logger.debug("Execution testcase in browser: {}".format(browser)) + # dump the json as string + browser = json.dumps(browser) + # send websocket about the feature has been queued + request = requests.get('http://cometa_socket:3001/feature/%s/queued' % feature_id, data={ + "user_id": user_data['user_id'], + "browser_info": browser, + "feature_result_id": feature_result_id, + "run_id": feature_run, + "datetime": datetime.datetime.utcnow().strftime('%Y-%m-%dT%H:%M:%SZ') + }) + # add missing variables to environment_variables dict + environment_variables['BROWSER_INFO'] = browser + environment_variables['feature_result_id'] = str(feature_result_id) + environment_variables['RUN_HASH'] = run_hash + # Add the current browser to the thread pool + job = django_rq.enqueue( + run_browser, + json_path, + environment_variables, + browser=browser, + feature_id=feature_id, + feature_result_id=feature_result_id, + user_data=user_data, + feature_run=feature_run, + job_timeout=7500) + jobs.append(job) + + notify = run_finished.delay(feature_run, feature_id, user_data, depends_on=jobs) + + # Wait for the thread pool to finish + return JsonResponse({}, status = 200) + +@require_http_methods(["GET"]) +@csrf_exempt +@xframe_options_exempt +def kill_task(request, pid): + subprocess.call("kill -15 %d" % int(pid), shell=True) + return JsonResponse({"success": True, "killed": pid}) + +@require_http_methods(["POST"]) +@csrf_exempt +@xframe_options_exempt +def set_test_schedule(request): + + # crontab object + # /etc/cron.d/crontab must be a file, if logs throws error '/etc/cron.d/crontab is a directory' + # please create the file behave/schedules/crontab and recreate behave docker + my_cron = CronTab(user=True, tabfile='/etc/cron.d/crontab') + # check if request contains jobId if so that mean there is more data than just feature_id and schedule + if not request.POST.__contains__("jobId"): + # crontab schedule string + schedule = request.POST['schedule'] + # crontab feature id that will be executed + feature_id = request.POST["feature_id"] + try: + feature_id = int(feature_id) + except: + logger.error('Unable to convert feature_id to int') + # crontab user id to execute feature + user_id = request.POST['user_id'] + try: + user_id = int(user_id) + except: + logger.error('Unable to convert user_id to int') + curl_post_data = json.dumps({ + 'feature_id': feature_id + }) + curl_headers = { + 'Content-Type': 'application/json', + 'COMETA-ORIGIN': 'CRONTAB', + 'COMETA-USER': str(user_id) + } + curl_headers = ' '.join(['-H "%s: %s"' % (key, value) for key, value in curl_headers.items()]) + # Now only the feature_id is required to run feature from behave -> django -> behave + command = 'root curl --data \'%s\' %s -X POST http://django:8000/exectest/' % (curl_post_data, curl_headers) + logger.debug("command find -> " + command) + found = False + for job in my_cron: + logger.debug("crontab command -> " + job.command) + if curl_post_data in job.command: + logger.debug("command found") + if schedule: + # Check schedule is valid + if not CronSlices.is_valid(schedule): + return JsonResponse({ 'error': 'Invalid schedule format.' }, status = 400) + job.setall(schedule) + job.set_command(command) + else: + my_cron.remove(job) + my_cron.write() + return JsonResponse({'message':'Schedule is removed!'}) + found = True + + if not schedule: + return JsonResponse({ 'message': 'Not found but nothing to do' }, status = 200) + + if found == False: + logger.debug("Create crontab line") + logger.debug("Command:", str(command)) + job = my_cron.new(command=command) + logger.debug("Schedule:", str(schedule)) + job.setall(schedule) + my_cron.write() + + if job.is_valid() == False: + return JsonResponse({ 'error': 'Job is not valid' }, status = 404) + else: + my_cron.write() + #my_cron.write() + return JsonResponse({ 'message': 'schedule is updated!' }) + else: # cronjob has jobid + # save request data to variables + jobId = request.POST.__getitem__('jobId') + schedule = request.POST.__getitem__('schedule') + command = request.POST.__getitem__('command').replace("", jobId) + comment = request.POST.__getitem__('comment').replace("", jobId) + + # join command and comment together + command = "%s %s" % (command, comment) + + # create a job with new command + job = my_cron.new(command=command) + # set jobs schedule + job.setall(schedule) + # check if job is valid + if job.is_valid() == False: + return JsonResponse({'error':'Job is not valid'}, status = 404) + # finally write to the crontab + my_cron.write() + return JsonResponse({'message':'schedule is updated!'}) + + +@require_http_methods(["POST"]) +@csrf_exempt +@xframe_options_exempt +def remove_test_schedule(request): + if not request.POST.__contains__("jobId"): + return JsonResponse({'success': False, 'error':'No jobId found'}, status=404) + # get job id + jobId = request.POST.__getitem__('jobId') + # crontab object + my_cron = CronTab(user=True, tabfile='/etc/cron.d/crontab') + # loop over all the jobs + for job in my_cron: + # job to look for + jobToLookFor = """"jobId":%d""" % int(jobId) + logger.debug(job.command) + logger.debug(jobToLookFor) + if jobToLookFor in job.command: + my_cron.remove(job) + my_cron.write() + return JsonResponse({'success': 'True'}, status=200) + return JsonResponse({'success': False, 'error': 'No jobId found in schedules.'}, status=404) diff --git a/backend/behave/cometa_itself/environment.py b/backend/behave/cometa_itself/environment.py index cf59f640..ca185a29 100755 --- a/backend/behave/cometa_itself/environment.py +++ b/backend/behave/cometa_itself/environment.py @@ -11,11 +11,14 @@ # just to import secrets sys.path.append("/code") from src.backend.utility.functions import * +from src.backend.utility.cometa_logger import CometaLogger import secret_variables from src.backend.common import * +LOGGER_FORMAT = '\33[96m[%(asctime)s][%(feature_id)s][%(current_step)s/%(total_steps)s][%(levelname)s][%(filename)s:%(lineno)d](%(funcName)s) -\33[0m %(message)s' # setup logging -logger = logging.getLogger(__name__) +logging.setLoggerClass(CometaLogger) +logger = logging.getLogger('FeatureExecution') logger.setLevel(BEHAVE_DEBUG_LEVEL) # create a formatter for the logger formatter = logging.Formatter(LOGGER_FORMAT, LOGGER_DATE_FORMAT) @@ -35,6 +38,7 @@ # handle SIGTERM when user stops the testcase def stopExecution(signum, frame, context): + logger.warn("SIGTERM Found, will stop the session") context.aborted = True # check if context has a variable @@ -80,6 +84,10 @@ def decorated(*args, **kwargs): @error_handling() def before_all(context): + # Create a logger for file handler + fileHandle = logging.FileHandler(f"/code/src/logs/{os.environ['feature_result_id']}.log") + fileHandle.setFormatter(formatter) + logger.addHandler(fileHandle) # handle SIGTERM signal signal.signal(signal.SIGTERM, lambda signum, frame, ctx=context: stopExecution(signum, frame, ctx)) # create index counter for steps @@ -100,6 +108,10 @@ def before_all(context): context.PARAMETERS = os.environ['PARAMETERS'] # context.browser_info contains '{"os": "Windows", "device": null, "browser": "edge", "os_version": "10", "real_mobile": false, "browser_version": "84.0.522.49"}' context.browser_info = json.loads(os.environ['BROWSER_INFO']) + # set loop settings + context.insideLoop = False # meaning we are inside a loop + context.jumpLoopIndex = 0 # meaning how many indexes we need to jump after loop is finished + context.executedStepsInLoop = 0 # how many steps have been executed inside a loop # Get MD5 from browser information - we preserve this piece of code to be able to migrate style images of previous version browser_code = '%s-%s' % (context.browser_info['browser'], context.browser_info['browser_version']) @@ -141,6 +153,15 @@ def before_all(context): # save the continue on failure for feature to the context context.feature_continue_on_failure = data.get('continue_on_failure', False) + payload={ + "user_id": context.PROXY_USER['user_id'], + "browser_info": os.environ['BROWSER_INFO'], + "feature_result_id": os.environ['feature_result_id'], + "run_id": os.environ['feature_run'], + "datetime": datetime.datetime.utcnow().strftime('%Y-%m-%dT%H:%M:%SZ') + } + request = requests.get('http://cometa_socket:3001/feature/%s/initializing' % str(context.feature_id), data=payload) + # browser data context.cloud = context.browser_info.get("cloud", "browserstack") # default it back to browserstack incase it is not set. @@ -191,6 +212,7 @@ def before_all(context): # download preferences for chrome # context.downloadDirectoryInsideSelenium = r'/home/selenium/Downloads/%s' % str(os.environ['feature_result_id']) context.downloadDirectoryOutsideSelenium = r'/code/behave/downloads/%s' % str(os.environ['feature_result_id']) + context.uploadDirectoryOutsideSelenium = r'/code/behave/uploads/%s' % str(context.department['department_id']) # os.makedirs(context.downloadDirectoryInsideSelenium, exist_ok=True) os.makedirs(context.downloadDirectoryOutsideSelenium, exist_ok=True) # change outside directory owner @@ -203,6 +225,11 @@ def before_all(context): options = None if context.browser_info['browser'] == "chrome": options = webdriver.ChromeOptions() + # disable shm since newer chrome version will run out of memory + # https://github.com/stephen-fox/chrome-docker/issues/8 + # read more about chrome options: + # https://peter.sh/experiments/chromium-command-line-switches/ + options.add_argument("--disable-dev-shm-usage") # Handle local emulated mobile devices if context.browser_info.get('mobile_emulation', False): mobile_emulation = { @@ -220,6 +247,8 @@ def before_all(context): # save downloadedFiles in context context.downloadedFiles = {} + # save tempfiles in context + context.tempfiles = [] # call update task to create a task with pid. task = { @@ -288,6 +317,7 @@ def before_all(context): # update counters total context.counters['total'] = len(response.json()['results']) + os.environ['total_steps'] = str(context.counters['total']) # send a websocket request about that feature has been started request = requests.get('http://cometa_socket:3001/feature/%s/started' % context.feature_id, data={ @@ -296,16 +326,29 @@ def before_all(context): "feature_result_id": os.environ['feature_result_id'], "run_id": os.environ['feature_run'], "datetime": datetime.datetime.utcnow().strftime('%Y-%m-%dT%H:%M:%SZ') - }) + }) + + logger.info("Processing done ... will continue with the steps.") # Get video url with context of browser def get_video_url(context): # get the video url from browserstack backend - bsSessionRequest = requests.get("https://api.browserstack.com/automate/sessions/" + str(context.browser.session_id) + ".json", auth=requests.auth.HTTPBasicAuth("ralf.roeber@amvara.de", "zcGjRZCsJqP4egM1bZTo")) + bsSessionRequest = requests.get("https://api.browserstack.com/automate/sessions/" + str(context.browser.session_id) + ".json", auth=requests.auth.HTTPBasicAuth(BROWSERSTACK_USERNAME, BROWSERSTACK_PASSWORD)) return bsSessionRequest.json()['automation_session'].get('video_url', None) @error_handling() def after_all(context): + del os.environ['current_step'] + del os.environ['total_steps'] + # check if any alertboxes are open before quiting the browser + try: + while(context.browser.switch_to.alert): + logger.debug("Found an open alert before shutting down the browser...") + alert = context.browser.switch_to.alert + alert.dismiss() + except: + logger.debug("No alerts found ... before shutting down the browser...") + try: # for some reasons this throws error when running on browserstack with safari if context.cloud == "local": @@ -439,6 +482,15 @@ def after_all(context): if os.path.exists(context.downloadDirectoryOutsideSelenium): os.rmdir(context.downloadDirectoryOutsideSelenium) + # do some cleanup and remove all the temp files generated during the feature + logger.debug("Cleaning temp files: {}".format(pformat(context.tempfiles))) + for tempfile in context.tempfiles: + try: + os.remove(tempfile) + except Exception as err: + logger.error(f"Something went wrong while trying to delete temp file: {tempfile}") + logger.exception(err) + # call update task to delete a task with pid. task = { 'action': 'delete', @@ -451,8 +503,10 @@ def after_all(context): @error_handling() def before_step(context, step): + os.environ['current_step'] = str(context.counters['index'] + 1) # complete step name to let front know about the step that will be executed next step_name = "%s %s" % (step.keyword, step.name) + logger.info(f"-> {step_name}") # step index index = context.counters['index'] # pass all the data about the step to the step_data in context, step_data has name, screenshot, compare, enabled and type @@ -504,6 +558,13 @@ def after_step(context, step): # check if difference file is assigned if hasattr(context, 'DB_DIFFERENCE_SCREENSHOT'): screenshots['difference'] = context.DB_DIFFERENCE_SCREENSHOT + + # get step error + step_error = None + if 'custom_error' in context.step_data and context.step_data['custom_error'] is not None: + step_error = context.step_data['custom_error'] + elif hasattr(context, 'step_error'): + step_error = context.step_error # send websocket to front to let front know about the step requests.post('http://cometa_socket:3001/feature/%s/stepFinished' % context.feature_id, json={ "user_id": context.PROXY_USER['user_id'], @@ -515,13 +576,18 @@ def after_step(context, step): 'datetime': datetime.datetime.utcnow().strftime('%Y-%m-%dT%H:%M:%SZ'), 'step_result_info': step_result, 'step_time': step.duration, - 'error': context.step_error if hasattr(context, 'step_error') else None, + 'error': step_error, 'belongs_to': context.step_data['belongs_to'], 'screenshots': json.dumps(screenshots) # load screenshots object }) # update countes - context.counters['index'] += 1 + if context.jumpLoopIndex == 0: + context.counters['index'] += 1 + else: + context.counters['index'] += context.jumpLoopIndex + 1 + # update total value + context.counters['total'] += context.executedStepsInLoop # if step was executed successfully update the OK counter if json.loads(step_result)['success']: context.counters['ok'] += 1 diff --git a/backend/behave/cometa_itself/steps/actions.py b/backend/behave/cometa_itself/steps/actions.py index 5f12fa68..27ec2ccd 100755 --- a/backend/behave/cometa_itself/steps/actions.py +++ b/backend/behave/cometa_itself/steps/actions.py @@ -1,12 +1,16 @@ # -*- coding: UTF-8 -*- # ------------------------------------------------------- -# -# AMVARA CONSULTING 12.11.2017 -# Ralf Roeber +# This archive contains all the steps available in Cometa Front for execution. +# Steps not included are Enterprise Licenced Steps # # Changelog -# 21.11.2017 Compare.sh fuer besseren Vergleich von Images angelegt -# 12.11.2017 PoC Arbeiten abgeschlossen +# 2022-07-11 RRO Added some sleeps of 100ms to copy and saving of downloaded and edited excel files, as received IO timeouts on DAI-prod +# 2022-07-08 RRO added last_downloaded_file.suffix to handle generated generic filenames where the suffix is maintained +# 2022-03-04 RRO added new step "Search for "{something}" in IBM Cognos and click on first result" +# 2022-03-01 RRO added step to hit ok on alert, confirm or prompt window +# 2022-02-01 RRO Cleaning up :-) +# 2017-11-21 Compare.sh fuer besseren Vergleich von Images angelegt +# 2017-11-12 PoC Arbeiten abgeschlossen # # ------------------------------------------------------- from behave import * @@ -20,18 +24,17 @@ import requests import json import re -import pickle -import uuid +import csv import datetime import signal import logging import traceback import urllib.parse +import random # import PIL from subprocess import call, run -from selenium.common.exceptions import NoSuchElementException -from selenium.common.exceptions import WebDriverException +from selenium.common.exceptions import WebDriverException, NoAlertPresentException, ElementNotInteractableException, TimeoutException, StaleElementReferenceException from selenium import webdriver from selenium.webdriver.common.by import By from selenium.webdriver.support.ui import WebDriverWait @@ -41,6 +44,7 @@ from functools import wraps from selenium.webdriver import ActionChains from selenium.webdriver.remote.webelement import WebElement +from selenium.webdriver.remote.file_detector import LocalFileDetector # just to import secrets sys.path.append("/code") import secret_variables @@ -59,17 +63,19 @@ from Crypto import Random from Crypto.Cipher import AES import base64 +base64.encodestring = base64.encodebytes from hashlib import md5 from pathlib import Path from pprint import pprint from django.conf import settings +from tools import expected_conditions as CEC SCREENSHOT_PREFIX = getattr(secret_variables, 'COMETA_SCREENSHOT_PREFIX', '') ENCRYPTION_START = getattr(secret_variables, 'COMETA_ENCRYPTION_START', '') # setup logging -logger = logging.getLogger(__name__) +logger = logging.getLogger('FeatureExecution') DATETIMESTRING=time.strftime("%Y%m%d-%H%M%S") @@ -120,9 +126,54 @@ def returnDecrypted(value): if value.startswith(ENCRYPTION_START): value = decrypt(value) + # append value to be masked in logger + if value.strip(): + logger.updateMaskWords(value) return value +def dynamicDateGenerator(content: str): + # date pattern to look for + pattern = r'#today;?(?P[^;\n]+)?;?(?P(?:days|weeks|hours|minutes|seconds)=)?(?P-|\+)?(?P[0-9]+)?\b' # looks for ;format & ;daysDelta which are optional + # match pattern to the paramerters value + match = re.search(pattern, str(content)) + # if match was found + if match: + # get all the matches + groups = match.groupdict() + # will always be today + dateObject = datetime.datetime.today() + # set daysDelta to add or substract from the dateObject to default value if none is provided + groups['amount'] = 0 if groups['amount'] is None else groups['amount'] + # set the arithmetic operations like + (default in case not specified) or - + groups['operation'] = "+" if groups['operation'] is None else groups['operation'] + # set default expression to days= if none is provided + groups['expression'] = "days=" if groups['expression'] is None else groups['expression'] + # evaluate final date + dateObject = eval("dateObject %s datetime.timedelta(%s%s)" % (groups['operation'], groups['expression'], str(groups['amount']))) + # get the format from the match + groups['format'] = '%Y-%m-%d' if groups['format'] is None else groups['format'] + # finally format the object if format is not set, use "%Y-%m-%d" as default + finalDate = dateObject.strftime(groups['format']) + # replace the parameter + content = re.sub(pattern, finalDate, content) + return content + +def reset_element_highlight(context): + try: + if 'highlighted_element' in context: + element = context.highlighted_element['element'] + send_step_details(context, 'Resetting Highligthed element') + context.browser.execute_script(''' + const element = arguments[0]; + element.style.outline = window.cometa.oldOutlineValue; + element.style.outlineOffset = window.cometa.oldOutlineOffsetValue; + ''', element) + del context.highlighted_element + except Exception as err: + logger.exception(err) + + # ########################################################################## # # decorator to ease up making of steps ##################################### # # this decorator will reduce the code ###################################### # @@ -142,7 +193,7 @@ def decorator(func): @wraps(func) def execute(*args, **kwargs): - # arg[0] = context + # args[0] = context # get EnvironmentVariables env_variables = json.loads(args[0].VARIABLES) @@ -160,56 +211,50 @@ def execute(*args, **kwargs): if hasattr(args[0], 'step_error'): del args[0].step_error - # dates - dates = { - "today": "datetime.datetime.today()", -# "yesterday": "datetime.datetime.today() - datetime.timedelta(days=1)", -# "last_month": "datetime.datetime.today().replace(day=1) - datetime.timedelta(days=1)" - } - # replace variables in kwargs for parameter in kwargs: # replace variables for key in env_variables: variable_name = key['variable_name'] variable_value = str(key['variable_value']) - if args[0].text: + pattern = r'\${?%s(?:}|\b)' % variable_name + if args[0].text and 'Loop' not in save_message: # we do not want to replace all the variables inside the loop sub-steps # Replace in step description for multiline step values - args[0].text = args[0].text.replace(("$%s" % variable_name), returnDecrypted(variable_value)) - if re.search(r'\$%s\b' % variable_name, kwargs[parameter]): + args[0].text = re.sub(pattern, returnDecrypted(variable_value), args[0].text) + # ### + # variable was not being replaced correctly if variable contained another variable name in itself. + # ### + # args[0].text = args[0].text.replace(("$%s" % variable_name), returnDecrypted(variable_value)) + if re.search(pattern, kwargs[parameter]): # Replace in step content - kwargs[parameter] = kwargs[parameter].replace(("$%s" % variable_name), returnDecrypted(variable_value)) + kwargs[parameter]= re.sub(pattern, returnDecrypted(variable_value), kwargs[parameter]) + # kwargs[parameter] = kwargs[parameter].replace(("$%s" % variable_name), returnDecrypted(variable_value)) # replace job parameters - for parameter_key in job_parameters.keys(): + for parameter_key in job_parameters.keys(): # we do not want to replace all the parameters inside the loop sub-steps + if args[0].text and 'Loop' not in save_message: + args[0].text = re.sub(r'%%%s\b' % parameter_key, returnDecrypted(str(job_parameters[parameter_key])), args[0].text) if re.search(r'%%%s\b' % parameter_key, kwargs[parameter]): kwargs[parameter] = kwargs[parameter].replace(("%%%s" % parameter_key), str(job_parameters[parameter_key])) # decrypt the value incase it was not a varaible kwargs[parameter] = returnDecrypted(kwargs[parameter]) - # date pattern to look for - pattern = r'#(today);?([^;\n]+)?;?(-|\+|\*)?([0-9]+)?' # looks for ;format & ;daysDelta whichare optional - # match pattern to the paramerters value - match = re.search(pattern, kwargs[parameter]) - # if match was found - if match: - groups = match.groups() - # get the date from dates based on the match - dateObject = eval(dates[groups[0]]) # groups[0] can be today, yesterday or last_month and is required - # get daysDelta to add or substract from the dateObject - daysDelta = groups[3] if groups[3] is not None else 0 # groups[3] is the daysDelta value the last value in our match - # get the arithmetic operations like + (default in case not specified) or - - operation = groups[2] if groups[2] is not None else '+' # groups[2] is the operation used to add or substract from the date - # evaluate final date - dateObject = eval("dateObject %s datetime.timedelta(days=%s)" % (operation, str(daysDelta))) - # get the format from the match - dateFormat = groups[1] if groups[1] is not None else '%Y-%m-%d' - # finally format the object if format is not set, use "%Y-%m-%d" as default - finalDate = dateObject.strftime(dateFormat) - # replace the parameter - kwargs[parameter] = re.sub(pattern, finalDate, kwargs[parameter]) + # update using dynamic date + kwargs[parameter] = dynamicDateGenerator(kwargs[parameter]) + + # update dates inside the text + args[0].text = dynamicDateGenerator(args[0].text) + + # set a step timeout + step_timeout = args[0].step_data['timeout'] + if step_timeout > MAX_STEP_TIMEOUT: + logger.warn("Configured step timeout is higher than the max timeout value, will cap the value to: %d" % MAX_STEP_TIMEOUT) + step_timeout = MAX_STEP_TIMEOUT + # start the timeout - signal.signal(signal.SIGALRM, timeoutError) - signal.alarm(STEP_TIMEOUT) + signal.signal(signal.SIGALRM, lambda signum, frame, timeout=step_timeout: timeoutError(signum, frame, timeout)) + signal.alarm(step_timeout) + # set page load timeout + args[0].browser.set_page_load_timeout(step_timeout) # run the requested function result = func(*args, **kwargs) # if step executed without running into timeout cancel the timeout @@ -219,15 +264,22 @@ def execute(*args, **kwargs): # return True meaning everything went as expected return result except Exception as err: + # reset timeout incase of exception in function + signal.alarm(0) # print stack trace traceback.print_exc() # set the error message to the step_error inside context so we can pass it through websockets! - args[0].step_error = str(err) + args[0].step_error = logger.mask_values(str(err)) try: # save the result to databse as False since the step failed saveToDatabase(save_message, (time.time() - start_time) * 1000, 0, False, args[0]) - except: - pass + except Exception as err: + logger.error("Exception raised while trying to save step data to database.") + logger.exception(err) + + # check if feature was aborted + aborted = str(err) == "'aborted'" + logger.debug("Checking if feature was aborted: " + str(aborted)) # check the continue on failure hierarchy continue_on_failure = False # default value @@ -239,12 +291,15 @@ def execute(*args, **kwargs): ) # check if continue on failure is set - if continue_on_failure: + if continue_on_failure and not aborted: logger.debug("Not failing on %s because continue on failure is checked." % args[0].step_data['step_content']) logger.error("Error: %s" % str(err)) else: # fail the feature raise AssertionError(str(err)) + finally: + # reset element highligh is any + reset_element_highlight(args[0]) # if the user gets here means that something went wrong somewhere. return False return execute @@ -264,6 +319,12 @@ def saveToDatabase(step_name='', execution_time=0, pixel_diff=0, success=False, 'status': "Success" if success else "Failed", 'belongs_to': context.step_data['belongs_to'] } + # add custom error if exists + if 'custom_error' in context.step_data: + data['error'] = context.step_data['custom_error'] + elif hasattr(context, 'step_error'): + data['error'] = context.step_error + # add files try: data['files'] = json.dumps(context.downloadedFiles[context.counters['index']]) except: @@ -292,11 +353,11 @@ def saveToDatabase(step_name='', execution_time=0, pixel_diff=0, success=False, # Create current step result folder Path(context.SCREENSHOTS_STEP_PATH).mkdir(parents=True, exist_ok=True) # Check if feature needs screenshot - see #3014 for change to webp format - if context.step_data['screenshot']: + if context.step_data['screenshot'] or not success: # Take actual screenshot takeScreenshot(context, step_id) # Take actual HTML - takeHTMLSnapshot(context, step_id) + # takeHTMLSnapshot(context, step_id) # Check if feature needs compare if context.step_data['compare']: # -------------------- @@ -345,17 +406,22 @@ def saveToDatabase(step_name='', execution_time=0, pixel_diff=0, success=False, # -------------------- # Spawn thread to compare HTML # fn can take some time to compare html differences, therefore we will send a request to django once we have the result - html_params = { - 'HTML_PATH': context.HTML_PATH, - 'SOURCE_HTML': context.SOURCE_HTML, - 'COMPARE_HTML_FILE': context.COMPARE_HTML_FILE, - 'COMPARE_HTML_FILE_EXT': context.COMPARE_HTML_FILE_EXT, - 'feature_id': context.feature_id, - 'counters': { - 'index': context.counters['index'] - }, - 'step_id': step_id - } + + # Error since 18.3. 18:15pm File "steps/actions.py", line 355, in saveToDatabase 'SOURCE_HTML': context.SOURCE_HTML + # therefore commenting the follwing + +# html_params = { +# 'HTML_PATH': context.HTML_PATH, +# 'SOURCE_HTML': context.SOURCE_HTML, +# 'COMPARE_HTML_FILE': context.COMPARE_HTML_FILE, +# 'COMPARE_HTML_FILE_EXT': context.COMPARE_HTML_FILE_EXT, +# 'feature_id': context.feature_id, +# 'counters': { +# 'index': context.counters['index'] +# }, +# 'step_id': step_id +# } + # t = Thread(target=compareHTML, args=(html_params, )) # t.start() # Template should be actual screenshot of very first run, not of the last run @@ -376,8 +442,19 @@ def saveToDatabase(step_name='', execution_time=0, pixel_diff=0, success=False, } logger.debug("Writing data %s to database" % json.dumps(data)) requests.post('http://cometa_django:8000/setScreenshots/%s/' % str(step_id), json=data, headers={"Host": "cometa.local"}) + # add timestamps to the current image + if context.DB_CURRENT_SCREENSHOT: + addTimestampToImage(context.DB_CURRENT_SCREENSHOT, path=context.SCREENSHOTS_ROOT) return step_id +# add timestamp to the image using the imagemagic cli +def addTimestampToImage(image, path=None): + logger.debug(f"Adding timestamp to: {path}/{image}") + cmd=f"convert {path}/{image} -pointsize 20 -font DejaVu-Sans-Mono -fill 'RGBA(255,255,255,1.0)' -gravity SouthEast -annotate +20+20 \"$(date)\" {path}/{image}" + status = subprocess.call(cmd, shell=True, env={}) + if status != 0: + logger.error("Something happend during the timestamp watermark.") + # Automatically checks if there's still old styles and moves them to current path # Due to change in new images structure we have to check if the old style image is still there, # # if it is we copy it to style image path and delete the old one @@ -410,9 +487,18 @@ def takeScreenshot(context, step_id): final_screenshot_file = context.SCREENSHOTS_STEP_PATH + context.SCREENSHOT_FILE logger.debug("Final screenshot filename and path: %s" % final_screenshot_file) - # create the screenshot - logger.debug("Saving screenshot to file") - context.browser.save_screenshot(final_screenshot_file) + # check if an alert box exists + try: + context.browser.switch_to_alert() + logger.debug("Alert found ... if we take a screenshot now the alert box will be ignored...") + except Exception as err: + # create the screenshot + logger.debug("Saving screenshot to file") + try: + context.browser.save_screenshot(final_screenshot_file) + except Exception as err: + logger.error("Unable to take screenshot ...") + logger.exception(err) # transfer saved image name to context.COMPARE_IMAGE context.COMPARE_IMAGE = final_screenshot_file @@ -520,7 +606,7 @@ def compareImage(context): except Exception as e: logger.error(str(e)) -@timeout("Unable to retrieve compare metric content in seconds.") +# @timeout("Unable to retrieve compare metric content in seconds.") def waitMetric(metricFile): """ Waits for the given metric file to contain some content and returns the lines array @@ -540,7 +626,7 @@ def mySafeToFile(filename, myString): # waitFor(something,pageContext) .... waits until seeing or maxtries; returns true if found # ... FIXME ... also look in iFrames -@timeout("Unable to find specified text in seconds.") +# @timeout("Unable to find specified text in seconds.") def waitFor(context, something): while True: if something in context.browser.page_source: @@ -631,6 +717,20 @@ def step_impl(context,css_selector): else: raise err +# Moves the mouse to the css selector and double clicks +@step(u'I move mouse to "{selector}" and double click') +@done(u'I move mouse to "{selector}" and double click') +def step_impl(context,selector): + send_step_details(context, 'Looking for selector') + elem = waitSelector(context, "css", selector) + send_step_details(context, 'Double Clicking') + try: + ActionChains(context.browser).move_to_element(elem[0]).double_click().perform() + except Exception as err: + logger.error("Unable to double click on the element.") + logger.exception(err) + raise err + # Moves the mouse to the center of css selector @step(u'I move mouse over "{css_selector}"') @done(u'I move mouse over "{css_selector}"') @@ -640,6 +740,33 @@ def step_impl(context,css_selector): send_step_details(context, 'Clicking') ActionChains(context.browser).move_to_element(elem[0]).perform() +# Moves the mouse to random element in selector and click +@step(u'I can move mouse and click randomly "{x}" times on elements in "{selector}"') +@done(u'I can move mouse and click randomly "{x}" times on elements in "{selector}"') +def step_impl(context, x, selector): + send_step_details(context, 'Looking for selector') + elements = waitSelector(context, "css", selector) + + for i in range(int(x)): + # get a random number between 0 and elements + index = random.randint(0, (len(elements) - 1)) + # get element + element = elements[index] + send_step_details(context, 'Randomly clicking on element at index %d' % index) + try: + # display element to the screen + context.browser.execute_script("arguments[0].scrollIntoView({behavior: 'auto',block: 'center',inline: 'center'});", element) + ActionChains(context.browser).move_to_element(element).click().perform() + except Exception as err: + if isCommandNotSupported(err): + # I move mouse is not supported in the current device, falling back to "click element with css" + send_step_details(context, 'Incompatible step, falling back to "I click on css selector"') + click_element(context, element) + else: + raise err + # let the page breathe a bit + time.sleep(0.5) + # Set Environment ID @step('Environment "{env}"') @done('Environment "{env}"') @@ -648,33 +775,79 @@ def step_impl(context,env): if (item[0] == env): context.active_environment = item[1] -# # Test if can set an environment -# .... not used ... was only for POC -# @step(u'I enter environment "{env}"') -# @done(u'I enter environment "{env}"') -# def step_impl(context,env): -# print("Loop over read items:",len(context.environments)) -# for item in context.environments: -# print("i:",item) -# if (item[0] == env): -# print("FOUND:",env) -# print("Name:",item[1][0]) -# print("UserID:",item[1][1]) -# print("URI:",item[1][4]) -# # get uri and login -# context.browser.get(item[1][4]) -# MyLogin(context,item[1][1],item[1][2],item[1][3]) -# # transfer active environment to context -# context.active_environment = item[1] -# return True -# else: -# print("Nothing found in:",item) -# if ( "IBM Cognos content" in context.browser.page_source ): -# print("Found IBM Cognos Homepage") -# elem = waitSelector(context, "css", 'td.welcomeToolHeadingContainer') -# elem[0].click() -# else: -# raise CustomError("We are on Cognos Portal page?") +# Search for something in IBM Cognos and click on the first result. Tested & Works with CA 11.1 & 11.2 +@step(u'Search for "{something}" in IBM Cognos and click on first result') +@done(u'Search for "{something}" in IBM Cognos and click on first result') +def step_impl(context, something): + logger.debug("Searching for %s in IBM Cognos" % something ) + send_step_details(context, 'Searching for %s in IBM Cognos' % something ) + + # first try to find and click a CA11.1 or CA11.2 searchbox + # ... then select everything with CTRL+A and send the new searchstring + # ... Hit Enter and click on the first item in the search result + + # Looking for the searchbox + try: + # CA11.1 search box + logger.debug("Trying to search on CA11.1") + elm = context.browser.find_element_by_xpath("//*[@id='com.ibm.bi.search.search']") + logger.debug("elm returned") + logger.debug("Got elm %s " % elm ) + elm.click() + # select the opening slide input search element + elm = waitSelector(context, "//input[@type='search']")[0] + elm.click() + except: + try: + # CA11.2 search box + logger.debug("Trying to search on CA11.2") + + # Click on search icon - just in case browser is set to small to click on the input + try: + context.browser.find_element_by_xpath("//div[@role='search']").click() + logger.debug("Clicked search Icon ... searchbox should now be visible.") + except: + logger.debug("Tried to click search icon ... but failed. This happens, when browser is maximize and searchbox is visible.") + + # wait for animation to stop + time.sleep(0.01) + + # now click in input + elms = context.browser.find_elements_by_xpath("//input[@role='searchbox']") + logger.debug("Found %s Searchboxes" % len(elms)) + # we might need to loop here as Cognos produces various searchboxes the HTML + for elm in elms: + try: + elm.click() + logger.debug("==> Clicked on Element %s %s " % ( elm, elm.get_attribute("outerHTML") ) ) + except: + logger.debug("==> Element %s %s not clickable" % ( elm, elm.get_attribute("outerHTML") ) ) + except: + logger.debug("Could not find a CA11.1 or CA11.2 compatible searchbox.") + raise CustomError("Could not find a CA11.1 or CA11.2 compatible searchbox. Please have a look at the logfiles.") + + # sleep 10ms for cognos to react on the last click + time.sleep(0.01) + + # Press Control+A to mark whatever is in the searchbox + elm.send_keys(Keys.CONTROL, "a") + # send the search text + elm.send_keys(something) + elm.send_keys(Keys.ENTER) + + # wait half a second for results to appear + time.sleep(0.5) + + # get the first result and click on it + logger.debug("Trying to clicking on first result") + try: + waitSelector(context, "xpath", "//td//div[contains(.,'%s')]" % something)[0].click() + except: + logger.debug("Could not click on search result - maybe there was nothing found?") + raise CustomError("Could not click on search result - maybe there was nothing found? Please check the video or screenshots.") + + # wait 250ms a second for results to appear + time.sleep(0.25) # Allows to navigate to a folder in an IBM Cognos installation @step(u'I can go to IBM Cognos folder "{folder_name}"') @@ -705,7 +878,7 @@ def step_impl(context, folder_name): time.sleep(0.5) # waitFor(element) .... waits until innerText attribute of element contains something -@timeout("Couldn't find any text inside report for seconds.") +# @timeout("Couldn't find any text inside report for seconds.") def wait_for_element_text(context, css_selector): while True: elements = context.browser.find_elements_by_css_selector(css_selector) @@ -759,7 +932,20 @@ def step_imp(context): @step(u'I can test current IBM Cognos folder') @done(u'I can test current IBM Cognos folder') def step_impl(context): - test_folder(context) + test_folder_aso(context) + +# Allows to click on the OK button of an alert, confirm or prompt message +@step(u'I can click OK on alert, confirm or prompt message') +@done(u'I can click OK on alert, confirm or prompt message') +def step_impl(context): + try: + # try switching to the alert windows + alert = context.browser.switch_to.alert + # then hit the enter key + alert.accept() + except NoAlertPresentException: + logger.debug("Could not find the alert, confirm or prompt window") + raise CustomError('There was no alert, confirm or prompt window present.') # # FIXME documentation @@ -883,6 +1069,149 @@ def test_folder(context, parameters = {}): logger.debug("Overall results failed - some reprots have errors.") raise CustomError('Execution of reports in folder finish with some errors') +# +# FIXME documentation +# +def test_folder_aso(context, parameters = {}): + + # Make sure Team Folder is opened + open_team_folder(context) + send_step_details(context, 'Opening team folder') + + # Make sure all folder items are in the list + send_step_details(context, 'Listing all folder items') + cognos_scroll_folder_till_bottom(context, True) + + # Get all reports in current folder + elements = waitSelector(context, "xpath", '//div[@id="teamFoldersSlideoutContent"]/descendant::div[@title and @class="nameColumnDiv contentListFocusable clickable active"]') + + # Get only 5 first elements for testing + # elements = elements[:5] + total = len(elements) + if len(elements) == 0: + raise CustomError('Empty folder') + + # Test every report by clicking on it, run it, check content, and finally close + logger.debug("Looping over folder content with %d elements" % len(elements)) + + # Set the over_results_ok_variable to true + # ... if one report fails, then this will change to false + over_all_results_ok=True + + # Loop over elements in folder + for index, el in enumerate(elements): + fail=False # if we want to fail faster + print(' ==> Testing report #%s' % str(index)) + logger.debug(' ==================') + logger.debug(' ==> Testing report #%s of %s' % (str(int(index)+1),str(total))) + logger.debug(' ==================') + report_start_time=time.time() + # Make sure Team Folder is opened + logger.debug("makeing sure team folder is opened") + open_team_folder(context) + # Make sure all folder items are in the list + logger.debug("Scrolling folder to bottom") + cognos_scroll_folder_till_bottom(context, True) + # Retrieve element again as DOM can later be changed + logger.debug("Getting DOM element %d in folder" % (int(index)+1)) + element = context.browser.find_element_by_xpath('//div[@id="teamFoldersSlideoutContent"]/descendant::div[@title and @class="nameColumnDiv contentListFocusable clickable active"][position()=%s]' % str(int(index)+1)) + # save reportname for later usage + report_name=str(element.get_attribute('innerText')) + logger.debug(" ==> Executing report [%s] " % report_name) + # output something in the preview on cometa front + send_step_details(context, '%d/%d - Testing report %s' % (int(index+1), total, str(element.get_attribute('innerText')))) + # Open report + logger.debug("Clicking on report") + context.browser.execute_script("arguments[0].click();", element) + + # get the element that contains the storeId + storeIDElement = waitSelector(context, "xpath", "//div[contains(@id, 'i') and @class='pageView']")[0] + storeID = storeIDElement.get_attribute("id") + logger.info("Found storeID for the report: %s" % storeID) + + # wait for report iframe element using the storeID as a name attribute + logger.debug("Waiting for IBM Cognos iFrame to appear") + # Try to switch to new iframe if exists (only reports with prompts have iframe) + had_iframe = False + try: + iframe = waitSelector(context, "xpath", ("//iframe[@name='%s']" % storeID)) + except: + iframe = [] + if len(iframe) > 0: + logger.debug('Switched to iframe') + context.browser.switch_to_frame( iframe[0] ) + had_iframe = True + else: + # check if there is a loader displayed + loaderElements = context.browser.find_elements_by_css_selector("table.rsBlockerDlg") + logger.debug(loaderElements) + if len(loaderElements) > 0 and loaderElements[0].is_displayed(): + logger.debug("%s with storeID %s took longer than timeout to load." % (report_name, storeID)) + fail = True + else: + logger.debug("No iFrame found and no loader found, maybe report already failed or report is fully displayed...") + + # wait for viewer + time.sleep(5) + # Automatically fill all necessary prompts + logger.debug("Check for filling prompt with magic") + if not fail and auto_select_cognos_prompts_aso(context, parameters=parameters): + # promptPage was filled with success + logger.debug("Prompt Magic return true - which somehow means success") + else: + if not fail: + # promptPage failed somehow + logger.debug("Prompt Magic returned false - which means, we should fail this report [%s]." % report_name) + logger.info("You might want to look at the screenshot or video and adjust timeouts or fix a broken report.") + logger.debug("Saveing report timing as step result to database.") + over_all_results_ok=False + + # save to database + save_message="I can test current IBM Cognos folder > Report: %s failed" % report_name + saveToDatabase(save_message, (time.time() - report_start_time) * 1000, 0, False, context ) + + # switch content + logger.debug("Try switching to default content") + context.browser.switch_to_default_content() + + # try closing the report view + logger.debug("Trying to close report view") + close_ibm_cognos_view(context, parameters) + + # finally continue + logger.debug("Continue to next report as further testing on this report is not very useful.") + logger.debug(" <== Report [%s] DONE " % report_name) + continue + + # time.sleep(3) + # Check if inner text has something + if had_iframe: + logger.debug("Waiting for iFrame to render IBM Cognos content") + wait_for_element_text(context, 'body.viewer table, .clsViewerPage') + logger.debug("Try switching to default content") + context.browser.switch_to_default_content() + # else: + # logger.debug("Waiting for IBM Cognos pageViewContent") + # wait_for_element_text(context, '.pageViewContent') + # + # Save this report test to database, so the user can see in testresults the execution, screenshot and timings + # + logger.debug("Saveing report timing as step result to database") + save_message="I can test current IBM Cognos folder > Report: %s tested" % report_name + saveToDatabase(save_message, (time.time() - report_start_time) * 1000, 0, True, context ) + + # close the ibm cognos view + close_ibm_cognos_view(context, parameters) + logger.debug(" <== Report [%s] DONE " % report_name) + + # Finally, check if over_all_results_ok is false + # .... this means, we had some problems inside the report execution loop + # .... so we fail the overall test + if over_all_results_ok: + logger.debug("Overall results ok - all reports finished well.") + else: + logger.debug("Overall results failed - some reprots have errors.") + raise CustomError('Execution of reports in folder finish with some errors') # Closes the first IBM Cognos View in the drop down selector. @step(u'I can close the current IBM Cognos report view "{parameters}"') @@ -891,17 +1220,17 @@ def step_impl(context, parameters = {}): close_ibm_cognos_view(context, parameters) def close_ibm_cognos_view(context, parameters = {}): - # Close current report view - logger.debug("Closing current report content") - elements = waitSelector(context, 'css', '.appview:not(.hidden) [id="com.ibm.bi.glass.common.viewSwitcher"]') - elements[0].click() - elements = waitSelector(context, 'css', '.popover.switcher ul li:last-child .removeItemIcon') + # Close current report view + logger.debug("Closing current report content") + elements = waitSelector(context, 'css', '[id="com\.ibm\.bi\.glass\.common\.viewSwitcher"]') + context.browser.execute_script("arguments[0].click();", elements[0]) + elements = waitSelector(context, 'css', '.popover.switcher ul li:last-child .removeItemIcon') + elements[0].click() + time.sleep(1) + elements = context.browser.find_elements_by_css_selector('.button.dialogButton[id=ok]') + if len(elements) > 0: elements[0].click() - time.sleep(1) - elements = context.browser.find_elements_by_css_selector('.button.dialogButton[id=ok]') - if len(elements) > 0: - elements[0].click() - time.sleep(0.5) + time.sleep(0.5) # Allows to test all reports inside a folder of a Cognos installation with {key:value} parameters to autfill promptPages. Example: "PE|PeriodID:1269;CO:01" @@ -910,7 +1239,7 @@ def close_ibm_cognos_view(context, parameters = {}): def step_impl(context, parameters): # Load parameters parameters = load_parameters(parameters) - test_folder(context, parameters) + test_folder_aso(context, parameters) # FIX ME .... make this MIF unspecific # Test if can access to a folder relative to the root directory of the URL specified @@ -1009,39 +1338,6 @@ def step_impl(context): def step_impl(context): context.browser.refresh() -# # Logins in the system with the desired credentials ... FIXME ... this is legacy, should by MIF unspecific -# @step(u'Login as "{user}" using password "{password}" and PIN "{pin}"') -# @done(u'Login as "{user}" using password and PIN') -# def step_impl_login_as_user(context,user,password,pin): -# print("Trying login as user using password ") -# MyLogin(context,user,password,pin) - -# def MyLogin(context,user,password,pin): -# if ( password == '******' ): -# mypass = context.pwmiftest4env0 -# else: -# mypass = password -# search_box = waitSelector(context, "id", 'USERNAME-FIELD') -# search_box.send_keys(user) -# search_box = waitSelector(context, "id", 'PASSWORD-FIELD') -# search_box.send_keys(mypass) -# search_box = waitSelector(context, "id", 'PIN-FIELD') -# search_box.send_keys(pin) -# search_box.submit() -# time.sleep(2) -# pageSource = context.browser.page_source -# # if intermediate page is seen, then click through to homepage -# if ("goHomeID_link" in pageSource): -# waitSelector(context, "id", "goHomeID_link").click() -# # check if Homepage found -# if ( "IBM Cognos content" in pageSource ): -# print("Found IBM Cognos Homepage") -# try: -# elem = waitSelector(context, "css", 'td.welcomeToolHeadingContainer') -# elem[0].click() -# except: -# print("Strange, we seem to be on intermidate page, but click goHomeId_link not possible.") - # Scrolls the page to a given amount of pixels in the Y axis @step(u'Scroll to "{amount}"px') @done(u'Scroll to "{amount}"px') @@ -1055,50 +1351,28 @@ def step_iml(context, amount, selector): elements = waitSelector(context, "css", selector) context.browser.execute_script("arguments[0].scrollTo(0,%s)" % amount, elements[0]) -# Compare images with a desired prefix name -# @step(u'Compare Images "{fileprefix}"') -# def step_iml(context, fileprefix): -# start_time = time.time() -# context.COMPARE_IMAGE = context.SCREENSHOT_PATH + context.SCREENSHOT_FILE -# context.STYLE_IMAGE = context.SCREENSHOT_PATH+context.SCREENSHOT_FILE+'_style.png' -# context.DIFF_IMAGE = context.SCREENSHOT_PATH+context.SCREENSHOT_FILE+'_diff.png' -# if not os.path.isfile(context.STYLE_IMAGE): -# shutil.copy2(context.COMPARE_IMAGE, context.STYLE_IMAGE) -# try: -# compareImage(context) -# saveToDatabase('Compare Images with Prefix', time.time() - start_time, 0, True, context) -# except Exception as e: -# saveToDatabase('Compare Images with Prefix', time.time() - start_time, 0, False, context) -# assert(3==5) - -# Do a commpare images and saves it -# @step(u'Compare Images') -# def step_iml(context): -# start_time = time.time() -# context.COMPARE_IMAGE = context.SCREENSHOT_PATH + context.SCREENSHOT_FILE -# context.STYLE_IMAGE = context.SCREENSHOT_PATH+context.SCREENSHOT_FILE+'_style.png' -# context.DIFF_IMAGE = context.SCREENSHOT_PATH+context.SCREENSHOT_FILE+'_diff.png' -# if not os.path.isfile(context.STYLE_IMAGE): -# shutil.copy2(context.COMPARE_IMAGE, context.STYLE_IMAGE) -# try: -# compareImage(context) -# saveToDatabase('Compare Images ', time.time() - start_time, 0, True, context) -# except Exception as e: -# saveToDatabase('Compare Images ', time.time() - start_time, 0, False, context) -# assert(3==5) -# """ -# Check if STYLE IMAGE exists -# if not existing, copy actual Screenshot as style -# """ - # Set a value on an element, normally used for inputs @step(u'Set value "{text}" on "{selector}"') @done(u'Set value "{text}" on "{selector}"') def step_iml(context, text, selector): send_step_details(context, 'Looking for selector') element = waitSelector(context, "css", selector) - send_step_details(context, 'Setting value') - context.browser.execute_script('arguments[0].value = "%s";' % text, element[0]) + for i in range(0, 10): + try: + elementInteractable = WebDriverWait(context.browser, 10).until(CEC.element_to_be_interactable(element[0])) + if elementInteractable: + send_step_details(context, 'Setting value') + element[0].send_keys(text) + send_step_details(context, 'Checking if the value is set') + valueSet = WebDriverWait(context.browser, 10).until(CEC.text_to_be_present_in_element_value(element[0], text)) + if valueSet: + return True + except ElementNotInteractableException as err: + logger.error("Element is not interactable yet will wait.") + time.sleep(1) + except TimeoutException as err: + logger.error("Element was not clickable or value was unable to set, will try again.") + raise CustomError("Unable to set set the value, maybe there is another element in front?") # Send any keys, this simulates the keys pressed by the keyboard @step(u'Send keys "{keys}"') @@ -1124,6 +1398,29 @@ def step_iml(context, css_selector): send_step_details(context, 'Focusing on element') context.browser.execute_script('let elem=document.querySelector("'+css_selector+'"); elem.scrollIntoView(); elem.focus()') +# Highlight element +@step(u'Highlight element with "{selector}"') +@done(u'Highlight element with "{selector}"') +def step_iml(context, selector): + send_step_details(context, 'Looking for selector') + element = waitSelector(context, "css", selector) + if isinstance(element, list) and len(element) > 0: + element = element[0] + send_step_details(context, 'Highlighting the element') + context.browser.execute_script(''' + const element = arguments[0]; + if (!window.cometa) window.cometa = {} + window.cometa.oldOutlineValue = element.style.outline; + window.cometa.oldOutlineOffsetValue = element.style.outlineOffset; + element.style.outline = "2px solid #f00"; + element.style.outlineOffset = "1px"; + ''', element) + + # set highlight setting to be removed after step + context.highlighted_element = { + 'element': element + } + # Press Enter key @step(u'Press Enter') @done(u'Press Enter') @@ -1155,6 +1452,7 @@ def step_impl(context, keySet): keysUp = '' # Capitalizes and splits each index into simultaneous press key sets for key in i.split('+'): + key = key.strip() if key.upper() in dir(Keys): # Code transformation for special characters keys = keys + '.key_down(Keys.%s)' % key.upper() @@ -1164,7 +1462,7 @@ def step_impl(context, keySet): keys = keys + ".send_keys('%s')" % key.replace('\'', '\\\'') # Concats the key down and key up action keys = keys + keysUp - logger.debug('Sending the following keys' + keys) + logger.debug('Sending the following keys ' + keys) actions = ActionChains(context.browser) # Sends the trigger to press the keys eval(keys).perform() @@ -1199,7 +1497,7 @@ def step_impl(context): # Switches to a iframe tag inside the document within the specified ID @step(u'I can switch to iFrame with id "{iframe_id}"') @done(u'I can switch to iFrame with id "{iframe_id}"') -@timeout("Waited for seconds but was unable to find specified iFrame element.") +# @timeout("Waited for seconds but was unable to find specified iFrame element.") def step_impl(context,iframe_id): while True: try: @@ -1266,10 +1564,27 @@ def step_impl(context, css_selector): send_step_details(context, 'Looking for selector') waitSelector(context, "css", css_selector) +# Checks if it cannot see an element using a CSS Selector until timeout +@step(u'I cannot see element with selector "{selector}"') +@done(u'I cannot see element with css selector "{selector}"') +def cannot_see_selector(context, selector): + # log general information about this step + logger.debug("Running in Feature: %s " % context.feature_id ) + logger.debug("Step data: %s " % str(context.step_data) ) + timeout = context.step_data['timeout'] + logger.debug("Timeout is %s " % timeout) + + try: + waitSelector(context, "css", selector) + send_step_details(context, 'Looking for selector - it was found, which is bad') + raise CustomError("Found selector using %s. Which is bad." % selector) + except CometaTimeoutException as err: + logger.info("Element not found which in case is good!") + # Check if the source code in the previously selected iframe contains a link with text something @step(u'I can see a link with "{linktext}" in iframe') @done(u'I can see a link with "{linktext}" in iframe') -@timeout("Unable to find specified linktext inside the iFrames") +# @timeout("Unable to find specified linktext inside the iFrames") def step_impl(context,linktext): while True: for child_frame in context.browser.find_elements_by_tag_name('iframe'): @@ -1305,22 +1620,38 @@ def step_impl(context, css_selector, value): # Preformat css_selector and value variables css_selector = getVariableType(css_selector) value = getVariableType(value) - # Get selector reference in DOM - try: - if isinstance(css_selector, int): - # Change name for better understanding - index = css_selector - send_step_details(context, 'Looking for selector') - elements = waitSelector(context, "tag_name", "select") - # Index is a 1+ index, therefore subtract 1 and get the select element - index -= 1 - selector = Select(elements[index]) - else: - send_step_details(context, 'Looking for selector') - elements = waitSelector(context, "css", css_selector) - selector = Select(elements[0]) - except: - raise CustomError("Unable to find selector using %s." % css_selector) + + def element_selector(context, css_selector): + # Get selector reference in DOM + try: + if isinstance(css_selector, int): + # Change name for better understanding + index = css_selector + send_step_details(context, 'Looking for selector') + elements = waitSelector(context, "tag_name", "select") + # Index is a 1+ index, therefore subtract 1 and get the select element + index -= 1 + element = elements[index] + else: + send_step_details(context, 'Looking for selector') + elements = waitSelector(context, "css", css_selector) + element = elements[0] + return element + except: + raise CustomError("Unable to find selector using %s." % css_selector) + + element = element_selector(context, css_selector) + send_step_details(context, 'Waiting for the element to interactable.') + while True: + try: + if element.is_displayed() and element.is_enabled(): + break + except CometaTimeoutException as err: + raise CustomError("Selector never got enabled ... will not be able to select the value.") + except StaleElementReferenceException: + element = element_selector(context, css_selector) + + selector = Select(element) send_step_details(context, 'Selecting value/index') # Get value or index reference in DOM if isinstance(value, int): @@ -1464,9 +1795,16 @@ def step_impl(context): @step(u'I click on element with classname "{classname}"') @done(u'I click on element with classname "{classname}"') def step_impl(context, classname): - send_step_details(context, 'Looking for classname') - elem = waitSelector(context, "class", classname) - elem[0].click() + start_time = time.time() + try: + send_step_details(context, 'Looking for classname') + elem = waitSelector(context, "class", classname) + elem[0].click() + return True + except Exception as e: + raise CustomError("Could not interact with element having classname %s ." % classname) + logger.error(str(e)) + return False def click_on_element(elem): start_time = time.time() @@ -1525,7 +1863,7 @@ def find_and_click_link_id(context,linktext): return False # try find the element in css_selector, xpath or as a link_text -@timeout("Unable to find specified element in seconds.") +# @timeout("Unable to find specified element in seconds.") def find_element(context, linktext): counter = 0 while True: @@ -1634,14 +1972,28 @@ def step_impl(context, feature_id): def step_impl(context, feature_name): pass +# add parameter to job parameters +def addParameter(context, key, value): + job_params = json.loads(context.PARAMETERS) + job_params[key] = value + context.PARAMETERS = json.dumps(job_params) + # Run a JavaScript function in the current browser context @step(u'Run Javascript function "{function}"') @done(u'Run Javascript function') def step_impl(context, function): js_function = context.text - context.browser.execute_script(""" + # FIXME ... needs to set the script timeout accordingly to what was selected in cometa - see https://www.selenium.dev/selenium/docs/api/py/webdriver_remote/selenium.webdriver.remote.webdriver.html#selenium.webdriver.remote.webdriver.WebDriver.set_script_timeout + # driver.set_script_timeout(30) + try: + result = context.browser.execute_script(""" %s - """ % js_function) + """ % js_function) + + addParameter(context, "js_return", result) + except Exception as err: + addParameter(context, "js_return", "") + raise CustomError(err) # Click on element using an XPath Selector @step(u'click on element with xpath "{xpath}"') @@ -2057,7 +2409,7 @@ def getVariable(context, variable_name): return variable_value -def addVariable(context, variable_name, result): +def addVariable(context, variable_name, result, encrypted=False): # get the variables from the context env_variables = json.loads(context.VARIABLES) # check if variable_name is in the env_variables @@ -2067,23 +2419,37 @@ def addVariable(context, variable_name, result): index = index[0] logger.debug("Patching existing variable") env_variables[index]['variable_value'] = result - # make the request to cometa_django and add the environment variable - response = requests.patch('http://cometa_django:8000/api/variables/' + str(env_variables[index]['id']) + '/', headers={"Host": "cometa.local"}, json=env_variables[index]) + env_variables[index]['encrypted'] = encrypted + env_variables[index]['updated_by'] = context.PROXY_USER['user_id'] + + # do not update if scope is data-driven + if 'scope' in env_variables[index] and env_variables[index]['scope'] == 'data-driven': + logger.info("Will not send request to update the variable in co.meta since we are in 'data-driven' scope.") + else: + # make the request to cometa_django and add the environment variable + response = requests.patch('http://cometa_django:8000/api/variables/' + str(env_variables[index]['id']) + '/', headers={"Host": "cometa.local"}, json=env_variables[index]) + if response.status_code == 200: + env_variables[index] = response.json()['data'] else: # create new variable logger.debug("Creating variable") # create data to send to django - env_variables.append({ - "variable_name": variable_name, - "variable_value": result - }) update_data = { - "environment_id": int(context.feature_info['environment_id']), - "department_id": int(context.feature_info['department_id']), - "variables": env_variables + "environment": int(context.feature_info['environment_id']), + "department": int(context.feature_info['department_id']), + "feature": int(context.feature_id), + "variable_name": variable_name, + "variable_value": result, + "based": "environment", + "encrypted": encrypted, + "created_by": context.PROXY_USER['user_id'], + "updated_by": context.PROXY_USER['user_id'] } # make the request to cometa_django and add the environment variable response = requests.post('http://cometa_django:8000/api/variables/', headers={"Host": "cometa.local"}, json=update_data) + if response.status_code == 201: + env_variables.append(response.json()['data']) + # send a request to websockets about the environment variables update requests.post('http://cometa_socket:3001/sendAction', json={ 'type': '[Variables] Get All', @@ -2109,6 +2475,32 @@ def step_impl(context, css_selector, variable_name): # add variable addVariable(context, variable_name, result) +# save string value to environment variable, environment variable value has a maximum value of 255 characters. +@step(u'Save "{value}" to environment variable "{variable_name}"') +@done(u'Save "{value}" to environment variable "{variable_name}"') +def step_impl(context, value, variable_name): + send_step_details(context, 'Saving value to environment variable') + # add variable + addVariable(context, variable_name, value) + +# add a timestamp after the prefix to make it unique +@step(u'Add a timestamp to the "{prefix}" and save it to "{variable_name}"') +@done(u'Add a timestamp to the "{prefix}" and save it to "{variable_name}"') +def step_impl(context, prefix, variable_name): + # create the unique text + text = "%s-%.0f" % (prefix, time.time()) + addVariable(context, variable_name, text) + +@step(u'Create a string of random "{x}" numbers and save to "{variable_name}"') +@done(u'Create a string of random "{x}" numbers and save to "{variable_name}"') +def step_imp(context, x, variable_name): + import random + text = "" + for i in range(0, int(x)): + text += str(random.randint(0,9)) + + addVariable(context, variable_name, text) + def downloadFileFromURL(url, dest_folder, filename): file_path = os.path.join(dest_folder, filename) @@ -2121,23 +2513,75 @@ def downloadFileFromURL(url, dest_folder, filename): f.write(chunk) f.flush() os.fsync(f.fileno()) + + # we copy the very same file also to a generic filename "last_downloaded_file" maintaining suffix for convinience reasons in the same folder + file_path2 = os.path.join(dest_folder, "last_downloaded_file"+Path(file_path).suffix) + logger.debug("Copying file also to last_downloaded_file for convinience %s", file_path2) + shutil.copy(file_path,file_path2) + + # sleep some time to gives discs time to sync + time.sleep(0.25) + else: # HTTP status code 4XX/5XX logger.error("Download failed: status code {}\n{}".format(r.status_code, r.text)) -# download a file and watch which file is downloaded and assign them to feature_result and step_result, linktext can be a text, css_selector or even xpath +# Upload a file by selecting the upload input field and sending the keys with the folder/filename. Cometa offers folder uploads with files inside the headless browser in Downloads/ and uploads/ folder. Separate multiple files by semicolon. +@step(u'Upload a file by clicking on "{file_input_selector}" using file "{filename}"') +@done(u'Upload a file by clicking on "{file_input_selector}" using file "{filename}"') +def step_imp(context, file_input_selector, filename): + # save the old file detector + old_file_detector = context.browser.file_detector + # set the new file detector to LocalFileDetector + context.browser.file_detector = LocalFileDetector() + # select the upload element to send the filenames to + elements = waitSelector(context, "css", file_input_selector) + logger.debug("Before replacing filename: %s" % filename) + # get the target file or files + filename = uploadFileTarget(context, filename) + # do some logging + logger.debug("After replacing filename: %s" % filename) + logger.debug("Sending filename to input field") + # send the filename string to the input field + if type(filename) == list: + for file in filename: + elements[0].send_keys(file) + else: + elements[0].send_keys(filename) + # reset the file detector + context.browser.file_detector = old_file_detector + +# Attach a file from Downloads folder to the current feature-result. This is usefull for evaluating the file contents later on. The filename has to be the filename in the Downloads folders and will automatically link to the actual execution of the feature. Just write the filename - without mentioning the "Downloads/" folder +@step(u'Attach the "{filename}" from Downloads folder to the current execution results') +@done(u'Attach the "{filename}" from Downloads folder to the current execution results') +def step_imp(context, filename): + logger.debug("Attaching to current execution filename: %s" % filename) + # feature resultId + logger.debug("Feature ResultID: %s " % os.environ['feature_result_id']) + final_filename = [os.environ['feature_result_id']+'/'+filename] + # attaching the file to the steps + logger.debug("Step to attach the file to: %s " % context.counters['index']) + context.downloadedFiles[context.counters['index']] = final_filename + logger.debug("Attached: %s" % final_filename) + send_step_details(context, 'Attached file to to feature result') + +# Download a file and watch which file is downloaded and assign them to feature_result and step_result, linktext can be a text, css_selector or even xpath. The downloaded file name is as seen in the application. Cometa copies the archive to last_downloaded_file.suffix - where suffix is the same suffix as the original filename. @step(u'Download a file by clicking on "{linktext}"') @done(u'Download a file by clicking on "{linktext}"') def step_imp(context, linktext): if context.cloud != "local": raise CustomError("This step does not work in browserstack, please choose local browser and try again.") + # get already downloaded files + downloadedFiles = sum(list(context.downloadedFiles.values()), []) + logger.debug("Downloaded files during this feature: %s" % downloadedFiles) send_step_details(context, 'Preparing download') # get session id from browser session_id = str(context.browser.session_id) # selenoid download URL downloadURL = "http://cometa_selenoid:4444/download/%s/" % session_id - logger.debug(downloadURL) + logger.debug("DownloadURL: %s" % downloadURL) + logger.debug("Selenoid preference download directory: %s" % context.downloadDirectoryOutsideSelenium) # find and click on the element send_step_details(context, 'Looking for download link element') @@ -2160,23 +2604,32 @@ def step_imp(context, linktext): while counter < maxTimer: # get all the files downloaded from selenoid request = requests.get(downloadURL) + logger.debug("Download URL: %s" % downloadURL) + logger.debug("Request content: %s" % request.content) # get all links from download URL links = re.findall(br'href="([^\"]+)', request.content) + logger.debug("Links to be downloaded: %s" % links) # check if need to continue because files are still being downloaded CONTINUE=False + # remove already downloaded files from the links + links = [link.decode('utf-8') for link in links if "%s/%s" % (os.environ['feature_result_id'], link.decode('utf-8')) not in downloadedFiles] + + logger.debug("Final links after processing: %s" % links) + # check if files are still being downloaded for link in links: - link = link.decode('utf-8') - if re.match(r'.*\..*download\b', link): + logger.debug("Checking on download link: %s" % link) + if re.match(r'(?:.*?\..*?download\b|^\.com\..*)', link): # there are files still being downloaded - logger.debug('%s file is still being downloaded' % link) + logger.debug('==> %s file is still being downloaded' % link) CONTINUE=True break # check if atleast one file has been downloaded if len(links) > 0 and not CONTINUE: + logger.debug("There was no file to be downloaded at the link location") break # if break does not get triggered then sum the counter and sleep for 1s @@ -2188,17 +2641,19 @@ def step_imp(context, linktext): # loop over all links and download them to local folders for link in links: - link = link.decode('utf-8') logger.debug("Downloading %s..." % link) # generate link with download path and download file fileURL = "%s%s" % (downloadURL, link) # generate filename filename = urllib.parse.unquote(fileURL.split('/')[-1]).replace(" ", "_") # be careful with file names + # check if filename is empty ... then name the file cometa_download + logger.debug("calling function for saveing the file %s " % filename) downloadFileFromURL(fileURL, context.downloadDirectoryOutsideSelenium, filename) #add link to downloaded files [downloadedFiles.append("%s/%s" % (os.environ['feature_result_id'], x.split(os.sep)[-1])) for x in glob.glob(context.downloadDirectoryOutsideSelenium + "/*") if filename in x] # updated downloadedFiles in context + logger.debug("Attaching downloaded files to feature run: %s " % downloadedFiles) context.downloadedFiles[context.counters['index']] = downloadedFiles # schedule a job that runs a feature with specific key:value parameters separated by semi-colon (;) and crontab patterned schedules like "* * * * *" schedule can use and which are replaced dynamically. @@ -2260,6 +2715,332 @@ def step_imp(context): if response.status_code != 200: raise CustomError("No jobId found. That's on us though.") +# from https://stackoverflow.com/a/60049042/18232031 +def index_transform(excel_index): + match = re.match(r"^([a-z]+)(\d+)$", excel_index.lower()) + if not match: + raise CustomError("Invalid index") + + x_cell = -1 + for idx, char in enumerate(match.group(1)[::-1]): + x_cell += (26 ** idx) * (ord(char) - 96) # ord('a') == 97 + + y_cell = int(match.group(2)) - 1 + + return y_cell, x_cell + +def updateExcel(excelFilePath, cell, value, savePath): + # import openpyxl for excel modifications + from openpyxl import load_workbook + + # load excel file + wb = load_workbook(filename=excelFilePath) + + # get active sheet + sheet = wb.active + + # modify the cell with value + sheet[cell] = value + + # save excel file back + wb.save(filename=excelFilePath) + + # give some time for syncing filesystem + time.sleep(0.1) + +def updateCsv(excelFilePath, cell, value, savePath): + # importing the pandas library + import pandas as pd + + # reading the csv file + df = pd.read_csv(excelFilePath) + + # get excel equivalent to CSV index + indexes = index_transform(cell) + + # updating the column value/data + df.iloc[indexes[0], indexes[1]] = value + + # writing into the file + df.to_csv(savePath, index=False) + +# edit excel or csv file and set a value to a given cell. The file is saved on the same path. +@step(u'Edit "{file}" and set "{value}" to "{cell}"') +@done(u'Edit "{file}" and set "{value}" to "{cell}"') +def editFile(context, file, value, cell): + # get file path + filePath = uploadFileTarget(context, file) + logger.debug("File opening: %s", filePath) + + # convert csv file to execl before continuing + oldPath = filePath + filePath, isCSV = CSVtoExcel(context, filePath) + + updateExcel(filePath, cell, value, filePath) + + if isCSV: + ExcelToCSV(context, filePath, oldPath) + + updateSourceFile(context, oldPath, file) + + # give some time for syncing filesystem + time.sleep(0.1) + +# Opens excel file and tests that value is found in a given cell. +@step(u'Open "{excelfile}" and assert "{value}" is in cell "{cell}"') +@done(u'Open "{excelfile}" and assert "{value}" is in cell "{cell}"') +def editFile(context, excelfile, value, cell): + # import openpyxl for excel modifications + from openpyxl import load_workbook + + excelFilePath = uploadFileTarget(context, excelfile) + logger.debug("Excel file opening: %s", excelFilePath) + + # convert csv file to execl before continuing + oldPath = excelFilePath + excelFilePath, isCSV = CSVtoExcel(context, excelFilePath) + + # load excel file + wb = load_workbook(filename=excelFilePath) + + # get active sheet + sheet = wb.active + + # assert the cell with value + cell = sheet[cell] + logger.debug(cell.data_type) + cell_value = str(cell.internal_value).strip() + value = str(value).strip() + logger.debug("Cell value: %s" % cell_value) + logger.debug("Value to compare: %s" % value) + + assert cell_value == value, f"Cell value ({cell_value}) does not match expected value ({value})." + + # Opens excel file adds a variable to environment and sets the value as seen in Excel cell. +@step(u'Open "{excelfile}" and set environment variable "{variable_name}" with value from cell "{cell}"') +@done(u'Open "{excelfile}" and set environment variable "{variable_name}" with value from cell "{cell}"') +def editFile(context, excelfile, variable_name, cell): + # import openpyxl for excel modifications + from openpyxl import load_workbook + + excelFilePath = uploadFileTarget(context, excelfile) + logger.debug("Excel file opening: %s", excelFilePath) + + # convert csv file to execl before continuing + oldPath = excelFilePath + excelFilePath, isCSV = CSVtoExcel(context, excelFilePath) + + # load excel file + wb = load_workbook(filename=excelFilePath) + + # get active sheet + sheet = wb.active + + # assert the cell with value + logger.debug("Setting value value: %s to variable %s " % (sheet[cell].value, variable_name) ) + + # add variable + addVariable(context, variable_name, sheet[cell].value) + +# get total cells from the cell ranges and +def getTotalCells(sheet, cells, values=[]): + # split cells using semicolon (;) + cells = cells.split(";") + # save total cells to an array + totalCells = [] + # loop over all the cells and check their values agaist the values index + for cell_range in cells: + # check if cell range contains a letter only + # date pattern to look for + pattern = r'^(?P[A-Z]+)(?P[0-9]+):\1$' + # match pattern to the paramerters value + match = re.search(pattern, str(cell_range)) + # if match was found + if match: + # get all the matches + groups = match.groupdict() + # update the cell range + cell_range = "%s%s" % (cell_range, str(int(groups['row']) + len(values) - 1)) + logger.debug("New range: %s" % cell_range) + + cell = sheet[cell_range] + if type(cell) == tuple: + for x in cell: + for y in x: + totalCells.append(y) + else: + totalCells.append(cell) + return totalCells + +def CSVtoExcel(context, filePath): + NEWFILE=filePath + ISCSV=False + if filePath.endswith(".csv"): + NEWFILE="%s.xlsx" % filePath + send_step_details(context, 'Converting CSV file to Excel file.') + import pandas as pd + df = pd.read_csv(filePath) # or other encodings + df.to_excel(NEWFILE, index=None) + ISCSV=True + send_step_details(context, '') + return (NEWFILE, ISCSV) + +def ExcelToCSV(context, filePath, newPath): + if filePath.endswith(".xls") or filePath.endswith(".xlsx"): + send_step_details(context, 'Converting Excel file to CSV file.') + import pandas as pd + df = pd.read_excel(filePath) # or other encodings + df.to_csv(newPath, index=None) + return newPath + +# Assert values inside the excel file, generates a CSV file with the result. +@step(u'Open Excel from "{file}" and test that cells "{excel_range}" contain "{values}" options "{match_type}"') +@done(u'Open Excel from "{file}" and test that cells "{excel_range}" contain "{values}" options "{match_type}"') +def excel_step_implementation(context, file, excel_range, values, match_type): + + # match options + match_options = ['match exact', 'match any', 'match X number of times', 'match partial'] + # check if match type is one of next options + if match_type not in match_options: + raise CustomError("Unknown match_type, match type can be one of these options: %s." % ", ".join(match_options)) + + # import openpyxl for excel modifications + from openpyxl import load_workbook + + excelFilePath = uploadFileTarget(context, file) + logger.debug("Excel file opening: %s", excelFilePath) + + # check if file is a CSV file if so convert it to excel + OLDPATH=excelFilePath + excelFilePath, ISCSV = CSVtoExcel(context, excelFilePath) + logger.debug(f"After CSV convert: {excelFilePath} & {ISCSV}") + + # load excel file + wb = load_workbook(filename=excelFilePath) + + # get active sheet + sheet = wb.active + + # make sure that the cells and values contain the same number of object + values = values.split(";") + + # separate cells using semicolon + cells = getTotalCells(sheet, excel_range, values) + + + if len(cells) != len(values): + raise CustomError("Cells and values should contain the same number of properties separated by semicolon (;). Total cells found %d and total values found: %d." % (len(cells), len(values))) + + # save the result in a dict to later convert it to CSV + result = [] + + # compare cell value with values provided + for i in range(0, len(cells)): + result.append({ + "cell": cells[i].coordinate, + "cell_value": cells[i].value, + "expected_value": values[i], + "status": cells[i].value == values[i] + }) + + # logic based on match type + allStatus = [row['status'] for row in result] + overAllStatus = False + if match_type == 'match exact': + overAllStatus = not (False in allStatus) + elif match_type == 'match partial': + overAllStatus = True in allStatus + else: + raise CustomError("match_type: %s is not implemented yet." % match_type) + + # save date and time an later format it + dateTime = datetime.datetime.now() + + # generate a csv file + fileContent = [ + ["Feature ID", int(context.feature_id)], + ["Feature Result ID", int(os.environ['feature_result_id'])], + ["Step number (starts from 1)", context.counters['index'] + 1], + ["Date & Time", dateTime.strftime("%Y-%m-%d %H:%M:%S")], + ["Overall Status", "Passed" if overAllStatus else 'Failed'], + ["Option", match_type], + [""], + ["Comparison Details:"], + ["Cell", "Cell Value", "Expected Value", "Status"] + ] + + # print all the rows + for row in result: + fileContent.append([ + row["cell"], + row["cell_value"], + row["expected_value"], + row["status"] + ]) + + # save lists to file and link it to the step + fileName = "Excel_Assert_Values_%s.csv" % dateTime.strftime("%Y%m%d%H%M%S%f") + filePath = "%s/%s" % (context.downloadDirectoryOutsideSelenium, fileName) + + # open file and write to it + with open(filePath, 'w+', encoding="utf_8_sig") as fileHandle: + writer = csv.writer(fileHandle, dialect="excel", delimiter=",", lineterminator='\n') + writer.writerows(fileContent) + + # updated downloadedFiles in context + context.downloadedFiles[context.counters['index']] = ["%s/%s" % (os.environ['feature_result_id'], fileName)] + + if not overAllStatus: + raise CustomError("Excel assert values failed, please view the attachment for more details.") + +# possible options: do not count empty cells or include empty cells +@step(u'Open "{excelfile}" and compare the number of rows in the "{column}" column, starting from row "{starting_row}", to ensure that there are "{total_rows}" rows with option "{option}"') +@done(u'Open "{excelfile}" and compare the number of rows in the "{column}" column, starting from row "{starting_row}", to ensure that there are "{total_rows}" rows with option "{option}"') +def assert_row_count(context, excelfile, column, starting_row, total_rows, option): + # match options + assert_options = ['do not count empty cells', 'include empty cells'] + # check if match type is one of next options + if option not in assert_options: + raise CustomError("Unknown option, option can be one of these options: %s." % ", ".join(assert_options)) + + # import openpyxl for excel modifications + from openpyxl import load_workbook + + excelFilePath = uploadFileTarget(context, excelfile) + logger.debug("Excel file opening: %s", excelFilePath) + + # check if file is a CSV file if so convert it to excel + OLDPATH=excelFilePath + excelFilePath, ISCSV = CSVtoExcel(context, excelFilePath) + logger.debug(f"After CSV convert: {excelFilePath} & {ISCSV}") + + # load excel file + wb = load_workbook(filename=excelFilePath) + + # get active sheet + sheet = wb.active + # get max rows from the sheet with data + max_rows = sheet.max_row + + # get all rows from start to finish + cells = sheet[f"{column}{starting_row}:{column}{max_rows}"] + totalCells = len(cells) + + if option == "do not count empty cells": + totalCells = 0 + for cell in cells: + try: + cell = cell[0] + if cell is not None and str(cell.value).strip(): + totalCells += 1 + except Exception as err: + logger.error("Error occurred while trying to access cell value.") + logger.exception(err) + + logger.debug(f"Total cells found {totalCells} using option {option}.") + + assert totalCells == int(total_rows), f"The expected number of rows was {total_rows}, but the actual number of rows found using the '{option}' option was {totalCells}." + # saves css_selectors innertext into a list variable. use "unique:" to make values distinct/unique. Using the variable in other steps means, that it includes "unique:", e.g. use "unique:colors" in other steps. @step(u'Save list values in selector "{css_selector}" and save them to variable "{variable_name}"') @done(u'Save list values in selector "{css_selector}" and save them to variable "{variable_name}"') @@ -2275,12 +3056,12 @@ def imp(context, css_selector, variable_name): # TODO: implement options to be used in order to not polute the variable names if ( variable_name.startswith("unique") ): new_elements = [] - logger.debug("AMVARA: received variable starting with unique. Will order elements in list", element_values) + logger.debug("AMVARA: received variable starting with unique. Will order elements in list: %s." % str(element_values)) for i in element_values: if i not in new_elements: new_elements.append(i) element_values = new_elements - logger.debug("AMVARA: Ordered elements ", element_values) + logger.debug("AMVARA: Ordered elements: %s." % str(element_values)) # convert to string elements_list = ";".join(element_values) @@ -2318,27 +3099,56 @@ def step_imp(context, value_one, value_two, variance): if int(diff) > int(number_variance): raise CustomError("Difference (%s) is greater than variance (%s) specified." % (str(diff), str(number_variance))) +# generate One-Time Password (OTP) using a pairing-key +@step(u'Create one-time password of "{x}" digits using pairing-key "{value}" and save it to crypted variable "{variable_name}"') +@done(u'Create one-time password of "{x}" digits using pairing-key "{value}" and save it to crypted variable "{variable_name}"') +def step_imp(context, x, value, variable_name): + x = x.strip() + try: + x = int(x) + except: + x = 6 + send_step_details(context, 'x should be one of these numbers: 6, 7, 8. Defaulting to 6.') + logger.warn("x should be one of these numbers: 6, 7, 8. Defaulting to 6.") + if x not in (6, 7, 8): + x = 6 + send_step_details(context, 'x should be one of these numbers: 6, 7, 8. Defaulting to 6.') + logger.warn("x should be one of these numbers: 6, 7, 8. Defaulting to 6.") + import pyotp + totp = pyotp.TOTP(value, digits=x) + oneTimePassword = totp.now() + addVariable(context, variable_name, oneTimePassword, encrypted=True) -# compares a report cube's content to a list saved in variable -@step(u'Test IBM Cognos Cube Dimension to contain all values from list variable "{variable_name}" use prefix "{prefix}" and suffix "{suffix}"') -@done(u'Test IBM Cognos Cube Dimension to contain all values from list variable "{variable_name}" use prefix "{prefix}" and suffix "{suffix}"') -def imp(context, variable_name, prefix, suffix): +def test_ibm_cognos_cube(context, all_or_partial, variable_name, prefix, suffix): # get the variables from the context env_variables = json.loads(context.VARIABLES) # check if variable_name is in the env_variables index = [i for i,_ in enumerate(env_variables) if _['variable_name'] == variable_name] if len(index) == 0: - raise CustomError("No variable found with name: %s" % variable_name) + # if the length is 0 then the variable was not found + # so we can try using the value in the variable as if it is a list of values - see issue #3881 + logger.debug("Feature: %s - Will use variable as valuelist %s " % context.feature_id, variable_name) + send_step_details(context, 'Variable does not exist, will use the variable name as value seperate by semicolon.') + + # hold csv data that will later on be writen to a file + fileContent = [] + fileContent.append([""]) + fileContent.append(["Checking IBM Cognos Cube Dimention:"]) + fileContent.append(["Variable List", "Status"]) + + # save date and time an later format it + dateTime = datetime.datetime.now() # get variable value from the variables variable_value = env_variables[index[0]]['variable_value'] # split it from semi-colon (;) values = variable_value.split(";") - # save all the values not found in the report cube + # save all the values found and not found in the report cube + values_found = [] values_not_found = [] # run checks for each value in list @@ -2348,34 +3158,72 @@ def imp(context, variable_name, prefix, suffix): # trim the value to be searched search = search.strip() # click on search button - element = waitSelector(context, "xpath", '//span[text()="Search..."]') + element = waitSelector(context, "xpath", '//span[text()="Search..."]', 5) element[0].click() # wait for the popup window - waitSelector(context, "xpath", '//*[text()="Keywords:"]') + waitSelector(context, "xpath", '//*[text()="Keywords:"]', 5) # send keys once the popup window is open context.browser.switch_to.active_element.send_keys(search) # click on search - element = waitSelector(context, "xpath", '//button//span[text()="Search"]') + element = waitSelector(context, "xpath", '//button//span[text()="Search"]', 5) element[0].click() # sleep 500ms for search result to appear time.sleep(0.5) # don't throw an error if not found at the end fail the step and let user know about missing values try: # look for the search value in the search tree - waitSelector(context, "xpath", '//*[contains(@id, "Tree_Search")]//*[text()="%s"]' % search) + waitSelector(context, "xpath", '//*[contains(@id, "Tree_Search")]//*[text()="%s"]' % search, 5) logger.debug("Found %s in the report cube..." % search) + fileContent.append([search, "Found"]) + values_found.append("%s (%s)" % (value, search)) except Exception as e: logger.error(str(e)) # append the value in not founds values_not_found.append("%s (%s)" % (value, search)) + fileContent.append([search, "Not Found"]) # go back and search for next value - element = waitSelector(context, "css", "#idSourcesPane_btnClearSearch") + element = waitSelector(context, "css", "#idSourcesPane_btnClearSearch", 5) element[0].click() + fileContent.insert(0, ["Feature ID", int(context.feature_id)]) + fileContent.insert(1, ["Feature Result ID", int(os.environ['feature_result_id'])]) + fileContent.insert(2, ["Step number (starts from 1)", context.counters['index'] + 1]) + fileContent.insert(3, ["Variable", variable_name]) + fileContent.insert(4, ["Date & Time", dateTime.strftime("%Y-%m-%d %H:%M:%S")]) + fileContent.insert(5, ["Overall Status", "Failed" if len(values_not_found) > 0 else 'Passed']) + + # save lists to file and link it to the step + fileName = "IBM_cube_test_%s.csv" % dateTime.strftime("%Y%m%d%H%M%S%f") + filePath = "%s/%s" % (context.downloadDirectoryOutsideSelenium, fileName) + + # open file and write to it + with open(filePath, 'w+', encoding="utf_8_sig") as fileHandle: + writer = csv.writer(fileHandle, dialect="excel", delimiter=",", lineterminator='\n') + writer.writerows(fileContent) + + # updated downloadedFiles in context + context.downloadedFiles[context.counters['index']] = ["%s/%s" % (os.environ['feature_result_id'], fileName)] + # finally check if values_not_found has something if so fail step and let user know about the missing values - if len(values_not_found) > 0: + if (len(values_not_found) > 0 and all_or_partial == 'all') or (len(values_found) == 0): missin_values = "; ".join(values_not_found) raise CustomError("Here are the missing values in Report Cube: %s" % missin_values) +# compares a report cube's content to a list saved in variable +@step(u'Test IBM Cognos Cube Dimension to contain all values from list variable "{variable_name}" use prefix "{prefix}" and suffix "{suffix}"') +@done(u'Test IBM Cognos Cube Dimension to contain all values from list variable "{variable_name}" use prefix "{prefix}" and suffix "{suffix}"') +def imp(context, variable_name, prefix, suffix): + test_ibm_cognos_cube(context, 'all', variable_name, prefix, suffix) + +# compares a report cube's content to a list saved in variable +@step(u'Test IBM Cognos Cube Dimension to contain "{all_or_partial}" values from list variable "{variable_name}" use prefix "{prefix}" and suffix "{suffix}"') +@done(u'Test IBM Cognos Cube Dimension to contain "{all_or_partial}" values from list variable "{variable_name}" use prefix "{prefix}" and suffix "{suffix}"') +def imp(context, all_or_partial, variable_name, prefix, suffix): + + # check the value for all_or_partial + if all_or_partial not in ['all', 'partial']: + raise CustomError("all_or_partial value should be 'all' or 'partial'.") + + test_ibm_cognos_cube(context, all_or_partial, variable_name, prefix, suffix) @step(u'Test list of "{css_selector}" elements to contain "{all_or_partial}" values from list variable "{variable_names}" use prefix "{prefix}" and suffix "{suffix}"') @done(u'Test list of "{css_selector}" elements to contain "{all_or_partial}" values from list variable "{variable_names}" use prefix "{prefix}" and suffix "{suffix}"') @@ -2386,10 +3234,14 @@ def step_test(context, css_selector, all_or_partial, variable_names, prefix, suf if all_or_partial != 'all' and all_or_partial != 'partial': raise CustomError("all_or_partial value must be all or parcial") - # get all the values from css_selector - elements = waitSelector(context, "css", css_selector) + # try getting the elements else set empty array + try: + # get all the values from css_selector + elements = waitSelector(context, "css", css_selector) + except CustomError as customError: + elements = [] # get elements values - element_values = [element.get_attribute("innerText") or element.get_attribute("value") for element in elements] + element_values = [(element.get_attribute("innerText") or element.get_attribute("value")).strip() for element in elements] # get the variables from the context env_variables = json.loads(context.VARIABLES) @@ -2409,7 +3261,7 @@ def step_test(context, css_selector, all_or_partial, variable_names, prefix, suf values.extend(variable_value.split(";")) # add prefix and suffix to the values - values = [("%s%s%s" % (prefix, value, suffix)).strip() for value in values] + values = [("%s%s%s" % (prefix, value.strip(), suffix)).strip() for value in values] # equalize the lenght of both lists values_eq = values[:len(element_values)] if len(values) > len(element_values) else values @@ -2427,15 +3279,17 @@ def step_test(context, css_selector, all_or_partial, variable_names, prefix, suf dateTime = datetime.datetime.now() # save lists to file and link it to the step - fileName = "list_comparison_%s.csv" % dateTime.strftime("%Y%m%d%H%M%S") + fileName = "list_comparison_%s.csv" % dateTime.strftime("%Y%m%d%H%M%S%f") filePath = "%s/%s" % (context.downloadDirectoryOutsideSelenium, fileName) status = False - fileContent = "" + fileContent = [] if all_or_partial == 'all': - fileContent += """Comparison Details Unsorted:Element List|Variable List|Status\n""" + fileContent.append([""]) + fileContent.append(["Comparison Details Unsorted:"]) + fileContent.append(["Element List", "Variable List", "Status"]) # print element list and variable list for i in range(0, len(values) if len(values) >= len(element_values) else len(element_values)): @@ -2458,14 +3312,16 @@ def step_test(context, css_selector, all_or_partial, variable_names, prefix, suf if val == '' or element_val == '': result = "Ignored" - fileContent += """%s|%s|%s\n""" % ( + fileContent.append([ element_val, # Element List value val, # Variable List value, result # result of comparison - ) + ]) # add additional text to fileContent - fileContent += """\nComparison Details Sorted:\nSorted Element List|Sorted Variable List|Status\n""" + fileContent.append([""]) + fileContent.append(["Comparison Details Sorted:"]) + fileContent.append(["Sorted Element List", "Sorted Variable List", "Status"]) # print sorted element list and sorted variable list for i in range(0, len(values_sorted) if len(values_sorted) >= len(element_values_sorted) else len(element_values_sorted)): @@ -2488,16 +3344,18 @@ def step_test(context, css_selector, all_or_partial, variable_names, prefix, suf if val == '' or element_val == '': result = "Ignored" - fileContent += """%s|%s|%s\n""" % ( + fileContent.append([ element_val, # Element List value val, # Variable List value, result # result of comparison - ) + ]) status = (values_eq == element_values_eq) or (values_sorted_eq == element_sorted_values_eq) else: # add additional text to fileContent - fileContent += """\nChecking if variable list values are in elements list:\nVariable List|In Elements List\n""" + fileContent.append([""]) + fileContent.append(["Checking if variable list values are in elements list:"]) + fileContent.append(["Variable List", "In Elements List"]) # print variable list and if they are in element list with yes/no atLeastOneValueInElements = False @@ -2507,38 +3365,25 @@ def step_test(context, css_selector, all_or_partial, variable_names, prefix, suf result = "Yes" atLeastOneValueInElements = True # print result to the file - fileContent += """%s|%s\n""" % ( + fileContent.append([ value, # Variable List value, result # result of comparison - ) + ]) status = atLeastOneValueInElements - - fileContentStart = """sep=| -Feature ID|%d -Feature Result ID|%d -Step number (starts from 1)|%d -Selector|%s -Date & Time|%s -Overall Status|%s -Option|%s - -""" % ( - int(context.feature_id), # Feature ID - int(os.environ['feature_result_id']), # Feature Result ID - context.counters['index'] + 1, # Step number - css_selector, # Selector - dateTime.strftime("%Y-%m-%d %H:%M:%S"), # Date & Time - "Passed" if status else 'Failed', # Status (Global) - all_or_partial # option value - ) - - fileContent = fileContentStart + fileContent + fileContent.insert(0, ["Feature ID", int(context.feature_id)]) + fileContent.insert(1, ["Feature Result ID", int(os.environ['feature_result_id'])]) + fileContent.insert(2, ["Step number (starts from 1)", context.counters['index'] + 1]) + fileContent.insert(3, ["Selector", css_selector]) + fileContent.insert(4, ["Date & Time", dateTime.strftime("%Y-%m-%d %H:%M:%S")]) + fileContent.insert(5, ["Overall Status", "Passed" if status else 'Failed']) + fileContent.insert(6, ["Option", all_or_partial]) # open file and write to it - with open(filePath, 'w+') as fileHandle: - fileHandle.write(fileContent) + with open(filePath, 'w+', encoding="utf_8_sig") as fileHandle: + writer = csv.writer(fileHandle, dialect="excel", delimiter=",", lineterminator='\n') + writer.writerows(fileContent) # updated downloadedFiles in context context.downloadedFiles[context.counters['index']] = ["%s/%s" % (os.environ['feature_result_id'], fileName)] @@ -2548,13 +3393,101 @@ def step_test(context, css_selector, all_or_partial, variable_names, prefix, suf else: raise CustomError("Lists do not match, please check the attachment.") +@step(u'Assert "{value_one}" to be same as "{value_two}"') +@done(u'Assert "{value_one}" to be same as "{value_two}"') +def assert_imp(context, value_one, value_two): + assert_failed_error = f"{value_one} does not match {value_two}" + assert_failed_error = logger.mask_values(assert_failed_error) + assert value_one == value_two, assert_failed_error + +@step(u'Assert "{value_one}" to contain "{value_two}"') +@done(u'Assert "{value_one}" to contain "{value_two}"') +def assert_imp(context, value_one, value_two): + assert_failed_error = f"{value_one} does not contain {value_two}" + assert_failed_error = logger.mask_values(assert_failed_error) + assert value_two in value_one, assert_failed_error + +@step(u'Loop "{x}" times starting at "{index}" and do') +@done(u'Loop "{x}" times starting at "{index}" and do') +def step_loop(context, x, index): + # check if there was an error during loop + err = False + # save current step index before continuing + currentStepIndex = context.counters['index'] + # save substep index + subStepIndex = 0 + # get all the sub steps from text + steps = context.text + # set context.insideLoop to true + context.insideLoop = True + # set executedStepsInLoop value + context.executedStepsInLoop = 0 + # match regexp to find steps and step descriptions + steps = list(filter(None, re.findall(r".*\n?(?:\t'''(?:.|\n)+?'''\n?)?", steps))) + + try: + logger.debug("Steps: {}".format(steps)) + for i in range(int(index), int(x) + int(index)): + # update subStepIndex to currentStepIndex + subStepIndex = currentStepIndex + # add a index variable to context.JOB_PARAMETERS + params = json.loads(context.PARAMETERS) + params['index'] = i + context.PARAMETERS = json.dumps(params) + for step in steps: + logger.debug("Executing step: {}".format(step)) + # update steps executed + context.executedStepsInLoop += 1 + # update subStepIndex with +1 on each step + subStepIndex = subStepIndex + 1 + # update stepIndex with subStepIndex + context.counters['index'] = subStepIndex + # replace ''' to """ + step = step.replace("'''", "\"\"\"") + # print some information + send_step_details(context, "Executing step '%s' inside loop." % step.split('\n')[0]) + # execute the step + context.execute_steps(step) + except Exception as error: + err = True + err_msg = error + + # update current step index to Loop again + context.counters['index'] = currentStepIndex + # set jumpLoop value to steps count + context.jumpLoopIndex = len(steps) + # remove 1 execution from executedStepsInLoop + context.executedStepsInLoop -= context.jumpLoopIndex + + if err: + raise CustomError(err_msg) + +@step(u'End Loop') +@done(u'End Loop') +def step_endLoop(context): + try: + # remove index variable from context.JOB_PARAMETERS + params = json.loads(context.PARAMETERS) + del params['index'] + context.PARAMETERS = json.dumps(params) + except Exception as error: + logger.error("Some error occured while trying to remove 'index' parameter from parameters. Maybe the loop had 0 iterations.") + logger.debug("Here is the list of all parameters:") + logger.debug(context.PARAMETERS) + # set context.insideLoop to false + context.insideLoop = False + # reset jumpLoopIndex + context.jumpLoopIndex = 0 + # reset executedStepsInLoop + context.executedStepsInLoop = 0 + @step(u'Test list of "{css_selector}" elements to contain all or partial values from list variable "{variable_names}" use prefix "{prefix}" and suffix "{suffix}"') @done(u'Test list of "{css_selector}" elements to contain all or partial values from list variable "{variable_names}" use prefix "{prefix}" and suffix "{suffix}"') -def step(context, css_selector, variable_names, prefix, suffix): +def step_imp(context, css_selector, variable_names, prefix, suffix): # get all the values from css_selector elements = waitSelector(context, "css", css_selector) # get elements values - element_values = [element.get_attribute("innerText") or element.get_attribute("value") for element in elements] + element_values = [(element.get_attribute("innerText") or element.get_attribute("value")).strip() for element in elements] # get the variables from the context env_variables = json.loads(context.VARIABLES) @@ -2574,7 +3507,7 @@ def step(context, css_selector, variable_names, prefix, suffix): values.extend(variable_value.split(";")) # add prefix and suffix to the values - values = [("%s%s%s" % (prefix, value, suffix)).strip() for value in values] + values = [("%s%s%s" % (prefix, value.strip(), suffix)).strip() for value in values] # equalize the lenght of both lists values_eq = values[:len(element_values)] if len(values) > len(element_values) else values @@ -2592,28 +3525,21 @@ def step(context, css_selector, variable_names, prefix, suffix): dateTime = datetime.datetime.now() # save lists to file and link it to the step - fileName = "list_comparison_%s.csv" % dateTime.strftime("%Y%m%d%H%M%S") + fileName = "list_comparison_%s.csv" % dateTime.strftime("%Y%m%d%H%M%S%f") filePath = "%s/%s" % (context.downloadDirectoryOutsideSelenium, fileName) - fileContent = """sep=| -Feature ID|%d -Feature Result ID|%d -Step number (starts from 1)|%d -Selector|%s -Date & Time|%s -Overall Status|%s -Option|%s - -Comparison Details Unsorted: -Element List|Variable List|Status\n""" % ( - int(context.feature_id), # Feature ID - int(os.environ['feature_result_id']), # Feature Result ID - context.counters['index'] + 1, # Step number - css_selector, # Selector - dateTime.strftime("%Y-%m-%d %H:%M:%S"), # Date & Time - "Passed" if (values_eq == element_values_eq) or (values_sorted_eq == element_sorted_values_eq) else 'Failed', # Status (Global) - "All/Partial (Not changeable for now)" # option value - ) + fileContent = [ + ["Feature ID", int(context.feature_id)], + ["Feature Result ID", int(os.environ['feature_result_id'])], + ["Step number (starts from 1)", context.counters['index'] + 1], + ["Selector", css_selector], + ["Date & Time", dateTime.strftime("%Y-%m-%d %H:%M:%S")], + ["Overall Status", "Passed" if (values_eq == element_values_eq) or (values_sorted_eq == element_sorted_values_eq) else 'Failed'], + ["Option", "All/Partial (Not changeable for now)"], + [""], + ["Comparison Details Unsorted:"], + ["Element List", "Variable List", "Status"] + ] # print element list and variable list for i in range(0, len(values) if len(values) >= len(element_values) else len(element_values)): @@ -2636,14 +3562,16 @@ def step(context, css_selector, variable_names, prefix, suffix): if val == '' or element_val == '': result = "Ignored" - fileContent += """%s|%s|%s\n""" % ( + fileContent.append([ element_val, # Element List value val, # Variable List value, result # result of comparison - ) + ]) # add additional text to fileContent - fileContent += """\nComparison Details Sorted:\nSorted Element List|Sorted Variable List|Status\n""" + fileContent.append([""]) + fileContent.append(["Comparison Details Sorted:"]) + fileContent.append(["Sorted Element List", "Sorted Variable List", "Status"]) # print sorted element list and sorted variable list for i in range(0, len(values_sorted) if len(values_sorted) >= len(element_values_sorted) else len(element_values_sorted)): @@ -2666,15 +3594,16 @@ def step(context, css_selector, variable_names, prefix, suffix): if val == '' or element_val == '': result = "Ignored" - fileContent += """%s|%s|%s\n""" % ( + fileContent.append([ element_val, # Element List value val, # Variable List value, result # result of comparison - ) + ]) # open file and write to it - with open(filePath, 'w+') as fileHandle: - fileHandle.write(fileContent) + with open(filePath, 'w+', encoding="utf_8_sig") as fileHandle: + writer = csv.writer(fileHandle, dialect="excel", delimiter=",", lineterminator='\n') + writer.writerows(fileContent) # updated downloadedFiles in context context.downloadedFiles[context.counters['index']] = ["%s/%s" % (os.environ['feature_result_id'], fileName)] @@ -2683,3 +3612,28 @@ def step(context, css_selector, variable_names, prefix, suffix): return True else: raise CustomError("Lists do not match, please check the attachment.") + + +@step(u'Define Custom Error Message for next step: "{error_message}"') +@done(u'Define Custom Error Message for next step: "{error_message}"') +def step_imp(context, error_message): + # get next step index + next_step = context.counters['index'] + 1 + # get all the steps from the environment + steps = json.loads(os.environ['STEPS']) + # check that there is another step after the current step + if len(steps) > next_step: + # update the step definition + steps[next_step].update({ + "custom_error": logger.mask_values(error_message) + }) + os.environ['STEPS'] = json.dumps(steps) + logger.info(f"Custom error message set for step: {steps[next_step]['step_content']}") + else: + logger.warn(f"This is the last step, cannot assign custom error message to next step.") + +# Ignores undefined steps +@step(u'{step}') +@done(u'{step}') +def step_imp(context, step): + raise NotImplementedError(f"Unknown step found: '{step}'") diff --git a/backend/behave/cometa_itself/steps/tools/cognos.py b/backend/behave/cometa_itself/steps/tools/cognos.py index d9d053dd..891f6a98 100644 --- a/backend/behave/cometa_itself/steps/tools/cognos.py +++ b/backend/behave/cometa_itself/steps/tools/cognos.py @@ -4,21 +4,11 @@ import os import time import logging -from selenium.common.exceptions import StaleElementReferenceException +from selenium.common.exceptions import StaleElementReferenceException, NoSuchElementException from src.backend.common import * # setup logging -logger = logging.getLogger(__name__) -logger.setLevel(BEHAVE_DEBUG_LEVEL) -# create a formatter for the logger -formatter = logging.Formatter(LOGGER_FORMAT, LOGGER_DATE_FORMAT) -# create a stream logger -streamLogger = logging.StreamHandler() -# set the format of streamLogger to formatter -streamLogger.setFormatter(formatter) -# add the stream handle to logger -logger.addHandler(streamLogger) - +logger = logging.getLogger('FeatureExecution') """ Python library with functions used for IBM Cognos @@ -138,7 +128,7 @@ def auto_select_cognos_prompts(context, **kwargs): # ... There might by error pages in between ... with css:INPUT#cmdOK - they will fail the whole test logger.debug('Waiting for prompts or content to load') try: - waitSelector(context, 'css', 'table[lid=defaultPromptPage], .clsPromptComponent, .pageViewContent, body.viewer table, .clsViewerPage') + waitSelector(context, 'css', 'table[lid*=defaultPromptPage], .clsPromptComponent, .pageViewContent, body.viewer table, .clsViewerPage') except: logger.debug("No interesting content found ... this is most probly due to an error in the report. Will return false from prompt magic") return False @@ -151,6 +141,7 @@ def auto_select_cognos_prompts(context, **kwargs): if not inputs[0].is_displayed(): inputs = [] prompts = clsPromptComponents + inputs + logger.debug(prompts) isPrompt = len(prompts) > 0 if not isPrompt: logger.debug("This is not a promptpage ... so we might be done?") @@ -172,26 +163,119 @@ def auto_select_cognos_prompts(context, **kwargs): logger.debug('Please provide a value for the prompt "%s"' % str(id)) raise CustomError('Please provide a value for the prompt "%s"' % str(id)) - # Click on OK - # Does not work reliable because of language .... context.browser.find_element_by_xpath("//div[@class=\"clsPromptComponent\"]/button[text()='OK']|//button[span=\"OK\"]|//button[.=\"Finish\"]").click() - # Try clicking ok ... - # ... autogenerate prompts have "OK" Button - # ... promptPages have a promptButton with type finsh in a generate attribute - logger.debug("Trying to click ok on promptpage") - # FIXME ... needs fallbacks for autosubmit, second promptpage with next .... - elem = context.browser.find_element_by_xpath("//div[@class=\"clsPromptComponent\"]/button[text()='OK']|//button[span=\"OK\"]|//button[@*=\"finish\" and not(@disabled)]|//button[@*=\"oCV_NS_.promptAction('finish')\" and not(@disabled)]") + try: + # Click on OK + # Does not work reliable because of language .... context.browser.find_element_by_xpath("//div[@class=\"clsPromptComponent\"]/button[text()='OK']|//button[span=\"OK\"]|//button[.=\"Finish\"]").click() + # Try clicking ok ... + # ... autogenerate prompts have "OK" Button + # ... promptPages have a promptButton with type finsh in a generate attribute + logger.debug("Trying to click ok on promptpage") + # FIXME ... needs fallbacks for autosubmit, second promptpage with next .... + elem = context.browser.find_element_by_xpath("//div[@class=\"clsPromptComponent\"]/button[text()='OK']|//button[span=\"OK\"]|//button[@*=\"finish\" and not(@disabled)]|//button[@*=\"oCV_NS_.promptAction('finish')\" and not(@disabled)]") + + # if found click on submit + elem.click() + logger.debug("Clicked OK button") + except NoSuchElementException: + logger.info("NoSuchElementException - so there might be no OK button or something else happened. You might want to have a look at this") + return True + except StaleElementReferenceException: + # for some reason exception jumps after click this can be ignored. + logger.debug("[NOK] For some reason exception jumps ... might look into this deeper at some time") + return False + except: + logger.debug("Exception ... other element would receive the click or element not clickable") + # sleep some time and click again + time.sleep(0.5) + try: + elem.click() + logger.debug("Clicked OK button") + except: + logger.debug("[NOK] Still haveing exception ... will pass") + return False + + # Giving IBM Cognos page some time to react on click + logger.debug("Sleeping 0.5s to give IBM Cognos page some time to react on the click") + time.sleep(0.5) + logger.debug("Waiting for IBM Cognos progress spinner to disappear") + wait_until_selector_fails(context, '[src*="progress.gif"]') + logger.debug(" ==> DONE with filling this prompt magic, there might be more ") + + return True + +def auto_select_cognos_prompts_aso(context, **kwargs): + """ + Automatically fills the prompt controls within a report prompt page + + @param parameters: Key value dict of parameters (optional) + """ + + # Load kwargs + parameters = kwargs.get('parameters', {}) + iteration = 0 + while iteration<20: + iteration += 1 + logger.debug('Prompt #%d' % iteration) + + # Wait for either the value prompts or the report content + # ... There might by error pages in between ... with css:INPUT#cmdOK - they will fail the whole test + logger.debug('Waiting for prompts or content to load') + try: + waitSelector(context, 'css', '.clsPromptComponent') + except: + logger.debug("It looks like there are no prompts to fill ... maybe there is an error or report really has no prompts and is correct.") + logger.debug("Will return True anyways.") + return True + + # Check if current view is a prompt page + # If it's not, it means the report has successfully loaded without any prompt + clsPromptComponents = context.browser.find_elements_by_css_selector('.clsPromptComponent:not([pt])') + # inputs = context.browser.find_elements_by_xpath('//table[@lid="defaultPromptPage"]//input/parent::td/parent::tr/parent::tbody/parent::table/parent::div') + # if len(inputs) > 0: + # if not inputs[0].is_displayed(): + # inputs = [] + prompts = clsPromptComponents # + inputs + logger.debug(prompts) + isPrompt = len(prompts) > 0 + if not isPrompt: + logger.debug("This is not a promptpage ... so we might be done?") + break + print('Iteration %d: Found %d prompts in Cognos report.' % (iteration, len(prompts))) + logger.debug('Iteration %d: Found %d prompts in Cognos report.' % (iteration, len(prompts))) + for index, val in enumerate(prompts): + # Auto select prompt value + try: + logger.debug("Prompt #%d --> Will call select prompt magic now" % (index+1) ) + selectCognosPrompt_rro(context, controlIndex=index, parameters=parameters, prompts=prompts) + except PromptReferenceNotFound: + logger.debug('Unable to get prompt reference input at iteration %d of input index %d' % (iteration, index)) + raise CustomError('Unable to get prompt reference input at iteration %d of input index %d' % (iteration, index)) + except PromptReferenceNameNotFound: + logger.debug('Unable to get prompt reference name ID at iteration %d of input index %d' % (iteration, index)) + raise CustomError('Unable to get prompt reference name ID at iteration %d of input index %d' % (iteration, index)) + except PromptValueEmpty as id: + logger.debug('Please provide a value for the prompt "%s"' % str(id)) + raise CustomError('Please provide a value for the prompt "%s"' % str(id)) - # if found click on submit try: + # Click on OK + # Does not work reliable because of language .... context.browser.find_element_by_xpath("//div[@class=\"clsPromptComponent\"]/button[text()='OK']|//button[span=\"OK\"]|//button[.=\"Finish\"]").click() + # Try clicking ok ... + # ... autogenerate prompts have "OK" Button + # ... promptPages have a promptButton with type finsh in a generate attribute + logger.debug("Trying to click ok on promptpage") + # FIXME ... needs fallbacks for autosubmit, second promptpage with next .... + elem = context.browser.find_element_by_xpath("//div[@class=\"clsPromptComponent\"]/button[text()='OK']|//button[span=\"OK\"]|//button[@*=\"finish\" and not(@disabled)]|//button[@*=\"oCV_NS_.promptAction('finish')\" and not(@disabled)]") + + # if found click on submit elem.click() logger.debug("Clicked OK button") except StaleElementReferenceException: # for some reason exception jumps after click this can be ignored. logger.debug("[NOK] For some reason exception jumps ... might look into this deeper at some time") + return False except NoSuchElementException as exc: logger.info("NoSuchElementException - so there might be no OK button or something else happened. You might want to have a look at this") - logger.debug(exc) - return False except: logger.debug("Exception ... other element would receive the click or element not clickable") # sleep some time and click again @@ -201,6 +285,7 @@ def auto_select_cognos_prompts(context, **kwargs): logger.debug("Clicked OK button") except: logger.debug("[NOK] Still haveing exception ... will pass") + return False # Giving IBM Cognos page some time to react on click logger.debug("Sleeping 0.5s to give IBM Cognos page some time to react on the click") @@ -367,14 +452,14 @@ def selectCognosPrompt_rro(context, **kwargs): # If we have a value ... try to find it in selector if value: logger.debug("Value is set to %s - trying to find it in selector" % value) - elm=selector.find_elements_by_xpath('.//option[@value="%s"]' % value) + elm=selector.find_elements_by_xpath('.//option[@value="%s"] | .//option[text()="%s"] | .//option[@dv="%s"]' % (value, value, value)) if len(elm) > 0: logger.debug("Found option for the value") try: elm[0].click() logger.debug("Clicked on option") except: - elm[0].selected=true + elm[0].selected=True logger.debug("Setting selected to true") # we have no value for this selector ... fallback to choosing the one with index=optionIndex else: diff --git a/backend/behave/cometa_itself/steps/tools/common.py b/backend/behave/cometa_itself/steps/tools/common.py index 725d47ba..f29dba20 100644 --- a/backend/behave/cometa_itself/steps/tools/common.py +++ b/backend/behave/cometa_itself/steps/tools/common.py @@ -5,31 +5,35 @@ from .variables import * from functools import wraps from selenium.webdriver.remote.webelement import WebElement -import time, requests, json, os, datetime +from selenium.common.exceptions import InvalidSelectorException, NoSuchElementException +import time, requests, json, os, datetime, sys, subprocess, re, shutil from src.backend.common import * +from src.backend.utility.cometa_logger import CometaLogger +sys.path.append("/code") +from secret_variables import COMETA_UPLOAD_ENCRYPTION_PASSPHRASE # setup logging -logger = logging.getLogger(__name__) -logger.setLevel(BEHAVE_DEBUG_LEVEL) -# create a formatter for the logger -formatter = logging.Formatter(LOGGER_FORMAT, LOGGER_DATE_FORMAT) -# create a stream logger -streamLogger = logging.StreamHandler() -# set the format of streamLogger to formatter -streamLogger.setFormatter(formatter) -# add the stream handle to logger -logger.addHandler(streamLogger) +logging.setLoggerClass(CometaLogger) +logger = logging.getLogger('FeatureExecution') """ Python library with common utility functions """ +class CometaTimeoutException(Exception): + pass + +class CometaMaxTimeoutReachedException(Exception): + pass + # timeout error # throws an CustomError exception letting user know about the issue -def timeoutError(signum, frame, waitedFor=STEP_TIMEOUT, error="Step took more than %ds. Please try different configuration for the step or contact a system administrator to help you with the issue." % STEP_TIMEOUT): - print("Step took more than %ds" % waitedFor) - raise CustomError(error) +def timeoutError(signum, frame, timeout=MAX_STEP_TIMEOUT, error=None): + if error is None: + error = f"Step took more than configured time: {timeout}s." + raise CometaTimeoutException(error) +# DEPRECATED: def timeout( *_args, **_kwargs ): def decorator(func): @wraps(func) @@ -65,8 +69,10 @@ def execute(*args, **kwargs): # @param context - Object containing the webdriver context # @param selector_type: string - Type of selector to use, see below code for possible types # @param selector: string - Selector to use -@timeout("Waited for seconds but unable to find specified element.") -def waitSelector(context, selector_type, selector): +# @timeout("Waited for seconds but unable to find specified element.") +def waitSelector(context, selector_type, selector, max_timeout=None): + # set the start time for the step + start_time = time.time() #2288 - Split : id values into a valid css selector # example: "#hello:world" --> [id*=hello][id*=world] selectorWords = selector.split(' ') @@ -101,7 +107,7 @@ def waitSelector(context, selector_type, selector): types_new[selector_type] = selector_type_value types_new.update(types) # Loop until maxtries is reached and then exit with exception - while True: + while (time.time() - start_time < max_timeout if max_timeout is not None else True): for selec_type in list(types_new.keys()): try: elements = eval(types_new.get(selec_type, "css")) @@ -109,13 +115,31 @@ def waitSelector(context, selector_type, selector): if isinstance(elements, WebElement) or len(elements) > 0: return elements except CustomError as err: - logger.debug(err) + logger.error("Custom Error Exception occured during the selector find, will exit the search.") + logger.exception(err) + raise + except CometaTimeoutException as err: + logger.error("Timeout Exception occured during the selector find, will exit the search.") + logger.exception(err) # Max retries exceeded, raise error - raise CustomError("Waited for %ds but unable to find %s for type %s" % (MAXRETRIES, selector, selector_type)) - except: - pass + raise + except InvalidSelectorException as err: + logger.debug(f"Invalid Selector Exception: Selector Type: {selec_type}, Selector: {selector}.") + except NoSuchElementException as err: + logger.debug(f"No Such Element Exception: Selector Type: {selec_type}, Selector: {selector}.") + except KeyError: + raise + except Exception as err: + # logger.error("Exception occured during the selector find, will continue looking for the element.") + # logger.exception(err) + logger.error("Exception raised during the element search. No need to panic, will look with other type of selectors. More details in debug mode.") + logger.debug(f"Selector: {selector}") + logger.debug(f"Selector Type: {selec_type}") + logger.exception(err) # give page some time to render the search time.sleep(1) + raise CometaMaxTimeoutReachedException(f"Programmed to find the element in {max_timeout} seconds, max timeout reached.") + def element_has_class(element, classname): """ @@ -174,4 +198,110 @@ def click_element_by_css(context, selector): for el in elem: if el.is_displayed(): el.click() - break \ No newline at end of file + break + +def click_element(context, element): + if element.is_displayed(): + element.click() + +def tempFile(source): + # file ext + filename = os.path.basename(source).split('/')[-1] + target = "/tmp/%s" % filename + + # check if file exists + if os.path.exists(target): + # try removing the file + logger.debug(f"{target} file exists, trying to remove it.") + try: + os.remove(target) + except Exception as err: + logger.error("Unable to remove the file.") + logger.exception(err) + + # get the timestamp + ts = time.time() + logger.debug(f"Setting a different filename: /tmp/{ts}-{filename}") + target = f"/tmp/{ts}-{filename}" + + logger.info(f"TMP file will be created at {target} for {source}.") + + return target + +def decryptFile(source): + # get target file for the source + target = tempFile(source) + + logger.debug(f"Decrypting source {source}") + + try: + result = subprocess.run(["bash", "-c", f"gpg --output {target} --batch --passphrase {COMETA_UPLOAD_ENCRYPTION_PASSPHRASE} -d {source}"], stdout=subprocess.PIPE, stderr=subprocess.PIPE) + if result.returncode > 0: + # get the error + errOutput = result.stderr.decode('utf-8') + logger.error(errOutput) + raise Exception('Failed to decrypt the file, please contact an administrator.') + return target + except Exception as err: + raise Exception(str(err)) + +def encryptFile(source, target): + logger.debug(f"Encrypting source {source} to {target}") + + try: + result = subprocess.run(["bash", "-c", f"gpg --output {target} --batch --yes --passphrase {COMETA_UPLOAD_ENCRYPTION_PASSPHRASE} --symmetric --cipher-algo AES256 {source}"], stdout=subprocess.PIPE, stderr=subprocess.PIPE) + if result.returncode > 0: + # get the error + errOutput = result.stderr.decode('utf-8') + logger.error(errOutput) + raise Exception('Failed to encypt the file, please contact an administrator.') + return target + except Exception as err: + raise Exception(str(err)) + +def uploadFileTarget(context, source): + logger.debug(f"Source before processing: {source}") + files = source.split(";") + processedFiles = [] + for file in files: + # throw error in case no downloads is found + if 'downloads' not in file.lower() and 'uploads' not in file.lower(): + raise CustomError('Unknown file path, please use uploads/ or downloads/ to define where the file is located at.') + + logger.debug(f"Getting complete path for {file}") + filePath = re.sub("(?:D|d)ownloads\/", f"{context.downloadDirectoryOutsideSelenium}/", file) + filePath = re.sub("(?:U|u)ploads\/", f"{context.uploadDirectoryOutsideSelenium}/", filePath) + logger.debug(f"Final path for {file}: {filePath}") + + # check if file exists + if not os.path.exists(filePath): + raise CustomError(f"{file} does not exist, if this error persists please contact an administrator.") + + if 'downloads' in filePath: + # get temp file + target = tempFile(filePath) + + # copy the file to the target + shutil.copy2(filePath, target) + elif 'uploads' in filePath: + # decrypt the file and get the target + target = decryptFile(filePath) + + # append the target to the context for later processing and cleaning + context.tempfiles.append(target) + # append to processed files as well + processedFiles.append(target) + + return processedFiles if len(processedFiles) > 1 else processedFiles.pop() + +def updateSourceFile(context, source, target): + logger.debug(f"Source before processing: {source}") + target = re.sub("(?:D|d)ownloads\/", f"{context.downloadDirectoryOutsideSelenium}/", target) + target = re.sub("(?:U|u)ploads\/", f"{context.uploadDirectoryOutsideSelenium}/", target) + + if 'downloads' in target: + # copy the file to the target + shutil.copy2(source, target) + elif 'uploads' in target: + # decrypt the file and get the target + target = encryptFile(source, target) diff --git a/backend/behave/cometa_itself/steps/tools/expected_conditions.py b/backend/behave/cometa_itself/steps/tools/expected_conditions.py new file mode 100644 index 00000000..7c406551 --- /dev/null +++ b/backend/behave/cometa_itself/steps/tools/expected_conditions.py @@ -0,0 +1,38 @@ +from selenium.webdriver.support import expected_conditions as EC +from selenium.webdriver.remote.webelement import WebElement +from selenium.common.exceptions import StaleElementReferenceException +from selenium.webdriver.remote.webdriver import WebDriver + +class element_to_be_interactable(object): + """ + An expectation for checking that an element is interactable. + element - element in question that needs to become interactable. + returns the boolean depending if it is interactable. + """ + def __init__(self, element: WebElement): + self.element: WebElement = element + + def __call__(self, driver: WebDriver) -> bool: + element = EC.visibility_of(self.element)(driver) + if element and element.is_enabled(): + return True + return False + +class text_to_be_present_in_element_value(object): + """ + An expectation for checking if the given text is present in the element's + locator, text + """ + def __init__(self, element, text_): + self.element = element + self.text = text_ + + def __call__(self, driver): + try: + element_text = self.element.get_attribute("value") + if element_text: + return self.text in element_text + else: + return False + except StaleElementReferenceException: + return False \ No newline at end of file diff --git a/backend/behave/cometa_itself/steps/tools/variables.py b/backend/behave/cometa_itself/steps/tools/variables.py index f8db4844..2adef61d 100644 --- a/backend/behave/cometa_itself/steps/tools/variables.py +++ b/backend/behave/cometa_itself/steps/tools/variables.py @@ -4,6 +4,6 @@ """ # max step timeout, if step takes more than this timeout it will automatically stop the feature -STEP_TIMEOUT=180 +MAX_STEP_TIMEOUT=7200 # 2h MAXRETRIES=5 \ No newline at end of file diff --git a/backend/behave/poetry.lock b/backend/behave/poetry.lock index cef7f55b..ae36f553 100644 --- a/backend/behave/poetry.lock +++ b/backend/behave/poetry.lock @@ -1,32 +1,56 @@ +# This file is automatically @generated by Poetry 1.6.1 and should not be changed by hand. + [[package]] name = "arrow" -version = "1.1.0" +version = "1.2.3" description = "Better dates & times for Python" -category = "main" optional = false python-versions = ">=3.6" +files = [ + {file = "arrow-1.2.3-py3-none-any.whl", hash = "sha256:5a49ab92e3b7b71d96cd6bfcc4df14efefc9dfa96ea19045815914a6ab6b1fe2"}, + {file = "arrow-1.2.3.tar.gz", hash = "sha256:3934b30ca1b9f292376d9db15b19446088d12ec58629bc3f0da28fd55fb633a1"}, +] [package.dependencies] python-dateutil = ">=2.7.0" [[package]] name = "asgiref" -version = "3.3.4" +version = "3.7.2" description = "ASGI specs, helper code, and adapters" -category = "main" optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" +files = [ + {file = "asgiref-3.7.2-py3-none-any.whl", hash = "sha256:89b2ef2247e3b562a16eef663bc0e2e703ec6468e2fa8a5cd61cd449786d4f6e"}, + {file = "asgiref-3.7.2.tar.gz", hash = "sha256:9e0ce3aa93a819ba5b45120216b23878cf6e8525eb3848653452b4192b92afed"}, +] + +[package.dependencies] +typing-extensions = {version = ">=4", markers = "python_version < \"3.11\""} [package.extras] -tests = ["pytest", "pytest-asyncio", "mypy (>=0.800)"] +tests = ["mypy (>=0.800)", "pytest", "pytest-asyncio"] + +[[package]] +name = "async-timeout" +version = "4.0.3" +description = "Timeout context manager for asyncio programs" +optional = false +python-versions = ">=3.7" +files = [ + {file = "async-timeout-4.0.3.tar.gz", hash = "sha256:4640d96be84d82d02ed59ea2b7105a0f7b33abe8703703cd0ab0bf87c427522f"}, + {file = "async_timeout-4.0.3-py3-none-any.whl", hash = "sha256:7405140ff1230c310e51dc27b3145b9092d659ce68ff733fb0cefe3ee42be028"}, +] [[package]] name = "awesome-slugify" version = "1.6.5" description = "Python flexible slugify function" -category = "main" optional = false python-versions = "*" +files = [ + {file = "awesome-slugify-1.6.5.tar.gz", hash = "sha256:bbdec3fa2187917473a2efad092b57f7125a55f841a7cf6a1773178d32ccfd71"}, +] [package.dependencies] regex = "*" @@ -34,14 +58,17 @@ Unidecode = ">=0.04.14,<0.05" [[package]] name = "beautifulsoup4" -version = "4.9.3" +version = "4.12.2" description = "Screen-scraping library" -category = "main" optional = false -python-versions = "*" +python-versions = ">=3.6.0" +files = [ + {file = "beautifulsoup4-4.12.2-py3-none-any.whl", hash = "sha256:bd2520ca0d9d7d12694a53d44ac482d181b4ec1888909b035a3dbf40d0f57d4a"}, + {file = "beautifulsoup4-4.12.2.tar.gz", hash = "sha256:492bbc69dca35d12daac71c4db1bfff0c876c00ef4a2ffacce226d4638eb72da"}, +] [package.dependencies] -soupsieve = {version = ">1.2", markers = "python_version >= \"3.0\""} +soupsieve = ">1.2" [package.extras] html5lib = ["html5lib"] @@ -49,27 +76,61 @@ lxml = ["lxml"] [[package]] name = "certifi" -version = "2020.12.5" +version = "2023.5.7" description = "Python package for providing Mozilla's CA Bundle." -category = "main" optional = false -python-versions = "*" +python-versions = ">=3.6" +files = [ + {file = "certifi-2023.5.7-py3-none-any.whl", hash = "sha256:c6c2e98f5c7869efca1f8916fed228dd91539f9f1b444c314c06eef02980c716"}, + {file = "certifi-2023.5.7.tar.gz", hash = "sha256:0f0d56dc5a6ad56fd4ba36484d6cc34451e1c6548c61daad8c320169f91eddc7"}, +] [[package]] name = "chardet" version = "4.0.0" description = "Universal encoding detector for Python 2 and 3" -category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +files = [ + {file = "chardet-4.0.0-py2.py3-none-any.whl", hash = "sha256:f864054d66fd9118f2e67044ac8981a54775ec5b67aed0441892edb553d21da5"}, + {file = "chardet-4.0.0.tar.gz", hash = "sha256:0d6f53a15db4120f2b08c94f11e7d93d2c911ee118b6b30a04ec3ee8310179fa"}, +] + +[[package]] +name = "click" +version = "8.1.7" +description = "Composable command line interface toolkit" +optional = false +python-versions = ">=3.7" +files = [ + {file = "click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28"}, + {file = "click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "platform_system == \"Windows\""} + +[[package]] +name = "colorama" +version = "0.4.6" +description = "Cross-platform colored terminal text." +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +files = [ + {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, + {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, +] [[package]] name = "coreapi" version = "2.3.3" description = "Python client library for Core API." -category = "main" optional = false python-versions = "*" +files = [ + {file = "coreapi-2.3.3-py2.py3-none-any.whl", hash = "sha256:bf39d118d6d3e171f10df9ede5666f63ad80bba9a29a8ec17726a66cf52ee6f3"}, + {file = "coreapi-2.3.3.tar.gz", hash = "sha256:46145fcc1f7017c076a2ef684969b641d18a2991051fddec9458ad3f78ffc1cb"}, +] [package.dependencies] coreschema = "*" @@ -81,20 +142,26 @@ uritemplate = "*" name = "coreschema" version = "0.0.4" description = "Core Schema." -category = "main" optional = false python-versions = "*" +files = [ + {file = "coreschema-0.0.4-py2-none-any.whl", hash = "sha256:5e6ef7bf38c1525d5e55a895934ab4273548629f16aed5c0a6caa74ebf45551f"}, + {file = "coreschema-0.0.4.tar.gz", hash = "sha256:9503506007d482ab0867ba14724b93c18a33b22b6d19fb419ef2d239dd4a1607"}, +] [package.dependencies] jinja2 = "*" [[package]] name = "django" -version = "3.2.3" +version = "3.2.20" description = "A high-level Python Web framework that encourages rapid development and clean, pragmatic design." -category = "main" optional = false python-versions = ">=3.6" +files = [ + {file = "Django-3.2.20-py3-none-any.whl", hash = "sha256:a477ab326ae7d8807dc25c186b951ab8c7648a3a23f9497763c37307a2b5ef87"}, + {file = "Django-3.2.20.tar.gz", hash = "sha256:dec2a116787b8e14962014bf78e120bba454135108e1af9e9b91ade7b2964c40"}, +] [package.dependencies] asgiref = ">=3.3.2,<4" @@ -109,39 +176,86 @@ bcrypt = ["bcrypt"] name = "django-filter" version = "2.4.0" description = "Django-filter is a reusable Django application for allowing users to filter querysets dynamically." -category = "main" optional = false python-versions = ">=3.5" +files = [ + {file = "django-filter-2.4.0.tar.gz", hash = "sha256:84e9d5bb93f237e451db814ed422a3a625751cbc9968b484ecc74964a8696b06"}, + {file = "django_filter-2.4.0-py3-none-any.whl", hash = "sha256:e00d32cebdb3d54273c48f4f878f898dced8d5dfaad009438fe61ebdf535ace1"}, +] [package.dependencies] Django = ">=2.2" +[[package]] +name = "django-rq" +version = "2.8.1" +description = "An app that provides django integration for RQ (Redis Queue)" +optional = false +python-versions = "*" +files = [ + {file = "django-rq-2.8.1.tar.gz", hash = "sha256:ff053aa4d1b1e1acc47c99b4a21b514de8745894c00d1e6f4abc8b37d35d66d6"}, + {file = "django_rq-2.8.1-py2.py3-none-any.whl", hash = "sha256:f5d649dc57b5564011460b2b69c8a60a4f5f10ee8692b51d1dfc17035b1039b8"}, +] + +[package.dependencies] +django = ">=2.0" +redis = ">=3" +rq = ">=1.14" + +[package.extras] +sentry = ["raven (>=6.1.0)"] +testing = ["mock (>=2.0.0)"] + [[package]] name = "djangorestframework" -version = "3.12.4" +version = "3.14.0" description = "Web APIs for Django, made easy." -category = "main" optional = false -python-versions = ">=3.5" +python-versions = ">=3.6" +files = [ + {file = "djangorestframework-3.14.0-py3-none-any.whl", hash = "sha256:eb63f58c9f218e1a7d064d17a70751f528ed4e1d35547fdade9aaf4cd103fd08"}, + {file = "djangorestframework-3.14.0.tar.gz", hash = "sha256:579a333e6256b09489cbe0a067e66abe55c6595d8926be6b99423786334350c8"}, +] [package.dependencies] -django = ">=2.2" +django = ">=3.0" +pytz = "*" + +[[package]] +name = "et-xmlfile" +version = "1.1.0" +description = "An implementation of lxml.xmlfile for the standard library" +optional = false +python-versions = ">=3.6" +files = [ + {file = "et_xmlfile-1.1.0-py3-none-any.whl", hash = "sha256:a2ba85d1d6a74ef63837eed693bcb89c3f752169b0e3e7ae5b16ca5e1b3deada"}, + {file = "et_xmlfile-1.1.0.tar.gz", hash = "sha256:8eb9e2bc2f8c97e37a2dc85a09ecdcdec9d8a396530a6d5a33b30b9a92da0c5c"}, +] [[package]] name = "fake-factory" version = "9999.9.9" description = "The `fake-factory` package was deprecated on December 15th, 2016. Use the `Faker` package instead." -category = "main" optional = false python-versions = "*" +files = [ + {file = "fake-factory-9999.9.9.tar.gz", hash = "sha256:f5bd18deb22ad8cb4402513c025877bc6b50de58902d686b6b21ba8981dce260"}, + {file = "fake_factory-9999.9.9-py2.py3-none-any.whl", hash = "sha256:60c64a953c03e29fbee6b59fcb700c86a313778d68dc5816fc560ef515019d77"}, +] [[package]] name = "gunicorn" version = "20.1.0" description = "WSGI HTTP Server for UNIX" -category = "main" optional = false python-versions = ">=3.5" +files = [ + {file = "gunicorn-20.1.0-py3-none-any.whl", hash = "sha256:9dcc4547dbb1cb284accfb15ab5667a0e5d1881cc443e0677b4882a4067a807e"}, + {file = "gunicorn-20.1.0.tar.gz", hash = "sha256:e0a968b5ba15f8a328fdfd7ab1fcb5af4470c28aaf7e55df02a99bc13138e6e8"}, +] + +[package.dependencies] +setuptools = ">=3.0" [package.extras] eventlet = ["eventlet (>=0.24.1)"] @@ -153,9 +267,12 @@ tornado = ["tornado (>=0.2)"] name = "html-diff" version = "0.3.0" description = "HTML-formatted diff'ing of HTML snippets" -category = "main" optional = false python-versions = ">=3.5,<4.0" +files = [ + {file = "html-diff-0.3.0.tar.gz", hash = "sha256:4bf028fe21cae6547bd2cec7154f5f77a3a4747c0760243304ad8087b0285ac0"}, + {file = "html_diff-0.3.0-py3-none-any.whl", hash = "sha256:99b156fcf4731725e88dfd9959ca256fa76041f7cdc818a16f3002231a8a86ad"}, +] [package.dependencies] beautifulsoup4 = ">=4.8,<5.0" @@ -164,55 +281,254 @@ beautifulsoup4 = ">=4.8,<5.0" name = "idna" version = "2.10" description = "Internationalized Domain Names in Applications (IDNA)" -category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +files = [ + {file = "idna-2.10-py2.py3-none-any.whl", hash = "sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0"}, + {file = "idna-2.10.tar.gz", hash = "sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6"}, +] [[package]] name = "itypes" version = "1.2.0" description = "Simple immutable types for python." -category = "main" optional = false python-versions = "*" +files = [ + {file = "itypes-1.2.0-py2.py3-none-any.whl", hash = "sha256:03da6872ca89d29aef62773672b2d408f490f80db48b23079a4b194c86dd04c6"}, + {file = "itypes-1.2.0.tar.gz", hash = "sha256:af886f129dea4a2a1e3d36595a2d139589e4dd287f5cab0b40e799ee81570ff1"}, +] [[package]] name = "jinja2" -version = "3.0.0" +version = "3.1.2" description = "A very fast and expressive template engine." -category = "main" optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" +files = [ + {file = "Jinja2-3.1.2-py3-none-any.whl", hash = "sha256:6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61"}, + {file = "Jinja2-3.1.2.tar.gz", hash = "sha256:31351a702a408a9e7595a8fc6150fc3f43bb6bf7e319770cbc0db9df9437e852"}, +] [package.dependencies] -MarkupSafe = ">=2.0.0rc2" +MarkupSafe = ">=2.0" [package.extras] i18n = ["Babel (>=2.7)"] [[package]] name = "markupsafe" -version = "2.0.0" +version = "2.1.3" description = "Safely add untrusted strings to HTML/XML markup." -category = "main" +optional = false +python-versions = ">=3.7" +files = [ + {file = "MarkupSafe-2.1.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:cd0f502fe016460680cd20aaa5a76d241d6f35a1c3350c474bac1273803893fa"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e09031c87a1e51556fdcb46e5bd4f59dfb743061cf93c4d6831bf894f125eb57"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:68e78619a61ecf91e76aa3e6e8e33fc4894a2bebe93410754bd28fce0a8a4f9f"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:65c1a9bcdadc6c28eecee2c119465aebff8f7a584dd719facdd9e825ec61ab52"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:525808b8019e36eb524b8c68acdd63a37e75714eac50e988180b169d64480a00"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:962f82a3086483f5e5f64dbad880d31038b698494799b097bc59c2edf392fce6"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:aa7bd130efab1c280bed0f45501b7c8795f9fdbeb02e965371bbef3523627779"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c9c804664ebe8f83a211cace637506669e7890fec1b4195b505c214e50dd4eb7"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-win32.whl", hash = "sha256:10bbfe99883db80bdbaff2dcf681dfc6533a614f700da1287707e8a5d78a8431"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-win_amd64.whl", hash = "sha256:1577735524cdad32f9f694208aa75e422adba74f1baee7551620e43a3141f559"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ad9e82fb8f09ade1c3e1b996a6337afac2b8b9e365f926f5a61aacc71adc5b3c"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3c0fae6c3be832a0a0473ac912810b2877c8cb9d76ca48de1ed31e1c68386575"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b076b6226fb84157e3f7c971a47ff3a679d837cf338547532ab866c57930dbee"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bfce63a9e7834b12b87c64d6b155fdd9b3b96191b6bd334bf37db7ff1fe457f2"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:338ae27d6b8745585f87218a3f23f1512dbf52c26c28e322dbe54bcede54ccb9"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e4dd52d80b8c83fdce44e12478ad2e85c64ea965e75d66dbeafb0a3e77308fcc"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:df0be2b576a7abbf737b1575f048c23fb1d769f267ec4358296f31c2479db8f9"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5bbe06f8eeafd38e5d0a4894ffec89378b6c6a625ff57e3028921f8ff59318ac"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-win32.whl", hash = "sha256:dd15ff04ffd7e05ffcb7fe79f1b98041b8ea30ae9234aed2a9168b5797c3effb"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-win_amd64.whl", hash = "sha256:134da1eca9ec0ae528110ccc9e48041e0828d79f24121a1a146161103c76e686"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8e254ae696c88d98da6555f5ace2279cf7cd5b3f52be2b5cf97feafe883b58d2"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cb0932dc158471523c9637e807d9bfb93e06a95cbf010f1a38b98623b929ef2b"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9402b03f1a1b4dc4c19845e5c749e3ab82d5078d16a2a4c2cd2df62d57bb0707"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ca379055a47383d02a5400cb0d110cef0a776fc644cda797db0c5696cfd7e18e"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:b7ff0f54cb4ff66dd38bebd335a38e2c22c41a8ee45aa608efc890ac3e3931bc"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:c011a4149cfbcf9f03994ec2edffcb8b1dc2d2aede7ca243746df97a5d41ce48"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:56d9f2ecac662ca1611d183feb03a3fa4406469dafe241673d521dd5ae92a155"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-win32.whl", hash = "sha256:8758846a7e80910096950b67071243da3e5a20ed2546e6392603c096778d48e0"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-win_amd64.whl", hash = "sha256:787003c0ddb00500e49a10f2844fac87aa6ce977b90b0feaaf9de23c22508b24"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:2ef12179d3a291be237280175b542c07a36e7f60718296278d8593d21ca937d4"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2c1b19b3aaacc6e57b7e25710ff571c24d6c3613a45e905b1fde04d691b98ee0"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8afafd99945ead6e075b973fefa56379c5b5c53fd8937dad92c662da5d8fd5ee"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8c41976a29d078bb235fea9b2ecd3da465df42a562910f9022f1a03107bd02be"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d080e0a5eb2529460b30190fcfcc4199bd7f827663f858a226a81bc27beaa97e"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:69c0f17e9f5a7afdf2cc9fb2d1ce6aabdb3bafb7f38017c0b77862bcec2bbad8"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:504b320cd4b7eff6f968eddf81127112db685e81f7e36e75f9f84f0df46041c3"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:42de32b22b6b804f42c5d98be4f7e5e977ecdd9ee9b660fda1a3edf03b11792d"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-win32.whl", hash = "sha256:ceb01949af7121f9fc39f7d27f91be8546f3fb112c608bc4029aef0bab86a2a5"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-win_amd64.whl", hash = "sha256:1b40069d487e7edb2676d3fbdb2b0829ffa2cd63a2ec26c4938b2d34391b4ecc"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:8023faf4e01efadfa183e863fefde0046de576c6f14659e8782065bcece22198"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6b2b56950d93e41f33b4223ead100ea0fe11f8e6ee5f641eb753ce4b77a7042b"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9dcdfd0eaf283af041973bff14a2e143b8bd64e069f4c383416ecd79a81aab58"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:05fb21170423db021895e1ea1e1f3ab3adb85d1c2333cbc2310f2a26bc77272e"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:282c2cb35b5b673bbcadb33a585408104df04f14b2d9b01d4c345a3b92861c2c"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:ab4a0df41e7c16a1392727727e7998a467472d0ad65f3ad5e6e765015df08636"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:7ef3cb2ebbf91e330e3bb937efada0edd9003683db6b57bb108c4001f37a02ea"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:0a4e4a1aff6c7ac4cd55792abf96c915634c2b97e3cc1c7129578aa68ebd754e"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-win32.whl", hash = "sha256:fec21693218efe39aa7f8599346e90c705afa52c5b31ae019b2e57e8f6542bb2"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-win_amd64.whl", hash = "sha256:3fd4abcb888d15a94f32b75d8fd18ee162ca0c064f35b11134be77050296d6ba"}, + {file = "MarkupSafe-2.1.3.tar.gz", hash = "sha256:af598ed32d6ae86f1b747b82783958b1a4ab8f617b06fe68795c7f026abbdcad"}, +] + +[[package]] +name = "numpy" +version = "1.24.4" +description = "Fundamental package for array computing in Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "numpy-1.24.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c0bfb52d2169d58c1cdb8cc1f16989101639b34c7d3ce60ed70b19c63eba0b64"}, + {file = "numpy-1.24.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ed094d4f0c177b1b8e7aa9cba7d6ceed51c0e569a5318ac0ca9a090680a6a1b1"}, + {file = "numpy-1.24.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:79fc682a374c4a8ed08b331bef9c5f582585d1048fa6d80bc6c35bc384eee9b4"}, + {file = "numpy-1.24.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7ffe43c74893dbf38c2b0a1f5428760a1a9c98285553c89e12d70a96a7f3a4d6"}, + {file = "numpy-1.24.4-cp310-cp310-win32.whl", hash = "sha256:4c21decb6ea94057331e111a5bed9a79d335658c27ce2adb580fb4d54f2ad9bc"}, + {file = "numpy-1.24.4-cp310-cp310-win_amd64.whl", hash = "sha256:b4bea75e47d9586d31e892a7401f76e909712a0fd510f58f5337bea9572c571e"}, + {file = "numpy-1.24.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f136bab9c2cfd8da131132c2cf6cc27331dd6fae65f95f69dcd4ae3c3639c810"}, + {file = "numpy-1.24.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e2926dac25b313635e4d6cf4dc4e51c8c0ebfed60b801c799ffc4c32bf3d1254"}, + {file = "numpy-1.24.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:222e40d0e2548690405b0b3c7b21d1169117391c2e82c378467ef9ab4c8f0da7"}, + {file = "numpy-1.24.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7215847ce88a85ce39baf9e89070cb860c98fdddacbaa6c0da3ffb31b3350bd5"}, + {file = "numpy-1.24.4-cp311-cp311-win32.whl", hash = "sha256:4979217d7de511a8d57f4b4b5b2b965f707768440c17cb70fbf254c4b225238d"}, + {file = "numpy-1.24.4-cp311-cp311-win_amd64.whl", hash = "sha256:b7b1fc9864d7d39e28f41d089bfd6353cb5f27ecd9905348c24187a768c79694"}, + {file = "numpy-1.24.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1452241c290f3e2a312c137a9999cdbf63f78864d63c79039bda65ee86943f61"}, + {file = "numpy-1.24.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:04640dab83f7c6c85abf9cd729c5b65f1ebd0ccf9de90b270cd61935eef0197f"}, + {file = "numpy-1.24.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5425b114831d1e77e4b5d812b69d11d962e104095a5b9c3b641a218abcc050e"}, + {file = "numpy-1.24.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd80e219fd4c71fc3699fc1dadac5dcf4fd882bfc6f7ec53d30fa197b8ee22dc"}, + {file = "numpy-1.24.4-cp38-cp38-win32.whl", hash = "sha256:4602244f345453db537be5314d3983dbf5834a9701b7723ec28923e2889e0bb2"}, + {file = "numpy-1.24.4-cp38-cp38-win_amd64.whl", hash = "sha256:692f2e0f55794943c5bfff12b3f56f99af76f902fc47487bdfe97856de51a706"}, + {file = "numpy-1.24.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:2541312fbf09977f3b3ad449c4e5f4bb55d0dbf79226d7724211acc905049400"}, + {file = "numpy-1.24.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:9667575fb6d13c95f1b36aca12c5ee3356bf001b714fc354eb5465ce1609e62f"}, + {file = "numpy-1.24.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f3a86ed21e4f87050382c7bc96571755193c4c1392490744ac73d660e8f564a9"}, + {file = "numpy-1.24.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d11efb4dbecbdf22508d55e48d9c8384db795e1b7b51ea735289ff96613ff74d"}, + {file = "numpy-1.24.4-cp39-cp39-win32.whl", hash = "sha256:6620c0acd41dbcb368610bb2f4d83145674040025e5536954782467100aa8835"}, + {file = "numpy-1.24.4-cp39-cp39-win_amd64.whl", hash = "sha256:befe2bf740fd8373cf56149a5c23a0f601e82869598d41f8e188a0e9869926f8"}, + {file = "numpy-1.24.4-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:31f13e25b4e304632a4619d0e0777662c2ffea99fcae2029556b17d8ff958aef"}, + {file = "numpy-1.24.4-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95f7ac6540e95bc440ad77f56e520da5bf877f87dca58bd095288dce8940532a"}, + {file = "numpy-1.24.4-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:e98f220aa76ca2a977fe435f5b04d7b3470c0a2e6312907b37ba6068f26787f2"}, + {file = "numpy-1.24.4.tar.gz", hash = "sha256:80f5e3a4e498641401868df4208b74581206afbee7cf7b8329daae82676d9463"}, +] + +[[package]] +name = "numpy" +version = "1.25.1" +description = "Fundamental package for array computing in Python" +optional = false +python-versions = ">=3.9" +files = [ + {file = "numpy-1.25.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:77d339465dff3eb33c701430bcb9c325b60354698340229e1dff97745e6b3efa"}, + {file = "numpy-1.25.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d736b75c3f2cb96843a5c7f8d8ccc414768d34b0a75f466c05f3a739b406f10b"}, + {file = "numpy-1.25.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4a90725800caeaa160732d6b31f3f843ebd45d6b5f3eec9e8cc287e30f2805bf"}, + {file = "numpy-1.25.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c6c9261d21e617c6dc5eacba35cb68ec36bb72adcff0dee63f8fbc899362588"}, + {file = "numpy-1.25.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:0def91f8af6ec4bb94c370e38c575855bf1d0be8a8fbfba42ef9c073faf2cf19"}, + {file = "numpy-1.25.1-cp310-cp310-win32.whl", hash = "sha256:fd67b306320dcadea700a8f79b9e671e607f8696e98ec255915c0c6d6b818503"}, + {file = "numpy-1.25.1-cp310-cp310-win_amd64.whl", hash = "sha256:c1516db588987450b85595586605742879e50dcce923e8973f79529651545b57"}, + {file = "numpy-1.25.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6b82655dd8efeea69dbf85d00fca40013d7f503212bc5259056244961268b66e"}, + {file = "numpy-1.25.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e8f6049c4878cb16960fbbfb22105e49d13d752d4d8371b55110941fb3b17800"}, + {file = "numpy-1.25.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:41a56b70e8139884eccb2f733c2f7378af06c82304959e174f8e7370af112e09"}, + {file = "numpy-1.25.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d5154b1a25ec796b1aee12ac1b22f414f94752c5f94832f14d8d6c9ac40bcca6"}, + {file = "numpy-1.25.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:38eb6548bb91c421261b4805dc44def9ca1a6eef6444ce35ad1669c0f1a3fc5d"}, + {file = "numpy-1.25.1-cp311-cp311-win32.whl", hash = "sha256:791f409064d0a69dd20579345d852c59822c6aa087f23b07b1b4e28ff5880fcb"}, + {file = "numpy-1.25.1-cp311-cp311-win_amd64.whl", hash = "sha256:c40571fe966393b212689aa17e32ed905924120737194b5d5c1b20b9ed0fb171"}, + {file = "numpy-1.25.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3d7abcdd85aea3e6cdddb59af2350c7ab1ed764397f8eec97a038ad244d2d105"}, + {file = "numpy-1.25.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1a180429394f81c7933634ae49b37b472d343cccb5bb0c4a575ac8bbc433722f"}, + {file = "numpy-1.25.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d412c1697c3853c6fc3cb9751b4915859c7afe6a277c2bf00acf287d56c4e625"}, + {file = "numpy-1.25.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:20e1266411120a4f16fad8efa8e0454d21d00b8c7cee5b5ccad7565d95eb42dd"}, + {file = "numpy-1.25.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:f76aebc3358ade9eacf9bc2bb8ae589863a4f911611694103af05346637df1b7"}, + {file = "numpy-1.25.1-cp39-cp39-win32.whl", hash = "sha256:247d3ffdd7775bdf191f848be8d49100495114c82c2bd134e8d5d075fb386a1c"}, + {file = "numpy-1.25.1-cp39-cp39-win_amd64.whl", hash = "sha256:1d5d3c68e443c90b38fdf8ef40e60e2538a27548b39b12b73132456847f4b631"}, + {file = "numpy-1.25.1-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:35a9527c977b924042170a0887de727cd84ff179e478481404c5dc66b4170009"}, + {file = "numpy-1.25.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0d3fe3dd0506a28493d82dc3cf254be8cd0d26f4008a417385cbf1ae95b54004"}, + {file = "numpy-1.25.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:012097b5b0d00a11070e8f2e261128c44157a8689f7dedcf35576e525893f4fe"}, + {file = "numpy-1.25.1.tar.gz", hash = "sha256:9a3a9f3a61480cc086117b426a8bd86869c213fc4072e606f01c4e4b66eb92bf"}, +] + +[[package]] +name = "openpyxl" +version = "3.1.2" +description = "A Python library to read/write Excel 2010 xlsx/xlsm files" optional = false python-versions = ">=3.6" +files = [ + {file = "openpyxl-3.1.2-py2.py3-none-any.whl", hash = "sha256:f91456ead12ab3c6c2e9491cf33ba6d08357d802192379bb482f1033ade496f5"}, + {file = "openpyxl-3.1.2.tar.gz", hash = "sha256:a6f5977418eff3b2d5500d54d9db50c8277a368436f4e4f8ddb1be3422870184"}, +] + +[package.dependencies] +et-xmlfile = "*" + +[[package]] +name = "pandas" +version = "1.5.3" +description = "Powerful data structures for data analysis, time series, and statistics" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pandas-1.5.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:3749077d86e3a2f0ed51367f30bf5b82e131cc0f14260c4d3e499186fccc4406"}, + {file = "pandas-1.5.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:972d8a45395f2a2d26733eb8d0f629b2f90bebe8e8eddbb8829b180c09639572"}, + {file = "pandas-1.5.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:50869a35cbb0f2e0cd5ec04b191e7b12ed688874bd05dd777c19b28cbea90996"}, + {file = "pandas-1.5.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c3ac844a0fe00bfaeb2c9b51ab1424e5c8744f89860b138434a363b1f620f354"}, + {file = "pandas-1.5.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7a0a56cef15fd1586726dace5616db75ebcfec9179a3a55e78f72c5639fa2a23"}, + {file = "pandas-1.5.3-cp310-cp310-win_amd64.whl", hash = "sha256:478ff646ca42b20376e4ed3fa2e8d7341e8a63105586efe54fa2508ee087f328"}, + {file = "pandas-1.5.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6973549c01ca91ec96199e940495219c887ea815b2083722821f1d7abfa2b4dc"}, + {file = "pandas-1.5.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c39a8da13cede5adcd3be1182883aea1c925476f4e84b2807a46e2775306305d"}, + {file = "pandas-1.5.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f76d097d12c82a535fda9dfe5e8dd4127952b45fea9b0276cb30cca5ea313fbc"}, + {file = "pandas-1.5.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e474390e60ed609cec869b0da796ad94f420bb057d86784191eefc62b65819ae"}, + {file = "pandas-1.5.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5f2b952406a1588ad4cad5b3f55f520e82e902388a6d5a4a91baa8d38d23c7f6"}, + {file = "pandas-1.5.3-cp311-cp311-win_amd64.whl", hash = "sha256:bc4c368f42b551bf72fac35c5128963a171b40dce866fb066540eeaf46faa003"}, + {file = "pandas-1.5.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:14e45300521902689a81f3f41386dc86f19b8ba8dd5ac5a3c7010ef8d2932813"}, + {file = "pandas-1.5.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:9842b6f4b8479e41968eced654487258ed81df7d1c9b7b870ceea24ed9459b31"}, + {file = "pandas-1.5.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:26d9c71772c7afb9d5046e6e9cf42d83dd147b5cf5bcb9d97252077118543792"}, + {file = "pandas-1.5.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5fbcb19d6fceb9e946b3e23258757c7b225ba450990d9ed63ccceeb8cae609f7"}, + {file = "pandas-1.5.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:565fa34a5434d38e9d250af3c12ff931abaf88050551d9fbcdfafca50d62babf"}, + {file = "pandas-1.5.3-cp38-cp38-win32.whl", hash = "sha256:87bd9c03da1ac870a6d2c8902a0e1fd4267ca00f13bc494c9e5a9020920e1d51"}, + {file = "pandas-1.5.3-cp38-cp38-win_amd64.whl", hash = "sha256:41179ce559943d83a9b4bbacb736b04c928b095b5f25dd2b7389eda08f46f373"}, + {file = "pandas-1.5.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:c74a62747864ed568f5a82a49a23a8d7fe171d0c69038b38cedf0976831296fa"}, + {file = "pandas-1.5.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c4c00e0b0597c8e4f59e8d461f797e5d70b4d025880516a8261b2817c47759ee"}, + {file = "pandas-1.5.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a50d9a4336a9621cab7b8eb3fb11adb82de58f9b91d84c2cd526576b881a0c5a"}, + {file = "pandas-1.5.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dd05f7783b3274aa206a1af06f0ceed3f9b412cf665b7247eacd83be41cf7bf0"}, + {file = "pandas-1.5.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9f69c4029613de47816b1bb30ff5ac778686688751a5e9c99ad8c7031f6508e5"}, + {file = "pandas-1.5.3-cp39-cp39-win32.whl", hash = "sha256:7cec0bee9f294e5de5bbfc14d0573f65526071029d036b753ee6507d2a21480a"}, + {file = "pandas-1.5.3-cp39-cp39-win_amd64.whl", hash = "sha256:dfd681c5dc216037e0b0a2c821f5ed99ba9f03ebcf119c7dac0e9a7b960b9ec9"}, + {file = "pandas-1.5.3.tar.gz", hash = "sha256:74a3fd7e5a7ec052f183273dc7b0acd3a863edf7520f5d3a1765c04ffdb3b0b1"}, +] + +[package.dependencies] +numpy = [ + {version = ">=1.20.3", markers = "python_version < \"3.10\""}, + {version = ">=1.23.2", markers = "python_version >= \"3.11\""}, + {version = ">=1.21.0", markers = "python_version >= \"3.10\" and python_version < \"3.11\""}, +] +python-dateutil = ">=2.8.1" +pytz = ">=2020.1" + +[package.extras] +test = ["hypothesis (>=5.5.3)", "pytest (>=6.0)", "pytest-xdist (>=1.31)"] [[package]] name = "parse" -version = "1.19.0" +version = "1.19.1" description = "parse() is the opposite of format()" -category = "main" optional = false python-versions = "*" +files = [ + {file = "parse-1.19.1-py2.py3-none-any.whl", hash = "sha256:371ed3800dc63983832159cc9373156613947707bc448b5215473a219dbd4362"}, + {file = "parse-1.19.1.tar.gz", hash = "sha256:cc3a47236ff05da377617ddefa867b7ba983819c664e1afe46249e5b469be464"}, +] [[package]] name = "parse-type" version = "0.5.2" description = "Simplifies to build parse types based on the parse module" -category = "main" optional = false python-versions = ">=2.6, !=3.0.*, !=3.1.*" +files = [ + {file = "parse_type-0.5.2-py2.py3-none-any.whl", hash = "sha256:089a471b06327103865dfec2dd844230c3c658a4a1b5b4c8b6c16c8f77577f9e"}, + {file = "parse_type-0.5.2.tar.gz", hash = "sha256:7f690b18d35048c15438d6d0571f9045cffbec5907e0b1ccf006f889e3a38c0b"}, +] [package.dependencies] parse = ">=1.8.4" @@ -224,54 +540,252 @@ docs = ["sphinx (>=1.2)"] [[package]] name = "pillow" -version = "8.2.0" +version = "9.5.0" description = "Python Imaging Library (Fork)" -category = "main" optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" +files = [ + {file = "Pillow-9.5.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:ace6ca218308447b9077c14ea4ef381ba0b67ee78d64046b3f19cf4e1139ad16"}, + {file = "Pillow-9.5.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d3d403753c9d5adc04d4694d35cf0391f0f3d57c8e0030aac09d7678fa8030aa"}, + {file = "Pillow-9.5.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5ba1b81ee69573fe7124881762bb4cd2e4b6ed9dd28c9c60a632902fe8db8b38"}, + {file = "Pillow-9.5.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fe7e1c262d3392afcf5071df9afa574544f28eac825284596ac6db56e6d11062"}, + {file = "Pillow-9.5.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f36397bf3f7d7c6a3abdea815ecf6fd14e7fcd4418ab24bae01008d8d8ca15e"}, + {file = "Pillow-9.5.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:252a03f1bdddce077eff2354c3861bf437c892fb1832f75ce813ee94347aa9b5"}, + {file = "Pillow-9.5.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:85ec677246533e27770b0de5cf0f9d6e4ec0c212a1f89dfc941b64b21226009d"}, + {file = "Pillow-9.5.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:b416f03d37d27290cb93597335a2f85ed446731200705b22bb927405320de903"}, + {file = "Pillow-9.5.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:1781a624c229cb35a2ac31cc4a77e28cafc8900733a864870c49bfeedacd106a"}, + {file = "Pillow-9.5.0-cp310-cp310-win32.whl", hash = "sha256:8507eda3cd0608a1f94f58c64817e83ec12fa93a9436938b191b80d9e4c0fc44"}, + {file = "Pillow-9.5.0-cp310-cp310-win_amd64.whl", hash = "sha256:d3c6b54e304c60c4181da1c9dadf83e4a54fd266a99c70ba646a9baa626819eb"}, + {file = "Pillow-9.5.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:7ec6f6ce99dab90b52da21cf0dc519e21095e332ff3b399a357c187b1a5eee32"}, + {file = "Pillow-9.5.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:560737e70cb9c6255d6dcba3de6578a9e2ec4b573659943a5e7e4af13f298f5c"}, + {file = "Pillow-9.5.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:96e88745a55b88a7c64fa49bceff363a1a27d9a64e04019c2281049444a571e3"}, + {file = "Pillow-9.5.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d9c206c29b46cfd343ea7cdfe1232443072bbb270d6a46f59c259460db76779a"}, + {file = "Pillow-9.5.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cfcc2c53c06f2ccb8976fb5c71d448bdd0a07d26d8e07e321c103416444c7ad1"}, + {file = "Pillow-9.5.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:a0f9bb6c80e6efcde93ffc51256d5cfb2155ff8f78292f074f60f9e70b942d99"}, + {file = "Pillow-9.5.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:8d935f924bbab8f0a9a28404422da8af4904e36d5c33fc6f677e4c4485515625"}, + {file = "Pillow-9.5.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:fed1e1cf6a42577953abbe8e6cf2fe2f566daebde7c34724ec8803c4c0cda579"}, + {file = "Pillow-9.5.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:c1170d6b195555644f0616fd6ed929dfcf6333b8675fcca044ae5ab110ded296"}, + {file = "Pillow-9.5.0-cp311-cp311-win32.whl", hash = "sha256:54f7102ad31a3de5666827526e248c3530b3a33539dbda27c6843d19d72644ec"}, + {file = "Pillow-9.5.0-cp311-cp311-win_amd64.whl", hash = "sha256:cfa4561277f677ecf651e2b22dc43e8f5368b74a25a8f7d1d4a3a243e573f2d4"}, + {file = "Pillow-9.5.0-cp311-cp311-win_arm64.whl", hash = "sha256:965e4a05ef364e7b973dd17fc765f42233415974d773e82144c9bbaaaea5d089"}, + {file = "Pillow-9.5.0-cp312-cp312-win32.whl", hash = "sha256:22baf0c3cf0c7f26e82d6e1adf118027afb325e703922c8dfc1d5d0156bb2eeb"}, + {file = "Pillow-9.5.0-cp312-cp312-win_amd64.whl", hash = "sha256:432b975c009cf649420615388561c0ce7cc31ce9b2e374db659ee4f7d57a1f8b"}, + {file = "Pillow-9.5.0-cp37-cp37m-macosx_10_10_x86_64.whl", hash = "sha256:5d4ebf8e1db4441a55c509c4baa7a0587a0210f7cd25fcfe74dbbce7a4bd1906"}, + {file = "Pillow-9.5.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:375f6e5ee9620a271acb6820b3d1e94ffa8e741c0601db4c0c4d3cb0a9c224bf"}, + {file = "Pillow-9.5.0-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:99eb6cafb6ba90e436684e08dad8be1637efb71c4f2180ee6b8f940739406e78"}, + {file = "Pillow-9.5.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2dfaaf10b6172697b9bceb9a3bd7b951819d1ca339a5ef294d1f1ac6d7f63270"}, + {file = "Pillow-9.5.0-cp37-cp37m-manylinux_2_28_aarch64.whl", hash = "sha256:763782b2e03e45e2c77d7779875f4432e25121ef002a41829d8868700d119392"}, + {file = "Pillow-9.5.0-cp37-cp37m-manylinux_2_28_x86_64.whl", hash = "sha256:35f6e77122a0c0762268216315bf239cf52b88865bba522999dc38f1c52b9b47"}, + {file = "Pillow-9.5.0-cp37-cp37m-win32.whl", hash = "sha256:aca1c196f407ec7cf04dcbb15d19a43c507a81f7ffc45b690899d6a76ac9fda7"}, + {file = "Pillow-9.5.0-cp37-cp37m-win_amd64.whl", hash = "sha256:322724c0032af6692456cd6ed554bb85f8149214d97398bb80613b04e33769f6"}, + {file = "Pillow-9.5.0-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:a0aa9417994d91301056f3d0038af1199eb7adc86e646a36b9e050b06f526597"}, + {file = "Pillow-9.5.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:f8286396b351785801a976b1e85ea88e937712ee2c3ac653710a4a57a8da5d9c"}, + {file = "Pillow-9.5.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c830a02caeb789633863b466b9de10c015bded434deb3ec87c768e53752ad22a"}, + {file = "Pillow-9.5.0-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fbd359831c1657d69bb81f0db962905ee05e5e9451913b18b831febfe0519082"}, + {file = "Pillow-9.5.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f8fc330c3370a81bbf3f88557097d1ea26cd8b019d6433aa59f71195f5ddebbf"}, + {file = "Pillow-9.5.0-cp38-cp38-manylinux_2_28_aarch64.whl", hash = "sha256:7002d0797a3e4193c7cdee3198d7c14f92c0836d6b4a3f3046a64bd1ce8df2bf"}, + {file = "Pillow-9.5.0-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:229e2c79c00e85989a34b5981a2b67aa079fd08c903f0aaead522a1d68d79e51"}, + {file = "Pillow-9.5.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:9adf58f5d64e474bed00d69bcd86ec4bcaa4123bfa70a65ce72e424bfb88ed96"}, + {file = "Pillow-9.5.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:662da1f3f89a302cc22faa9f14a262c2e3951f9dbc9617609a47521c69dd9f8f"}, + {file = "Pillow-9.5.0-cp38-cp38-win32.whl", hash = "sha256:6608ff3bf781eee0cd14d0901a2b9cc3d3834516532e3bd673a0a204dc8615fc"}, + {file = "Pillow-9.5.0-cp38-cp38-win_amd64.whl", hash = "sha256:e49eb4e95ff6fd7c0c402508894b1ef0e01b99a44320ba7d8ecbabefddcc5569"}, + {file = "Pillow-9.5.0-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:482877592e927fd263028c105b36272398e3e1be3269efda09f6ba21fd83ec66"}, + {file = "Pillow-9.5.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3ded42b9ad70e5f1754fb7c2e2d6465a9c842e41d178f262e08b8c85ed8a1d8e"}, + {file = "Pillow-9.5.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c446d2245ba29820d405315083d55299a796695d747efceb5717a8b450324115"}, + {file = "Pillow-9.5.0-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8aca1152d93dcc27dc55395604dcfc55bed5f25ef4c98716a928bacba90d33a3"}, + {file = "Pillow-9.5.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:608488bdcbdb4ba7837461442b90ea6f3079397ddc968c31265c1e056964f1ef"}, + {file = "Pillow-9.5.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:60037a8db8750e474af7ffc9faa9b5859e6c6d0a50e55c45576bf28be7419705"}, + {file = "Pillow-9.5.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:07999f5834bdc404c442146942a2ecadd1cb6292f5229f4ed3b31e0a108746b1"}, + {file = "Pillow-9.5.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:a127ae76092974abfbfa38ca2d12cbeddcdeac0fb71f9627cc1135bedaf9d51a"}, + {file = "Pillow-9.5.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:489f8389261e5ed43ac8ff7b453162af39c3e8abd730af8363587ba64bb2e865"}, + {file = "Pillow-9.5.0-cp39-cp39-win32.whl", hash = "sha256:9b1af95c3a967bf1da94f253e56b6286b50af23392a886720f563c547e48e964"}, + {file = "Pillow-9.5.0-cp39-cp39-win_amd64.whl", hash = "sha256:77165c4a5e7d5a284f10a6efaa39a0ae8ba839da344f20b111d62cc932fa4e5d"}, + {file = "Pillow-9.5.0-pp38-pypy38_pp73-macosx_10_10_x86_64.whl", hash = "sha256:833b86a98e0ede388fa29363159c9b1a294b0905b5128baf01db683672f230f5"}, + {file = "Pillow-9.5.0-pp38-pypy38_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:aaf305d6d40bd9632198c766fb64f0c1a83ca5b667f16c1e79e1661ab5060140"}, + {file = "Pillow-9.5.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0852ddb76d85f127c135b6dd1f0bb88dbb9ee990d2cd9aa9e28526c93e794fba"}, + {file = "Pillow-9.5.0-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:91ec6fe47b5eb5a9968c79ad9ed78c342b1f97a091677ba0e012701add857829"}, + {file = "Pillow-9.5.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:cb841572862f629b99725ebaec3287fc6d275be9b14443ea746c1dd325053cbd"}, + {file = "Pillow-9.5.0-pp39-pypy39_pp73-macosx_10_10_x86_64.whl", hash = "sha256:c380b27d041209b849ed246b111b7c166ba36d7933ec6e41175fd15ab9eb1572"}, + {file = "Pillow-9.5.0-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7c9af5a3b406a50e313467e3565fc99929717f780164fe6fbb7704edba0cebbe"}, + {file = "Pillow-9.5.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5671583eab84af046a397d6d0ba25343c00cd50bce03787948e0fff01d4fd9b1"}, + {file = "Pillow-9.5.0-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:84a6f19ce086c1bf894644b43cd129702f781ba5751ca8572f08aa40ef0ab7b7"}, + {file = "Pillow-9.5.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:1e7723bd90ef94eda669a3c2c19d549874dd5badaeefabefd26053304abe5799"}, + {file = "Pillow-9.5.0.tar.gz", hash = "sha256:bf548479d336726d7a0eceb6e767e179fbde37833ae42794602631a070d630f1"}, +] + +[package.extras] +docs = ["furo", "olefile", "sphinx (>=2.4)", "sphinx-copybutton", "sphinx-inline-tabs", "sphinx-removed-in", "sphinxext-opengraph"] +tests = ["check-manifest", "coverage", "defusedxml", "markdown2", "olefile", "packaging", "pyroma", "pytest", "pytest-cov", "pytest-timeout"] [[package]] name = "psycopg2" -version = "2.8.6" +version = "2.9.6" description = "psycopg2 - Python-PostgreSQL Database Adapter" -category = "main" optional = false -python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*" +python-versions = ">=3.6" +files = [ + {file = "psycopg2-2.9.6-cp310-cp310-win32.whl", hash = "sha256:f7a7a5ee78ba7dc74265ba69e010ae89dae635eea0e97b055fb641a01a31d2b1"}, + {file = "psycopg2-2.9.6-cp310-cp310-win_amd64.whl", hash = "sha256:f75001a1cbbe523e00b0ef896a5a1ada2da93ccd752b7636db5a99bc57c44494"}, + {file = "psycopg2-2.9.6-cp311-cp311-win32.whl", hash = "sha256:53f4ad0a3988f983e9b49a5d9765d663bbe84f508ed655affdb810af9d0972ad"}, + {file = "psycopg2-2.9.6-cp311-cp311-win_amd64.whl", hash = "sha256:b81fcb9ecfc584f661b71c889edeae70bae30d3ef74fa0ca388ecda50b1222b7"}, + {file = "psycopg2-2.9.6-cp36-cp36m-win32.whl", hash = "sha256:11aca705ec888e4f4cea97289a0bf0f22a067a32614f6ef64fcf7b8bfbc53744"}, + {file = "psycopg2-2.9.6-cp36-cp36m-win_amd64.whl", hash = "sha256:36c941a767341d11549c0fbdbb2bf5be2eda4caf87f65dfcd7d146828bd27f39"}, + {file = "psycopg2-2.9.6-cp37-cp37m-win32.whl", hash = "sha256:869776630c04f335d4124f120b7fb377fe44b0a7645ab3c34b4ba42516951889"}, + {file = "psycopg2-2.9.6-cp37-cp37m-win_amd64.whl", hash = "sha256:a8ad4a47f42aa6aec8d061fdae21eaed8d864d4bb0f0cade5ad32ca16fcd6258"}, + {file = "psycopg2-2.9.6-cp38-cp38-win32.whl", hash = "sha256:2362ee4d07ac85ff0ad93e22c693d0f37ff63e28f0615a16b6635a645f4b9214"}, + {file = "psycopg2-2.9.6-cp38-cp38-win_amd64.whl", hash = "sha256:d24ead3716a7d093b90b27b3d73459fe8cd90fd7065cf43b3c40966221d8c394"}, + {file = "psycopg2-2.9.6-cp39-cp39-win32.whl", hash = "sha256:1861a53a6a0fd248e42ea37c957d36950da00266378746588eab4f4b5649e95f"}, + {file = "psycopg2-2.9.6-cp39-cp39-win_amd64.whl", hash = "sha256:ded2faa2e6dfb430af7713d87ab4abbfc764d8d7fb73eafe96a24155f906ebf5"}, + {file = "psycopg2-2.9.6.tar.gz", hash = "sha256:f15158418fd826831b28585e2ab48ed8df2d0d98f502a2b4fe619e7d5ca29011"}, +] [[package]] name = "psycopg2-binary" -version = "2.8.6" +version = "2.9.6" description = "psycopg2 - Python-PostgreSQL Database Adapter" -category = "main" optional = false -python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*" +python-versions = ">=3.6" +files = [ + {file = "psycopg2-binary-2.9.6.tar.gz", hash = "sha256:1f64dcfb8f6e0c014c7f55e51c9759f024f70ea572fbdef123f85318c297947c"}, + {file = "psycopg2_binary-2.9.6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d26e0342183c762de3276cca7a530d574d4e25121ca7d6e4a98e4f05cb8e4df7"}, + {file = "psycopg2_binary-2.9.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c48d8f2db17f27d41fb0e2ecd703ea41984ee19362cbce52c097963b3a1b4365"}, + {file = "psycopg2_binary-2.9.6-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ffe9dc0a884a8848075e576c1de0290d85a533a9f6e9c4e564f19adf8f6e54a7"}, + {file = "psycopg2_binary-2.9.6-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8a76e027f87753f9bd1ab5f7c9cb8c7628d1077ef927f5e2446477153a602f2c"}, + {file = "psycopg2_binary-2.9.6-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6460c7a99fc939b849431f1e73e013d54aa54293f30f1109019c56a0b2b2ec2f"}, + {file = "psycopg2_binary-2.9.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ae102a98c547ee2288637af07393dd33f440c25e5cd79556b04e3fca13325e5f"}, + {file = "psycopg2_binary-2.9.6-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:9972aad21f965599ed0106f65334230ce826e5ae69fda7cbd688d24fa922415e"}, + {file = "psycopg2_binary-2.9.6-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:7a40c00dbe17c0af5bdd55aafd6ff6679f94a9be9513a4c7e071baf3d7d22a70"}, + {file = "psycopg2_binary-2.9.6-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:cacbdc5839bdff804dfebc058fe25684cae322987f7a38b0168bc1b2df703fb1"}, + {file = "psycopg2_binary-2.9.6-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:7f0438fa20fb6c7e202863e0d5ab02c246d35efb1d164e052f2f3bfe2b152bd0"}, + {file = "psycopg2_binary-2.9.6-cp310-cp310-win32.whl", hash = "sha256:b6c8288bb8a84b47e07013bb4850f50538aa913d487579e1921724631d02ea1b"}, + {file = "psycopg2_binary-2.9.6-cp310-cp310-win_amd64.whl", hash = "sha256:61b047a0537bbc3afae10f134dc6393823882eb263088c271331602b672e52e9"}, + {file = "psycopg2_binary-2.9.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:964b4dfb7c1c1965ac4c1978b0f755cc4bd698e8aa2b7667c575fb5f04ebe06b"}, + {file = "psycopg2_binary-2.9.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:afe64e9b8ea66866a771996f6ff14447e8082ea26e675a295ad3bdbffdd72afb"}, + {file = "psycopg2_binary-2.9.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:15e2ee79e7cf29582ef770de7dab3d286431b01c3bb598f8e05e09601b890081"}, + {file = "psycopg2_binary-2.9.6-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dfa74c903a3c1f0d9b1c7e7b53ed2d929a4910e272add6700c38f365a6002820"}, + {file = "psycopg2_binary-2.9.6-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b83456c2d4979e08ff56180a76429263ea254c3f6552cd14ada95cff1dec9bb8"}, + {file = "psycopg2_binary-2.9.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0645376d399bfd64da57148694d78e1f431b1e1ee1054872a5713125681cf1be"}, + {file = "psycopg2_binary-2.9.6-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e99e34c82309dd78959ba3c1590975b5d3c862d6f279f843d47d26ff89d7d7e1"}, + {file = "psycopg2_binary-2.9.6-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:4ea29fc3ad9d91162c52b578f211ff1c931d8a38e1f58e684c45aa470adf19e2"}, + {file = "psycopg2_binary-2.9.6-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:4ac30da8b4f57187dbf449294d23b808f8f53cad6b1fc3623fa8a6c11d176dd0"}, + {file = "psycopg2_binary-2.9.6-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e78e6e2a00c223e164c417628572a90093c031ed724492c763721c2e0bc2a8df"}, + {file = "psycopg2_binary-2.9.6-cp311-cp311-win32.whl", hash = "sha256:1876843d8e31c89c399e31b97d4b9725a3575bb9c2af92038464231ec40f9edb"}, + {file = "psycopg2_binary-2.9.6-cp311-cp311-win_amd64.whl", hash = "sha256:b4b24f75d16a89cc6b4cdff0eb6a910a966ecd476d1e73f7ce5985ff1328e9a6"}, + {file = "psycopg2_binary-2.9.6-cp36-cp36m-win32.whl", hash = "sha256:498807b927ca2510baea1b05cc91d7da4718a0f53cb766c154c417a39f1820a0"}, + {file = "psycopg2_binary-2.9.6-cp36-cp36m-win_amd64.whl", hash = "sha256:0d236c2825fa656a2d98bbb0e52370a2e852e5a0ec45fc4f402977313329174d"}, + {file = "psycopg2_binary-2.9.6-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:34b9ccdf210cbbb1303c7c4db2905fa0319391bd5904d32689e6dd5c963d2ea8"}, + {file = "psycopg2_binary-2.9.6-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:84d2222e61f313c4848ff05353653bf5f5cf6ce34df540e4274516880d9c3763"}, + {file = "psycopg2_binary-2.9.6-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:30637a20623e2a2eacc420059be11527f4458ef54352d870b8181a4c3020ae6b"}, + {file = "psycopg2_binary-2.9.6-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8122cfc7cae0da9a3077216528b8bb3629c43b25053284cc868744bfe71eb141"}, + {file = "psycopg2_binary-2.9.6-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:38601cbbfe600362c43714482f43b7c110b20cb0f8172422c616b09b85a750c5"}, + {file = "psycopg2_binary-2.9.6-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:c7e62ab8b332147a7593a385d4f368874d5fe4ad4e341770d4983442d89603e3"}, + {file = "psycopg2_binary-2.9.6-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:2ab652e729ff4ad76d400df2624d223d6e265ef81bb8aa17fbd63607878ecbee"}, + {file = "psycopg2_binary-2.9.6-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:c83a74b68270028dc8ee74d38ecfaf9c90eed23c8959fca95bd703d25b82c88e"}, + {file = "psycopg2_binary-2.9.6-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:d4e6036decf4b72d6425d5b29bbd3e8f0ff1059cda7ac7b96d6ac5ed34ffbacd"}, + {file = "psycopg2_binary-2.9.6-cp37-cp37m-win32.whl", hash = "sha256:a8c28fd40a4226b4a84bdf2d2b5b37d2c7bd49486b5adcc200e8c7ec991dfa7e"}, + {file = "psycopg2_binary-2.9.6-cp37-cp37m-win_amd64.whl", hash = "sha256:51537e3d299be0db9137b321dfb6a5022caaab275775680e0c3d281feefaca6b"}, + {file = "psycopg2_binary-2.9.6-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:cf4499e0a83b7b7edcb8dabecbd8501d0d3a5ef66457200f77bde3d210d5debb"}, + {file = "psycopg2_binary-2.9.6-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7e13a5a2c01151f1208d5207e42f33ba86d561b7a89fca67c700b9486a06d0e2"}, + {file = "psycopg2_binary-2.9.6-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0e0f754d27fddcfd74006455b6e04e6705d6c31a612ec69ddc040a5468e44b4e"}, + {file = "psycopg2_binary-2.9.6-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d57c3fd55d9058645d26ae37d76e61156a27722097229d32a9e73ed54819982a"}, + {file = "psycopg2_binary-2.9.6-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:71f14375d6f73b62800530b581aed3ada394039877818b2d5f7fc77e3bb6894d"}, + {file = "psycopg2_binary-2.9.6-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:441cc2f8869a4f0f4bb408475e5ae0ee1f3b55b33f350406150277f7f35384fc"}, + {file = "psycopg2_binary-2.9.6-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:65bee1e49fa6f9cf327ce0e01c4c10f39165ee76d35c846ade7cb0ec6683e303"}, + {file = "psycopg2_binary-2.9.6-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:af335bac6b666cc6aea16f11d486c3b794029d9df029967f9938a4bed59b6a19"}, + {file = "psycopg2_binary-2.9.6-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:cfec476887aa231b8548ece2e06d28edc87c1397ebd83922299af2e051cf2827"}, + {file = "psycopg2_binary-2.9.6-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:65c07febd1936d63bfde78948b76cd4c2a411572a44ac50719ead41947d0f26b"}, + {file = "psycopg2_binary-2.9.6-cp38-cp38-win32.whl", hash = "sha256:4dfb4be774c4436a4526d0c554af0cc2e02082c38303852a36f6456ece7b3503"}, + {file = "psycopg2_binary-2.9.6-cp38-cp38-win_amd64.whl", hash = "sha256:02c6e3cf3439e213e4ee930308dc122d6fb4d4bea9aef4a12535fbd605d1a2fe"}, + {file = "psycopg2_binary-2.9.6-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:e9182eb20f41417ea1dd8e8f7888c4d7c6e805f8a7c98c1081778a3da2bee3e4"}, + {file = "psycopg2_binary-2.9.6-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:8a6979cf527e2603d349a91060f428bcb135aea2be3201dff794813256c274f1"}, + {file = "psycopg2_binary-2.9.6-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8338a271cb71d8da40b023a35d9c1e919eba6cbd8fa20a54b748a332c355d896"}, + {file = "psycopg2_binary-2.9.6-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e3ed340d2b858d6e6fb5083f87c09996506af483227735de6964a6100b4e6a54"}, + {file = "psycopg2_binary-2.9.6-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f81e65376e52f03422e1fb475c9514185669943798ed019ac50410fb4c4df232"}, + {file = "psycopg2_binary-2.9.6-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bfb13af3c5dd3a9588000910178de17010ebcccd37b4f9794b00595e3a8ddad3"}, + {file = "psycopg2_binary-2.9.6-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:4c727b597c6444a16e9119386b59388f8a424223302d0c06c676ec8b4bc1f963"}, + {file = "psycopg2_binary-2.9.6-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:4d67fbdaf177da06374473ef6f7ed8cc0a9dc640b01abfe9e8a2ccb1b1402c1f"}, + {file = "psycopg2_binary-2.9.6-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:0892ef645c2fabb0c75ec32d79f4252542d0caec1d5d949630e7d242ca4681a3"}, + {file = "psycopg2_binary-2.9.6-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:02c0f3757a4300cf379eb49f543fb7ac527fb00144d39246ee40e1df684ab514"}, + {file = "psycopg2_binary-2.9.6-cp39-cp39-win32.whl", hash = "sha256:c3dba7dab16709a33a847e5cd756767271697041fbe3fe97c215b1fc1f5c9848"}, + {file = "psycopg2_binary-2.9.6-cp39-cp39-win_amd64.whl", hash = "sha256:f6a88f384335bb27812293fdb11ac6aee2ca3f51d3c7820fe03de0a304ab6249"}, +] [[package]] name = "pycryptodome" -version = "3.10.1" +version = "3.18.0" description = "Cryptographic library for Python" -category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +files = [ + {file = "pycryptodome-3.18.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:d1497a8cd4728db0e0da3c304856cb37c0c4e3d0b36fcbabcc1600f18504fc54"}, + {file = "pycryptodome-3.18.0-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:928078c530da78ff08e10eb6cada6e0dff386bf3d9fa9871b4bbc9fbc1efe024"}, + {file = "pycryptodome-3.18.0-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:157c9b5ba5e21b375f052ca78152dd309a09ed04703fd3721dce3ff8ecced148"}, + {file = "pycryptodome-3.18.0-cp27-cp27m-manylinux2014_aarch64.whl", hash = "sha256:d20082bdac9218649f6abe0b885927be25a917e29ae0502eaf2b53f1233ce0c2"}, + {file = "pycryptodome-3.18.0-cp27-cp27m-musllinux_1_1_aarch64.whl", hash = "sha256:e8ad74044e5f5d2456c11ed4cfd3e34b8d4898c0cb201c4038fe41458a82ea27"}, + {file = "pycryptodome-3.18.0-cp27-cp27m-win32.whl", hash = "sha256:62a1e8847fabb5213ccde38915563140a5b338f0d0a0d363f996b51e4a6165cf"}, + {file = "pycryptodome-3.18.0-cp27-cp27m-win_amd64.whl", hash = "sha256:16bfd98dbe472c263ed2821284118d899c76968db1a6665ade0c46805e6b29a4"}, + {file = "pycryptodome-3.18.0-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:7a3d22c8ee63de22336679e021c7f2386f7fc465477d59675caa0e5706387944"}, + {file = "pycryptodome-3.18.0-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:78d863476e6bad2a592645072cc489bb90320972115d8995bcfbee2f8b209918"}, + {file = "pycryptodome-3.18.0-cp27-cp27mu-manylinux2014_aarch64.whl", hash = "sha256:b6a610f8bfe67eab980d6236fdc73bfcdae23c9ed5548192bb2d530e8a92780e"}, + {file = "pycryptodome-3.18.0-cp27-cp27mu-musllinux_1_1_aarch64.whl", hash = "sha256:422c89fd8df8a3bee09fb8d52aaa1e996120eafa565437392b781abec2a56e14"}, + {file = "pycryptodome-3.18.0-cp35-abi3-macosx_10_9_universal2.whl", hash = "sha256:9ad6f09f670c466aac94a40798e0e8d1ef2aa04589c29faa5b9b97566611d1d1"}, + {file = "pycryptodome-3.18.0-cp35-abi3-macosx_10_9_x86_64.whl", hash = "sha256:53aee6be8b9b6da25ccd9028caf17dcdce3604f2c7862f5167777b707fbfb6cb"}, + {file = "pycryptodome-3.18.0-cp35-abi3-manylinux2014_aarch64.whl", hash = "sha256:10da29526a2a927c7d64b8f34592f461d92ae55fc97981aab5bbcde8cb465bb6"}, + {file = "pycryptodome-3.18.0-cp35-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f21efb8438971aa16924790e1c3dba3a33164eb4000106a55baaed522c261acf"}, + {file = "pycryptodome-3.18.0-cp35-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4944defabe2ace4803f99543445c27dd1edbe86d7d4edb87b256476a91e9ffa4"}, + {file = "pycryptodome-3.18.0-cp35-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:51eae079ddb9c5f10376b4131be9589a6554f6fd84f7f655180937f611cd99a2"}, + {file = "pycryptodome-3.18.0-cp35-abi3-musllinux_1_1_i686.whl", hash = "sha256:83c75952dcf4a4cebaa850fa257d7a860644c70a7cd54262c237c9f2be26f76e"}, + {file = "pycryptodome-3.18.0-cp35-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:957b221d062d5752716923d14e0926f47670e95fead9d240fa4d4862214b9b2f"}, + {file = "pycryptodome-3.18.0-cp35-abi3-win32.whl", hash = "sha256:795bd1e4258a2c689c0b1f13ce9684fa0dd4c0e08680dcf597cf9516ed6bc0f3"}, + {file = "pycryptodome-3.18.0-cp35-abi3-win_amd64.whl", hash = "sha256:b1d9701d10303eec8d0bd33fa54d44e67b8be74ab449052a8372f12a66f93fb9"}, + {file = "pycryptodome-3.18.0-pp27-pypy_73-manylinux2010_x86_64.whl", hash = "sha256:cb1be4d5af7f355e7d41d36d8eec156ef1382a88638e8032215c215b82a4b8ec"}, + {file = "pycryptodome-3.18.0-pp27-pypy_73-win32.whl", hash = "sha256:fc0a73f4db1e31d4a6d71b672a48f3af458f548059aa05e83022d5f61aac9c08"}, + {file = "pycryptodome-3.18.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:f022a4fd2a5263a5c483a2bb165f9cb27f2be06f2f477113783efe3fe2ad887b"}, + {file = "pycryptodome-3.18.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:363dd6f21f848301c2dcdeb3c8ae5f0dee2286a5e952a0f04954b82076f23825"}, + {file = "pycryptodome-3.18.0-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:12600268763e6fec3cefe4c2dcdf79bde08d0b6dc1813887e789e495cb9f3403"}, + {file = "pycryptodome-3.18.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:4604816adebd4faf8810782f137f8426bf45fee97d8427fa8e1e49ea78a52e2c"}, + {file = "pycryptodome-3.18.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:01489bbdf709d993f3058e2996f8f40fee3f0ea4d995002e5968965fa2fe89fb"}, + {file = "pycryptodome-3.18.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3811e31e1ac3069988f7a1c9ee7331b942e605dfc0f27330a9ea5997e965efb2"}, + {file = "pycryptodome-3.18.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6f4b967bb11baea9128ec88c3d02f55a3e338361f5e4934f5240afcb667fdaec"}, + {file = "pycryptodome-3.18.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:9c8eda4f260072f7dbe42f473906c659dcbadd5ae6159dfb49af4da1293ae380"}, + {file = "pycryptodome-3.18.0.tar.gz", hash = "sha256:c9adee653fc882d98956e33ca2c1fb582e23a8af7ac82fee75bd6113c55a0413"}, +] [[package]] name = "pydash" -version = "5.0.0" +version = "5.1.2" description = "The kitchen sink of Python utility libraries for doing \"stuff\" in a functional way. Based on the Lo-Dash Javascript library." -category = "main" optional = false python-versions = ">=3.6" +files = [ + {file = "pydash-5.1.2-py3-none-any.whl", hash = "sha256:a2e0c0900fc5a3cc16c9556d34d7e8a59caa9adebcf319876c9a3ccc35dc545f"}, + {file = "pydash-5.1.2.tar.gz", hash = "sha256:0556062f6583c21fa70dd9a2c1a4e63bbe8cd54e14fecdfb579b46c36d93836f"}, +] [package.extras] -dev = ["black", "coverage", "docformatter", "flake8", "flake8-black", "flake8-bugbear", "flake8-isort", "invoke", "isort", "pylint", "pytest", "pytest-cov", "pytest-flake8", "pytest-pylint", "sphinx", "sphinx-rtd-theme", "tox", "twine", "wheel"] +dev = ["Sphinx", "black", "build", "coverage", "docformatter", "flake8", "flake8-black", "flake8-bugbear", "flake8-isort", "invoke", "isort", "pylint", "pytest", "pytest-cov", "sphinx-rtd-theme", "tox", "twine", "wheel"] + +[[package]] +name = "pyotp" +version = "2.8.0" +description = "Python One Time Password Library" +optional = false +python-versions = ">=3.7" +files = [ + {file = "pyotp-2.8.0-py3-none-any.whl", hash = "sha256:889d037fdde6accad28531fc62a790f089e5dfd5b638773e9ee004cce074a2e5"}, + {file = "pyotp-2.8.0.tar.gz", hash = "sha256:c2f5e17d9da92d8ec1f7de6331ab08116b9115adbabcba6e208d46fc49a98c5a"}, +] [[package]] name = "python-crontab" -version = "2.5.1" +version = "2.7.1" description = "Python Crontab API" -category = "main" optional = false python-versions = "*" +files = [ + {file = "python-crontab-2.7.1.tar.gz", hash = "sha256:b21af4647c7bbb848fef2f020616c6b0289dcb9f94b4f991a55310ff9bec5749"}, + {file = "python_crontab-2.7.1-py3-none-any.whl", hash = "sha256:9c374d1c9d401afdd8dd958f20077f74c158ab3fffb9604296802715e887fe48"}, +] [package.dependencies] python-dateutil = "*" @@ -282,38 +796,154 @@ cron-schedule = ["croniter"] [[package]] name = "python-dateutil" -version = "2.8.1" +version = "2.8.2" description = "Extensions to the standard Python datetime module" -category = "main" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" +files = [ + {file = "python-dateutil-2.8.2.tar.gz", hash = "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86"}, + {file = "python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"}, +] [package.dependencies] six = ">=1.5" [[package]] name = "pytz" -version = "2021.1" +version = "2023.3" description = "World timezone definitions, modern and historical" -category = "main" optional = false python-versions = "*" +files = [ + {file = "pytz-2023.3-py2.py3-none-any.whl", hash = "sha256:a151b3abb88eda1d4e34a9814df37de2a80e301e68ba0fd856fb9b46bfbbbffb"}, + {file = "pytz-2023.3.tar.gz", hash = "sha256:1d8ce29db189191fb55338ee6d0387d82ab59f3d00eac103412d64e0ebd0c588"}, +] + +[[package]] +name = "redis" +version = "5.0.0" +description = "Python client for Redis database and key-value store" +optional = false +python-versions = ">=3.7" +files = [ + {file = "redis-5.0.0-py3-none-any.whl", hash = "sha256:06570d0b2d84d46c21defc550afbaada381af82f5b83e5b3777600e05d8e2ed0"}, + {file = "redis-5.0.0.tar.gz", hash = "sha256:5cea6c0d335c9a7332a460ed8729ceabb4d0c489c7285b0a86dbbf8a017bd120"}, +] + +[package.dependencies] +async-timeout = {version = ">=4.0.2", markers = "python_full_version <= \"3.11.2\""} + +[package.extras] +hiredis = ["hiredis (>=1.0.0)"] +ocsp = ["cryptography (>=36.0.1)", "pyopenssl (==20.0.1)", "requests (>=2.26.0)"] [[package]] name = "regex" -version = "2021.4.4" +version = "2023.6.3" description = "Alternative regular expression module, to replace re." -category = "main" optional = false -python-versions = "*" +python-versions = ">=3.6" +files = [ + {file = "regex-2023.6.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:824bf3ac11001849aec3fa1d69abcb67aac3e150a933963fb12bda5151fe1bfd"}, + {file = "regex-2023.6.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:05ed27acdf4465c95826962528f9e8d41dbf9b1aa8531a387dee6ed215a3e9ef"}, + {file = "regex-2023.6.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0b49c764f88a79160fa64f9a7b425620e87c9f46095ef9c9920542ab2495c8bc"}, + {file = "regex-2023.6.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8e3f1316c2293e5469f8f09dc2d76efb6c3982d3da91ba95061a7e69489a14ef"}, + {file = "regex-2023.6.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:43e1dd9d12df9004246bacb79a0e5886b3b6071b32e41f83b0acbf293f820ee8"}, + {file = "regex-2023.6.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4959e8bcbfda5146477d21c3a8ad81b185cd252f3d0d6e4724a5ef11c012fb06"}, + {file = "regex-2023.6.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:af4dd387354dc83a3bff67127a124c21116feb0d2ef536805c454721c5d7993d"}, + {file = "regex-2023.6.3-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:2239d95d8e243658b8dbb36b12bd10c33ad6e6933a54d36ff053713f129aa536"}, + {file = "regex-2023.6.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:890e5a11c97cf0d0c550eb661b937a1e45431ffa79803b942a057c4fb12a2da2"}, + {file = "regex-2023.6.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:a8105e9af3b029f243ab11ad47c19b566482c150c754e4c717900a798806b222"}, + {file = "regex-2023.6.3-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:25be746a8ec7bc7b082783216de8e9473803706723b3f6bef34b3d0ed03d57e2"}, + {file = "regex-2023.6.3-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:3676f1dd082be28b1266c93f618ee07741b704ab7b68501a173ce7d8d0d0ca18"}, + {file = "regex-2023.6.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:10cb847aeb1728412c666ab2e2000ba6f174f25b2bdc7292e7dd71b16db07568"}, + {file = "regex-2023.6.3-cp310-cp310-win32.whl", hash = "sha256:dbbbfce33cd98f97f6bffb17801b0576e653f4fdb1d399b2ea89638bc8d08ae1"}, + {file = "regex-2023.6.3-cp310-cp310-win_amd64.whl", hash = "sha256:c5f8037000eb21e4823aa485149f2299eb589f8d1fe4b448036d230c3f4e68e0"}, + {file = "regex-2023.6.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c123f662be8ec5ab4ea72ea300359023a5d1df095b7ead76fedcd8babbedf969"}, + {file = "regex-2023.6.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9edcbad1f8a407e450fbac88d89e04e0b99a08473f666a3f3de0fd292badb6aa"}, + {file = "regex-2023.6.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dcba6dae7de533c876255317c11f3abe4907ba7d9aa15d13e3d9710d4315ec0e"}, + {file = "regex-2023.6.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:29cdd471ebf9e0f2fb3cac165efedc3c58db841d83a518b082077e612d3ee5df"}, + {file = "regex-2023.6.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:12b74fbbf6cbbf9dbce20eb9b5879469e97aeeaa874145517563cca4029db65c"}, + {file = "regex-2023.6.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0c29ca1bd61b16b67be247be87390ef1d1ef702800f91fbd1991f5c4421ebae8"}, + {file = "regex-2023.6.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d77f09bc4b55d4bf7cc5eba785d87001d6757b7c9eec237fe2af57aba1a071d9"}, + {file = "regex-2023.6.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ea353ecb6ab5f7e7d2f4372b1e779796ebd7b37352d290096978fea83c4dba0c"}, + {file = "regex-2023.6.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:10590510780b7541969287512d1b43f19f965c2ece6c9b1c00fc367b29d8dce7"}, + {file = "regex-2023.6.3-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:e2fbd6236aae3b7f9d514312cdb58e6494ee1c76a9948adde6eba33eb1c4264f"}, + {file = "regex-2023.6.3-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:6b2675068c8b56f6bfd5a2bda55b8accbb96c02fd563704732fd1c95e2083461"}, + {file = "regex-2023.6.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:74419d2b50ecb98360cfaa2974da8689cb3b45b9deff0dcf489c0d333bcc1477"}, + {file = "regex-2023.6.3-cp311-cp311-win32.whl", hash = "sha256:fb5ec16523dc573a4b277663a2b5a364e2099902d3944c9419a40ebd56a118f9"}, + {file = "regex-2023.6.3-cp311-cp311-win_amd64.whl", hash = "sha256:09e4a1a6acc39294a36b7338819b10baceb227f7f7dbbea0506d419b5a1dd8af"}, + {file = "regex-2023.6.3-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:0654bca0cdf28a5956c83839162692725159f4cda8d63e0911a2c0dc76166525"}, + {file = "regex-2023.6.3-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:463b6a3ceb5ca952e66550a4532cef94c9a0c80dc156c4cc343041951aec1697"}, + {file = "regex-2023.6.3-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:87b2a5bb5e78ee0ad1de71c664d6eb536dc3947a46a69182a90f4410f5e3f7dd"}, + {file = "regex-2023.6.3-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6343c6928282c1f6a9db41f5fd551662310e8774c0e5ebccb767002fcf663ca9"}, + {file = "regex-2023.6.3-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b6192d5af2ccd2a38877bfef086d35e6659566a335b1492786ff254c168b1693"}, + {file = "regex-2023.6.3-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:74390d18c75054947e4194019077e243c06fbb62e541d8817a0fa822ea310c14"}, + {file = "regex-2023.6.3-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:742e19a90d9bb2f4a6cf2862b8b06dea5e09b96c9f2df1779e53432d7275331f"}, + {file = "regex-2023.6.3-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:8abbc5d54ea0ee80e37fef009e3cec5dafd722ed3c829126253d3e22f3846f1e"}, + {file = "regex-2023.6.3-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:c2b867c17a7a7ae44c43ebbeb1b5ff406b3e8d5b3e14662683e5e66e6cc868d3"}, + {file = "regex-2023.6.3-cp36-cp36m-musllinux_1_1_ppc64le.whl", hash = "sha256:d831c2f8ff278179705ca59f7e8524069c1a989e716a1874d6d1aab6119d91d1"}, + {file = "regex-2023.6.3-cp36-cp36m-musllinux_1_1_s390x.whl", hash = "sha256:ee2d1a9a253b1729bb2de27d41f696ae893507c7db224436abe83ee25356f5c1"}, + {file = "regex-2023.6.3-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:61474f0b41fe1a80e8dfa70f70ea1e047387b7cd01c85ec88fa44f5d7561d787"}, + {file = "regex-2023.6.3-cp36-cp36m-win32.whl", hash = "sha256:0b71e63226e393b534105fcbdd8740410dc6b0854c2bfa39bbda6b0d40e59a54"}, + {file = "regex-2023.6.3-cp36-cp36m-win_amd64.whl", hash = "sha256:bbb02fd4462f37060122e5acacec78e49c0fbb303c30dd49c7f493cf21fc5b27"}, + {file = "regex-2023.6.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b862c2b9d5ae38a68b92e215b93f98d4c5e9454fa36aae4450f61dd33ff48487"}, + {file = "regex-2023.6.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:976d7a304b59ede34ca2921305b57356694f9e6879db323fd90a80f865d355a3"}, + {file = "regex-2023.6.3-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:83320a09188e0e6c39088355d423aa9d056ad57a0b6c6381b300ec1a04ec3d16"}, + {file = "regex-2023.6.3-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9427a399501818a7564f8c90eced1e9e20709ece36be701f394ada99890ea4b3"}, + {file = "regex-2023.6.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7178bbc1b2ec40eaca599d13c092079bf529679bf0371c602edaa555e10b41c3"}, + {file = "regex-2023.6.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:837328d14cde912af625d5f303ec29f7e28cdab588674897baafaf505341f2fc"}, + {file = "regex-2023.6.3-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:2d44dc13229905ae96dd2ae2dd7cebf824ee92bc52e8cf03dcead37d926da019"}, + {file = "regex-2023.6.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:d54af539295392611e7efbe94e827311eb8b29668e2b3f4cadcfe6f46df9c777"}, + {file = "regex-2023.6.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:7117d10690c38a622e54c432dfbbd3cbd92f09401d622902c32f6d377e2300ee"}, + {file = "regex-2023.6.3-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:bb60b503ec8a6e4e3e03a681072fa3a5adcbfa5479fa2d898ae2b4a8e24c4591"}, + {file = "regex-2023.6.3-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:65ba8603753cec91c71de423a943ba506363b0e5c3fdb913ef8f9caa14b2c7e0"}, + {file = "regex-2023.6.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:271f0bdba3c70b58e6f500b205d10a36fb4b58bd06ac61381b68de66442efddb"}, + {file = "regex-2023.6.3-cp37-cp37m-win32.whl", hash = "sha256:9beb322958aaca059f34975b0df135181f2e5d7a13b84d3e0e45434749cb20f7"}, + {file = "regex-2023.6.3-cp37-cp37m-win_amd64.whl", hash = "sha256:fea75c3710d4f31389eed3c02f62d0b66a9da282521075061ce875eb5300cf23"}, + {file = "regex-2023.6.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8f56fcb7ff7bf7404becdfc60b1e81a6d0561807051fd2f1860b0d0348156a07"}, + {file = "regex-2023.6.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:d2da3abc88711bce7557412310dfa50327d5769a31d1c894b58eb256459dc289"}, + {file = "regex-2023.6.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a99b50300df5add73d307cf66abea093304a07eb017bce94f01e795090dea87c"}, + {file = "regex-2023.6.3-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5708089ed5b40a7b2dc561e0c8baa9535b77771b64a8330b684823cfd5116036"}, + {file = "regex-2023.6.3-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:687ea9d78a4b1cf82f8479cab23678aff723108df3edeac098e5b2498879f4a7"}, + {file = "regex-2023.6.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4d3850beab9f527f06ccc94b446c864059c57651b3f911fddb8d9d3ec1d1b25d"}, + {file = "regex-2023.6.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e8915cc96abeb8983cea1df3c939e3c6e1ac778340c17732eb63bb96247b91d2"}, + {file = "regex-2023.6.3-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:841d6e0e5663d4c7b4c8099c9997be748677d46cbf43f9f471150e560791f7ff"}, + {file = "regex-2023.6.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:9edce5281f965cf135e19840f4d93d55b3835122aa76ccacfd389e880ba4cf82"}, + {file = "regex-2023.6.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:b956231ebdc45f5b7a2e1f90f66a12be9610ce775fe1b1d50414aac1e9206c06"}, + {file = "regex-2023.6.3-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:36efeba71c6539d23c4643be88295ce8c82c88bbd7c65e8a24081d2ca123da3f"}, + {file = "regex-2023.6.3-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:cf67ca618b4fd34aee78740bea954d7c69fdda419eb208c2c0c7060bb822d747"}, + {file = "regex-2023.6.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:b4598b1897837067a57b08147a68ac026c1e73b31ef6e36deeeb1fa60b2933c9"}, + {file = "regex-2023.6.3-cp38-cp38-win32.whl", hash = "sha256:f415f802fbcafed5dcc694c13b1292f07fe0befdb94aa8a52905bd115ff41e88"}, + {file = "regex-2023.6.3-cp38-cp38-win_amd64.whl", hash = "sha256:d4f03bb71d482f979bda92e1427f3ec9b220e62a7dd337af0aa6b47bf4498f72"}, + {file = "regex-2023.6.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:ccf91346b7bd20c790310c4147eee6ed495a54ddb6737162a36ce9dbef3e4751"}, + {file = "regex-2023.6.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b28f5024a3a041009eb4c333863d7894d191215b39576535c6734cd88b0fcb68"}, + {file = "regex-2023.6.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e0bb18053dfcfed432cc3ac632b5e5e5c5b7e55fb3f8090e867bfd9b054dbcbf"}, + {file = "regex-2023.6.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9a5bfb3004f2144a084a16ce19ca56b8ac46e6fd0651f54269fc9e230edb5e4a"}, + {file = "regex-2023.6.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5c6b48d0fa50d8f4df3daf451be7f9689c2bde1a52b1225c5926e3f54b6a9ed1"}, + {file = "regex-2023.6.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:051da80e6eeb6e239e394ae60704d2b566aa6a7aed6f2890a7967307267a5dc6"}, + {file = "regex-2023.6.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a4c3b7fa4cdaa69268748665a1a6ff70c014d39bb69c50fda64b396c9116cf77"}, + {file = "regex-2023.6.3-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:457b6cce21bee41ac292d6753d5e94dcbc5c9e3e3a834da285b0bde7aa4a11e9"}, + {file = "regex-2023.6.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:aad51907d74fc183033ad796dd4c2e080d1adcc4fd3c0fd4fd499f30c03011cd"}, + {file = "regex-2023.6.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:0385e73da22363778ef2324950e08b689abdf0b108a7d8decb403ad7f5191938"}, + {file = "regex-2023.6.3-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:c6a57b742133830eec44d9b2290daf5cbe0a2f1d6acee1b3c7b1c7b2f3606df7"}, + {file = "regex-2023.6.3-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:3e5219bf9e75993d73ab3d25985c857c77e614525fac9ae02b1bebd92f7cecac"}, + {file = "regex-2023.6.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:e5087a3c59eef624a4591ef9eaa6e9a8d8a94c779dade95d27c0bc24650261cd"}, + {file = "regex-2023.6.3-cp39-cp39-win32.whl", hash = "sha256:20326216cc2afe69b6e98528160b225d72f85ab080cbdf0b11528cbbaba2248f"}, + {file = "regex-2023.6.3-cp39-cp39-win_amd64.whl", hash = "sha256:bdff5eab10e59cf26bc479f565e25ed71a7d041d1ded04ccf9aee1d9f208487a"}, + {file = "regex-2023.6.3.tar.gz", hash = "sha256:72d1a25bf36d2050ceb35b517afe13864865268dfb45910e2e17a84be6cbfeb0"}, +] [[package]] name = "requests" version = "2.25.1" description = "Python HTTP for Humans." -category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +files = [ + {file = "requests-2.25.1-py2.py3-none-any.whl", hash = "sha256:c210084e36a42ae6b9219e00e48287def368a26d03a048ddad7bfee44f75871e"}, + {file = "requests-2.25.1.tar.gz", hash = "sha256:27973dd4a904a4f13b263a19c866c13b92a39ed1c964655f025f3f8d3d75b804"}, +] [package.dependencies] certifi = ">=2017.4.17" @@ -322,503 +952,298 @@ idna = ">=2.5,<3" urllib3 = ">=1.21.1,<1.27" [package.extras] -security = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)"] +security = ["cryptography (>=1.3.4)", "pyOpenSSL (>=0.14)"] socks = ["PySocks (>=1.5.6,!=1.5.7)", "win-inet-pton"] +[[package]] +name = "rq" +version = "1.15.1" +description = "RQ is a simple, lightweight, library for creating background jobs, and processing them." +optional = false +python-versions = ">=3.6" +files = [ + {file = "rq-1.15.1-py2.py3-none-any.whl", hash = "sha256:6e243d8d9c4af4686ded4b01b25ea1ff4bac4fc260b02638fbe9c8c17b004bd1"}, + {file = "rq-1.15.1.tar.gz", hash = "sha256:1f49f4ac1a084044bb8e95b3f305c0bf17e55618b08c18e0b60c080f12d6f008"}, +] + +[package.dependencies] +click = ">=5.0.0" +redis = ">=4.0.0" + [[package]] name = "selenium" version = "3.141.0" description = "Python bindings for Selenium" -category = "main" optional = false python-versions = "*" +files = [ + {file = "selenium-3.141.0-py2.py3-none-any.whl", hash = "sha256:2d7131d7bc5a5b99a2d9b04aaf2612c411b03b8ca1b1ee8d3de5845a9be2cb3c"}, + {file = "selenium-3.141.0.tar.gz", hash = "sha256:deaf32b60ad91a4611b98d8002757f29e6f2c2d5fcaf202e1c9ad06d6772300d"}, +] [package.dependencies] urllib3 = "*" [[package]] name = "sentry-sdk" -version = "1.1.0" +version = "1.28.1" description = "Python client for Sentry (https://sentry.io)" -category = "main" optional = false python-versions = "*" +files = [ + {file = "sentry-sdk-1.28.1.tar.gz", hash = "sha256:dcd88c68aa64dae715311b5ede6502fd684f70d00a7cd4858118f0ba3153a3ae"}, + {file = "sentry_sdk-1.28.1-py2.py3-none-any.whl", hash = "sha256:6bdb25bd9092478d3a817cb0d01fa99e296aea34d404eac3ca0037faa5c2aa0a"}, +] [package.dependencies] certifi = "*" -urllib3 = ">=1.10.0" +urllib3 = {version = ">=1.26.11", markers = "python_version >= \"3.6\""} [package.extras] aiohttp = ["aiohttp (>=3.5)"] +arq = ["arq (>=0.23)"] beam = ["apache-beam (>=2.12)"] bottle = ["bottle (>=0.12.13)"] celery = ["celery (>=3)"] chalice = ["chalice (>=1.16.0)"] django = ["django (>=1.8)"] falcon = ["falcon (>=1.4)"] -flask = ["flask (>=0.11)", "blinker (>=1.1)"] -pure_eval = ["pure-eval", "executing", "asttokens"] +fastapi = ["fastapi (>=0.79.0)"] +flask = ["blinker (>=1.1)", "flask (>=0.11)", "markupsafe"] +grpcio = ["grpcio (>=1.21.1)"] +httpx = ["httpx (>=0.16.0)"] +huey = ["huey (>=2)"] +loguru = ["loguru (>=0.5)"] +opentelemetry = ["opentelemetry-distro (>=0.35b0)"] +pure-eval = ["asttokens", "executing", "pure-eval"] +pymongo = ["pymongo (>=3.1)"] pyspark = ["pyspark (>=2.4.4)"] +quart = ["blinker (>=1.1)", "quart (>=0.16.1)"] rq = ["rq (>=0.6)"] sanic = ["sanic (>=0.8)"] sqlalchemy = ["sqlalchemy (>=1.2)"] +starlette = ["starlette (>=0.19.1)"] +starlite = ["starlite (>=1.48)"] tornado = ["tornado (>=5)"] +[[package]] +name = "setuptools" +version = "68.0.0" +description = "Easily download, build, install, upgrade, and uninstall Python packages" +optional = false +python-versions = ">=3.7" +files = [ + {file = "setuptools-68.0.0-py3-none-any.whl", hash = "sha256:11e52c67415a381d10d6b462ced9cfb97066179f0e871399e006c4ab101fc85f"}, + {file = "setuptools-68.0.0.tar.gz", hash = "sha256:baf1fdb41c6da4cd2eae722e135500da913332ab3f2f5c7d33af9b492acb5235"}, +] + +[package.extras] +docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-hoverxref (<2)", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (==0.8.3)", "sphinx-reredirects", "sphinxcontrib-towncrier"] +testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pip (>=19.1)", "pip-run (>=8.8)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-ruff", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] +testing-integration = ["build[virtualenv]", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"] + [[package]] name = "simplejson" -version = "3.17.2" +version = "3.19.1" description = "Simple, fast, extensible JSON encoder/decoder for Python" -category = "main" optional = false python-versions = ">=2.5, !=3.0.*, !=3.1.*, !=3.2.*" +files = [ + {file = "simplejson-3.19.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:412e58997a30c5deb8cab5858b8e2e5b40ca007079f7010ee74565cc13d19665"}, + {file = "simplejson-3.19.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:e765b1f47293dedf77946f0427e03ee45def2862edacd8868c6cf9ab97c8afbd"}, + {file = "simplejson-3.19.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:3231100edee292da78948fa0a77dee4e5a94a0a60bcba9ed7a9dc77f4d4bb11e"}, + {file = "simplejson-3.19.1-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:081ea6305b3b5e84ae7417e7f45956db5ea3872ec497a584ec86c3260cda049e"}, + {file = "simplejson-3.19.1-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:f253edf694ce836631b350d758d00a8c4011243d58318fbfbe0dd54a6a839ab4"}, + {file = "simplejson-3.19.1-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:5db86bb82034e055257c8e45228ca3dbce85e38d7bfa84fa7b2838e032a3219c"}, + {file = "simplejson-3.19.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:69a8b10a4f81548bc1e06ded0c4a6c9042c0be0d947c53c1ed89703f7e613950"}, + {file = "simplejson-3.19.1-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:58ee5e24d6863b22194020eb62673cf8cc69945fcad6b283919490f6e359f7c5"}, + {file = "simplejson-3.19.1-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:73d0904c2471f317386d4ae5c665b16b5c50ab4f3ee7fd3d3b7651e564ad74b1"}, + {file = "simplejson-3.19.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:66d780047c31ff316ee305c3f7550f352d87257c756413632303fc59fef19eac"}, + {file = "simplejson-3.19.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:cd4d50a27b065447c9c399f0bf0a993bd0e6308db8bbbfbc3ea03b41c145775a"}, + {file = "simplejson-3.19.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0c16ec6a67a5f66ab004190829eeede01c633936375edcad7cbf06d3241e5865"}, + {file = "simplejson-3.19.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:17a963e8dd4d81061cc05b627677c1f6a12e81345111fbdc5708c9f088d752c9"}, + {file = "simplejson-3.19.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7e78d79b10aa92f40f54178ada2b635c960d24fc6141856b926d82f67e56d169"}, + {file = "simplejson-3.19.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ad071cd84a636195f35fa71de2186d717db775f94f985232775794d09f8d9061"}, + {file = "simplejson-3.19.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6e7c70f19405e5f99168077b785fe15fcb5f9b3c0b70b0b5c2757ce294922c8c"}, + {file = "simplejson-3.19.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:54fca2b26bcd1c403146fd9461d1da76199442297160721b1d63def2a1b17799"}, + {file = "simplejson-3.19.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:48600a6e0032bed17c20319d91775f1797d39953ccfd68c27f83c8d7fc3b32cb"}, + {file = "simplejson-3.19.1-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:93f5ac30607157a0b2579af59a065bcfaa7fadeb4875bf927a8f8b6739c8d910"}, + {file = "simplejson-3.19.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:6b79642a599740603ca86cf9df54f57a2013c47e1dd4dd2ae4769af0a6816900"}, + {file = "simplejson-3.19.1-cp310-cp310-win32.whl", hash = "sha256:d9f2c27f18a0b94107d57294aab3d06d6046ea843ed4a45cae8bd45756749f3a"}, + {file = "simplejson-3.19.1-cp310-cp310-win_amd64.whl", hash = "sha256:5673d27806085d2a413b3be5f85fad6fca4b7ffd31cfe510bbe65eea52fff571"}, + {file = "simplejson-3.19.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:79c748aa61fd8098d0472e776743de20fae2686edb80a24f0f6593a77f74fe86"}, + {file = "simplejson-3.19.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:390f4a8ca61d90bcf806c3ad644e05fa5890f5b9a72abdd4ca8430cdc1e386fa"}, + {file = "simplejson-3.19.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d61482b5d18181e6bb4810b4a6a24c63a490c3a20e9fbd7876639653e2b30a1a"}, + {file = "simplejson-3.19.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2541fdb7467ef9bfad1f55b6c52e8ea52b3ce4a0027d37aff094190a955daa9d"}, + {file = "simplejson-3.19.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:46133bc7dd45c9953e6ee4852e3de3d5a9a4a03b068bd238935a5c72f0a1ce34"}, + {file = "simplejson-3.19.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f96def94576f857abf58e031ce881b5a3fc25cbec64b2bc4824824a8a4367af9"}, + {file = "simplejson-3.19.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9f14ecca970d825df0d29d5c6736ff27999ee7bdf5510e807f7ad8845f7760ce"}, + {file = "simplejson-3.19.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:66389b6b6ee46a94a493a933a26008a1bae0cfadeca176933e7ff6556c0ce998"}, + {file = "simplejson-3.19.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:22b867205cd258050c2625325fdd9a65f917a5aff22a23387e245ecae4098e78"}, + {file = "simplejson-3.19.1-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:c39fa911e4302eb79c804b221ddec775c3da08833c0a9120041dd322789824de"}, + {file = "simplejson-3.19.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:65dafe413b15e8895ad42e49210b74a955c9ae65564952b0243a18fb35b986cc"}, + {file = "simplejson-3.19.1-cp311-cp311-win32.whl", hash = "sha256:f05d05d99fce5537d8f7a0af6417a9afa9af3a6c4bb1ba7359c53b6257625fcb"}, + {file = "simplejson-3.19.1-cp311-cp311-win_amd64.whl", hash = "sha256:b46aaf0332a8a9c965310058cf3487d705bf672641d2c43a835625b326689cf4"}, + {file = "simplejson-3.19.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:b438e5eaa474365f4faaeeef1ec3e8d5b4e7030706e3e3d6b5bee6049732e0e6"}, + {file = "simplejson-3.19.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aa9d614a612ad02492f704fbac636f666fa89295a5d22b4facf2d665fc3b5ea9"}, + {file = "simplejson-3.19.1-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:46e89f58e4bed107626edce1cf098da3664a336d01fc78fddcfb1f397f553d44"}, + {file = "simplejson-3.19.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:96ade243fb6f3b57e7bd3b71e90c190cd0f93ec5dce6bf38734a73a2e5fa274f"}, + {file = "simplejson-3.19.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ed18728b90758d171f0c66c475c24a443ede815cf3f1a91e907b0db0ebc6e508"}, + {file = "simplejson-3.19.1-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:6a561320485017ddfc21bd2ed5de2d70184f754f1c9b1947c55f8e2b0163a268"}, + {file = "simplejson-3.19.1-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:2098811cd241429c08b7fc5c9e41fcc3f59f27c2e8d1da2ccdcf6c8e340ab507"}, + {file = "simplejson-3.19.1-cp36-cp36m-musllinux_1_1_ppc64le.whl", hash = "sha256:8f8d179393e6f0cf6c7c950576892ea6acbcea0a320838c61968ac7046f59228"}, + {file = "simplejson-3.19.1-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:eff87c68058374e45225089e4538c26329a13499bc0104b52b77f8428eed36b2"}, + {file = "simplejson-3.19.1-cp36-cp36m-win32.whl", hash = "sha256:d300773b93eed82f6da138fd1d081dc96fbe53d96000a85e41460fe07c8d8b33"}, + {file = "simplejson-3.19.1-cp36-cp36m-win_amd64.whl", hash = "sha256:37724c634f93e5caaca04458f267836eb9505d897ab3947b52f33b191bf344f3"}, + {file = "simplejson-3.19.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:74bf802debe68627227ddb665c067eb8c73aa68b2476369237adf55c1161b728"}, + {file = "simplejson-3.19.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:70128fb92932524c89f373e17221cf9535d7d0c63794955cc3cd5868e19f5d38"}, + {file = "simplejson-3.19.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8090e75653ea7db75bc21fa5f7bcf5f7bdf64ea258cbbac45c7065f6324f1b50"}, + {file = "simplejson-3.19.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a755f7bfc8adcb94887710dc70cc12a69a454120c6adcc6f251c3f7b46ee6aac"}, + {file = "simplejson-3.19.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0ccb2c1877bc9b25bc4f4687169caa925ffda605d7569c40e8e95186e9a5e58b"}, + {file = "simplejson-3.19.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:919bc5aa4d8094cf8f1371ea9119e5d952f741dc4162810ab714aec948a23fe5"}, + {file = "simplejson-3.19.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:e333c5b62e93949f5ac27e6758ba53ef6ee4f93e36cc977fe2e3df85c02f6dc4"}, + {file = "simplejson-3.19.1-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:3a4480e348000d89cf501b5606415f4d328484bbb431146c2971123d49fd8430"}, + {file = "simplejson-3.19.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:cb502cde018e93e75dc8fc7bb2d93477ce4f3ac10369f48866c61b5e031db1fd"}, + {file = "simplejson-3.19.1-cp37-cp37m-win32.whl", hash = "sha256:f41915a4e1f059dfad614b187bc06021fefb5fc5255bfe63abf8247d2f7a646a"}, + {file = "simplejson-3.19.1-cp37-cp37m-win_amd64.whl", hash = "sha256:3844305bc33d52c4975da07f75b480e17af3558c0d13085eaa6cc2f32882ccf7"}, + {file = "simplejson-3.19.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:1cb19eacb77adc5a9720244d8d0b5507421d117c7ed4f2f9461424a1829e0ceb"}, + {file = "simplejson-3.19.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:926957b278de22797bfc2f004b15297013843b595b3cd7ecd9e37ccb5fad0b72"}, + {file = "simplejson-3.19.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:b0e9a5e66969f7a47dc500e3dba8edc3b45d4eb31efb855c8647700a3493dd8a"}, + {file = "simplejson-3.19.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:79d46e7e33c3a4ef853a1307b2032cfb7220e1a079d0c65488fbd7118f44935a"}, + {file = "simplejson-3.19.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:344a5093b71c1b370968d0fbd14d55c9413cb6f0355fdefeb4a322d602d21776"}, + {file = "simplejson-3.19.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:23fbb7b46d44ed7cbcda689295862851105c7594ae5875dce2a70eeaa498ff86"}, + {file = "simplejson-3.19.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4d3025e7e9ddb48813aec2974e1a7e68e63eac911dd5e0a9568775de107ac79a"}, + {file = "simplejson-3.19.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:87b190e6ceec286219bd6b6f13547ca433f977d4600b4e81739e9ac23b5b9ba9"}, + {file = "simplejson-3.19.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:dc935d8322ba9bc7b84f99f40f111809b0473df167bf5b93b89fb719d2c4892b"}, + {file = "simplejson-3.19.1-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:3b652579c21af73879d99c8072c31476788c8c26b5565687fd9db154070d852a"}, + {file = "simplejson-3.19.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6aa7ca03f25b23b01629b1c7f78e1cd826a66bfb8809f8977a3635be2ec48f1a"}, + {file = "simplejson-3.19.1-cp38-cp38-win32.whl", hash = "sha256:08be5a241fdf67a8e05ac7edbd49b07b638ebe4846b560673e196b2a25c94b92"}, + {file = "simplejson-3.19.1-cp38-cp38-win_amd64.whl", hash = "sha256:ca56a6c8c8236d6fe19abb67ef08d76f3c3f46712c49a3b6a5352b6e43e8855f"}, + {file = "simplejson-3.19.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:6424d8229ba62e5dbbc377908cfee9b2edf25abd63b855c21f12ac596cd18e41"}, + {file = "simplejson-3.19.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:547ea86ca408a6735335c881a2e6208851027f5bfd678d8f2c92a0f02c7e7330"}, + {file = "simplejson-3.19.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:889328873c35cb0b2b4c83cbb83ec52efee5a05e75002e2c0c46c4e42790e83c"}, + {file = "simplejson-3.19.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:44cdb4e544134f305b033ad79ae5c6b9a32e7c58b46d9f55a64e2a883fbbba01"}, + {file = "simplejson-3.19.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dc2b3f06430cbd4fac0dae5b2974d2bf14f71b415fb6de017f498950da8159b1"}, + {file = "simplejson-3.19.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d125e754d26c0298715bdc3f8a03a0658ecbe72330be247f4b328d229d8cf67f"}, + {file = "simplejson-3.19.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:476c8033abed7b1fd8db62a7600bf18501ce701c1a71179e4ce04ac92c1c5c3c"}, + {file = "simplejson-3.19.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:199a0bcd792811c252d71e3eabb3d4a132b3e85e43ebd93bfd053d5b59a7e78b"}, + {file = "simplejson-3.19.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:a79b439a6a77649bb8e2f2644e6c9cc0adb720fc55bed63546edea86e1d5c6c8"}, + {file = "simplejson-3.19.1-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:203412745fed916fc04566ecef3f2b6c872b52f1e7fb3a6a84451b800fb508c1"}, + {file = "simplejson-3.19.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5ca922c61d87b4c38f37aa706520328ffe22d7ac1553ef1cadc73f053a673553"}, + {file = "simplejson-3.19.1-cp39-cp39-win32.whl", hash = "sha256:3e0902c278243d6f7223ba3e6c5738614c971fd9a887fff8feaa8dcf7249c8d4"}, + {file = "simplejson-3.19.1-cp39-cp39-win_amd64.whl", hash = "sha256:d396b610e77b0c438846607cd56418bfc194973b9886550a98fd6724e8c6cfec"}, + {file = "simplejson-3.19.1-py3-none-any.whl", hash = "sha256:4710806eb75e87919b858af0cba4ffedc01b463edc3982ded7b55143f39e41e1"}, + {file = "simplejson-3.19.1.tar.gz", hash = "sha256:6277f60848a7d8319d27d2be767a7546bc965535b28070e310b3a9af90604a4c"}, +] [[package]] name = "six" version = "1.16.0" description = "Python 2 and 3 compatibility utilities" -category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" +files = [ + {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, + {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, +] [[package]] name = "soupsieve" -version = "2.2.1" +version = "2.4.1" description = "A modern CSS selector implementation for Beautiful Soup." -category = "main" optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" +files = [ + {file = "soupsieve-2.4.1-py3-none-any.whl", hash = "sha256:1c1bfee6819544a3447586c889157365a27e10d88cde3ad3da0cf0ddf646feb8"}, + {file = "soupsieve-2.4.1.tar.gz", hash = "sha256:89d12b2d5dfcd2c9e8c22326da9d9aa9cb3dfab0a83a024f05704076ee8d35ea"}, +] [[package]] name = "sqlparse" -version = "0.4.1" +version = "0.4.4" description = "A non-validating SQL parser." -category = "main" optional = false python-versions = ">=3.5" +files = [ + {file = "sqlparse-0.4.4-py3-none-any.whl", hash = "sha256:5430a4fe2ac7d0f93e66f1efc6e1338a41884b7ddf2a350cedd20ccc4d9d28f3"}, + {file = "sqlparse-0.4.4.tar.gz", hash = "sha256:d446183e84b8349fa3061f0fe7f06ca94ba65b426946ffebe6e3e8295332420c"}, +] + +[package.extras] +dev = ["build", "flake8"] +doc = ["sphinx"] +test = ["pytest", "pytest-cov"] [[package]] name = "tqdm" -version = "4.60.0" +version = "4.65.0" description = "Fast, Extensible Progress Meter" -category = "main" optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7" +python-versions = ">=3.7" +files = [ + {file = "tqdm-4.65.0-py3-none-any.whl", hash = "sha256:c4f53a17fe37e132815abceec022631be8ffe1b9381c2e6e30aa70edc99e9671"}, + {file = "tqdm-4.65.0.tar.gz", hash = "sha256:1871fb68a86b8fb3b59ca4cdd3dcccbc7e6d613eeed31f4c332531977b89beb5"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "platform_system == \"Windows\""} [package.extras] dev = ["py-make (>=0.1.0)", "twine", "wheel"] notebook = ["ipywidgets (>=6)"] +slack = ["slack-sdk"] telegram = ["requests"] +[[package]] +name = "typing-extensions" +version = "4.7.1" +description = "Backported and Experimental Type Hints for Python 3.7+" +optional = false +python-versions = ">=3.7" +files = [ + {file = "typing_extensions-4.7.1-py3-none-any.whl", hash = "sha256:440d5dd3af93b060174bf433bccd69b0babc3b15b1a8dca43789fd7f61514b36"}, + {file = "typing_extensions-4.7.1.tar.gz", hash = "sha256:b75ddc264f0ba5615db7ba217daeb99701ad295353c45f9e95963337ceeeffb2"}, +] + [[package]] name = "unidecode" version = "0.04.21" description = "ASCII transliterations of Unicode text" -category = "main" optional = false python-versions = "*" +files = [ + {file = "Unidecode-0.04.21-py2.py3-none-any.whl", hash = "sha256:61f807220eda0203a774a09f84b4304a3f93b5944110cc132af29ddb81366883"}, + {file = "Unidecode-0.04.21.tar.gz", hash = "sha256:280a6ab88e1f2eb5af79edff450021a0d3f0448952847cd79677e55e58bad051"}, +] [[package]] name = "uritemplate" -version = "3.0.1" -description = "URI templates" -category = "main" +version = "4.1.1" +description = "Implementation of RFC 6570 URI Templates" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +python-versions = ">=3.6" +files = [ + {file = "uritemplate-4.1.1-py2.py3-none-any.whl", hash = "sha256:830c08b8d99bdd312ea4ead05994a38e8936266f84b9a7878232db50b044e02e"}, + {file = "uritemplate-4.1.1.tar.gz", hash = "sha256:4346edfc5c3b79f694bccd6d6099a322bbeb628dbf2cd86eea55a456ce5124f0"}, +] [[package]] name = "urllib3" -version = "1.26.4" +version = "1.26.16" description = "HTTP library with thread-safe connection pooling, file post, and more." -category = "main" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4" +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" +files = [ + {file = "urllib3-1.26.16-py2.py3-none-any.whl", hash = "sha256:8d36afa7616d8ab714608411b4a3b13e58f463aee519024578e062e141dce20f"}, + {file = "urllib3-1.26.16.tar.gz", hash = "sha256:8f135f6502756bde6b2a9b28989df5fbe87c9970cecaa69041edcce7f0589b14"}, +] [package.extras] -secure = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "certifi", "ipaddress"] +brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)", "brotlipy (>=0.6.0)"] +secure = ["certifi", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "ipaddress", "pyOpenSSL (>=0.14)", "urllib3-secure-extra"] socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] -brotli = ["brotlipy (>=0.6.0)"] [metadata] -lock-version = "1.1" +lock-version = "2.0" python-versions = "^3.8" -content-hash = "40897941617d18c829205f5a39f0d4f9acfdfec847d01e76a7b85eff9db76516" - -[metadata.files] -arrow = [ - {file = "arrow-1.1.0-py3-none-any.whl", hash = "sha256:8cbe6a629b1c54ae11b52d6d9e70890089241958f63bc59467e277e34b7a5378"}, - {file = "arrow-1.1.0.tar.gz", hash = "sha256:b8fe13abf3517abab315e09350c903902d1447bd311afbc17547ba1cb3ff5bd8"}, -] -asgiref = [ - {file = "asgiref-3.3.4-py3-none-any.whl", hash = "sha256:92906c611ce6c967347bbfea733f13d6313901d54dcca88195eaeb52b2a8e8ee"}, - {file = "asgiref-3.3.4.tar.gz", hash = "sha256:d1216dfbdfb63826470995d31caed36225dcaf34f182e0fa257a4dd9e86f1b78"}, -] -awesome-slugify = [ - {file = "awesome-slugify-1.6.5.tar.gz", hash = "sha256:bbdec3fa2187917473a2efad092b57f7125a55f841a7cf6a1773178d32ccfd71"}, -] -beautifulsoup4 = [ - {file = "beautifulsoup4-4.9.3-py2-none-any.whl", hash = "sha256:4c98143716ef1cb40bf7f39a8e3eec8f8b009509e74904ba3a7b315431577e35"}, - {file = "beautifulsoup4-4.9.3-py3-none-any.whl", hash = "sha256:fff47e031e34ec82bf17e00da8f592fe7de69aeea38be00523c04623c04fb666"}, - {file = "beautifulsoup4-4.9.3.tar.gz", hash = "sha256:84729e322ad1d5b4d25f805bfa05b902dd96450f43842c4e99067d5e1369eb25"}, -] -certifi = [ - {file = "certifi-2020.12.5-py2.py3-none-any.whl", hash = "sha256:719a74fb9e33b9bd44cc7f3a8d94bc35e4049deebe19ba7d8e108280cfd59830"}, - {file = "certifi-2020.12.5.tar.gz", hash = "sha256:1a4995114262bffbc2413b159f2a1a480c969de6e6eb13ee966d470af86af59c"}, -] -chardet = [ - {file = "chardet-4.0.0-py2.py3-none-any.whl", hash = "sha256:f864054d66fd9118f2e67044ac8981a54775ec5b67aed0441892edb553d21da5"}, - {file = "chardet-4.0.0.tar.gz", hash = "sha256:0d6f53a15db4120f2b08c94f11e7d93d2c911ee118b6b30a04ec3ee8310179fa"}, -] -coreapi = [ - {file = "coreapi-2.3.3-py2.py3-none-any.whl", hash = "sha256:bf39d118d6d3e171f10df9ede5666f63ad80bba9a29a8ec17726a66cf52ee6f3"}, - {file = "coreapi-2.3.3.tar.gz", hash = "sha256:46145fcc1f7017c076a2ef684969b641d18a2991051fddec9458ad3f78ffc1cb"}, -] -coreschema = [ - {file = "coreschema-0.0.4-py2-none-any.whl", hash = "sha256:5e6ef7bf38c1525d5e55a895934ab4273548629f16aed5c0a6caa74ebf45551f"}, - {file = "coreschema-0.0.4.tar.gz", hash = "sha256:9503506007d482ab0867ba14724b93c18a33b22b6d19fb419ef2d239dd4a1607"}, -] -django = [ - {file = "Django-3.2.3-py3-none-any.whl", hash = "sha256:7e0a1393d18c16b503663752a8b6790880c5084412618990ce8a81cc908b4962"}, - {file = "Django-3.2.3.tar.gz", hash = "sha256:13ac78dbfd189532cad8f383a27e58e18b3d33f80009ceb476d7fcbfc5dcebd8"}, -] -django-filter = [ - {file = "django-filter-2.4.0.tar.gz", hash = "sha256:84e9d5bb93f237e451db814ed422a3a625751cbc9968b484ecc74964a8696b06"}, - {file = "django_filter-2.4.0-py3-none-any.whl", hash = "sha256:e00d32cebdb3d54273c48f4f878f898dced8d5dfaad009438fe61ebdf535ace1"}, -] -djangorestframework = [ - {file = "djangorestframework-3.12.4-py3-none-any.whl", hash = "sha256:6d1d59f623a5ad0509fe0d6bfe93cbdfe17b8116ebc8eda86d45f6e16e819aaf"}, - {file = "djangorestframework-3.12.4.tar.gz", hash = "sha256:f747949a8ddac876e879190df194b925c177cdeb725a099db1460872f7c0a7f2"}, -] -fake-factory = [ - {file = "fake-factory-9999.9.9.tar.gz", hash = "sha256:f5bd18deb22ad8cb4402513c025877bc6b50de58902d686b6b21ba8981dce260"}, - {file = "fake_factory-9999.9.9-py2.py3-none-any.whl", hash = "sha256:60c64a953c03e29fbee6b59fcb700c86a313778d68dc5816fc560ef515019d77"}, -] -gunicorn = [ - {file = "gunicorn-20.1.0-py3-none-any.whl", hash = "sha256:9dcc4547dbb1cb284accfb15ab5667a0e5d1881cc443e0677b4882a4067a807e"}, - {file = "gunicorn-20.1.0.tar.gz", hash = "sha256:e0a968b5ba15f8a328fdfd7ab1fcb5af4470c28aaf7e55df02a99bc13138e6e8"}, -] -html-diff = [ - {file = "html-diff-0.3.0.tar.gz", hash = "sha256:4bf028fe21cae6547bd2cec7154f5f77a3a4747c0760243304ad8087b0285ac0"}, - {file = "html_diff-0.3.0-py3-none-any.whl", hash = "sha256:99b156fcf4731725e88dfd9959ca256fa76041f7cdc818a16f3002231a8a86ad"}, -] -idna = [ - {file = "idna-2.10-py2.py3-none-any.whl", hash = "sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0"}, - {file = "idna-2.10.tar.gz", hash = "sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6"}, -] -itypes = [ - {file = "itypes-1.2.0-py2.py3-none-any.whl", hash = "sha256:03da6872ca89d29aef62773672b2d408f490f80db48b23079a4b194c86dd04c6"}, - {file = "itypes-1.2.0.tar.gz", hash = "sha256:af886f129dea4a2a1e3d36595a2d139589e4dd287f5cab0b40e799ee81570ff1"}, -] -jinja2 = [ - {file = "Jinja2-3.0.0-py3-none-any.whl", hash = "sha256:2f2de5285cf37f33d33ecd4a9080b75c87cd0c1994d5a9c6df17131ea1f049c6"}, - {file = "Jinja2-3.0.0.tar.gz", hash = "sha256:ea8d7dd814ce9df6de6a761ec7f1cac98afe305b8cdc4aaae4e114b8d8ce24c5"}, -] -markupsafe = [ - {file = "MarkupSafe-2.0.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:2efaeb1baff547063bad2b2893a8f5e9c459c4624e1a96644bbba08910ae34e0"}, - {file = "MarkupSafe-2.0.0-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:441ce2a8c17683d97e06447fcbccbdb057cbf587c78eb75ae43ea7858042fe2c"}, - {file = "MarkupSafe-2.0.0-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:45535241baa0fc0ba2a43961a1ac7562ca3257f46c4c3e9c0de38b722be41bd1"}, - {file = "MarkupSafe-2.0.0-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:90053234a6479738fd40d155268af631c7fca33365f964f2208867da1349294b"}, - {file = "MarkupSafe-2.0.0-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:3b54a9c68995ef4164567e2cd1a5e16db5dac30b2a50c39c82db8d4afaf14f63"}, - {file = "MarkupSafe-2.0.0-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:f58b5ba13a5689ca8317b98439fccfbcc673acaaf8241c1869ceea40f5d585bf"}, - {file = "MarkupSafe-2.0.0-cp36-cp36m-win32.whl", hash = "sha256:a00dce2d96587651ef4fa192c17e039e8cfab63087c67e7d263a5533c7dad715"}, - {file = "MarkupSafe-2.0.0-cp36-cp36m-win_amd64.whl", hash = "sha256:007dc055dbce5b1104876acee177dbfd18757e19d562cd440182e1f492e96b95"}, - {file = "MarkupSafe-2.0.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:a08cd07d3c3c17cd33d9e66ea9dee8f8fc1c48e2d11bd88fd2dc515a602c709b"}, - {file = "MarkupSafe-2.0.0-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:3c352ff634e289061711608f5e474ec38dbaa21e3e168820d53d5f4015e5b91b"}, - {file = "MarkupSafe-2.0.0-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:32200f562daaab472921a11cbb63780f1654552ae49518196fc361ed8e12e901"}, - {file = "MarkupSafe-2.0.0-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:fef86115fdad7ae774720d7103aa776144cf9b66673b4afa9bcaa7af990ed07b"}, - {file = "MarkupSafe-2.0.0-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:e79212d09fc0e224d20b43ad44bb0a0a3416d1e04cf6b45fed265114a5d43d20"}, - {file = "MarkupSafe-2.0.0-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:79b2ae94fa991be023832e6bcc00f41dbc8e5fe9d997a02db965831402551730"}, - {file = "MarkupSafe-2.0.0-cp37-cp37m-win32.whl", hash = "sha256:3261fae28155e5c8634dd7710635fe540a05b58f160cef7713c7700cb9980e66"}, - {file = "MarkupSafe-2.0.0-cp37-cp37m-win_amd64.whl", hash = "sha256:e4570d16f88c7f3032ed909dc9e905a17da14a1c4cfd92608e3fda4cb1208bbd"}, - {file = "MarkupSafe-2.0.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8f806bfd0f218477d7c46a11d3e52dc7f5fdfaa981b18202b7dc84bbc287463b"}, - {file = "MarkupSafe-2.0.0-cp38-cp38-manylinux1_i686.whl", hash = "sha256:e77e4b983e2441aff0c0d07ee711110c106b625f440292dfe02a2f60c8218bd6"}, - {file = "MarkupSafe-2.0.0-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:031bf79a27d1c42f69c276d6221172417b47cb4b31cdc73d362a9bf5a1889b9f"}, - {file = "MarkupSafe-2.0.0-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:83cf0228b2f694dcdba1374d5312f2277269d798e65f40344964f642935feac1"}, - {file = "MarkupSafe-2.0.0-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:4cc563836f13c57f1473bc02d1e01fc37bab70ad4ee6be297d58c1d66bc819bf"}, - {file = "MarkupSafe-2.0.0-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:d00a669e4a5bec3ee6dbeeeedd82a405ced19f8aeefb109a012ea88a45afff96"}, - {file = "MarkupSafe-2.0.0-cp38-cp38-win32.whl", hash = "sha256:161d575fa49395860b75da5135162481768b11208490d5a2143ae6785123e77d"}, - {file = "MarkupSafe-2.0.0-cp38-cp38-win_amd64.whl", hash = "sha256:58bc9fce3e1557d463ef5cee05391a05745fd95ed660f23c1742c711712c0abb"}, - {file = "MarkupSafe-2.0.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:3fb47f97f1d338b943126e90b79cad50d4fcfa0b80637b5a9f468941dbbd9ce5"}, - {file = "MarkupSafe-2.0.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:dab0c685f21f4a6c95bfc2afd1e7eae0033b403dd3d8c1b6d13a652ada75b348"}, - {file = "MarkupSafe-2.0.0-cp39-cp39-manylinux1_i686.whl", hash = "sha256:664832fb88b8162268928df233f4b12a144a0c78b01d38b81bdcf0fc96668ecb"}, - {file = "MarkupSafe-2.0.0-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:df561f65049ed3556e5b52541669310e88713fdae2934845ec3606f283337958"}, - {file = "MarkupSafe-2.0.0-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:24bbc3507fb6dfff663af7900a631f2aca90d5a445f272db5fc84999fa5718bc"}, - {file = "MarkupSafe-2.0.0-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:87de598edfa2230ff274c4de7fcf24c73ffd96208c8e1912d5d0fee459767d75"}, - {file = "MarkupSafe-2.0.0-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:a19d39b02a24d3082856a5b06490b714a9d4179321225bbf22809ff1e1887cc8"}, - {file = "MarkupSafe-2.0.0-cp39-cp39-win32.whl", hash = "sha256:4aca81a687975b35e3e80bcf9aa93fe10cd57fac37bf18b2314c186095f57e05"}, - {file = "MarkupSafe-2.0.0-cp39-cp39-win_amd64.whl", hash = "sha256:70820a1c96311e02449591cbdf5cd1c6a34d5194d5b55094ab725364375c9eb2"}, - {file = "MarkupSafe-2.0.0.tar.gz", hash = "sha256:4fae0677f712ee090721d8b17f412f1cbceefbf0dc180fe91bab3232f38b4527"}, -] -parse = [ - {file = "parse-1.19.0.tar.gz", hash = "sha256:9ff82852bcb65d139813e2a5197627a94966245c897796760a3a2a8eb66f020b"}, -] -parse-type = [ - {file = "parse_type-0.5.2-py2.py3-none-any.whl", hash = "sha256:089a471b06327103865dfec2dd844230c3c658a4a1b5b4c8b6c16c8f77577f9e"}, - {file = "parse_type-0.5.2.tar.gz", hash = "sha256:7f690b18d35048c15438d6d0571f9045cffbec5907e0b1ccf006f889e3a38c0b"}, -] -pillow = [ - {file = "Pillow-8.2.0-cp36-cp36m-macosx_10_10_x86_64.whl", hash = "sha256:dc38f57d8f20f06dd7c3161c59ca2c86893632623f33a42d592f097b00f720a9"}, - {file = "Pillow-8.2.0-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:a013cbe25d20c2e0c4e85a9daf438f85121a4d0344ddc76e33fd7e3965d9af4b"}, - {file = "Pillow-8.2.0-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:8bb1e155a74e1bfbacd84555ea62fa21c58e0b4e7e6b20e4447b8d07990ac78b"}, - {file = "Pillow-8.2.0-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:c5236606e8570542ed424849f7852a0ff0bce2c4c8d0ba05cc202a5a9c97dee9"}, - {file = "Pillow-8.2.0-cp36-cp36m-win32.whl", hash = "sha256:12e5e7471f9b637762453da74e390e56cc43e486a88289995c1f4c1dc0bfe727"}, - {file = "Pillow-8.2.0-cp36-cp36m-win_amd64.whl", hash = "sha256:5afe6b237a0b81bd54b53f835a153770802f164c5570bab5e005aad693dab87f"}, - {file = "Pillow-8.2.0-cp37-cp37m-macosx_10_10_x86_64.whl", hash = "sha256:cb7a09e173903541fa888ba010c345893cd9fc1b5891aaf060f6ca77b6a3722d"}, - {file = "Pillow-8.2.0-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:0d19d70ee7c2ba97631bae1e7d4725cdb2ecf238178096e8c82ee481e189168a"}, - {file = "Pillow-8.2.0-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:083781abd261bdabf090ad07bb69f8f5599943ddb539d64497ed021b2a67e5a9"}, - {file = "Pillow-8.2.0-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:c6b39294464b03457f9064e98c124e09008b35a62e3189d3513e5148611c9388"}, - {file = "Pillow-8.2.0-cp37-cp37m-win32.whl", hash = "sha256:01425106e4e8cee195a411f729cff2a7d61813b0b11737c12bd5991f5f14bcd5"}, - {file = "Pillow-8.2.0-cp37-cp37m-win_amd64.whl", hash = "sha256:3b570f84a6161cf8865c4e08adf629441f56e32f180f7aa4ccbd2e0a5a02cba2"}, - {file = "Pillow-8.2.0-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:031a6c88c77d08aab84fecc05c3cde8414cd6f8406f4d2b16fed1e97634cc8a4"}, - {file = "Pillow-8.2.0-cp38-cp38-manylinux1_i686.whl", hash = "sha256:66cc56579fd91f517290ab02c51e3a80f581aba45fd924fcdee01fa06e635812"}, - {file = "Pillow-8.2.0-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:6c32cc3145928c4305d142ebec682419a6c0a8ce9e33db900027ddca1ec39178"}, - {file = "Pillow-8.2.0-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:624b977355cde8b065f6d51b98497d6cd5fbdd4f36405f7a8790e3376125e2bb"}, - {file = "Pillow-8.2.0-cp38-cp38-win32.whl", hash = "sha256:5cbf3e3b1014dddc45496e8cf38b9f099c95a326275885199f427825c6522232"}, - {file = "Pillow-8.2.0-cp38-cp38-win_amd64.whl", hash = "sha256:463822e2f0d81459e113372a168f2ff59723e78528f91f0bd25680ac185cf797"}, - {file = "Pillow-8.2.0-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:95d5ef984eff897850f3a83883363da64aae1000e79cb3c321915468e8c6add5"}, - {file = "Pillow-8.2.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b91c36492a4bbb1ee855b7d16fe51379e5f96b85692dc8210831fbb24c43e484"}, - {file = "Pillow-8.2.0-cp39-cp39-manylinux1_i686.whl", hash = "sha256:d68cb92c408261f806b15923834203f024110a2e2872ecb0bd2a110f89d3c602"}, - {file = "Pillow-8.2.0-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:f217c3954ce5fd88303fc0c317af55d5e0204106d86dea17eb8205700d47dec2"}, - {file = "Pillow-8.2.0-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:5b70110acb39f3aff6b74cf09bb4169b167e2660dabc304c1e25b6555fa781ef"}, - {file = "Pillow-8.2.0-cp39-cp39-win32.whl", hash = "sha256:a7d5e9fad90eff8f6f6106d3b98b553a88b6f976e51fce287192a5d2d5363713"}, - {file = "Pillow-8.2.0-cp39-cp39-win_amd64.whl", hash = "sha256:238c197fc275b475e87c1453b05b467d2d02c2915fdfdd4af126145ff2e4610c"}, - {file = "Pillow-8.2.0-pp36-pypy36_pp73-macosx_10_10_x86_64.whl", hash = "sha256:0e04d61f0064b545b989126197930807c86bcbd4534d39168f4aa5fda39bb8f9"}, - {file = "Pillow-8.2.0-pp36-pypy36_pp73-manylinux2010_i686.whl", hash = "sha256:63728564c1410d99e6d1ae8e3b810fe012bc440952168af0a2877e8ff5ab96b9"}, - {file = "Pillow-8.2.0-pp36-pypy36_pp73-manylinux2010_x86_64.whl", hash = "sha256:c03c07ed32c5324939b19e36ae5f75c660c81461e312a41aea30acdd46f93a7c"}, - {file = "Pillow-8.2.0-pp37-pypy37_pp73-macosx_10_10_x86_64.whl", hash = "sha256:4d98abdd6b1e3bf1a1cbb14c3895226816e666749ac040c4e2554231068c639b"}, - {file = "Pillow-8.2.0-pp37-pypy37_pp73-manylinux2010_i686.whl", hash = "sha256:aac00e4bc94d1b7813fe882c28990c1bc2f9d0e1aa765a5f2b516e8a6a16a9e4"}, - {file = "Pillow-8.2.0-pp37-pypy37_pp73-manylinux2010_x86_64.whl", hash = "sha256:22fd0f42ad15dfdde6c581347eaa4adb9a6fc4b865f90b23378aa7914895e120"}, - {file = "Pillow-8.2.0-pp37-pypy37_pp73-win32.whl", hash = "sha256:e98eca29a05913e82177b3ba3d198b1728e164869c613d76d0de4bde6768a50e"}, - {file = "Pillow-8.2.0-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:8b56553c0345ad6dcb2e9b433ae47d67f95fc23fe28a0bde15a120f25257e291"}, - {file = "Pillow-8.2.0.tar.gz", hash = "sha256:a787ab10d7bb5494e5f76536ac460741788f1fbce851068d73a87ca7c35fc3e1"}, -] -psycopg2 = [ - {file = "psycopg2-2.8.6-cp27-cp27m-win32.whl", hash = "sha256:068115e13c70dc5982dfc00c5d70437fe37c014c808acce119b5448361c03725"}, - {file = "psycopg2-2.8.6-cp27-cp27m-win_amd64.whl", hash = "sha256:d160744652e81c80627a909a0e808f3c6653a40af435744de037e3172cf277f5"}, - {file = "psycopg2-2.8.6-cp34-cp34m-win32.whl", hash = "sha256:b8cae8b2f022efa1f011cc753adb9cbadfa5a184431d09b273fb49b4167561ad"}, - {file = "psycopg2-2.8.6-cp34-cp34m-win_amd64.whl", hash = "sha256:f22ea9b67aea4f4a1718300908a2fb62b3e4276cf00bd829a97ab5894af42ea3"}, - {file = "psycopg2-2.8.6-cp35-cp35m-win32.whl", hash = "sha256:26e7fd115a6db75267b325de0fba089b911a4a12ebd3d0b5e7acb7028bc46821"}, - {file = "psycopg2-2.8.6-cp35-cp35m-win_amd64.whl", hash = "sha256:00195b5f6832dbf2876b8bf77f12bdce648224c89c880719c745b90515233301"}, - {file = "psycopg2-2.8.6-cp36-cp36m-win32.whl", hash = "sha256:a49833abfdede8985ba3f3ec641f771cca215479f41523e99dace96d5b8cce2a"}, - {file = "psycopg2-2.8.6-cp36-cp36m-win_amd64.whl", hash = "sha256:f974c96fca34ae9e4f49839ba6b78addf0346777b46c4da27a7bf54f48d3057d"}, - {file = "psycopg2-2.8.6-cp37-cp37m-win32.whl", hash = "sha256:6a3d9efb6f36f1fe6aa8dbb5af55e067db802502c55a9defa47c5a1dad41df84"}, - {file = "psycopg2-2.8.6-cp37-cp37m-win_amd64.whl", hash = "sha256:56fee7f818d032f802b8eed81ef0c1232b8b42390df189cab9cfa87573fe52c5"}, - {file = "psycopg2-2.8.6-cp38-cp38-win32.whl", hash = "sha256:ad2fe8a37be669082e61fb001c185ffb58867fdbb3e7a6b0b0d2ffe232353a3e"}, - {file = "psycopg2-2.8.6-cp38-cp38-win_amd64.whl", hash = "sha256:56007a226b8e95aa980ada7abdea6b40b75ce62a433bd27cec7a8178d57f4051"}, - {file = "psycopg2-2.8.6-cp39-cp39-win32.whl", hash = "sha256:2c93d4d16933fea5bbacbe1aaf8fa8c1348740b2e50b3735d1b0bf8154cbf0f3"}, - {file = "psycopg2-2.8.6-cp39-cp39-win_amd64.whl", hash = "sha256:d5062ae50b222da28253059880a871dc87e099c25cb68acf613d9d227413d6f7"}, - {file = "psycopg2-2.8.6.tar.gz", hash = "sha256:fb23f6c71107c37fd667cb4ea363ddeb936b348bbd6449278eb92c189699f543"}, -] -psycopg2-binary = [ - {file = "psycopg2-binary-2.8.6.tar.gz", hash = "sha256:11b9c0ebce097180129e422379b824ae21c8f2a6596b159c7659e2e5a00e1aa0"}, - {file = "psycopg2_binary-2.8.6-cp27-cp27m-macosx_10_6_intel.macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl", hash = "sha256:d14b140a4439d816e3b1229a4a525df917d6ea22a0771a2a78332273fd9528a4"}, - {file = "psycopg2_binary-2.8.6-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:1fabed9ea2acc4efe4671b92c669a213db744d2af8a9fc5d69a8e9bc14b7a9db"}, - {file = "psycopg2_binary-2.8.6-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:f5ab93a2cb2d8338b1674be43b442a7f544a0971da062a5da774ed40587f18f5"}, - {file = "psycopg2_binary-2.8.6-cp27-cp27m-win32.whl", hash = "sha256:b4afc542c0ac0db720cf516dd20c0846f71c248d2b3d21013aa0d4ef9c71ca25"}, - {file = "psycopg2_binary-2.8.6-cp27-cp27m-win_amd64.whl", hash = "sha256:e74a55f6bad0e7d3968399deb50f61f4db1926acf4a6d83beaaa7df986f48b1c"}, - {file = "psycopg2_binary-2.8.6-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:0deac2af1a587ae12836aa07970f5cb91964f05a7c6cdb69d8425ff4c15d4e2c"}, - {file = "psycopg2_binary-2.8.6-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:ad20d2eb875aaa1ea6d0f2916949f5c08a19c74d05b16ce6ebf6d24f2c9f75d1"}, - {file = "psycopg2_binary-2.8.6-cp34-cp34m-win32.whl", hash = "sha256:950bc22bb56ee6ff142a2cb9ee980b571dd0912b0334aa3fe0fe3788d860bea2"}, - {file = "psycopg2_binary-2.8.6-cp34-cp34m-win_amd64.whl", hash = "sha256:b8a3715b3c4e604bcc94c90a825cd7f5635417453b253499664f784fc4da0152"}, - {file = "psycopg2_binary-2.8.6-cp35-cp35m-macosx_10_6_intel.macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl", hash = "sha256:d1b4ab59e02d9008efe10ceabd0b31e79519da6fb67f7d8e8977118832d0f449"}, - {file = "psycopg2_binary-2.8.6-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:ac0c682111fbf404525dfc0f18a8b5f11be52657d4f96e9fcb75daf4f3984859"}, - {file = "psycopg2_binary-2.8.6-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:7d92a09b788cbb1aec325af5fcba9fed7203897bbd9269d5691bb1e3bce29550"}, - {file = "psycopg2_binary-2.8.6-cp35-cp35m-win32.whl", hash = "sha256:aaa4213c862f0ef00022751161df35804127b78adf4a2755b9f991a507e425fd"}, - {file = "psycopg2_binary-2.8.6-cp35-cp35m-win_amd64.whl", hash = "sha256:c2507d796fca339c8fb03216364cca68d87e037c1f774977c8fc377627d01c71"}, - {file = "psycopg2_binary-2.8.6-cp36-cp36m-macosx_10_6_intel.macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl", hash = "sha256:ee69dad2c7155756ad114c02db06002f4cded41132cc51378e57aad79cc8e4f4"}, - {file = "psycopg2_binary-2.8.6-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:e82aba2188b9ba309fd8e271702bd0d0fc9148ae3150532bbb474f4590039ffb"}, - {file = "psycopg2_binary-2.8.6-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:d5227b229005a696cc67676e24c214740efd90b148de5733419ac9aaba3773da"}, - {file = "psycopg2_binary-2.8.6-cp36-cp36m-win32.whl", hash = "sha256:a0eb43a07386c3f1f1ebb4dc7aafb13f67188eab896e7397aa1ee95a9c884eb2"}, - {file = "psycopg2_binary-2.8.6-cp36-cp36m-win_amd64.whl", hash = "sha256:e1f57aa70d3f7cc6947fd88636a481638263ba04a742b4a37dd25c373e41491a"}, - {file = "psycopg2_binary-2.8.6-cp37-cp37m-macosx_10_6_intel.macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl", hash = "sha256:833709a5c66ca52f1d21d41865a637223b368c0ee76ea54ca5bad6f2526c7679"}, - {file = "psycopg2_binary-2.8.6-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:ba28584e6bca48c59eecbf7efb1576ca214b47f05194646b081717fa628dfddf"}, - {file = "psycopg2_binary-2.8.6-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:6a32f3a4cb2f6e1a0b15215f448e8ce2da192fd4ff35084d80d5e39da683e79b"}, - {file = "psycopg2_binary-2.8.6-cp37-cp37m-win32.whl", hash = "sha256:0e4dc3d5996760104746e6cfcdb519d9d2cd27c738296525d5867ea695774e67"}, - {file = "psycopg2_binary-2.8.6-cp37-cp37m-win_amd64.whl", hash = "sha256:cec7e622ebc545dbb4564e483dd20e4e404da17ae07e06f3e780b2dacd5cee66"}, - {file = "psycopg2_binary-2.8.6-cp38-cp38-macosx_10_9_x86_64.macosx_10_9_intel.macosx_10_10_intel.macosx_10_10_x86_64.whl", hash = "sha256:ba381aec3a5dc29634f20692349d73f2d21f17653bda1decf0b52b11d694541f"}, - {file = "psycopg2_binary-2.8.6-cp38-cp38-manylinux1_i686.whl", hash = "sha256:a0c50db33c32594305b0ef9abc0cb7db13de7621d2cadf8392a1d9b3c437ef77"}, - {file = "psycopg2_binary-2.8.6-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:2dac98e85565d5688e8ab7bdea5446674a83a3945a8f416ad0110018d1501b94"}, - {file = "psycopg2_binary-2.8.6-cp38-cp38-win32.whl", hash = "sha256:bd1be66dde2b82f80afb9459fc618216753f67109b859a361cf7def5c7968729"}, - {file = "psycopg2_binary-2.8.6-cp38-cp38-win_amd64.whl", hash = "sha256:8cd0fb36c7412996859cb4606a35969dd01f4ea34d9812a141cd920c3b18be77"}, - {file = "psycopg2_binary-2.8.6-cp39-cp39-macosx_10_9_x86_64.macosx_10_9_intel.macosx_10_10_intel.macosx_10_10_x86_64.whl", hash = "sha256:89705f45ce07b2dfa806ee84439ec67c5d9a0ef20154e0e475e2b2ed392a5b83"}, - {file = "psycopg2_binary-2.8.6-cp39-cp39-manylinux1_i686.whl", hash = "sha256:42ec1035841b389e8cc3692277a0bd81cdfe0b65d575a2c8862cec7a80e62e52"}, - {file = "psycopg2_binary-2.8.6-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:7312e931b90fe14f925729cde58022f5d034241918a5c4f9797cac62f6b3a9dd"}, - {file = "psycopg2_binary-2.8.6-cp39-cp39-win32.whl", hash = "sha256:6422f2ff0919fd720195f64ffd8f924c1395d30f9a495f31e2392c2efafb5056"}, - {file = "psycopg2_binary-2.8.6-cp39-cp39-win_amd64.whl", hash = "sha256:15978a1fbd225583dd8cdaf37e67ccc278b5abecb4caf6b2d6b8e2b948e953f6"}, -] -pycryptodome = [ - {file = "pycryptodome-3.10.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:1c5e1ca507de2ad93474be5cfe2bfa76b7cf039a1a32fc196f40935944871a06"}, - {file = "pycryptodome-3.10.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:6260e24d41149268122dd39d4ebd5941e9d107f49463f7e071fd397e29923b0c"}, - {file = "pycryptodome-3.10.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:3f840c49d38986f6e17dbc0673d37947c88bc9d2d9dba1c01b979b36f8447db1"}, - {file = "pycryptodome-3.10.1-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:2dea65df54349cdfa43d6b2e8edb83f5f8d6861e5cf7b1fbc3e34c5694c85e27"}, - {file = "pycryptodome-3.10.1-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:e61e363d9a5d7916f3a4ce984a929514c0df3daf3b1b2eb5e6edbb131ee771cf"}, - {file = "pycryptodome-3.10.1-cp27-cp27m-manylinux2014_aarch64.whl", hash = "sha256:2603c98ae04aac675fefcf71a6c87dc4bb74a75e9071ae3923bbc91a59f08d35"}, - {file = "pycryptodome-3.10.1-cp27-cp27m-win32.whl", hash = "sha256:38661348ecb71476037f1e1f553159b80d256c00f6c0b00502acac891f7116d9"}, - {file = "pycryptodome-3.10.1-cp27-cp27m-win_amd64.whl", hash = "sha256:1723ebee5561628ce96748501cdaa7afaa67329d753933296321f0be55358dce"}, - {file = "pycryptodome-3.10.1-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:77997519d8eb8a4adcd9a47b9cec18f9b323e296986528186c0e9a7a15d6a07e"}, - {file = "pycryptodome-3.10.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:99b2f3fc51d308286071d0953f92055504a6ffe829a832a9fc7a04318a7683dd"}, - {file = "pycryptodome-3.10.1-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:e0a4d5933a88a2c98bbe19c0c722f5483dc628d7a38338ac2cb64a7dbd34064b"}, - {file = "pycryptodome-3.10.1-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:d3d6958d53ad307df5e8469cc44474a75393a434addf20ecd451f38a72fe29b8"}, - {file = "pycryptodome-3.10.1-cp27-cp27mu-manylinux2014_aarch64.whl", hash = "sha256:a8eb8b6ea09ec1c2535bf39914377bc8abcab2c7d30fa9225eb4fe412024e427"}, - {file = "pycryptodome-3.10.1-cp35-abi3-macosx_10_9_x86_64.whl", hash = "sha256:31c1df17b3dc5f39600a4057d7db53ac372f492c955b9b75dd439f5d8b460129"}, - {file = "pycryptodome-3.10.1-cp35-abi3-manylinux1_i686.whl", hash = "sha256:a3105a0eb63eacf98c2ecb0eb4aa03f77f40fbac2bdde22020bb8a536b226bb8"}, - {file = "pycryptodome-3.10.1-cp35-abi3-manylinux1_x86_64.whl", hash = "sha256:a92d5c414e8ee1249e850789052608f582416e82422502dc0ac8c577808a9067"}, - {file = "pycryptodome-3.10.1-cp35-abi3-manylinux2010_i686.whl", hash = "sha256:60386d1d4cfaad299803b45a5bc2089696eaf6cdd56f9fc17479a6f89595cfc8"}, - {file = "pycryptodome-3.10.1-cp35-abi3-manylinux2010_x86_64.whl", hash = "sha256:501ab36aae360e31d0ec370cf5ce8ace6cb4112060d099b993bc02b36ac83fb6"}, - {file = "pycryptodome-3.10.1-cp35-abi3-manylinux2014_aarch64.whl", hash = "sha256:fc7489a50323a0df02378bc2fff86eb69d94cc5639914346c736be981c6a02e7"}, - {file = "pycryptodome-3.10.1-cp35-abi3-win32.whl", hash = "sha256:9b6f711b25e01931f1c61ce0115245a23cdc8b80bf8539ac0363bdcf27d649b6"}, - {file = "pycryptodome-3.10.1-cp35-abi3-win_amd64.whl", hash = "sha256:7fd519b89585abf57bf47d90166903ec7b43af4fe23c92273ea09e6336af5c07"}, - {file = "pycryptodome-3.10.1-pp27-pypy_73-macosx_10_9_x86_64.whl", hash = "sha256:09c1555a3fa450e7eaca41ea11cd00afe7c91fef52353488e65663777d8524e0"}, - {file = "pycryptodome-3.10.1-pp27-pypy_73-manylinux1_x86_64.whl", hash = "sha256:758949ca62690b1540dfb24ad773c6da9cd0e425189e83e39c038bbd52b8e438"}, - {file = "pycryptodome-3.10.1-pp27-pypy_73-manylinux2010_x86_64.whl", hash = "sha256:e3bf558c6aeb49afa9f0c06cee7fb5947ee5a1ff3bd794b653d39926b49077fa"}, - {file = "pycryptodome-3.10.1-pp27-pypy_73-win32.whl", hash = "sha256:f977cdf725b20f6b8229b0c87acb98c7717e742ef9f46b113985303ae12a99da"}, - {file = "pycryptodome-3.10.1-pp36-pypy36_pp73-macosx_10_9_x86_64.whl", hash = "sha256:6d2df5223b12437e644ce0a3be7809471ffa71de44ccd28b02180401982594a6"}, - {file = "pycryptodome-3.10.1-pp36-pypy36_pp73-manylinux1_x86_64.whl", hash = "sha256:98213ac2b18dc1969a47bc65a79a8fca02a414249d0c8635abb081c7f38c91b6"}, - {file = "pycryptodome-3.10.1-pp36-pypy36_pp73-manylinux2010_x86_64.whl", hash = "sha256:12222a5edc9ca4a29de15fbd5339099c4c26c56e13c2ceddf0b920794f26165d"}, - {file = "pycryptodome-3.10.1-pp36-pypy36_pp73-win32.whl", hash = "sha256:6bbf7fee7b7948b29d7e71fcacf48bac0c57fb41332007061a933f2d996f9713"}, - {file = "pycryptodome-3.10.1.tar.gz", hash = "sha256:3e2e3a06580c5f190df843cdb90ea28d61099cf4924334d5297a995de68e4673"}, -] -pydash = [ - {file = "pydash-5.0.0-py3-none-any.whl", hash = "sha256:0d87f879a3df4ad9389ab6d63c69eea078517d41541ddd5744cfcff3396e8543"}, - {file = "pydash-5.0.0.tar.gz", hash = "sha256:845262df83b5411742e5f7f7dbfa5ed4d0ddac6d7d0a13c4375c6a3c40d4e8f4"}, -] -python-crontab = [ - {file = "python-crontab-2.5.1.tar.gz", hash = "sha256:4bbe7e720753a132ca4ca9d4094915f40e9d9dc8a807a4564007651018ce8c31"}, -] -python-dateutil = [ - {file = "python-dateutil-2.8.1.tar.gz", hash = "sha256:73ebfe9dbf22e832286dafa60473e4cd239f8592f699aa5adaf10050e6e1823c"}, - {file = "python_dateutil-2.8.1-py2.py3-none-any.whl", hash = "sha256:75bb3f31ea686f1197762692a9ee6a7550b59fc6ca3a1f4b5d7e32fb98e2da2a"}, -] -pytz = [ - {file = "pytz-2021.1-py2.py3-none-any.whl", hash = "sha256:eb10ce3e7736052ed3623d49975ce333bcd712c7bb19a58b9e2089d4057d0798"}, - {file = "pytz-2021.1.tar.gz", hash = "sha256:83a4a90894bf38e243cf052c8b58f381bfe9a7a483f6a9cab140bc7f702ac4da"}, -] -regex = [ - {file = "regex-2021.4.4-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:619d71c59a78b84d7f18891fe914446d07edd48dc8328c8e149cbe0929b4e000"}, - {file = "regex-2021.4.4-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:47bf5bf60cf04d72bf6055ae5927a0bd9016096bf3d742fa50d9bf9f45aa0711"}, - {file = "regex-2021.4.4-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:281d2fd05555079448537fe108d79eb031b403dac622621c78944c235f3fcf11"}, - {file = "regex-2021.4.4-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:bd28bc2e3a772acbb07787c6308e00d9626ff89e3bfcdebe87fa5afbfdedf968"}, - {file = "regex-2021.4.4-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:7c2a1af393fcc09e898beba5dd59196edaa3116191cc7257f9224beaed3e1aa0"}, - {file = "regex-2021.4.4-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:c38c71df845e2aabb7fb0b920d11a1b5ac8526005e533a8920aea97efb8ec6a4"}, - {file = "regex-2021.4.4-cp36-cp36m-manylinux2014_i686.whl", hash = "sha256:96fcd1888ab4d03adfc9303a7b3c0bd78c5412b2bfbe76db5b56d9eae004907a"}, - {file = "regex-2021.4.4-cp36-cp36m-manylinux2014_x86_64.whl", hash = "sha256:ade17eb5d643b7fead300a1641e9f45401c98eee23763e9ed66a43f92f20b4a7"}, - {file = "regex-2021.4.4-cp36-cp36m-win32.whl", hash = "sha256:e8e5b509d5c2ff12f8418006d5a90e9436766133b564db0abaec92fd27fcee29"}, - {file = "regex-2021.4.4-cp36-cp36m-win_amd64.whl", hash = "sha256:11d773d75fa650cd36f68d7ca936e3c7afaae41b863b8c387a22aaa78d3c5c79"}, - {file = "regex-2021.4.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:d3029c340cfbb3ac0a71798100ccc13b97dddf373a4ae56b6a72cf70dfd53bc8"}, - {file = "regex-2021.4.4-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:18c071c3eb09c30a264879f0d310d37fe5d3a3111662438889ae2eb6fc570c31"}, - {file = "regex-2021.4.4-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:4c557a7b470908b1712fe27fb1ef20772b78079808c87d20a90d051660b1d69a"}, - {file = "regex-2021.4.4-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:01afaf2ec48e196ba91b37451aa353cb7eda77efe518e481707e0515025f0cd5"}, - {file = "regex-2021.4.4-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:3a9cd17e6e5c7eb328517969e0cb0c3d31fd329298dd0c04af99ebf42e904f82"}, - {file = "regex-2021.4.4-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:90f11ff637fe8798933fb29f5ae1148c978cccb0452005bf4c69e13db951e765"}, - {file = "regex-2021.4.4-cp37-cp37m-manylinux2014_i686.whl", hash = "sha256:919859aa909429fb5aa9cf8807f6045592c85ef56fdd30a9a3747e513db2536e"}, - {file = "regex-2021.4.4-cp37-cp37m-manylinux2014_x86_64.whl", hash = "sha256:339456e7d8c06dd36a22e451d58ef72cef293112b559010db3d054d5560ef439"}, - {file = "regex-2021.4.4-cp37-cp37m-win32.whl", hash = "sha256:67bdb9702427ceddc6ef3dc382455e90f785af4c13d495f9626861763ee13f9d"}, - {file = "regex-2021.4.4-cp37-cp37m-win_amd64.whl", hash = "sha256:32e65442138b7b76dd8173ffa2cf67356b7bc1768851dded39a7a13bf9223da3"}, - {file = "regex-2021.4.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1e1c20e29358165242928c2de1482fb2cf4ea54a6a6dea2bd7a0e0d8ee321500"}, - {file = "regex-2021.4.4-cp38-cp38-manylinux1_i686.whl", hash = "sha256:314d66636c494ed9c148a42731b3834496cc9a2c4251b1661e40936814542b14"}, - {file = "regex-2021.4.4-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:6d1b01031dedf2503631d0903cb563743f397ccaf6607a5e3b19a3d76fc10480"}, - {file = "regex-2021.4.4-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:741a9647fcf2e45f3a1cf0e24f5e17febf3efe8d4ba1281dcc3aa0459ef424dc"}, - {file = "regex-2021.4.4-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:4c46e22a0933dd783467cf32b3516299fb98cfebd895817d685130cc50cd1093"}, - {file = "regex-2021.4.4-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:e512d8ef5ad7b898cdb2d8ee1cb09a8339e4f8be706d27eaa180c2f177248a10"}, - {file = "regex-2021.4.4-cp38-cp38-manylinux2014_i686.whl", hash = "sha256:980d7be47c84979d9136328d882f67ec5e50008681d94ecc8afa8a65ed1f4a6f"}, - {file = "regex-2021.4.4-cp38-cp38-manylinux2014_x86_64.whl", hash = "sha256:ce15b6d103daff8e9fee13cf7f0add05245a05d866e73926c358e871221eae87"}, - {file = "regex-2021.4.4-cp38-cp38-win32.whl", hash = "sha256:a91aa8619b23b79bcbeb37abe286f2f408d2f2d6f29a17237afda55bb54e7aac"}, - {file = "regex-2021.4.4-cp38-cp38-win_amd64.whl", hash = "sha256:c0502c0fadef0d23b128605d69b58edb2c681c25d44574fc673b0e52dce71ee2"}, - {file = "regex-2021.4.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:598585c9f0af8374c28edd609eb291b5726d7cbce16be6a8b95aa074d252ee17"}, - {file = "regex-2021.4.4-cp39-cp39-manylinux1_i686.whl", hash = "sha256:ee54ff27bf0afaf4c3b3a62bcd016c12c3fdb4ec4f413391a90bd38bc3624605"}, - {file = "regex-2021.4.4-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:7d9884d86dd4dd489e981d94a65cd30d6f07203d90e98f6f657f05170f6324c9"}, - {file = "regex-2021.4.4-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:bf5824bfac591ddb2c1f0a5f4ab72da28994548c708d2191e3b87dd207eb3ad7"}, - {file = "regex-2021.4.4-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:563085e55b0d4fb8f746f6a335893bda5c2cef43b2f0258fe1020ab1dd874df8"}, - {file = "regex-2021.4.4-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:b9c3db21af35e3b3c05764461b262d6f05bbca08a71a7849fd79d47ba7bc33ed"}, - {file = "regex-2021.4.4-cp39-cp39-manylinux2014_i686.whl", hash = "sha256:3916d08be28a1149fb97f7728fca1f7c15d309a9f9682d89d79db75d5e52091c"}, - {file = "regex-2021.4.4-cp39-cp39-manylinux2014_x86_64.whl", hash = "sha256:fd45ff9293d9274c5008a2054ecef86a9bfe819a67c7be1afb65e69b405b3042"}, - {file = "regex-2021.4.4-cp39-cp39-win32.whl", hash = "sha256:fa4537fb4a98fe8fde99626e4681cc644bdcf2a795038533f9f711513a862ae6"}, - {file = "regex-2021.4.4-cp39-cp39-win_amd64.whl", hash = "sha256:97f29f57d5b84e73fbaf99ab3e26134e6687348e95ef6b48cfd2c06807005a07"}, - {file = "regex-2021.4.4.tar.gz", hash = "sha256:52ba3d3f9b942c49d7e4bc105bb28551c44065f139a65062ab7912bef10c9afb"}, -] -requests = [ - {file = "requests-2.25.1-py2.py3-none-any.whl", hash = "sha256:c210084e36a42ae6b9219e00e48287def368a26d03a048ddad7bfee44f75871e"}, - {file = "requests-2.25.1.tar.gz", hash = "sha256:27973dd4a904a4f13b263a19c866c13b92a39ed1c964655f025f3f8d3d75b804"}, -] -selenium = [ - {file = "selenium-3.141.0-py2.py3-none-any.whl", hash = "sha256:2d7131d7bc5a5b99a2d9b04aaf2612c411b03b8ca1b1ee8d3de5845a9be2cb3c"}, - {file = "selenium-3.141.0.tar.gz", hash = "sha256:deaf32b60ad91a4611b98d8002757f29e6f2c2d5fcaf202e1c9ad06d6772300d"}, -] -sentry-sdk = [ - {file = "sentry-sdk-1.1.0.tar.gz", hash = "sha256:c1227d38dca315ba35182373f129c3e2722e8ed999e52584e6aca7d287870739"}, - {file = "sentry_sdk-1.1.0-py2.py3-none-any.whl", hash = "sha256:c7d380a21281e15be3d9f67a3c4fbb4f800c481d88ff8d8931f39486dd7b4ada"}, -] -simplejson = [ - {file = "simplejson-3.17.2-cp27-cp27m-macosx_10_13_x86_64.whl", hash = "sha256:2d3eab2c3fe52007d703a26f71cf649a8c771fcdd949a3ae73041ba6797cfcf8"}, - {file = "simplejson-3.17.2-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:813846738277729d7db71b82176204abc7fdae2f566e2d9fcf874f9b6472e3e6"}, - {file = "simplejson-3.17.2-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:292c2e3f53be314cc59853bd20a35bf1f965f3bc121e007ab6fd526ed412a85d"}, - {file = "simplejson-3.17.2-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:0dd9d9c738cb008bfc0862c9b8fa6743495c03a0ed543884bf92fb7d30f8d043"}, - {file = "simplejson-3.17.2-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:42b8b8dd0799f78e067e2aaae97e60d58a8f63582939af60abce4c48631a0aa4"}, - {file = "simplejson-3.17.2-cp27-cp27m-win32.whl", hash = "sha256:8042040af86a494a23c189b5aa0ea9433769cc029707833f261a79c98e3375f9"}, - {file = "simplejson-3.17.2-cp27-cp27m-win_amd64.whl", hash = "sha256:034550078a11664d77bc1a8364c90bb7eef0e44c2dbb1fd0a4d92e3997088667"}, - {file = "simplejson-3.17.2-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:fed0f22bf1313ff79c7fc318f7199d6c2f96d4de3234b2f12a1eab350e597c06"}, - {file = "simplejson-3.17.2-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:2e7b57c2c146f8e4dadf84977a83f7ee50da17c8861fd7faf694d55e3274784f"}, - {file = "simplejson-3.17.2-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:da3c55cdc66cfc3fffb607db49a42448785ea2732f055ac1549b69dcb392663b"}, - {file = "simplejson-3.17.2-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:c1cb29b1fced01f97e6d5631c3edc2dadb424d1f4421dad079cb13fc97acb42f"}, - {file = "simplejson-3.17.2-cp33-cp33m-win32.whl", hash = "sha256:8f713ea65958ef40049b6c45c40c206ab363db9591ff5a49d89b448933fa5746"}, - {file = "simplejson-3.17.2-cp33-cp33m-win_amd64.whl", hash = "sha256:344e2d920a7f27b4023c087ab539877a1e39ce8e3e90b867e0bfa97829824748"}, - {file = "simplejson-3.17.2-cp34-cp34m-win32.whl", hash = "sha256:05b43d568300c1cd43f95ff4bfcff984bc658aa001be91efb3bb21df9d6288d3"}, - {file = "simplejson-3.17.2-cp34-cp34m-win_amd64.whl", hash = "sha256:cff6453e25204d3369c47b97dd34783ca820611bd334779d22192da23784194b"}, - {file = "simplejson-3.17.2-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:8acf76443cfb5c949b6e781c154278c059b09ac717d2757a830c869ba000cf8d"}, - {file = "simplejson-3.17.2-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:869a183c8e44bc03be1b2bbcc9ec4338e37fa8557fc506bf6115887c1d3bb956"}, - {file = "simplejson-3.17.2-cp35-cp35m-manylinux2010_i686.whl", hash = "sha256:5c659a0efc80aaaba57fcd878855c8534ecb655a28ac8508885c50648e6e659d"}, - {file = "simplejson-3.17.2-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:72d8a3ffca19a901002d6b068cf746be85747571c6a7ba12cbcf427bfb4ed971"}, - {file = "simplejson-3.17.2-cp35-cp35m-manylinux2014_aarch64.whl", hash = "sha256:4b3442249d5e3893b90cb9f72c7d6ce4d2ea144d2c0d9f75b9ae1e5460f3121a"}, - {file = "simplejson-3.17.2-cp35-cp35m-win32.whl", hash = "sha256:e058c7656c44fb494a11443191e381355388443d543f6fc1a245d5d238544396"}, - {file = "simplejson-3.17.2-cp35-cp35m-win_amd64.whl", hash = "sha256:934115642c8ba9659b402c8bdbdedb48651fb94b576e3b3efd1ccb079609b04a"}, - {file = "simplejson-3.17.2-cp36-cp36m-macosx_10_13_x86_64.whl", hash = "sha256:ffd4e4877a78c84d693e491b223385e0271278f5f4e1476a4962dca6824ecfeb"}, - {file = "simplejson-3.17.2-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:10fc250c3edea4abc15d930d77274ddb8df4803453dde7ad50c2f5565a18a4bb"}, - {file = "simplejson-3.17.2-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:76ac9605bf2f6d9b56abf6f9da9047a8782574ad3531c82eae774947ae99cc3f"}, - {file = "simplejson-3.17.2-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:7f10f8ba9c1b1430addc7dd385fc322e221559d3ae49b812aebf57470ce8de45"}, - {file = "simplejson-3.17.2-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:bc00d1210567a4cdd215ac6e17dc00cb9893ee521cee701adfd0fa43f7c73139"}, - {file = "simplejson-3.17.2-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:af4868da7dd53296cd7630687161d53a7ebe2e63814234631445697bd7c29f46"}, - {file = "simplejson-3.17.2-cp36-cp36m-win32.whl", hash = "sha256:7d276f69bfc8c7ba6c717ba8deaf28f9d3c8450ff0aa8713f5a3280e232be16b"}, - {file = "simplejson-3.17.2-cp36-cp36m-win_amd64.whl", hash = "sha256:a55c76254d7cf8d4494bc508e7abb993a82a192d0db4552421e5139235604625"}, - {file = "simplejson-3.17.2-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:9a2b7543559f8a1c9ed72724b549d8cc3515da7daf3e79813a15bdc4a769de25"}, - {file = "simplejson-3.17.2-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:311f5dc2af07361725033b13cc3d0351de3da8bede3397d45650784c3f21fbcf"}, - {file = "simplejson-3.17.2-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:2862beabfb9097a745a961426fe7daf66e1714151da8bb9a0c430dde3d59c7c0"}, - {file = "simplejson-3.17.2-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:afebfc3dd3520d37056f641969ce320b071bc7a0800639c71877b90d053e087f"}, - {file = "simplejson-3.17.2-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:d4813b30cb62d3b63ccc60dd12f2121780c7a3068db692daeb90f989877aaf04"}, - {file = "simplejson-3.17.2-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:3fabde09af43e0cbdee407555383063f8b45bfb52c361bc5da83fcffdb4fd278"}, - {file = "simplejson-3.17.2-cp37-cp37m-win32.whl", hash = "sha256:ceaa28a5bce8a46a130cd223e895080e258a88d51bf6e8de2fc54a6ef7e38c34"}, - {file = "simplejson-3.17.2-cp37-cp37m-win_amd64.whl", hash = "sha256:9551f23e09300a9a528f7af20e35c9f79686d46d646152a0c8fc41d2d074d9b0"}, - {file = "simplejson-3.17.2-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:c94dc64b1a389a416fc4218cd4799aa3756f25940cae33530a4f7f2f54f166da"}, - {file = "simplejson-3.17.2-cp38-cp38-manylinux1_i686.whl", hash = "sha256:b59aa298137ca74a744c1e6e22cfc0bf9dca3a2f41f51bc92eb05695155d905a"}, - {file = "simplejson-3.17.2-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:ad8f41c2357b73bc9e8606d2fa226233bf4d55d85a8982ecdfd55823a6959995"}, - {file = "simplejson-3.17.2-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:845a14f6deb124a3bcb98a62def067a67462a000e0508f256f9c18eff5847efc"}, - {file = "simplejson-3.17.2-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:d0b64409df09edb4c365d95004775c988259efe9be39697d7315c42b7a5e7e94"}, - {file = "simplejson-3.17.2-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:55d65f9cc1b733d85ef95ab11f559cce55c7649a2160da2ac7a078534da676c8"}, - {file = "simplejson-3.17.2.tar.gz", hash = "sha256:75ecc79f26d99222a084fbdd1ce5aad3ac3a8bd535cd9059528452da38b68841"}, -] -six = [ - {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, - {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, -] -soupsieve = [ - {file = "soupsieve-2.2.1-py3-none-any.whl", hash = "sha256:c2c1c2d44f158cdbddab7824a9af8c4f83c76b1e23e049479aa432feb6c4c23b"}, - {file = "soupsieve-2.2.1.tar.gz", hash = "sha256:052774848f448cf19c7e959adf5566904d525f33a3f8b6ba6f6f8f26ec7de0cc"}, -] -sqlparse = [ - {file = "sqlparse-0.4.1-py3-none-any.whl", hash = "sha256:017cde379adbd6a1f15a61873f43e8274179378e95ef3fede90b5aa64d304ed0"}, - {file = "sqlparse-0.4.1.tar.gz", hash = "sha256:0f91fd2e829c44362cbcfab3e9ae12e22badaa8a29ad5ff599f9ec109f0454e8"}, -] -tqdm = [ - {file = "tqdm-4.60.0-py2.py3-none-any.whl", hash = "sha256:daec693491c52e9498632dfbe9ccfc4882a557f5fa08982db1b4d3adbe0887c3"}, - {file = "tqdm-4.60.0.tar.gz", hash = "sha256:ebdebdb95e3477ceea267decfc0784859aa3df3e27e22d23b83e9b272bf157ae"}, -] -unidecode = [ - {file = "Unidecode-0.04.21-py2.py3-none-any.whl", hash = "sha256:61f807220eda0203a774a09f84b4304a3f93b5944110cc132af29ddb81366883"}, - {file = "Unidecode-0.04.21.tar.gz", hash = "sha256:280a6ab88e1f2eb5af79edff450021a0d3f0448952847cd79677e55e58bad051"}, -] -uritemplate = [ - {file = "uritemplate-3.0.1-py2.py3-none-any.whl", hash = "sha256:07620c3f3f8eed1f12600845892b0e036a2420acf513c53f7de0abd911a5894f"}, - {file = "uritemplate-3.0.1.tar.gz", hash = "sha256:5af8ad10cec94f215e3f48112de2022e1d5a37ed427fbd88652fa908f2ab7cae"}, -] -urllib3 = [ - {file = "urllib3-1.26.4-py2.py3-none-any.whl", hash = "sha256:2f4da4594db7e1e110a944bb1b551fdf4e6c136ad42e4234131391e21eb5b0df"}, - {file = "urllib3-1.26.4.tar.gz", hash = "sha256:e7b021f7241115872f92f43c6508082facffbd1c048e3c6e2bb9c2a157e28937"}, -] +content-hash = "f4a74fb373e4f8c8f9b9b4d4ac37b3715ab82faf29bf9d07b9fa0e229f7acf75" diff --git a/backend/behave/pyproject.toml b/backend/behave/pyproject.toml index 45c51256..2cbab5b7 100644 --- a/backend/behave/pyproject.toml +++ b/backend/behave/pyproject.toml @@ -7,30 +7,35 @@ license = "MIT" [tool.poetry.dependencies] python = "^3.8" -arrow = "^1.1.0" +arrow = "^1.2.3" html-diff = "^0.3.0" -tqdm = "^4.60.0" +tqdm = "^4.65.0" six = "^1.16.0" -python-crontab = "^2.5.1" -pycryptodome = "^3.10.1" +python-crontab = "^2.7.1" +pycryptodome = "^3.18.0" awesome-slugify = "^1.6.5" -sentry-sdk = "^1.1.0" -Pillow = "^8.2.0" -simplejson = "^3.17.2" +sentry-sdk = "^1.28.1" +Pillow = "^9.5.0" +simplejson = "^3.19.1" selenium = "^3.141.0" -requests = "^2.25.1" -python-dateutil = "^2.8.1" -pydash = "^5.0.0" -psycopg2 = "^2.8.6" -psycopg2-binary = "^2.8.6" +requests = "~2.25.1" +python-dateutil = "^2.8.2" +pydash = "^5.1.2" +psycopg2 = "^2.9.6" +psycopg2-binary = "^2.9.6" parse-type = "^0.5.2" -parse = "^1.19.0" +parse = "^1.19.1" fake-factory = "^9999.9.9" -djangorestframework = "^3.12.4" +djangorestframework = "^3.14.0" django-filter = "^2.4.0" -Django = "^3.2.3" +Django = "^3.2.20" coreapi = "^2.3.3" gunicorn = "^20.1.0" +openpyxl = "^3.1.2" +pandas = "^1.5.3" +pyotp = "^2.8.0" +redis = "^5.0.0" +django-rq = "^2.8.1" [tool.poetry.dev-dependencies] diff --git a/backend/behave/run_remote_from_django.sh b/backend/behave/run_remote_from_django.sh index 6e1fc9a0..462282b2 100755 --- a/backend/behave/run_remote_from_django.sh +++ b/backend/behave/run_remote_from_django.sh @@ -30,4 +30,7 @@ ln -sf /opt/code/cometa_itself/environment.py ${FOLDERPATH}/environment.py # execute the testcase echo "1 - /usr/local/bin/behave $FEATURE_FILE --summary --junit --junit-directory ${FOLDERPATH}/junit_reports/" -/usr/local/bin/behave "$FEATURE_FILE" --summary --junit --junit-directory ${FOLDERPATH}/junit_reports/ \ No newline at end of file +/usr/local/bin/behave "$FEATURE_FILE" \ + --summary --junit --junit-directory \ + ${FOLDERPATH}/junit_reports/ --no-skipped \ + --quiet --no-multiline --format=null \ No newline at end of file diff --git a/backend/behave/uploads/dummy.docx b/backend/behave/uploads/dummy.docx new file mode 100644 index 00000000..5cfc5872 Binary files /dev/null and b/backend/behave/uploads/dummy.docx differ diff --git a/backend/behave/uploads/dummy.mp4 b/backend/behave/uploads/dummy.mp4 new file mode 100755 index 00000000..fb0c4df9 Binary files /dev/null and b/backend/behave/uploads/dummy.mp4 differ diff --git a/backend/behave/uploads/dummy.pdf b/backend/behave/uploads/dummy.pdf new file mode 100644 index 00000000..9ed8f043 Binary files /dev/null and b/backend/behave/uploads/dummy.pdf differ diff --git a/backend/behave/uploads/dummy.png b/backend/behave/uploads/dummy.png new file mode 100644 index 00000000..8079f0dc Binary files /dev/null and b/backend/behave/uploads/dummy.png differ diff --git a/backend/behave/uploads/dummy.txt b/backend/behave/uploads/dummy.txt new file mode 100644 index 00000000..c62a0006 --- /dev/null +++ b/backend/behave/uploads/dummy.txt @@ -0,0 +1,2 @@ +This is a dummy file which can be used for testing uploads with cometa. + diff --git a/backend/behave/uploads/dummy.xlsx b/backend/behave/uploads/dummy.xlsx new file mode 100644 index 00000000..bdb07349 Binary files /dev/null and b/backend/behave/uploads/dummy.xlsx differ diff --git a/backend/crontabs/cometa_django_crontab b/backend/crontabs/cometa_django_crontab index 4f554c33..d88886e1 100755 --- a/backend/crontabs/cometa_django_crontab +++ b/backend/crontabs/cometa_django_crontab @@ -7,4 +7,4 @@ # --- # Execute housekeeping videos and feature results, every day at 1:00 am # --- -1 0 * * * /usr/local/bin/python /opt/code/manage.py cleanup_videos; /usr/local/bin/python /opt/code/manage.py cleanup_results --all-departments; +0 0 * * * /usr/local/bin/python /opt/code/manage.py cleanup_videos; /usr/local/bin/python /opt/code/manage.py cleanup_results --all-departments --days 90; diff --git a/backend/docker/build/behave/entry.sh b/backend/docker/build/behave/entry.sh index 90447ae0..2a3dd233 100755 --- a/backend/docker/build/behave/entry.sh +++ b/backend/docker/build/behave/entry.sh @@ -11,15 +11,15 @@ function install_cron() { # sh -c service rsyslog start; tail -f /dev/null # FIXME: This fails every time # Install requirements apt update -apt install --no-install-recommends -y rsyslog vim jq nano +apt install --no-install-recommends -y rsyslog vim jq nano supervisor service rsyslog start apt-get purge -y exim* # Upgrade PIP python -m pip install -U pip # Install poetry package manager -curl -sSL https://raw.githubusercontent.com/python-poetry/poetry/master/get-poetry.py | python - +curl -sSL https://install.python-poetry.org | python3 - # Create symbolic link to Poetry so it's available as command everywhere -ln -s /root/.poetry/bin/poetry /usr/local/bin/poetry +ln -s /root/.local/bin/poetry /usr/local/bin/poetry # Disable creation of virtual env poetry config virtualenvs.create false # Install project dependencies @@ -33,17 +33,37 @@ python manage.py migrate # install crontab install_cron - # Start Django server # python manage.py runserver 0.0.0.0:8001 # get processor cores CPUCORES=`getconf _NPROCESSORS_ONLN` # calculate workers -GUNI_WORKERS=$((($CPUCORES*2+1))) +# GUNI_WORKERS=$((($CPUCORES*2+1))) +GUNI_WORKERS=$((($CPUCORES+1))) + +# configure django-rq service in supervisor +cat < /etc/supervisor/conf.d/django-rq.conf +[program:django-rq] +environment=PYTHONUNBUFFERED=1 +process_name=%(program_name)s-%(process_num)s +command=python manage.py rqworker default +numprocs=6 +directory=/opt/code/behave_django +stopsignal=TERM +autostart=true +autorestart=true +stdout_logfile=/proc/1/fd/1 +stdout_logfile_maxbytes=0 +stderr_logfile=/proc/1/fd/1 +stderr_logfile_maxbytes=0 +EOF + +# start supervisord to spin django-rq workers. +supervisord -c /etc/supervisor/supervisord.conf # spin up gunicorn -gunicorn behave_django.wsgi:application --workers=${WORKERS:-$GUNI_WORKERS} --threads=${THREADS:-2} --worker-class=gthread --bind 0.0.0.0:8001 +gunicorn behave_django.wsgi:application --workers=2 --threads=${THREADS:-2} --worker-class=gthread --bind 0.0.0.0:8001 --preload # SEMFILE="stop_behave_semaphore.sem" diff --git a/backend/scripts/create_backup.sh b/backend/scripts/create_backup.sh new file mode 100755 index 00000000..5f92ce26 --- /dev/null +++ b/backend/scripts/create_backup.sh @@ -0,0 +1,107 @@ +#!/bin/bash + +# CHANGELOG +# 2023-01-30 - RRO - adding vacuum full before backup to reclaim tablespace +# 2022-04-12 - RRO - Copied create_backup.sh to cometa repo, so it's not flying around. +# 2021.03.23 - ASO - Added /Cometa mountpoint to /etc/fstab so script does not have to. + +# Save date and time +DATE=$(date '+%Y%m%d%H%M%S') +TIMESTAMP=$(date '+%s') +BACKUPDIR=/backups +TMPFILE=$(mktemp) +# final file name +BKPFILENAME=cometa_backup_${DATE}_${TIMESTAMP}.gz +# database container name +CONTAINERNAME=cometa_postgres +# command to execute on the container +COMMAND="pg_dump postgres -U postgres | gzip > /code/${BKPFILENAME}" + +# cometa backend folder depending on hostname +HOSTNAME=$(hostname) +BACKEND=/var/www/cometa/backend +NO_OF_FILES_TO_KEEP_IN_BACKUP=30 +case $HOSTNAME in + amvara3) + BACKEND=/home/amvara/projects/cometa/backend + BACKUPDIR=/data/backups + NO_OF_FILES_TO_KEEP_IN_BACKUP=60 + ;; + amvara2) + BACKEND=/home/amvara/projects/cometa/cometa/backend + BACKUPDIR=/data/backups + NO_OF_FILES_TO_KEEP_IN_BACKUP=60 + ;; + sgdem0005126) + BACKEND=/var/www/cometa/backend + ;; + sgdem0005125) + BACKEND=/var/www/cometa/backend + ;; + development) + BACKEND=/home/arslansb/projects/cometa/backend + BACKUPDIR=/home/arslansb/projects/cometa_bkps + ;; +esac + +# automate a bit failed behaviour +function failed() { + log_res "[failed]" + error "More details about the error:" + cat ${TMPFILE} + exit ${1:-255} +} + +# do some cleanup like remove the tmp file +function cleanup() { + log_wfr "Removing tmp file " + rm ${TMPFILE} && log_res "[done]" || log_res "[failed]" +} + +# create a trap to execute cleanup whenever script exists. +trap cleanup EXIT + +# import logger +test `command -v log_wfr` || source ${BACKEND}/../helpers/logger.sh + +# print some valuable information +debug "Here are the variables used:" +debug "BACKEND => ${BACKEND}" +debug "BACKUPDIR => ${BACKUPDIR}" +debug "BKPFILENAME => ${BKPFILENAME}" +debug "COMMAND => ${COMMAND}" +debug "CONTAINERNAME => ${CONTAINERNAME}" +debug "DATE => ${DATE}" +debug "HOSTNAME => ${HOSTNAME}" +debug "NO OF FILES TO KEEP IN BACKUP => ${NO_OF_FILES_TO_KEEP_IN_BACKUP}" +debug "TMPFILE => ${TMPFILE}" +debug "TIMESTAMP => ${TIMESTAMP}" + +# create the backup file not already exists +log_wfr "Making sure ${BACKUPDIR} exists " +mkdir -p $BACKUPDIR &>${TMPFILE} && log_res "[done]" || failed 5 + +# make sure that the db_data folder has correct permissions +log_wfr "Making sure that the db_data folder has correct permissions " +chown -R 999:root ${BACKEND}/db_data &>${TMPFILE} && log_res "[done]" || failed 10 + +# clear django sessions table +log_wfr "Clearing Django Sessions " +docker exec cometa_django bash -c "python manage.py clearsessions" &>${TMPFILE} && log_res "[done]" || failed 15 + +# Vacuum the database ... carefull this creates tables locks +log_wfr "Recovering space after clearing django sessions " +docker exec cometa_postgres bash -c "psql -U postgres postgres --command \"vacuum full\"" &>${TMPFILE} && log_res "[done]" || failed 20 + +# check if postgres container is running and create the backup +log_wfr "Creating the backup file " +# create the zip file named cometa_backup_ and the date for easy knowing of the recent file as well as timestamp in case we need to do some cleaning. +docker exec ${CONTAINERNAME} bash -c "${COMMAND}" &>${TMPFILE} && log_res "[done]" || failed 25 +log_wfr "Move the backup file to the backups dir " +mv ${BACKEND}/${BKPFILENAME} $BACKUPDIR &>${TMPFILE} && log_res "[done]" || failed 30 + +# for more information checkout https://www.postgresql.org/docs/9.1/backup-dump.html on how to create a backup and how to restore. + +# remove older backups +log_wfr "Cleaning up old backups " +/usr/bin/find $BACKUPDIR/ -type f -mtime +$NO_OF_FILES_TO_KEEP_IN_BACKUP -name '*.gz' -print -delete &>${TMPFILE} && log_res "[done]" || failed 35 \ No newline at end of file diff --git a/backend/scripts/housekeeping.sh b/backend/scripts/housekeeping.sh new file mode 100755 index 00000000..7477b07d --- /dev/null +++ b/backend/scripts/housekeeping.sh @@ -0,0 +1,24 @@ +#!/bin/bash + +# CHANGELOG +# 2022-05-11 - ASO - changed echo to eval and getLatestBrowsers to deploy_selenoid otherwise json would throw error. +# 2022-05-09 - RRO - adding get ./getLatestBrowsers.sh to automatically update +# 2022-04-12 - RRO - Copied create_backup.sh to cometa repo, so it's not flying around. +# 2021.03.23 - ASO - Added /Cometa mountpoint to /etc/fstab so script does not have to. + +# get some directory based on the script +SCRIPTDIR=$(dirname "$0") +BACKEND=${SCRIPTDIR}/../ +BASEDIR=${BACKEND}/../ +# import logger +test `command -v log_wfr` || source ${BASEDIR}/helpers/logger.sh + + +# Use existing create_backup.sh script +${SCRIPTDIR}/create_backup.sh + +# +# get latests browsers +# +info "Checking latest browser information" +cd ${BACKEND}/selenoid; ./deploy_selenoid.sh -n 10 \ No newline at end of file diff --git a/backend/scripts/integration-with-gitlab.sh b/backend/scripts/integration-with-gitlab.sh old mode 100644 new mode 100755 index fbbfb28c..d90b0a70 --- a/backend/scripts/integration-with-gitlab.sh +++ b/backend/scripts/integration-with-gitlab.sh @@ -10,105 +10,293 @@ # ########################################################################### # # Changelog: +# 2023-09-07 ASO batch execution based on folder_id or department_id # 2021-06-21 RRO added script for gitlab ci integration to cometa-backend-project # # ########################################################################### -# MAIN VARIABLES -GITLAB_HOST="https://git.amvara.de" -GITLAB_USER="" -GITLAB_PASSWORD="" -GITLAB_LOGIN_BODY=$(curl -c cookies.txt ${CURL_OPTIONS} -i "${GITLAB_HOST}/users/sign_in" -s) -CSRF_TOKEN=$(echo $GITLAB_LOGIN_BODY | grep -oE 'name="authenticity_token" value="([^"]*)"' | cut -d' ' -f2 | sed "s/value=//g;s/\"//g") -# Add --insecure if executing in localhost -CURL_OPTIONS=" " - -function help() { - echo -ne "${0} [OPTIONS] +usage() { + echo -ne "${0} -e [-f ] [-u ] [-p ] [-pp] [-cf ] OPTIONS: - -f|--feature Specify the feature id that you would like to execute. - -e|--environment Specify the url that you would like to test - -d|--debug Enables debugging + --feature, -f Feature ID to execute, property can be repeated. + --folder Folder ID, where the features will be executed, property can be repeated. (Check --recursive) + --department Department ID, where the feature will be executed. (Check --recursive) + --environment, -e Co.meta URL where your features are. + --username, -u Username to use for login. + --password, -p Password to use for login. + --prompt-pwd, -pp Prompts the password for login. + --cred-file, -cf Creadential file. Please follow the format specified below. + --recursive, -r Recursively run features in sub-folders (Only useful when running with --folder or --department). + --wait, -w Wait for the feature to finish. EXAMPLES: - ${0} -f 272 - ${0} --feture 272 - ${0} -f 272,273,274 + +Credential File: + Credentials file should follow the following format: + username= + password= " exit 1; } -function checkIfNumber() { +cleanup() { + local exit_code=$? + debug "Doing some cleanup ..." + if [[ ${exit_code} -gt 0 ]]; then + if [[ `cat ${TMPFILE} | wc -l` -gt 0 ]]; then + info "error file:" + cat ${TMPFILE} + fi + fi + rm ${TMPFILE} ${COOKIES_FILE} +} + +check_int() { # number regexp to check input variable - number_REGEXP='^[0-9]+$' - if ! [[ $1 =~ $number_REGEXP ]]; then - echo "error: Specified non integer value for -f|--feature" + local number_pattern='^[0-9]+$' + if ! [[ ${1} =~ ${number_pattern} ]]; then + critical "error: expected number but recieved: ${1}." exit 2; fi } -if [[ $# -lt 4 ]]; then - echo "No options or not all required options specified..." - help -fi +parse_credential_file() { + local cred_file=${1} + if [[ ! -r ${cred_file} ]]; then + critical "error: unable to read credential file." + exit 3; + fi -while [[ $# -gt 0 ]] -do - key="$1" - case $key in - -f|--feature) - FEATURES=($(echo $2 | sed "s/,/ /g")) - shift - shift - ;; - -e|--environment) - COMETA_LOGIN_URL="https://$2/callback?iss=https%3A%2F%2Fgit.amvara.de&target_link_uri=https%3A%2F%2F$2%2F&method=get&oidc_callback=https%3A%2F%2F$2%2Fcallback" - echo "COMETA LOGIN URL: ${COMETA_LOGIN_URL}" - CURL_URL="https://$2/backend/exectest/" - echo "CURL URL: ${CURL_URL}" - shift - shift - ;; - -d|--debug) - set -x - DEBUG=TRUE - shift - ;; - *) - echo "Unknown option $key ... please check the options below..." - help - shift - ;; - esac -done + local username=`cat ${cred_file} | grep -i "username" | cut -d= -f2` + local password=`cat ${cred_file} | grep -i "password" | cut -d= -f2` + + if [[ -z ${username} && -z ${password} ]]; then + critical "error: unable to find username or password in credential file." + exit 4 + fi + + if [[ ! -z ${username} ]]; then + USERNAME=${username} + fi + + if [[ ! -z ${password} ]]; then + PASSWORD=${password} + fi +} + +load_logger() { + local logger_file=/tmp/.cometa_logger + if [[ ! -r ${logger_file} ]]; then + curl -o ${logger_file} -s https://raw.githubusercontent.com/cometa-rocks/cometa/master/helpers/logger.sh + + if [[ $? -gt 0 ]]; then + echo "Unable to download logger file ... please try again." + exit 6 + fi + fi + source ${logger_file} +} -# add -vvv if debug flag is set -test ${DEBUG:-FALSE} == TRUE && CURL_OPTIONS="$CURL_OPTIONS -vvv " +login_gitlab() { + local gitlab_host="https://git.amvara.de" + local gitlab_body=`curl -c ${COOKIES_FILE} ${CURL_OPTIONS} -i "${gitlab_host}/users/sign_in" -s` + local csrf_token=`echo ${gitlab_body} | grep -oE 'name="authenticity_token" value="([^"]*)"' | cut -d' ' -f2 | sed "s/value=//g;s/\"//g"` + local cometa_login_url="https://${ENVIRONMENT}/callback?iss=https%3A%2F%2Fgit.amvara.de&target_link_uri=https%3A%2F%2F${ENVIRONMENT}%2F&method=get&oidc_callback=https%3A%2F%2F${ENVIRONMENT}%2Fcallback" -# LOGIN to GITLAB -curl ${CURL_OPTIONS} -b cookies.txt -c cookies.txt -i "${GITLAB_HOST}/users/sign_in" \ - --data "user[login]=${GITLAB_USER}&user[password]=${GITLAB_PASSWORD}" \ - --data-urlencode "authenticity_token=${CSRF_TOKEN}" -s -o /dev/null -# LOGIN to CO.META -echo "------------COMETA LOGIN URL: ${COMETA_LOGIN_URL}" -LOGIN_TO_COMETA=$(curl ${CURL_OPTIONS} -b cookies.txt -c cookies.txt -Li "${COMETA_LOGIN_URL}" -s) + log_wfr "Logging in to co.meta environment (${ENVIRONMENT})" + + curl ${CURL_OPTIONS} -b ${COOKIES_FILE} -c ${COOKIES_FILE} -i "${gitlab_host}/users/sign_in" \ + --data "user[login]=${USERNAME}&user[password]=${PASSWORD}" \ + --data-urlencode "authenticity_token=${csrf_token}" -s -o ${TMPFILE} + + local cometa_login=$(curl ${CURL_OPTIONS} -b ${COOKIES_FILE} -c ${COOKIES_FILE} -Li "${cometa_login_url}" -s) + local redirect_url=$(echo $cometa_login | grep -Eo 'redirectUri.?=.?".*?"' | grep -o "\".*\"" | xargs) + + # Check if the find_redirect_url is empty, if it is empty, exit the script + if [[ -z ${redirect_url} ]]; then + log_res "[failed]" + critical "error: redirect URL is empty, continuing the test makes no sense." + exit 4 + fi + + curl -b ${COOKIES_FILE} -c ${COOKIES_FILE} -Li ${CURL_OPTIONS} "$redirect_url" -s -o ${TMPFILE} + log_res "[done]" +} + +loader() { + spin=("-" "\\" "|" "/") + for i in "${spin[@]}"; do + echo -ne "\b$i" + sleep $(awk "BEGIN { print ( ${1:-1}/4 ) }") + done +} -FIND_REDIRECT_URL=$(echo $LOGIN_TO_COMETA | grep -o 'window.location=.*"' | sed "s/window.location=[^\"]*//g;s/\"//g;s/;//g") +wait_for_feature() { + local feature=${1} + local status_url="https://${ENVIRONMENT}/backend/featureStatus/${feature}/?onlyProgress=true" -# Check if the find_redirect_url is empty, if it is empty, exit the script -if [[ -z ${FIND_REDIRECT_URL} ]]; then - echo "Redirect URL is empty, continuing the test makes no sense" - exit 3 + # save the cursor position just in case. + printf "\033[s" + loader 2 + while [ $(curl -b ${COOKIES_FILE} ${CURL_OPTIONS} ${status_url} \ + -c ${COOKIES_FILE} -L -s -o ${TMPFILE} -w "%{http_code}") == "206" ]; do + printf "\033[u" + cat ${TMPFILE} + loader + done + # print the end file + printf "\033[u" + cat ${TMPFILE} + cat ${TMPFILE} | grep "Overall Status" | grep -q "Failed" + if [[ $? -eq 0 ]]; then + error "Feature overall status is failed, will exit with non-zero code." + return 1 + fi + + return 0 +} + +run_features() { + local execute_url="https://${ENVIRONMENT}/backend/exectest/" + + for feature in ${FEATURES[@]}; do + log_wfr "Running Feature: $feature" + curl -b ${COOKIES_FILE} ${CURL_OPTIONS} -c ${COOKIES_FILE} -L -X POST \ + -d '{"feature_id": '${feature}', "wait": true}' $execute_url -s -o ${TMPFILE} && \ + log_res "done" || log_res "failed" + + if [[ ! -z ${WAIT_FOR_FEATURE} ]]; then + wait_for_feature ${feature} + fi + done +} + +run_batch() { + local execute_url="https://${ENVIRONMENT}/backend/exec_batch/" + + # check + if [[ ${#FOLDERS[@]} -gt 0 && ! -z ${DEPARTMENT} ]]; then + critical "error: --folder and --department can not be used at the same time, as of now." + exit 7; + fi + + if [[ ${#FOLDERS[@]} -gt 0 ]]; then + for folder in ${FOLDERS[@]}; do + log_wfr "Running Folder: $folder" + curl -b ${COOKIES_FILE} ${CURL_OPTIONS} -c ${COOKIES_FILE} -L -X POST \ + -d '{"folder_id": '${folder}', "recursive": '${RECURSIVE:-false}'}' $execute_url -s -o ${TMPFILE} && \ + log_res "done" || log_res "failed" + done + else # department + log_wfr "Running Department: $DEPARTMENT" + curl -b ${COOKIES_FILE} ${CURL_OPTIONS} -c ${COOKIES_FILE} -L -X POST \ + -d '{"department_id": '${DEPARTMENT}', "recursive": '${RECURSIVE:-false}'}' $execute_url -s -o ${TMPFILE} && \ + log_res "done" || log_res "failed" + fi +} + +main() { + # login to gitlab + login_gitlab + + # if there is atleast one feature run it. + if [[ ${#FEATURES[@]} -gt 0 ]]; then + run_features + exit 0; + fi + + # if there is atleast one folder run it. + if [[ ${#FOLDERS[@]} -gt 0 ]] || [[ ! -z ${DEPARTMENT} ]]; then + run_batch + exit 0; + fi +} + +# trap exit +trap "cleanup" EXIT + +# check if there is at least 1 cli parameter +if [[ $# -eq 0 ]]; then + info "No options passed." + usage fi -curl -b cookies.txt -c cookies.txt -Li ${CURL_OPTIONS} "$FIND_REDIRECT_URL" -s -o /dev/null - -echo "Login to co.meta => done." +# load logger +load_logger + +# GLOBAL Variables +FEATURES=( ) +FOLDERS=( ) +ENVIRONMENT=localhost +USERNAME="" +PASSWORD="" +TMPFILE=$(mktemp) +COOKIES_FILE=$(mktemp) +CURL_OPTIONS=" --insecure " + +# parse cli parameters +while [[ $# -gt 0 ]] +do + case ${1} in + --feature|-f) + if check_int ${2}; then + FEATURES=( ${FEATURES[@]} ${2} ) + fi + shift + shift + ;; + --environment|-e) + ENVIRONMENT=${2} + shift + shift + ;; + --username|-u) + USERNAME=${2} + shift + shift + ;; + --password|-p) + PASSWORD=${2} + shift + shift + ;; + --prompt-pwd|-pp) + read -esp 'Password: ' PASSWORD + shift + ;; + --cred-file|-cf) + parse_credential_file ${2} + shift + shift + ;; + --wait|-w) + WAIT_FOR_FEATURE=1 + shift + ;; + --folder) + if check_int ${2}; then + FOLDERS=( ${FOLDERS[@]} ${2} ) + fi + shift + shift + ;; + --department) + if check_int ${2}; then + DEPARTMENT=${2} + fi + shift + shift + ;; + --recursive|-r) + RECURSIVE=true + shift + ;; + *) + usage + shift + ;; + esac +done -for FEATURE in ${FEATURES[@]}; do - checkIfNumber $FEATURE - echo -n "Running Feature: $FEATURE => " - # execute test - curl -b cookies.txt ${CURL_OPTIONS} -c cookies.txt -L -X POST -d '{"feature_id": '${FEATURE}'}' $CURL_URL -s -done \ No newline at end of file +main \ No newline at end of file diff --git a/backend/selenoid/.fluxbox/aerokube b/backend/selenoid/.fluxbox/aerokube new file mode 100644 index 00000000..f652912e --- /dev/null +++ b/backend/selenoid/.fluxbox/aerokube @@ -0,0 +1,175 @@ +############################################################################### +# +# name: aerokube (based on ubuntu_light) +# made: paultag +# date: 16-10-2010 +# http://pault.ag/ +# +############################################################## BACKGROUND ###### + +# background: flat +# background.color: #2c001e +# background.colorTo: #2c001e + +background: fullscreen +background.pixmap: /usr/share/images/fluxbox/aerokube.png + +############################################################## FONTS ########## + + +menu.frame.font: Ubuntu-10:bold +menu.title.font: Ubuntu-12:bold +toolbar.clock.font: Ubuntu-10:bold +toolbar.workspace.font: Ubuntu-12:bold +toolbar.iconbar.focused.font: Ubuntu-10:bold +toolbar.iconbar.unfocused.font: Ubuntu-10 +window.font: Ubuntu-10 + +############################################################## MENU ########### + +menu.bevelWidth: 1 + +#menu.itemHeight: 35 +#menu.titleHeight: 21 +menu.borderColor: #525252 +menu.borderWidth: 1 + +menu.bullet: Triangle +menu.bullet.position: Right +menu.frame.underlineColor: #ffffff + +menu.title: flat gradient rectangle +menu.title.justify: center +menu.title.color: #FFFFFF +menu.title.colorTo: #FFFFFF +menu.title.textColor: #333333 + +menu.frame: flat gradient crossdiagonal +menu.frame.justify: left +menu.frame.color: #FFFFFF +menu.frame.colorTo: #FFFFFF +menu.frame.textColor: #333333 +menu.frame.disableColor: #aea79f + +menu.hilite: flat gradient rectangle +menu.hilite.color: #aea79f +menu.hilite.colorTo: #aea79f +menu.hilite.textColor: #000000 + +############################################################## TOOLBAR ######## + +toolbar.bevelWidth: 5 + +toolbar.borderWidth: 0 +toolbar.borderColor: #aea79f + +toolbar.height: 0 + +toolbar.justify: center + +toolbar: flat solid +toolbar.pixmap: +toolbar.color: #dee1e6 +toolbar.colorTo: #333333 + +toolbar.clock: # parentrelative +toolbar.clock.justify: Center +toolbar.clock.color: #ffffff +toolbar.clock.colorTo: # +toolbar.clock.textColor: #333333 + +toolbar.workspace: parentrelative +toolbar.workspace.justify: Center +toolbar.workspace.color: # +toolbar.workspace.colorTo: # +toolbar.workspace.textColor: #333333 + +toolbar.button: parentrelative +toolbar.button.color: #333333 +toolbar.button.colorTo: #333333 +toolbar.button.picColor: #333333 +toolbar.button.pressed: parentrelative +toolbar.button.pressed.color: #aea79f +toolbar.button.pressed.colorTo: #aea79f +toolbar.button.pressed.picColor: #000000 + +toolbar.iconbar.borderWidth: 1 +toolbar.iconbar.borderColor: #333333 + +toolbar.iconbar.empty: parentrelative +toolbar.iconbar.empty.color: # +toolbar.iconbar.empty.colorTo: # + +toolbar.iconbar.focused.borderWidth: 1 +toolbar.iconbar.focused.borderColor: #333333 +toolbar.iconbar.focused: flat gradient rectangle +toolbar.iconbar.focused.color: #FFFFFF +toolbar.iconbar.focused.colorTo: #FFFFFF +toolbar.iconbar.focused.textColor: #333333 +toolbar.iconbar.focused.justify: center + +toolbar.iconbar.unfocused.borderWidth: 1 +toolbar.iconbar.unfocused.borderColor: #525252 +toolbar.iconbar.unfocused: flat gradient rectangle +toolbar.iconbar.unfocused.color: #aea79f +toolbar.iconbar.unfocused.colorTo: #aea79f +toolbar.iconbar.unfocused.textColor: #444444 +toolbar.iconbar.unfocused.justify: center + +############################################################## WINDOW ######### + +window.roundCorners: TopRight TopLeft +window.bevelWidth: 4 +window.shade: false +window.borderWidth: 1 +window.borderColor: #333333 + +window.justify: Center +window.title.height: 21 + +window.title.focus: flat gradient rectangle +window.title.focus.color: #000000 +window.title.focus.colorTo: #333333 +window.title.unfocus: flat gradient rectangle +window.title.unfocus.color: #111111 +window.title.unfocus.colorTo: #444444 + +window.label.focus: parentrelative +window.label.focus.color: # +window.label.focus.colorTo: # +window.label.focus.textColor: #FFFFFF +window.label.unfocus: parentrelative +window.label.unfocus.color: # +window.label.unfocus.colorTo: # +window.label.unfocus.textColor: #aea79f + +window.button.focus: parentrelative +window.button.focus.color: # +window.button.focus.colorTo: # +window.button.focus.picColor: #FFFFFF +window.button.unfocus: parentrelative +window.button.unfocus.Color: # +window.button.unfocus.ColorTo: # +window.button.unfocus.picColor: #888888 +window.button.pressed: parentrelative +window.button.pressed.color: # +window.button.pressed.colorTo: # +window.button.pressed.picColor: #000000 + +window.handle.focus: flat +window.handle.focus.color: #1f1f1f +window.handle.focus.colorTo: #1f1f1f +window.handle.unfocus: lat +window.handle.unfocus.color: #1f1f1f +window.handle.unfocus.colorTo: #1f1f1f +window.handleWidth: 2 + +window.grip.focus: flat +window.grip.focus.color: #525252 +window.grip.focus.colorTo: #525252 +window.grip.unfocus: flat +window.grip.unfocus.color: #1f1f1f +window.grip.unfocus.colorTo: #1f1f1f + +############################################################################### +# EOF \ No newline at end of file diff --git a/backend/selenoid/.fluxbox/init b/backend/selenoid/.fluxbox/init new file mode 100644 index 00000000..43cb8631 --- /dev/null +++ b/backend/selenoid/.fluxbox/init @@ -0,0 +1,70 @@ +session.screen0.titlebar.left: Stick +session.screen0.titlebar.right: Minimize Maximize Close +session.screen0.clientMenu.usePixmap: true +session.screen0.tabs.usePixmap: true +session.screen0.tabs.maxOver: false +session.screen0.tabs.intitlebar: true +session.screen0.window.focus.alpha: 255 +session.screen0.window.unfocus.alpha: 255 +session.screen0.tab.placement: TopLeft +session.screen0.tab.width: 64 +session.screen0.menu.alpha: 255 +session.screen0.slit.placement: RightBottom +session.screen0.slit.acceptKdeDockapps: true +session.screen0.slit.alpha: 255 +session.screen0.slit.maxOver: false +session.screen0.slit.onhead: 0 +session.screen0.slit.autoHide: false +session.screen0.slit.layer: Dock +session.screen0.toolbar.placement: TopCenter +session.screen0.toolbar.onhead: 1 +session.screen0.toolbar.visible: true +session.screen0.toolbar.layer: Dock +session.screen0.toolbar.tools: clock +session.screen0.toolbar.alpha:0 +session.screen0.toolbar.height: 0 +session.screen0.toolbar.maxOver: true +session.screen0.toolbar.autoHide: false +session.screen0.toolbar.widthPercent: 10 +session.screen0.maxDisableResize: false +session.screen0.autoRaise: true +session.screen0.noFocusWhileTypingDelay: 0 +session.screen0.clickRaises: true +session.screen0.tabFocusModel: ClickToTabFocus +session.screen0.menuDelay: 200 +session.screen0.focusNewWindows: true +session.screen0.colPlacementDirection: TopToBottom +session.screen0.workspaceNames: Workspace 1,Workspace 2,Workspace 3,Workspace 4, +session.screen0.windowPlacement: RowMinOverlapPlacement +session.screen0.tooltipDelay: 500 +session.screen0.rowPlacementDirection: LeftToRight +session.screen0.fullMaximization: false +session.screen0.strftimeFormat: %d %b, %a %02k:%M:%S +session.screen0.focusModel: ClickFocus +session.screen0.workspacewarping: true +session.screen0.opaqueMove: true +session.screen0.showwindowposition: false +session.screen0.focusSameHead: false +session.screen0.edgeSnapThreshold: 10 +session.screen0.allowRemoteActions: false +session.screen0.maxIgnoreIncrement: true +session.screen0.windowMenu: /home/selenium/.fluxbox/windowmenu +session.screen0.maxDisableMove: false +session.screen0.defaultDeco: NORMAL +session.screen0.workspaces: 4 +session.tabPadding: 0 +session.tabsAttachArea: Window +session.keyFile: ~/.fluxbox/keys +session.ignoreBorder: false +session.cacheMax: 200 +session.slitlistFile: /home/selenium/.fluxbox/slitlist +session.autoRaiseDelay: 250 +session.configVersion: 13 +session.styleFile: /home/selenium/.fluxbox/aerokube +session.forcePseudoTransparency: false +session.cacheLife: 5 +session.doubleClickInterval: 250 +session.colorsPerChannel: 4 +session.styleOverlay: /home/selenium/.fluxbox/overlay +session.appsFile: /home/selenium/.fluxbox/apps +session.menuFile: ~/.fluxbox/menu diff --git a/backend/selenoid/browsers.json b/backend/selenoid/browsers.json deleted file mode 100644 index c576c6ed..00000000 --- a/backend/selenoid/browsers.json +++ /dev/null @@ -1,292 +0,0 @@ -{ - "MicrosoftEdge": { - "default": "96.0", - "versions": { - "95.0": { - "image": "browsers/edge:95.0", - "port": "4444", - "path": "/", - "volumes": [ - "/var/projects/public/cometa/backend/selenoid/amvara.png:/usr/share/images/fluxbox/aerokube.png" - ] - }, - "94.0": { - "image": "browsers/edge:94.0", - "port": "4444", - "path": "/", - "volumes": [ - "/var/projects/public/cometa/backend/selenoid/amvara.png:/usr/share/images/fluxbox/aerokube.png" - ] - }, - "93.0": { - "image": "browsers/edge:93.0", - "port": "4444", - "path": "/", - "volumes": [ - "/var/projects/public/cometa/backend/selenoid/amvara.png:/usr/share/images/fluxbox/aerokube.png" - ] - }, - "92.0": { - "image": "browsers/edge:92.0", - "port": "4444", - "path": "/", - "volumes": [ - "/var/projects/public/cometa/backend/selenoid/amvara.png:/usr/share/images/fluxbox/aerokube.png" - ] - }, - "91.0": { - "image": "browsers/edge:91.0", - "port": "4444", - "path": "/", - "volumes": [ - "/var/projects/public/cometa/backend/selenoid/amvara.png:/usr/share/images/fluxbox/aerokube.png" - ] - }, - "90.0": { - "image": "browsers/edge:90.0", - "port": "4444", - "path": "/", - "volumes": [ - "/var/projects/public/cometa/backend/selenoid/amvara.png:/usr/share/images/fluxbox/aerokube.png" - ] - }, - "89.0": { - "image": "browsers/edge:89.0", - "port": "4444", - "path": "/", - "volumes": [ - "/var/projects/public/cometa/backend/selenoid/amvara.png:/usr/share/images/fluxbox/aerokube.png" - ] - }, - "88.0": { - "image": "browsers/edge:88.0", - "port": "4444", - "path": "/", - "volumes": [ - "/var/projects/public/cometa/backend/selenoid/amvara.png:/usr/share/images/fluxbox/aerokube.png" - ] - }, - "96.0": { - "image": "browsers/edge:96.0", - "port": "4444", - "path": "/", - "volumes": [ - "/var/projects/public/cometa/backend/selenoid/amvara.png:/usr/share/images/fluxbox/aerokube.png" - ] - } - } - }, - "firefox": { - "default": "95.0", - "versions": { - "94.0": { - "image": "selenoid/vnc_firefox:94.0", - "port": "4444", - "path": "/wd/hub", - "volumes": [ - "/var/projects/public/cometa/backend/selenoid/amvara.png:/usr/share/images/fluxbox/aerokube.png" - ] - }, - "93.0": { - "image": "selenoid/vnc_firefox:93.0", - "port": "4444", - "path": "/wd/hub", - "volumes": [ - "/var/projects/public/cometa/backend/selenoid/amvara.png:/usr/share/images/fluxbox/aerokube.png" - ] - }, - "92.0": { - "image": "selenoid/vnc_firefox:92.0", - "port": "4444", - "path": "/wd/hub", - "volumes": [ - "/var/projects/public/cometa/backend/selenoid/amvara.png:/usr/share/images/fluxbox/aerokube.png" - ] - }, - "91.0": { - "image": "selenoid/vnc_firefox:91.0", - "port": "4444", - "path": "/wd/hub", - "volumes": [ - "/var/projects/public/cometa/backend/selenoid/amvara.png:/usr/share/images/fluxbox/aerokube.png" - ] - }, - "90.0": { - "image": "selenoid/vnc_firefox:90.0", - "port": "4444", - "path": "/wd/hub", - "volumes": [ - "/var/projects/public/cometa/backend/selenoid/amvara.png:/usr/share/images/fluxbox/aerokube.png" - ] - }, - "89.0": { - "image": "selenoid/vnc_firefox:89.0", - "port": "4444", - "path": "/wd/hub", - "volumes": [ - "/var/projects/public/cometa/backend/selenoid/amvara.png:/usr/share/images/fluxbox/aerokube.png" - ] - }, - "88.0": { - "image": "selenoid/vnc_firefox:88.0", - "port": "4444", - "path": "/wd/hub", - "volumes": [ - "/var/projects/public/cometa/backend/selenoid/amvara.png:/usr/share/images/fluxbox/aerokube.png" - ] - }, - "87.0": { - "image": "selenoid/vnc_firefox:87.0", - "port": "4444", - "path": "/wd/hub", - "volumes": [ - "/var/projects/public/cometa/backend/selenoid/amvara.png:/usr/share/images/fluxbox/aerokube.png" - ] - }, - "95.0": { - "image": "selenoid/vnc_firefox:95.0", - "port": "4444", - "path": "/wd/hub", - "volumes": [ - "/var/projects/public/cometa/backend/selenoid/amvara.png:/usr/share/images/fluxbox/aerokube.png" - ] - } - } - }, - "chrome": { - "default": "96.0", - "versions": { - "95.0": { - "image": "selenoid/vnc_chrome:95.0", - "port": "4444", - "volumes": [ - "/var/projects/public/cometa/backend/selenoid/amvara.png:/usr/share/images/fluxbox/aerokube.png" - ] - }, - "94.0": { - "image": "selenoid/vnc_chrome:94.0", - "port": "4444", - "volumes": [ - "/var/projects/public/cometa/backend/selenoid/amvara.png:/usr/share/images/fluxbox/aerokube.png" - ] - }, - "93.0": { - "image": "selenoid/vnc_chrome:93.0", - "port": "4444", - "volumes": [ - "/var/projects/public/cometa/backend/selenoid/amvara.png:/usr/share/images/fluxbox/aerokube.png" - ] - }, - "92.0": { - "image": "selenoid/vnc_chrome:92.0", - "port": "4444", - "volumes": [ - "/var/projects/public/cometa/backend/selenoid/amvara.png:/usr/share/images/fluxbox/aerokube.png" - ] - }, - "91.0": { - "image": "selenoid/vnc_chrome:91.0", - "port": "4444", - "volumes": [ - "/var/projects/public/cometa/backend/selenoid/amvara.png:/usr/share/images/fluxbox/aerokube.png" - ] - }, - "90.0": { - "image": "selenoid/vnc_chrome:90.0", - "port": "4444", - "volumes": [ - "/var/projects/public/cometa/backend/selenoid/amvara.png:/usr/share/images/fluxbox/aerokube.png" - ] - }, - "89.0": { - "image": "selenoid/vnc_chrome:89.0", - "port": "4444", - "volumes": [ - "/var/projects/public/cometa/backend/selenoid/amvara.png:/usr/share/images/fluxbox/aerokube.png" - ] - }, - "88.0": { - "image": "selenoid/vnc_chrome:88.0-1", - "port": "4444", - "volumes": [ - "/var/projects/public/cometa/backend/selenoid/amvara.png:/usr/share/images/fluxbox/aerokube.png" - ] - }, - "96.0": { - "image": "selenoid/vnc_chrome:96.0", - "port": "4444", - "volumes": [ - "/var/projects/public/cometa/backend/selenoid/amvara.png:/usr/share/images/fluxbox/aerokube.png" - ] - } - } - }, - "opera": { - "default": "82.0", - "versions": { - "81.0": { - "image": "selenoid/vnc_opera:81.0", - "port": "4444", - "volumes": [ - "/var/projects/public/cometa/backend/selenoid/amvara.png:/usr/share/images/fluxbox/aerokube.png" - ] - }, - "80.0": { - "image": "selenoid/vnc_opera:80.0", - "port": "4444", - "volumes": [ - "/var/projects/public/cometa/backend/selenoid/amvara.png:/usr/share/images/fluxbox/aerokube.png" - ] - }, - "79.0": { - "image": "selenoid/vnc_opera:79.0", - "port": "4444", - "volumes": [ - "/var/projects/public/cometa/backend/selenoid/amvara.png:/usr/share/images/fluxbox/aerokube.png" - ] - }, - "78.0": { - "image": "selenoid/vnc_opera:78.0", - "port": "4444", - "volumes": [ - "/var/projects/public/cometa/backend/selenoid/amvara.png:/usr/share/images/fluxbox/aerokube.png" - ] - }, - "77.0": { - "image": "selenoid/vnc_opera:77.0", - "port": "4444", - "volumes": [ - "/var/projects/public/cometa/backend/selenoid/amvara.png:/usr/share/images/fluxbox/aerokube.png" - ] - }, - "76.0": { - "image": "selenoid/vnc_opera:76.0", - "port": "4444", - "volumes": [ - "/var/projects/public/cometa/backend/selenoid/amvara.png:/usr/share/images/fluxbox/aerokube.png" - ] - }, - "75.0": { - "image": "selenoid/vnc_opera:75.0", - "port": "4444", - "volumes": [ - "/var/projects/public/cometa/backend/selenoid/amvara.png:/usr/share/images/fluxbox/aerokube.png" - ] - }, - "74.0": { - "image": "selenoid/vnc_opera:74.0", - "port": "4444", - "volumes": [ - "/var/projects/public/cometa/backend/selenoid/amvara.png:/usr/share/images/fluxbox/aerokube.png" - ] - }, - "82.0": { - "image": "selenoid/vnc_opera:82.0", - "port": "4444", - "volumes": [ - "/var/projects/public/cometa/backend/selenoid/amvara.png:/usr/share/images/fluxbox/aerokube.png" - ] - } - } - } -} diff --git a/backend/selenoid/deploy_selenoid.sh b/backend/selenoid/deploy_selenoid.sh index 01260c39..d2531cab 100755 --- a/backend/selenoid/deploy_selenoid.sh +++ b/backend/selenoid/deploy_selenoid.sh @@ -1,150 +1,80 @@ #!/bin/bash pushd $(dirname "$0") &> /dev/null -# Compares version with dot notation -function version_compare () { - function sub_ver () { - local len=${#1} - temp=${1%%"."*} && indexOf=`echo ${1%%"."*} | echo ${#temp}` - echo -e "${1:0:indexOf}" - } - function cut_dot () { - local offset=${#1} - local length=${#2} - echo -e "${2:((++offset)):length}" - } - if [ -z "$1" ] || [ -z "$2" ]; then - echo "=" && exit 0 - fi - local v1=`echo -e "${1}" | tr -d '[[:space:]]'` - local v2=`echo -e "${2}" | tr -d '[[:space:]]'` - local v1_sub=`sub_ver $v1` - local v2_sub=`sub_ver $v2` - if (( v1_sub > v2_sub )); then - echo ">" - elif (( v1_sub < v2_sub )); then - echo "<" - else - version_compare `cut_dot $v1_sub $v1` `cut_dot $v2_sub $v2` - fi -} +HELPERS="../../helpers" +# source logger function if not sourced already +test `command -v log_wfr` || source ${HELPERS}/logger.sh + +info "Executing Deploy Selenoid Script" install_jq() { + info "Installing jq" if [ $(command -v apt-get) ]; then + debug "Using apt-get to install jq" sudo apt-get install -y jq elif [ $(command -v zypper) ]; then + debug "Using zypper to install jq" sudo zypper install -y jq else - echo "Unable to find apt-get or zypper command, other commands are not implemented yet." + critical "Unable to find apt-get or zypper command, other commands are not implemented yet." exit 1 fi } -log () { - echo "" - echo "==================================" - echo " $1" - echo "==================================" - echo "" -} +info "*********************** COMETA ***********************" +info "This script automatically retrieves the latest browsers" +info "for Selenoid and recreates/updates the docker." +info "******************************************************" -echo "*********************** AMVARA ***********************" -echo "This script automatically retrieves the latest browsers" -echo "for Selenoid and recreates/updates the docker." -echo "******************************************************" - -# Create browser.json file for Selenoid docker if not exists -if [[ -f "browsers.json" ]]; then - log "Pulling browser images from browsers.json ..." - install_jq - # Update browser versions to latest - browsers=($(cat browsers.json | jq -r 'keys|.[]')) - for browser in "${browsers[@]}" - do - # Get current browser version - version=$(cat browsers.json | jq -r ".$browser.default") - # Get latest version from Docker Hub - if [ "$browser" == "MicrosoftEdge" ]; then - response=$(curl --silent https://hub.docker.com/v2/repositories/browsers/edge/tags?name=.0&ordering=last_updated&page_size=1) - latest_version=$(echo ${response} | jq .results[0].name --raw-output | cut -d- -f1) - else - response=$(curl --silent https://hub.docker.com/v2/repositories/selenoid/$browser/tags?name=.0&ordering=last_updated&page_size=1) - latest_version=$(echo ${response} | jq .results[0].name --raw-output | cut -d- -f1) - fi - greater=$(version_compare $latest_version $version) - # Compare and update version if mismatches, also checks if latest is valid - if [ "$greater" == ">" ] && [ ! -z "$latest_version" ]; then - echo "Browser $browser is outdated, updating to $latest_version..." - tmp=$(mktemp) - # Update default - jq ".$browser.default = \"$latest_version\"" browsers.json > "$tmp" - sudo mv "$tmp" browsers.json - # Update version - jq ".$browser.versions += {\"$latest_version\": .$browser.versions.\"$version\"}" browsers.json > "$tmp" - sudo mv "$tmp" browsers.json - # Update browser image - if [ "$browser" == "MicrosoftEdge" ]; then - jq ".$browser.versions.\"$latest_version\".image = \"browsers/edge:$latest_version\"" browsers.json > "$tmp" - else - jq ".$browser.versions.\"$latest_version\".image = \"selenoid/vnc_$browser:$latest_version\"" browsers.json > "$tmp" - fi - # Move temporal modifed browsers to final file - sudo mv "$tmp" browsers.json - fi - done - cat browsers.json | jq -r '..|.image?|strings' | xargs -I{} docker pull {} - docker pull selenoid/video-recorder:latest-release +# install jq +if [ $(command -v jq) ]; then + info "jq: already installed." else - log "Browsers file not found, creating browsers.json ..." - ./cm_selenoid selenoid configure --vnc --config-dir ./ --last-versions 1 --force -fi - -# Run migrations -runMigrations=true - -if [ "$runMigrations" = true ] ; then - # Migration #1 - Stop cometa_zalenium if running - if [ "$(docker ps -q -f name=cometa_zalenium)" ]; then - log "Migration #1 - Remove zalenium" - docker stop cometa_zalenium - fi + install_jq fi +# run getLatestBrowser.sh script to update browsers file +./getLatestBrowsers.sh $@ +# pull selenoid/video-recorder if not already pulled +docker image ls | grep selenoid/video-recorder | grep -q "latest-release" && info "selenoid/video-recorder: already pulled." || { info "pulling selenoid/video-recorder"; docker pull selenoid/video-recorder:latest-release; } # Replacing Selenoid browser containers background with custom customBg=/data/cometa/config/custom-desktop.png +pwd=`pwd` # Check if custom image has been provided in /data/cometa/config/ if [ -f "$customBg" ]; then bgImage=$customBg else - pwd=`pwd` bgImage="${pwd}/amvara.png" fi -log "Using ${bgImage} as background for Selenium containers" +fluxboxPath=${pwd}/.fluxbox + +debug "Using ${bgImage} as background for Selenium containers" +debug "Using ${fluxboxPath}" -log "Replacing background image volume in Selenoid images ..." +info "Replacing background image volume in Selenoid images ..." # Replace bgPath with used image -sudo sed -i "s||$bgImage|g" browsers.json +sed -i_template "s||$bgImage|g;s||$fluxboxPath|g" browsers.json # make sure downloads folder exists mkdir -p ../behave/downloads -log "Replacing downloads folder in Selenoid Chrome Image ..." -pushd ../behave/downloads +info "Replacing downloads folder in Selenoid Chrome Image ..." +pushd ../behave/downloads &> /dev/null downloadsPath=`pwd` -popd -sudo sed -i "s||$downloadsPath|g" browsers.json +popd &> /dev/null +sed -i_template "s||$downloadsPath|g" browsers.json # Run or update selenoid hub if [ "$(docker ps -q -f name=cometa_selenoid)" ]; then - log "Selenoid docker is running, sending signal to hot update configuration ..." + log_wfr "Selenoid docker is running, sending signal to hot update configuration ..." # Selenoid is running # Send signal to update configuration - docker kill -s HUP cometa_selenoid + docker kill -s HUP cometa_selenoid &> /dev/null && log_res "[done]" || log_res "[failed]" else - log "Selenoid docker was not found or exited, recreating docker ..." + log_wfr "Selenoid docker was not found or exited, recreating docker ..." # Run Selenoid cd .. - docker-compose up -d --force-recreate selenoid + docker-compose up -d --force-recreate selenoid &> /dev/null && log_res "[done]" || log_res "[failed]" fi popd &> /dev/null || true # adding true else script fails with exit code 1 in client01 diff --git a/backend/selenoid/getLatestBrowsers.sh b/backend/selenoid/getLatestBrowsers.sh new file mode 100755 index 00000000..a97e64f4 --- /dev/null +++ b/backend/selenoid/getLatestBrowsers.sh @@ -0,0 +1,252 @@ +#!/bin/bash + +# ################################################################ +# WHAT IS THIS SCRIPT FOR? # +# ################################################################ +# This script will recreate browsers.json file with latest x # +# browser version specified. It will also pull missing images # +# if needed and will also remove unused browser images. # +# ################################################################ +# AUTHOR # +# ################################################################ +# COMETA ROCKS S.L. # +# ################################################################ + +# CHANGELOG VERSION BY COMMENT +# 22-06-17 0.4 RROEBER Adding Volume with for Upload Dummy Files +# 22-04-06 0.3 ASOHAIL Fixed jq issues with jq version 1.5 +# 22-04-05 0.2 ASOHAIL Added change log and added output to stdout. +# 22-04-04 0.1 ASOHAIL Created this script. + +# ################################################################ +# ERROR CODES # +# ################################################################ +# 0 SUCCESS # +# 10 HELP MSG ( UNKNOWN OPTION FOUND ) # +# ################################################################ + +# ################################################################ +# TODO LIST # +# ################################################################ +# 1.- Add log ouput # +# 2.- Show how much space has been saved. # +# ################################################################ + +# Initialize variables with default values +SCRIPT_LOCATION=$(cd "$(dirname "$0")" ; pwd -P) # Current script full path +HELPERS="${SCRIPT_LOCATION}/../../helpers" +TOTAL_BROWSER_VERSIONS=${COMETA_TOTAL_BROWSER_VERSION:-3} # Total amount of version per browser that will be pulled. +BROWSERS_CONTENT="{}" # Save the final output that will be saved in browsers.json. +IMAGES_TO_REMOVE=() # List of docker images that needs to be deleted at the end. +IMAGES_TO_PULL=() # List of docker images that needs to be pulled at the end. +# Browsers that we want to pull. +BROWSERS_INFORMATION=`cat<<-EOF +{ + "browsers/edge": { + "browser_key": "MicrosoftEdge", + "options": { + "path": "/" + } + }, + "selenoid/vnc_firefox": { + "browser_key": "firefox", + "options": { + "path": "/wd/hub" + } + }, + "selenoid/vnc_chrome": { + "browser_key": "chrome", + "options": {} + }, + "selenoid/vnc_opera": { + "browser_key": "opera", + "options": {} + } +} +EOF +` +BROWSERS=`echo ${BROWSERS_INFORMATION} | jq -r 'keys[]'` # Get all the browser keys. +# Basic options needed for each browser version inside browser.json file +# Variables in bgPath and downloadsPath are replace with deploy_selenoid.sh +BASE_OPTIONS=`cat<<-EOF +{ + "image": "", + "port": "4444", + "volumes": [ + ":/usr/share/images/fluxbox/aerokube.png", + "/init:/home/selenium/.fluxbox/init", + "/aerokube:/home/selenium/.fluxbox/aerokube", + "/../uploads:/home/selenium/uploads" + ] +} +EOF +` + +# SOURCE LOGGER FUNCTIONS +test `command -v log_wfr` || source ${HELPERS}/logger.sh + +# ################################################################ +# FUNCTIONS # +# ################################################################ + +# ################################################################ +# VERSION INFORMATION FUNCTION # +# Returns version information. # +# ################################################################ +function show_version_information() { + # Get script version from changelog + DATE=`cat $0 | grep -A1 CHANGELOG | head -n2 | tail -n1 | sed 's/# //g' | cut -f1` + VERSION=`cat $0 | grep -A1 CHANGELOG | head -n2 | tail -n1 | sed 's/# //g' | cut -f2` + AUTHOR=`cat $0 | grep -A1 CHANGELOG | head -n2 | tail -n1 | sed 's/# //g' | cut -f4` + COMMIT=`cat $0 | grep -A1 CHANGELOG | head -n2 | tail -n1 | sed 's/# //g' | cut -f6` + + echo -ne "Here is version information about ${0}. + +SCRIPT VERSION: v${VERSION} +LAST MODIFICATION DATE: ${DATE} +LAST MODIFIED BY: ${AUTHOR} +LAST MODIFICATION MESSAGE: ${COMMIT} +" + exit 0; +} + +# ################################################################ +# HELP FUNCTION # +# @param1 - String that will be displayed to user on start # +# Returns usage information. # +# ################################################################ +function help() { + echo -ne "${1} + +Usage: +${0} [--dry-run|-h|-n|-v] + +OPTIONS: + --dry-run - Do not update browsers.json show changes to stdout. Will not pull or remove docker images. + -h - Shows this help message. + -n - Number of version to use for each browser. Defaults to 10. + -v - Shows version information about the script. + +EXAMPLES: + ${0} -n 5 + ${0} --dry-run -n 1 +"; + exit 10; # showing usage error code +} + +# ################################################################ +# Main function that will populate all the necessary variables # +# to generate browsers.json file, pull, remove images # +# ################################################################ +function main() { + # loop over browser + for BROWSER in ${BROWSERS} + do + log_wfr "Working on ${BROWSER}" + # get browser key + BROWSER_KEY=`echo ${BROWSERS_INFORMATION} | jq -r .[\"${BROWSER}\"].browser_key` + # make a request to docker's api and get latest TOTAL_BROWSER_VERSIONS browser versions + REQUEST_URL="https://hub.docker.com/v2/repositories/${BROWSER}/tags?name=.0&ordering=last_updated&page_size=${TOTAL_BROWSER_VERSIONS}" + RESPONSE=`curl --silent ${REQUEST_URL}` + # get versions in array + LATEST_VERSIONS=`echo ${RESPONSE} | jq -r '.results|.[]|.name'` + # get default version which will always be the first one + DEFAULT_VERSIONS=`echo ${RESPONSE} | jq -r '.results|.[0]|.name'` + # generate the browser verisons + VERSIONS_JSON="{\"${BROWSER_KEY}\": {\"default\": \"${DEFAULT_VERSIONS}\", \"versions\": {}}}" + # loop over all version + for VERSION in ${LATEST_VERSIONS} + do + # check if this version need image pulling + IMAGES_TO_PULL+=`docker image ls | grep ${BROWSER} | grep -q "${VERSION}" || echo "${BROWSER}:${VERSION} "` + # generate a version data + VERSION_JSON=`echo ${BASE_OPTIONS} | jq --argjson browserOptions "$(echo ${BROWSERS_INFORMATION} | jq .[\"${BROWSER}\"].options)" '. + $browserOptions'` + # replace image in VERSION_JSON + VERSION_JSON=`echo ${VERSION_JSON} | sed "s##${BROWSER}:${VERSION}#"` + VERSION_JSON="{\"${VERSION}\": ${VERSION_JSON}}" + # append version to versions + VERSIONS_JSON=`echo ${VERSIONS_JSON} | jq --argjson version "$(echo ${VERSION_JSON} | jq .)" '.[].versions |= . + $version'` + done + # append browser content to browsers + BROWSERS_CONTENT=`echo ${BROWSERS_CONTENT} | jq --argjson browser "$(echo ${VERSIONS_JSON} | jq .)" '. |= . + $browser'` + # get image that we want to remove since they won't be used anymore + IMAGES_TO_REMOVE+=`docker image ls | grep ${BROWSER} | grep -Ev "${LATEST_VERSIONS// /\|}" | sed "s/ \{1,\}/ /g" | cut -d' ' -f3; echo -ne " "` + log_res "[done]" + done + + # add any other dangling browser image to images to remove + IMAGES_TO_REMOVE+=`docker image ls | grep -E "(selenoid|browsers)/" | grep -Ev "${BROWSERS// /\|}|selenoid/video-recorder" | sed "s/ \{1,\}/ /g" | cut -d' ' -f3 | xargs` + # remove last space from images to remove just in case there is any + IMAGES_TO_REMOVE=`echo ${IMAGES_TO_REMOVE} | sed "s/ $//g"` + + if [[ "${DRYRUN:-FALSE}" == "TRUE" ]] + then + info "browsers.json:" + echo ${BROWSERS_CONTENT} | jq . + info "pull images: ${IMAGES_TO_PULL:-No new images to pull.}" + info "remove images: ${IMAGES_TO_REMOVE:-No images to remove.}" + else + # pull all the missing images. + if [[ "${IMAGES_TO_PULL}" ]] + then + for IMAGE in ${IMAGES_TO_PULL} + do + log_wfr "pulling: ${IMAGE}" + docker pull ${IMAGE} &> /dev/null + exit_code=$? + if [[ ${exit_code} == 0 ]] + then + log_res "[done]" + else + log_res "[failed]" + info "Failed to get browser image from Docker Registry, will not continue... please check" + exit 5 + fi + done + fi + + # remove any old images. + if [[ "${IMAGES_TO_REMOVE}" ]] + then + log_wfr "removing unused images: ${IMAGES_TO_REMOVE}" + docker image rm ${IMAGES_TO_REMOVE} &> /dev/null && log_res "[done]" || log_res "[failed]" + fi + + # set the browser.json content to BROWSERS_CONTENT + echo ${BROWSERS_CONTENT} | jq . > `dirname "$0"`/browsers.json + fi +} + +# Listen to incomming arguments + +## Loop over all the arguments +while [[ $# -gt 0 ]]; do + ARGUMENT="$1" + case ${ARGUMENT} in + -n) + # set TOTAL_BROWSER_VERSION variable + info "Number of browser versions change to ${2}" + TOTAL_BROWSER_VERSIONS="${2}" + shift + shift + ;; + --dry-run) + # set DRYRUN variable to true + info "Dry run set to True." + DRYRUN="TRUE" + shift + ;; + -v|--version|version) + show_version_information + ;; + -h|--help|-?|help) + help "Here is how to use this script." + ;; + *) + help "Unknown option (${ARGUMENT}) found." + ;; + esac +done + +# run main function +main diff --git a/backend/src/backend/admin.py b/backend/src/backend/admin.py index c6737045..288ad7b4 100755 --- a/backend/src/backend/admin.py +++ b/backend/src/backend/admin.py @@ -90,6 +90,13 @@ def get_queryset(self, request): # Return all feature runs, including those marked as removed return Feature_Runs.all_objects +class AdminDataDriven_run(admin.ModelAdmin): + model = DataDriven_Runs + + def get_queryset(self, request): + # Return all feature runs, including those marked as removed + return DataDriven_Runs.all_objects + class AdminFeature_result(admin.ModelAdmin): model = Feature_result search_fields = ['feature_name', 'app_name', 'environment_name', 'department_name'] @@ -124,14 +131,17 @@ class AdminFolder(admin.ModelAdmin): class AdminFolder_Feature(admin.ModelAdmin): model = Folder_Feature - search_fields = ['folder__name', 'feature__name'] + search_fields = ['folder__name', 'feature__feature_name'] + list_filter = ( + ('folder__department', admin.RelatedOnlyFieldListFilter), + ) list_display = ('folder', 'feature') class AdminPermissions(admin.ModelAdmin): readonly_fields=('view_admin_panel',) fieldsets = ( ('Basic', { - 'fields': ('permission_name',), + 'fields': ('permission_name', 'permission_power'), 'description': 'If any of the permissions are set to False, the user will be able to modfiy, delete, etc their objects like features, etc.' }), ('OIDC Accounts', { @@ -174,11 +184,16 @@ class AdminPermissions(admin.ModelAdmin): ('remove_feature_runs'), ) }), - ('FrontEnd', { + ('FrontEnd Admin', { 'fields': ( ('view_admin_panel', 'view_departments_panel', 'view_applications_panel', 'view_browsers_panel', 'view_environments_panel', 'view_features_panel', 'view_accounts_panel'), ) }), + ('FrontEnd Department Admin Panel Options', { + 'fields': ( + ('show_all_departments', 'show_department_users'), + ) + }), ('Environment Variables', { 'fields': ( ('create_variable', 'edit_variable', 'delete_variable'), @@ -186,10 +201,18 @@ class AdminPermissions(admin.ModelAdmin): }) ) -class AdminEnvironmentVariables(admin.ModelAdmin): - model = EnvironmentVariables +@admin.register(Variable) +class AdminVariables(admin.ModelAdmin): search_fields = ['variable_name', 'variable_value'] - list_display = ('department', 'environment', 'variable_name', 'variable_value') + list_display = ('variable_name', 'get_truncated_variable_value', 'department', 'environment', 'feature', 'created_by', 'updated_by') + list_filter = ('based', 'department', 'environment', 'created_on', 'updated_on') + list_display_links = ('variable_name',) + readonly_fields = ('in_use', 'created_on', 'updated_on') + + def get_truncated_variable_value(self, obj): + if len(obj.variable_value) > 50: + return f"{obj.variable_value[:50]} ..." + return obj.variable_value class AdminCloud(admin.ModelAdmin): model = Cloud @@ -247,6 +270,22 @@ class AdminUsageInvoice(admin.ModelAdmin): list_display = ('user', 'stripe_invoice_id', 'period_start', 'period_end', 'hours', 'cloud', 'status', 'modified_on', ) list_filter = ('user', 'cloud', 'status') +class AdminFile(admin.ModelAdmin): + model = File + search_fields = ['id', 'name', 'path', 'mime', 'department__department_name', 'uploaded_by__name'] + list_display = ('id', 'name', 'path', 'mime', 'uploaded_by', 'department', 'is_removed', 'fileExistsOnFS') + list_filter = ('department', 'created_on', 'is_removed') + + def get_queryset(self, request): + return self.model.all_objects.get_queryset() + + @admin.display(boolean=True) + def fileExistsOnFS(self, obj): + return os.path.exists(obj.path) + + fileExistsOnFS.short_description = "Does file exist on FS?" + + admin.site.register(OIDCAccount, AdminOIDCAccount) admin.site.register(Account_role, AdminAccount_role) admin.site.register(Step, AdminStep) @@ -256,14 +295,15 @@ class AdminUsageInvoice(admin.ModelAdmin): admin.site.register(Folder, AdminFolder) admin.site.register(Folder_Feature, AdminFolder_Feature) admin.site.register(Feature_Runs, AdminFeature_run) +admin.site.register(DataDriven_Runs, AdminDataDriven_run) admin.site.register(MiamiContact) admin.site.register(Application) admin.site.register(Environment) admin.site.register(Department) admin.site.register(Browser) +admin.site.register(File, AdminFile) admin.site.register(Action) admin.site.register(Permissions, AdminPermissions) -admin.site.register(EnvironmentVariables, AdminEnvironmentVariables) admin.site.register(Cloud, AdminCloud) admin.site.register(AuthenticationProvider) admin.site.register(Schedule) @@ -274,6 +314,6 @@ class AdminUsageInvoice(admin.ModelAdmin): admin.site.register(PaymentRequest, AdminPaymentRequest) admin.site.register(StripeWebhook, AdminStripeWebhooks) admin.site.register(UserSubscription, AdminUserSubscription) -admin.site.register(UsageInvoice, AdminUsageInvoice) +admin.site.register(UsageInvoice, AdminUsageInvoice), # Register your models here. diff --git a/backend/src/backend/common.py b/backend/src/backend/common.py index 0638b175..de89d179 100644 --- a/backend/src/backend/common.py +++ b/backend/src/backend/common.py @@ -28,4 +28,6 @@ LOGGER_FORMAT = '\33[96m[%(asctime)s][%(levelname)s][%(filename)s:%(lineno)d](%(funcName)s) -\33[0m %(message)s' LOGGER_DATE_FORMAT = '%Y-%m-%d %H:%M:%S' # Folders MAX drill down level -MAX_FOLDER_HIERARCHY=20 \ No newline at end of file +MAX_FOLDER_HIERARCHY=20 +# Uploads folder +UPLOADS_FOLDER='/code/behave/uploads' \ No newline at end of file diff --git a/backend/src/backend/generatePDF.py b/backend/src/backend/generatePDF.py index f2b54965..9b409812 100644 --- a/backend/src/backend/generatePDF.py +++ b/backend/src/backend/generatePDF.py @@ -14,28 +14,25 @@ from django.core.validators import validate_email from rest_framework.response import Response from django.conf import settings -import base64 +import base64, os from django.http import HttpResponse from backend.views import render_to_pdf, bytesToMegaBytes from backend.common import * +from backend.utility.functions import getLogger from django.forms.models import model_to_dict # just to import secrets import sys -from os.path import isfile +from os.path import isfile, exists sys.path.append("/code") import secret_variables # Python basics -import socket import logging import logging.handlers import datetime -import smtplib -import re # logger information -logger = logging.getLogger(__name__) -logger.setLevel(BEHAVE_DEBUG_LEVEL) +logger = getLogger() DOMAIN = getattr(secret_variables, 'COMETA_DOMAIN', '') SCREENSHOT_PREFIX = getattr(secret_variables, 'COMETA_SCREENSHOT_PREFIX', '') @@ -69,6 +66,9 @@ def get(self, request): # logger assignment self.my_logger = logger + + # save the pdf content to a variable for easy access later on + self.PDFContent = "" # Get the feature result id via get. We do it in here plain self.feature_result_id = self.request.GET.get('feature_result_id', None) @@ -79,62 +79,87 @@ def get(self, request): # Filter objects to get that result id self.feature = self.GetFeature() + # save the pdf download path + self.downloadPath = "/code/behave/pdf" + self.downloadFullPath = "%s/%s-%s.pdf" % (self.downloadPath, str(self.feature.feature_name), str(self.feature_result_id)) + + # check if download path exists + if not exists(self.downloadPath): + self.my_logger.debug("Download path does not exists .... creating one...") + os.makedirs(self.downloadPath) + + # check if lock file exists if so that mean the pdf file is being generate + if exists(self.downloadFullPath + ".lock"): + return HttpResponse("PDF File for Feature: %s and Feature Result ID: %s is still being generated, please try again later." % (str(self.feature.feature_name), str(self.feature_result_id))) + # generate pdf url self.pdfURL = "https://%s/backend/pdf/?feature_result_id=%s" % (DOMAIN, self.feature_result_id) - # Get the feature id on feature_id, because it comes like this: & Filter to get the email settings of the feature - feature_id = re.search(r'\d+', str(self.feature.feature_id)).group() - feature_template = Feature.objects.filter(feature_id=feature_id)[0] - # Assigning class variables to use in all the functions. - self.feature_template = feature_template - self.feature_id = feature_id + self.feature_template = self.feature.feature_id + self.feature_id = self.feature.feature_id_id # If the request GET parameter "download" is present, download the PDF instead of emailing it to it's recipient download = self.request.GET.get('download', None) + # check if file already exists + if not exists(self.downloadFullPath): + self.my_logger.debug("Creating a lock file for %s" % self.downloadFullPath) + # create a lock file to check if pdf is still being generated or not + self.touch(self.downloadFullPath + ".lock") + + # Get the steps from the executed feature. + steps = Step_result.objects.filter(feature_result_id=self.feature_result_id).order_by("step_result_id") + self.steps = steps + + # Get the feature result screenshots. + self.screenshots_array = self.GetStepsAndScreenshots() + + # Calculate percentatge of OK steps and NOK steps + try: + self.percentok = int((self.feature.ok * 100) / self.feature.total) + except ZeroDivisionError: + self.percentok = 0 + try: + self.percentnok = int(((self.feature.fails + self.feature.skipped) * 100) / self.feature.total) + except ZeroDivisionError: + self.percentnok = 0 + self.totalnok = int(self.feature.fails) + int(self.feature.skipped) + + # Build the HTML and then render it into a PDF. + self.pdf = self.BuildHtmlAndRenderPdf() + + # save the pdf to file + with open(self.downloadFullPath, 'wb') as f: + f.write(self.pdf.content) + + # remove lock file once finished + if exists(self.downloadFullPath + ".lock"): + self.my_logger.debug("Removing lock file for %s" % self.downloadFullPath) + os.remove(self.downloadFullPath + ".lock") + # Validate the emails. If emailsend is set to false or all emails are bad, we get out of execution. # We don't raise exception here to not cause issues when debugging, as having email set as false is not an error. # 2020-07-28 ABP Mark this "if" as an exception to download parameter, email send is not required to download directly the PDF file if (download != 'true' and download != 'false') and self.ValidateEmails() == False: return HttpResponse("400 - Invalid email address(es).") - # Get the steps from the executed feature. - steps = Step_result.objects.filter(feature_result_id=self.feature_result_id).order_by("step_result_id") - self.steps = steps - - # Get feature result by ID - feature_result = Feature_result.objects.get(feature_result_id=self.feature_result_id) - - # Get the feature result screenshots. - self.screenshots_array = self.GetStepsAndScreenshots() - - # Calculate percentatge of OK steps and NOK steps - try: - self.percentok = int((self.feature.ok * 100) / self.feature.total) - except ZeroDivisionError: - self.percentok = 0 - try: - self.percentnok = int(((self.feature.fails + self.feature.skipped) * 100) / self.feature.total) - except ZeroDivisionError: - self.percentnok = 0 - self.totalnok = int(self.feature.fails) + int(self.feature.skipped) - - # Build the HTML and then render it into a PDF. - self.pdf = self.BuildHtmlAndRenderPdf() + # read file content and save it to PDFContent + with open(self.downloadFullPath, 'rb') as f: + PDFContent = f.read() # download param should contain something, otherwise it is considered Falsy, ex: ?download=true if download == 'true': # Download the PDF - response = HttpResponse(self.pdf.content, content_type='application/pdf') + response = HttpResponse(PDFContent, content_type='application/pdf') response['Content-Disposition'] = 'attachment; filename="%s-%s.pdf"' % (str(self.feature.feature_name), str(self.feature.result_date)) - response['Content-Length'] = len(self.pdf.content) + response['Content-Length'] = len(PDFContent) return response elif download == 'false': # Send response with PDF preview - response = HttpResponse(self.pdf.content, content_type='application/pdf') + response = HttpResponse(PDFContent, content_type='application/pdf') response['Content-Disposition'] = 'inline; filename="%s-%s.pdf"' % (str(self.feature.feature_name), str(self.feature.result_date)) - response['Content-Length'] = len(self.pdf.content) + response['Content-Length'] = len(PDFContent) return response else: # Build the subject and the emailbody. @@ -160,15 +185,23 @@ def StartLogger(self): return my_logger + """ + Small function for touch functionality + """ + def touch(self, fname): + try: + os.utime(fname, None) + except OSError: + open(fname, 'a').close() + """ Get the current feature result and check that it actually exists. """ def GetFeature(self): - feature = Feature_result.objects.filter(feature_result_id=self.feature_result_id) - # If the feature is not found then we return error, if not get item - if(len(feature) > 0): - feature = feature[0] - else: + try: + feature = Feature_result.objects.get(feature_result_id=self.feature_result_id) + except Feature_result.DoesNotExist: + # If the feature is not found then we return error, if not get item self.my_logger.critical('[GeneratePDF] Error while retrieving feature info: Feature not found.') raise ValueError("503 Feature not found") @@ -273,6 +306,7 @@ def GetStepsAndScreenshots(self): screenshots_array[step.step_result_id].append({'photo2': None}) screenshots_array[step.step_result_id].append({'photo3': None}) screenshots_array[step.step_result_id].append({'count' : step.count}) + screenshots_array[step.step_result_id].append({'error': step.error}) # Change the images from None to the base64 string, only if the image exists. We use i+4 because screenshots are located in indexs 4 5 and 6 for i in range(0, len(listphotos)): index = i+4 diff --git a/backend/src/backend/middlewares/authentication.py b/backend/src/backend/middlewares/authentication.py index 6cd2d9e1..4f63c6f5 100644 --- a/backend/src/backend/middlewares/authentication.py +++ b/backend/src/backend/middlewares/authentication.py @@ -36,6 +36,8 @@ def __call__(self, request): try: # get the host from request HTTP_HOST = request.META.get('HTTP_HOST', DOMAIN) + if HTTP_HOST == 'cometa.local': + raise Exception("User session none existent from behave.") if not re.match(r'^(cometa.*\.amvara\..*)|(.*\.cometa\.rocks)$', HTTP_HOST): HTTP_HOST = 'cometa_front' # make a request to cometa_front to get info about the logged in user @@ -46,7 +48,7 @@ def __call__(self, request): }) # save user_info to self self.user_info = response.json().get('userinfo', {}) - except Exception as error: # if executed from crontab + except Exception as error: # if executed from crontab or sent by behave self.user_info = {} # create a session variable @@ -106,6 +108,7 @@ def createSession(self, request): # create the object user inside request sessio HTTP_COMETA_ORIGIN = request.META.get("HTTP_COMETA_ORIGIN", '') HTTP_COMETA_USER = request.META.get("HTTP_COMETA_USER", None) # Used to know which user scheduled a feature SERVER_PORT = request.META.get('SERVER_PORT', '443') + HTTP_X_FORWARDED_PROTO = request.META.get('HTTP_X_FORWARDED_PROTO', 'http') # get the superuser permissions superuser = Permissions.objects.filter(permission_name="SUPERUSER")[0] @@ -131,12 +134,22 @@ def createSession(self, request): # create the object user inside request sessio return True # don't save the user in case accessing from port 8000 - if HTTP_HOST == "cometa.local" or SERVER_PORT == '8000': + if HTTP_HOST == "cometa.local" or (HTTP_X_FORWARDED_PROTO == "http" and SERVER_PORT == '8000'): # create a dummy user with admin rights user = OIDCAccount(name=HTTP_HOST, email=REMOTE_ADDR, user_permissions=superuser) # set the session user to dummy serialized user request.session['user'] = OIDCAccountLoginSerializer(user, many=False).data return True + + return JsonResponse({ + "success": False, + "error": """ + Unable to determin user information. + This could mean that oAuth provider did not return user information. + + Try again later or please contact us @ %s + """ % secret_variables.COMETA_FEEDBACK_MAIL + }, status=200) # get the user from the OIDCAccounts model users = OIDCAccount.objects.filter(email=REMOTE_USER) @@ -188,9 +201,19 @@ def register(self, request): # create account if missing # check if fullName or email are missing in the request if fullName == None or email == None or fullName == "(null)" or email == "(null)": return False - + # create the account user = OIDCAccount(name=fullName, email=email) + # is this first ever user + first_user = OIDCAccount.objects.all().count() == 0 + if first_user: + try: + # find the superuser role and assign it to the first ever user. + superuser = Permissions.objects.get(permission_name="SUPERUSER") + user.user_permissions = superuser + except Exception as err: + logger.error("Seems like SUPERUSER role isn't created yet....") + logger.exception(err) user.save() # if no invite code was set then add user to default department diff --git a/backend/src/backend/mixins.py b/backend/src/backend/mixins.py index 0e44389f..1eef1db9 100644 --- a/backend/src/backend/mixins.py +++ b/backend/src/backend/mixins.py @@ -35,12 +35,15 @@ def fast_loader(cls, queryset:QuerySet): ) return queryset -class EnvironmentVariablesMixin(): +class VariablesMixin(): @classmethod def fast_loader(cls, queryset:QuerySet): queryset = queryset.select_related( 'department', - 'environment' + 'environment', + 'feature', + 'updated_by', + 'created_by' ) return queryset diff --git a/backend/src/backend/models.py b/backend/src/backend/models.py index d575d5f5..8417865b 100755 --- a/backend/src/backend/models.py +++ b/backend/src/backend/models.py @@ -10,9 +10,18 @@ from django.conf import settings from django.db.models.query import ModelIterable, QuerySet import warnings -from backend.utility.functions import getBrowserKey, getStepResultScreenshotsPath, get_model +from backend.utility.functions import getBrowserKey, getStepResultScreenshotsPath, get_model, getLogger import shutil from backend.common import * +from django.db.models.signals import post_delete, pre_delete +from django.dispatch import receiver + +# GLOBAL VARIABLES + +# check if we are inside a loop +insideLoop = False +# get logger +logger = getLogger() # Step Keywords step_keywords = ( @@ -22,6 +31,23 @@ ('and', 'and',), ) +# File Status +file_status = ( + ('Unknown', 'Unknown',), + ('Processing', 'Processing',), + ('Scanning', 'Scanning',), + ('Encrypting', 'Encrypting',), + ('Done', 'Done',), + ('Error', 'Error',), +) + +# Variable Base +variable_base = ( + ('department', 'Department',), + ('environment', 'Environment',), + ('feature', 'Feature',), +) + # utility functions @@ -97,7 +123,7 @@ def backup_feature_info(feature): else: print('backup_feature_info: Feature file %s not found.' % orig_file) -def recursiveSubSteps(steps, feature_trace): +def recursiveSubSteps(steps, feature_trace, parent_department_id=None): updatedSteps = steps.copy() index = 0 for step in steps: @@ -106,7 +132,7 @@ def recursiveSubSteps(steps, feature_trace): if subFeatureExecution: featureNameOrId = subFeatureExecution.group(1) # get subfeature - subFeature = Feature.objects.filter(feature_id=featureNameOrId) if featureNameOrId.isnumeric() else Feature.objects.filter(feature_name=featureNameOrId) + subFeature = Feature.objects.filter(feature_id=featureNameOrId, department_id=parent_department_id) if featureNameOrId.isnumeric() else Feature.objects.filter(feature_name=featureNameOrId, department_id=parent_department_id) if subFeature.exists(): subFeature = subFeature[0] # check if we would get caught in infinite loop @@ -127,7 +153,7 @@ def recursiveSubSteps(steps, feature_trace): for idx, val in enumerate(subFeatureSteps): subFeatureSteps[idx]['continue_on_failure'] = True # check if substeps contain other subfeatures - subSteps = recursiveSubSteps(subFeatureSteps, feature_trace) + subSteps = recursiveSubSteps(subFeatureSteps, feature_trace, subFeature.department_id) # check if subSteps returned False if isinstance(subSteps, bool) and not subSteps: return False @@ -152,6 +178,45 @@ def recursiveSubSteps(steps, feature_trace): index += updateIndex return updatedSteps +# adds step to the featureFile +def add_step_to_feature_file(step, featureFile): + global insideLoop + + startingIndent = "\n\t\t" + if insideLoop and not re.search(r'^.*Loop "(.*)" times starting at "(.*)" and do', step['step_content']): + startingIndent = "\n\t\t\t" + + if not insideLoop and re.search(r'^.*End Loop', step['step_content']): + featureFile.write('\n\t\t\t"""') + + # before saving to the file check if step is javascript function if so save the content as step description + if "Javascript" in step['step_content']: + write_multiline_javascript_step(featureFile, step) + elif "Send keys" in step['step_content']: + write_multiline_send_keys_step(featureFile, step) + else: + featureFile.write('%s%s %s' % (startingIndent, step['step_keyword'], step['step_content'].replace('\\xa0', ' ').replace('\n', ''))) + + if insideLoop and re.search(r'^.*Loop "(.*)" times starting at "(.*)" and do', step['step_content']): + featureFile.write('\n\t\t\t"""') + +def checkLoop(step): + global insideLoop + + # check if step is a loop if so set loop value to true + loop = re.search(r'^.*Loop "(.*)" times starting at "(.*)" and do', step['step_content']) + if loop and insideLoop: + # throw error + return {"success": False, "error": "Multi level loop are not allowed."} + if loop: + insideLoop = True + # check if step is end loop + endLoop = re.search(r'^.*End Loop', step['step_content']) + if endLoop: + insideLoop = False + + return {"success": True, "insideLoop": insideLoop} + # create_feature_file # Creates the .feature file @@ -159,68 +224,82 @@ def recursiveSubSteps(steps, feature_trace): # @param steps: Array - Array of the steps definition # @param feature_id: Feature - Info of the feature def create_feature_file(feature, steps, featureFileName): - featureFile = open(featureFileName+'.feature', 'w+') - featureFile.write('Feature: '+feature.feature_name+'\n\n') - - # save the steps to save to database before removing old steps - stepsToAdd = [] - - featureFile.write('\tScenario: First') - for step in steps: - # check if for some reason substeps are sent us from front and ignore them - if "step_type" in step and step['step_type'] == "substep": - continue - # remove the step type before adding it to the stepsToAdd to avoid old feature to show wrong steps - step['step_type'] = None - # add current step to the list to be added to the database - stepsToAdd.append(step) - # check if step is set to enabled - if step['enabled'] == True: - # remove belongs to from step - step.pop('belongs_to', None) - # check if current feature is a sub feature execution - subFeature = re.search(r'^.*Run feature with (?:name|id) "(.*)"', step['step_content']) - if subFeature: - try: - # get recursive steps from the sub feature - subSteps = recursiveSubSteps([step], [feature.feature_id]) - except Exception as error: - return {"success": False, "error": str(error)} - # otherwise loop over substeps - for subStep in subSteps: - # check if substep is enabled - if subStep['enabled']: - subStep['step_type'] = "substep" - # add the substep found in substep - stepsToAdd.append(subStep) - # save to the file - # before saving to the file check if step is javascript function if so save the content as step description - if "Javascript" in subStep['step_content']: - write_multiline_javascript_step(featureFile, subStep) - elif "Send keys" in subStep['step_content']: - write_multiline_send_keys_step(featureFile, subStep) - else: - featureFile.write('\n\t\t%s %s' % (subStep['step_keyword'], subStep['step_content'].replace('\\xa0', ' '))) - else: - # if enabled and not a sub feature execution add to the file - # before saving to the file check if step is javascript function if so save the content as step description - if "Javascript" in step['step_content']: - write_multiline_javascript_step(featureFile, step) - elif "Send keys" in step['step_content']: - write_multiline_send_keys_step(featureFile, step) - else: - featureFile.write('\n\t\t%s %s' % (step['step_keyword'], step['step_content'].replace('\\xa0', ' '))) - featureFile.write('\n') - # close the file handle - featureFile.close() + with open(featureFileName+'.feature', 'w+') as featureFile: + featureFile.write('Feature: '+feature.feature_name+'\n\n') + + # save the steps to save to database before removing old steps + stepsToAdd = [] + + featureFile.write('\tScenario: First') + for step in steps: + # check if for some reason substeps are sent us from front and ignore them + if "step_type" in step and step['step_type'] == "substep": + continue + # remove the step type before adding it to the stepsToAdd to avoid old feature to show wrong steps + step['step_type'] = None + # add current step to the list to be added to the database + stepsToAdd.append(step) + # check if step is set to enabled + if step['enabled'] == True: + # remove belongs to from step + step.pop('belongs_to', None) + # check if current feature is a sub feature execution + subFeature = re.search(r'^.*Run feature with (?:name|id) "(.*)"', step['step_content']) + # check if its a loop related step + result = checkLoop(step) + if not result['success']: + return result + if subFeature: + try: + # get recursive steps from the sub feature + subSteps = recursiveSubSteps([step], [feature.feature_id], feature.department_id) + except Exception as error: + return {"success": False, "error": str(error)} + # otherwise loop over substeps + for subStep in subSteps: + # check if substep is enabled + if subStep['enabled']: + # check if its a loop related step + subResult = checkLoop(subStep) + if not subResult['success']: + return subResult + subStep['step_type'] = "substep" + # add the substep found in substep + stepsToAdd.append(subStep) + # save to the file + add_step_to_feature_file(subStep, featureFile) + else: + # if enabled and not a sub feature execution add to the file + add_step_to_feature_file(step, featureFile) + featureFile.write('\n') # delete all the steps from the database Step.objects.filter(feature_id=feature.feature_id).delete() + # gather all the variables found during the save steps + variables_used = [] # save all the steps found in stepsToAdd to the database for step in stepsToAdd: if step.get("step_type", None) == None: step['step_type'] = "subfeature" if re.search(r'^.*Run feature with (?:name|id) "(.*)"', step['step_content']) else "normal" + # check if step contains a variable + regexPattern = r'\$(?P.+?\b)|variable "(?P.+?\b)|save .+?to "(?P.+?\b)' + matches = re.findall(regexPattern, step['step_content'].replace('\\xa0', ' ')) + if matches: + for match in matches: + # get the variable var_name + variable_name = match[0] or match[1] or match[2] + if variable_name: + variables_used.append(variable_name) + + # change sleep step timeout + sleep_match = re.findall(r'I (?:can )?sleep "(.*?)".*', step['step_content'].replace('\\xa0', ' ')) + if sleep_match: + try: + step['timeout'] = int(sleep_match[0]) + 5 + except ValueError: + # default timeout will be set later on + pass Step.objects.create( feature_id = feature.feature_id, step_keyword = step['step_keyword'], @@ -233,24 +312,44 @@ def create_feature_file(feature, steps, featureFileName): belongs_to = step.get('belongs_to', feature.feature_id), timeout = step.get('timeout', 60) ) + + # update all the variables + # get all the variable with this name and from same department as the current feature + vars = Variable.objects.filter(department_id=feature.department_id, variable_name__in=variables_used) + # reset variables used in the feature + feature.variable_in_use.set(vars) # return success true return {"success": True} def write_multiline_javascript_step(featureFile, step): + global insideLoop + + startingIndent = "\t\t" + quotes = '"""' + if insideLoop: + startingIndent = "\t\t\t" + quotes = "'''" # pattern to get all js code inside "" js_function_pattern = re.search(r'Run Javascript function "(.*)"', step['step_content'], re.MULTILINE|re.DOTALL) # get the code from the pattern js_function = js_function_pattern.group(1) - featureFile.write('\n\t\t%s %s\n' % (step['step_keyword'], u'Run Javascript function "// function is set in step description!!"'.replace('\\xa0', ' '))) - featureFile.write('\t\t\t"""\n\t\t\t%s\n\t\t\t"""' % js_function.replace("\n", "\n\t\t\t")) + featureFile.write('\n%s%s %s\n' % (startingIndent, step['step_keyword'], u'Run Javascript function "// function is set in step description!!"'.replace('\\xa0', ' '))) + featureFile.write('%s\t%s\n%s\t%s\n%s\t%s' % (startingIndent, quotes, startingIndent, js_function.replace("\n", f"\n{startingIndent}\t"), startingIndent, quotes)) def write_multiline_send_keys_step(featureFile, step): + global insideLoop + + startingIndent = "\t\t" + quotes = '"""' + if insideLoop: + startingIndent = "\t\t\t" + quotes = "'''" # pattern to get all js code inside "" js_function_pattern = re.search(r'Send keys "(.*)"', step['step_content'], re.MULTILINE|re.DOTALL) # get the code from the pattern js_function = js_function_pattern.group(1) - featureFile.write('\n\t\t%s %s\n' % (step['step_keyword'], u'Send keys "// text is set in step description!!"'.replace('\\xa0', ' '))) - featureFile.write('\t\t\t"""\n\t\t\t%s\n\t\t\t"""' % js_function.replace("\n", "\n\t\t\t")) + featureFile.write('\n%s%s %s\n' % (startingIndent, step['step_keyword'], u'Send keys "// text is set in step description!!"'.replace('\\xa0', ' '))) + featureFile.write('%s\t%s\n%s\t%s\n%s\t%s' % (startingIndent, quotes, startingIndent, js_function.replace("\n", "\n\t\t\t"), startingIndent, quotes)) # create_json_file # Creates the .json file @@ -343,6 +442,10 @@ class Meta: available_objects = SoftDeletableManager() all_objects = models.Manager() + def restore(self, using=None): + self.is_removed = False + self.save(using=using) + def delete(self, using=None, soft=True, *args, **kwargs): """ Soft delete object (set its ``is_removed`` field to True). @@ -366,6 +469,7 @@ def delete(self, using=None, soft=True, *args, **kwargs): class Permissions(models.Model): permission_id = models.AutoField(primary_key=True) permission_name = models.CharField(max_length=255, blank=False, null=False, unique=True) + permission_power = models.IntegerField(default=0, help_text="Allows to specify the power level for the permission.") # OIDCAccount related create_account = models.BooleanField(default=False) @@ -420,6 +524,10 @@ class Permissions(models.Model): view_features_panel = models.BooleanField(default=False) view_accounts_panel = models.BooleanField(default=False) + # FrontEnd department admin panel related + show_all_departments = models.BooleanField(default=False) + show_department_users = models.BooleanField(default=False) + # Folder related create_folder = models.BooleanField(default=False) delete_folder = models.BooleanField(default=False) @@ -433,10 +541,12 @@ def __str__( self ): return u"%s" % self.permission_name def __unicode__(self): return u'%s' % self.permission_name + @classmethod def get_default_permission(cls): permission, created = cls.objects.get_or_create( permission_name='ANONYMOUS', + permission_power=10, create_feature=True, edit_feature=True, run_feature=True, @@ -492,7 +602,8 @@ def __str__( self ): def save(self, *args, **kwargs): self.slug = slugify(self.app_name) super(Application, self).save(*args, **kwargs) - + def __str__( self ): + return f"{self.app_name} ({self.app_id})" class Meta: ordering = ['app_name'] verbose_name_plural = "Applications" @@ -502,7 +613,7 @@ class Environment(models.Model): environment_name = models.CharField(max_length=100) created_on = models.DateTimeField(default=datetime.datetime.utcnow, editable=True, null=False, blank=False) def __str__( self ): - return u"Environment_name = %s" % self.environment_name + return f"{self.environment_name} ({self.environment_id})" class Meta: ordering = ['environment_name'] verbose_name_plural = "Environments" @@ -536,7 +647,7 @@ def save(self, *args, **kwargs): super(Department, self).save(*args, **kwargs) def __str__( self ): - return u"Department_name = %s" % self.department_name + return f"{self.department_name} ({self.department_id})" class Meta: ordering = ['department_name'] verbose_name_plural = "Departments" @@ -568,7 +679,7 @@ class Feature(models.Model): environment_id = models.IntegerField() environment_name = models.CharField(max_length=255) steps = models.IntegerField() - schedule = models.CharField(max_length=255) + schedule = models.CharField(max_length=255, blank=True, null=True) department_id = models.IntegerField() department_name = models.CharField(max_length=255) screenshot = models.TextField(null=False) @@ -592,7 +703,7 @@ class Feature(models.Model): info = models.ForeignKey('Feature_Runs', on_delete=models.SET_NULL, null=True, default=None, related_name='info') readonly_fields=('feature_id',) def __str__( self ): - return u"Feature_name "+str(self.feature_id)+" = %s" % self.feature_name + return f"{self.feature_name} ({self.feature_id})" def save(self, *args, **kwargs): self.slug = slugify(self.feature_name) @@ -608,10 +719,9 @@ def save(self, *args, **kwargs): if not kwargs.get('dontSaveSteps', False): # get featureFileName featureFileName = get_feature_path(self)['fullPath'] - # Create / Update .feature and jsons whenever feature info is updated / created steps = kwargs.get('steps', list(Step.objects.filter(feature_id=self.feature_id).order_by('id').values())) - print("Saving steps received from Front:", steps) + logger.debug(f"Saving steps received from Front: {steps}") # Create .feature response = create_feature_file(self, steps, featureFileName) # check if infinite loop was found @@ -760,6 +870,7 @@ class Step_result(models.Model): screenshot_difference = models.CharField(max_length=255, default='', null=True, blank=True) screenshot_template = models.CharField(max_length=255, default='', null=True, blank=True) belongs_to = models.IntegerField(null=True) # feature that step belongs to + error = models.TextField(null=True, blank=True) class Meta: ordering = ['step_result_id'] @@ -847,7 +958,7 @@ class Meta: class Folder(models.Model): folder_id = models.AutoField(primary_key=True) name = models.CharField(max_length=255, default="New Folder") - owner = models.ForeignKey(OIDCAccount, on_delete=models.CASCADE) # will be removed once new settings has been settled. + owner = models.ForeignKey(OIDCAccount, on_delete=models.SET_NULL, null=True) # will be removed once new settings has been settled. department = models.ForeignKey(Department, on_delete=models.CASCADE, null=True) parent_id = models.ForeignKey("self", on_delete=models.CASCADE, null=True, blank=True, related_name='child') created_on = models.DateTimeField(default=datetime.datetime.utcnow, editable=True, null=False, blank=False) @@ -878,12 +989,89 @@ def checkFoldersHierarchy(self, parent_id): print("hierarchy = %d" % hierarchy) return hierarchy < MAX_FOLDER_HIERARCHY + def getAllSubFeatures(self): + query = """ + WITH RECURSIVE recursive_folders AS ( + SELECT bf.* + FROM backend_folder bf + WHERE bf.folder_id = %s + + UNION + + SELECT bf.* + FROM backend_folder bf JOIN recursive_folders rf ON bf.parent_id_id = rf.folder_id + ) + SELECT DISTINCT 1 as folder_id, bf.feature_id + FROM (recursive_folders rf + LEFT JOIN backend_folder_feature bff + ON rf.folder_id = bff.folder_id) + LEFT JOIN backend_feature bf + ON bf.feature_id = bff.feature_id + ORDER BY bf.feature_id; + """ + # save results + features = [] + # run the query + feature_ids = Folder.objects.raw(query, [self.folder_id]) + # check if folder has any features or subfeatures inside + if len(feature_ids) > 0: + for feature_id in feature_ids: + if feature_id.feature_id is not None: + features.append(feature_id.feature_id) + return features + + def getAllSubFolders(self): + query = """ + WITH RECURSIVE recursive_folders AS ( + SELECT bf.* + FROM backend_folder bf + WHERE bf.folder_id = %s + + UNION + + SELECT bf.* + FROM backend_folder bf JOIN recursive_folders rf ON bf.parent_id_id = rf.folder_id + ) + SELECT folder_id + FROM recursive_folders rf + WHERE folder_id != %s + ORDER BY folder_id; + """ + # save results + folders = [] + # run the query + folder_ids = Folder.objects.raw(query, [self.folder_id, self.folder_id]) + # check if folder has any subfolders inside + if len(folder_ids) > 0: + for folder_id in folder_ids: + if folder_id.folder_id is not None: + folders.append(folder_id.folder_id) + return folders + def save(self, *args, **kwargs): - print(self.parent_id_id) + # get old values + old_instance = None + try: + old_instance = Folder.objects.get(folder_id=self.folder_id) + except: + pass + # get parent if not self.checkFoldersHierarchy(self.parent_id_id): raise Exception("This folder creation surpases MAX_FOLDER_HIERARCHY (%d) set by admin." % MAX_FOLDER_HIERARCHY) + # check if parent department and self department are different + if self.parent_id is not None and ( self.department != self.parent_id.department ): + # set self department to parent department + self.department = self.parent_id.department + + if old_instance is not None and (old_instance.department != self.department): + # change all sub folders and features department to self department + updated_features = Feature.objects.filter(feature_id__in=self.getAllSubFeatures()).update(department_id=self.department.department_id, department_name=self.department.department_name) + print("Updated Features: %d" % updated_features) + updated_folders = Folder.objects.filter(folder_id__in=self.getAllSubFolders()).update(department=self.department) + print("Updated SubFolders: %d" % updated_folders) + super(Folder, self).save(*args) class Folder_Feature(models.Model): @@ -917,14 +1105,33 @@ def do_delete(self, *args, **kwargs): deleteTemplate = kwargs.get('deleteTemplate', False) # delete all feature_results and pass in deleteTemplate - for fr in self.feature_results.filter(archived=False): - fr.delete(deleteTemplate=deleteTemplate) + # 2023-07-05 - ASOHAIL - Since Feature Runs are no longer in use do not delete Feature Results on it's delete. + # for fr in self.feature_results.filter(archived=False): + # fr.delete(deleteTemplate=deleteTemplate) # if everything is ok delete the object and return true # super(Feature_Runs, self).delete() return True - + +class DataDriven_Runs(SoftDeletableModel): + run_id = models.AutoField(primary_key=True) + file = models.ForeignKey("File", on_delete=models.SET_NULL, null=True, related_name="ddr_file") + feature_results = models.ManyToManyField(Feature_result) + date_time = models.DateTimeField(default=datetime.datetime.utcnow, editable=True, null=False, blank=False) + archived = models.BooleanField(default=False) + status = models.CharField(max_length=100, default='') + total = models.IntegerField(default=0) + fails = models.IntegerField(default=0) + ok = models.IntegerField(default=0) + skipped = models.IntegerField(default=0) + execution_time = models.IntegerField(default=0) + pixel_diff = models.BigIntegerField(default=0) + + class Meta: + ordering = ['-date_time'] + verbose_name_plural = "Data Driven Runs" + class Feature_Task(models.Model): task_id = models.AutoField(primary_key=True) feature = models.ForeignKey(Feature, on_delete=models.CASCADE, related_name="feature_tasks") @@ -948,6 +1155,30 @@ class Meta: ordering = ['variable_name'] verbose_name_plural = "Environment Variables" +class Variable(models.Model): + id = models.AutoField(primary_key=True) + department = models.ForeignKey(Department, on_delete=models.CASCADE, related_name="department_variable") + environment = models.ForeignKey(Environment, on_delete=models.SET_NULL, related_name="environment_variable", null=True) + feature = models.ForeignKey(Feature, on_delete=models.CASCADE, related_name="feature_variable", null=True) + variable_name = models.CharField(max_length=100, default=None, blank=False, null=False) + variable_value = models.TextField() + encrypted = models.BooleanField(default=False) + based = models.CharField(max_length=100, choices=variable_base, default='feature') + in_use = models.ManyToManyField(Feature, editable=False, related_name="variable_in_use") + created_by = models.ForeignKey(OIDCAccount, on_delete=models.SET_NULL, related_name="variable_owner", null=True) + updated_by = models.ForeignKey(OIDCAccount, on_delete=models.SET_NULL, related_name="variable_modifier", null=True) + created_on = models.DateTimeField(default=datetime.datetime.utcnow, editable=False, null=False, blank=False) + updated_on = models.DateTimeField(default=datetime.datetime.utcnow, editable=False, null=False, blank=False) + + def save(self, *args, **kwargs): + self.updated_on = datetime.datetime.utcnow() + return super(Variable, self).save(*args, **kwargs) + + class Meta: + ordering = ['variable_name'] + verbose_name_plural = "Variables" + unique_together = ('department', 'environment', 'feature', 'variable_name', 'based') + class Cloud(models.Model): id = models.AutoField(primary_key=True) name = models.CharField(max_length=255, default=None, blank=False, null=False) @@ -1185,3 +1416,62 @@ class UsageInvoice(models.Model): created_on = models.DateTimeField(auto_now_add=True, editable=True, null=False, blank=False, help_text='When was created') modified_on = models.DateTimeField(auto_now=True, editable=True, null=False, blank=False, help_text='When was the latest modification') error = models.TextField(help_text='If there was an error during payment, it will appear here') + +# stores uploaded files data +class File(SoftDeletableModel): + id = models.AutoField(primary_key=True) + name = models.CharField(max_length=255) + path = models.CharField(max_length=255) + uploadPath = models.CharField(max_length=255) + size = models.IntegerField() + type = models.CharField(max_length=255) + mime = models.CharField(max_length=255) + md5sum = models.CharField(max_length=255) + department = models.ForeignKey(Department, on_delete=models.CASCADE, related_name="files") + status = models.CharField(max_length=10, choices=file_status, default="Unknown") + uploaded_by = models.ForeignKey(OIDCAccount, on_delete=models.SET_NULL, null=True) + created_on = models.DateTimeField(default=datetime.datetime.utcnow, editable=True, null=False, blank=False, help_text='When was created') + + def restore(self, using=None): + # restore the file back to original state if exists + if os.path.exists(self.path) and '__deleted' in self.path: + newPath = self.path.replace("__deleted", "") + logger.debug(f"Restoring {self.path} to {newPath}.") + shutil.move(self.path, newPath) + self.path = newPath + + return super().restore(using) + + def delete(self, using=None, soft=True, *args, **kwargs): + # if soft is set to True add __deleted to the filename + if soft: + logger.debug(f"Soft delete: Moving {self.path} to {self.path}__deleted") + try: + shutil.move(self.path, f"{self.path}__deleted") + except FileNotFoundError: + logger.info("File not found, doing some cleanup.") + self.path = self.path + "__deleted" + + return super().delete(using=using, soft=soft, *args, **kwargs) + +class FileData(SoftDeletableModel): + id = models.AutoField(primary_key=True) + file = models.ForeignKey(File, on_delete=models.CASCADE, related_name="file") + data = models.JSONField(default=dict) + extras = models.JSONField(default=dict) + created_on = models.DateTimeField(default=datetime.datetime.utcnow, editable=True, null=False, blank=False, help_text='When was created') + + class Meta: + verbose_name_plural = "Files Data" + +""" +Make sure to delete the files on the fs when ever deleting the record. +Also works when removing department. +""" +@receiver(post_delete, sender=File) +def post_file_delete(instance, sender, using, **kwargs): + logger.debug(f"Deleting file {instance.path} from the file system.") + # check if file exists on the fs + if os.path.exists(instance.path): + os.unlink(instance.path) + diff --git a/backend/src/backend/paginations.py b/backend/src/backend/paginations.py index 8b56aa7a..21e0e6f0 100755 --- a/backend/src/backend/paginations.py +++ b/backend/src/backend/paginations.py @@ -3,4 +3,4 @@ class StandardResultsSetPagination(PageNumberPagination): page_size_query_param = 'size' page_size = 200 - max_page_size = 500 \ No newline at end of file + max_page_size = 5000 \ No newline at end of file diff --git a/backend/src/backend/serializers.py b/backend/src/backend/serializers.py index 61115943..80051a35 100755 --- a/backend/src/backend/serializers.py +++ b/backend/src/backend/serializers.py @@ -76,8 +76,8 @@ def get_feedback_mail(self, instance): def get_integration_apps(self, instance): return [x[1] for x in IntegrationApplications] - def get_permissions(self, instance): - results = Permissions.objects.all() + def get_permissions(self, instance: OIDCAccount): + results = Permissions.objects.filter(permission_power__lte=instance.user_permissions.permission_power) return [x[0] for x in results.values_list("permission_name")] def get_clouds(self, instance): @@ -118,6 +118,12 @@ class Meta: model = OIDCAccount fields = '__all__' +class BasicOIDCAccountSerializer_WithoutUserID(serializers.ModelSerializer): + class Meta: + model = OIDCAccount + fields = ['name', 'email'] + + ################################ # Permissions model serializer # ################################ @@ -179,15 +185,42 @@ class RegularFeatureResultInfoSerializer(serializers.Serializer): ################################## # Feature Runs model serializers # ################################## -class FeatureRunsSerializer(serializers.ModelSerializer, FeatureRunsMixin): - - feature_results = serializers.SerializerMethodField() - date_time = serializers.DateTimeField(format=datetimeTZFormat) +class FeatureRunsSerializer(serializers.ModelSerializer): + + # feature_results = serializers.SerializerMethodField() + feature_results = FeatureResultSerializer(many=True, read_only=True) + run_id = serializers.IntegerField(read_only=True) + is_removed = serializers.BooleanField(read_only=True) + archived = serializers.BooleanField(read_only=True) + status = serializers.CharField(read_only=True) + total = serializers.IntegerField(read_only=True) + fails = serializers.IntegerField(read_only=True) + ok = serializers.IntegerField(read_only=True) + skipped = serializers.IntegerField(read_only=True) + execution_time = serializers.IntegerField(read_only=True) + pixel_diff = serializers.IntegerField(read_only=True) + feature = serializers.CharField(source='feature.feature_id', read_only=True) + date_time = serializers.DateTimeField(format=datetimeTZFormat, read_only=True) class Meta: model = Feature_Runs - fields = '__all__' - extra_fields = ['feature_results'] + fields = ( + "run_id", + "is_removed", + "archived", + "status", + "total", + "fails", + "ok", + "skipped", + "execution_time", + "pixel_diff", + "feature", + "date_time", + "feature_results" + ) + # extra_fields = ['feature_results'] + # exclude = ["feature_results"] def create(self, validated_data): # create feature run object fr = Feature_Runs.objects.create(**validated_data) @@ -202,10 +235,16 @@ def create(self, validated_data): feature.save(dontSaveSteps=True) # return feature run that was created at the start return fr + + @staticmethod + def setup_eager_loading(queryset): + #select_related for 'to-one' relationships + queryset = queryset.select_related('feature') - def get_feature_results(self, instance): - ret = FeatureResultSerializer(instance.feature_results, many=True).data - return ret + #prefetch_related for 'to-many' relationships + queryset = queryset.prefetch_related('feature_results') + + return queryset def sumField(querySet, field_name): return functools.reduce(lambda total, fr: total + getattr(fr, field_name), querySet, 0) @@ -259,6 +298,59 @@ class Meta: model = Feature fields = ('feature_id', 'feature_name',) +class FeatureTreeSerializer(serializers.ModelSerializer, FeatureMixin): + class Meta: + model = Feature + fields = ('feature_id', 'feature_name',) + + def to_representation(self, feature): + treeRep = { + "type": "feature", + "name": feature.feature_name, + "id": feature.feature_id + } + return treeRep + +class FeatureHasSubFeatureSerializer(serializers.ModelSerializer, FeatureMixin): + class Meta: + model = Feature + fields = ('feature_id', 'feature_name', 'depends_on_others') + + def to_representation(self, feature): + uses_steps = Step.objects.filter(feature_id=feature.feature_id).exclude(belongs_to=feature.feature_id).order_by('belongs_to').distinct('belongs_to').values_list('belongs_to', flat=True) + used_in_steps = Step.objects.filter(belongs_to=feature.feature_id).exclude(feature_id=feature.feature_id).order_by('belongs_to').distinct('belongs_to').values_list('feature_id', flat=True) + # get all childs + childrens = [ + { + "name": "Uses", + "type": "folder", + "children": FeatureTreeSerializer(Feature.objects.filter(feature_id__in=uses_steps), many=True).data + }, + { + "name": "Used By", + "type": "folder", + "children": FeatureTreeSerializer(Feature.objects.filter(feature_id__in=used_in_steps), many=True).data + }, + { + "name": "Variables", + "type": "variable", + "children": VariablesTreeNoFeatureSerializer(Variable.objects.filter(in_use=feature.feature_id), many=True).data + } + ] + + + treeRep = { + "type": "feature", + "name": feature.feature_name, + "id": feature.feature_id, + "depends_on_others": feature.depends_on_others, + "children": childrens + } + + return treeRep + + + ########################## # Step model serializers # ########################## @@ -370,16 +462,104 @@ def create(self, validated_data): return new_app +################################ +# File model serializers # +################################ +class FileSerializer(serializers.ModelSerializer): + uploaded_by = BasicOIDCAccountSerializer(many=False) + class Meta: + model = File + exclude = ('path',) + + def create(self, validated_data): + return File.objects.create(**validated_data) + + +###################################### +# Data Driven Runs model serializers # +###################################### +class DataDrivenRunsSerializer(serializers.ModelSerializer): + + # feature_results = FeatureResultSerializer(many=True, read_only=True) + run_id = serializers.IntegerField(read_only=True) + is_removed = serializers.BooleanField(read_only=True) + archived = serializers.BooleanField(read_only=True) + status = serializers.CharField(read_only=True) + total = serializers.IntegerField(read_only=True) + fails = serializers.IntegerField(read_only=True) + ok = serializers.IntegerField(read_only=True) + skipped = serializers.IntegerField(read_only=True) + execution_time = serializers.IntegerField(read_only=True) + pixel_diff = serializers.IntegerField(read_only=True) + file = FileSerializer(read_only=True) + date_time = serializers.DateTimeField(format=datetimeTZFormat, read_only=True) + + class Meta: + model = Feature_Runs + fields = ( + "run_id", + "is_removed", + "archived", + "status", + "total", + "fails", + "ok", + "skipped", + "execution_time", + "pixel_diff", + "file", + "date_time", + # "feature_results" + ) + # extra_fields = ['feature_results'] + # exclude = ["feature_results"] + + @staticmethod + def setup_eager_loading(queryset): + #select_related for 'to-one' relationships + queryset = queryset.select_related('file') + + #prefetch_related for 'to-many' relationships + queryset = queryset.prefetch_related('feature_results') + + return queryset + + +################################ +# FileData model serializers # +################################ +class FileDataSerializer(serializers.ModelSerializer): + data = serializers.JSONField() + class Meta: + model = FileData + fields = '__all__' + depth = 0 + ################################ # Department model serializers # ################################ class DepartmentSerializer(serializers.ModelSerializer): + files = FileSerializer(many=True) + class Meta: model = Department fields = '__all__' def create(self, validated_data): return Department.objects.create(**validated_data) + +class DepartmentWithUsersSerializer(serializers.ModelSerializer): + users = serializers.SerializerMethodField() + files = FileSerializer(many=True) + + class Meta: + model = Department + fields = '__all__' + extra_fields = ['users', 'files'] + + def get_users(self, instance): + accounts = OIDCAccount.objects.filter(user_id__in=instance.account_role_set.all().values_list('user', flat=True)) + return OIDCAccountSerializer(accounts, many=True).data ############################ # Action model serializers # @@ -432,16 +612,47 @@ def create(self, validated_data): ########################################### # Environment Variables model serializers # ########################################### -class EnvironmentVariablesSerializer(serializers.ModelSerializer, EnvironmentVariablesMixin): - +class VariablesSerializer(serializers.ModelSerializer, VariablesMixin): + + # retrieve OIDC account data as read_only + created_by_name = serializers.CharField( source='created_by.name', read_only=True) + updated_by_name = serializers.CharField( source='updated_by.name', read_only=True) + # department name + department_name = serializers.CharField( source='department.department_name', read_only=True) + # environemnt_name + environment_name = serializers.CharField( source='environment.environment_name', read_only=True) + # feature_name + feature_name = serializers.CharField( source='feature.feature_name', read_only=True) class Meta: - model = EnvironmentVariables - depth = 2 + model = Variable fields = '__all__' - def create(self, validated_data): - return EnvironmentVariables.objects.create(**validated_data) - +class VariablesTreeSerializer(serializers.ModelSerializer, VariablesMixin): + class Meta: + model = Variable + fields = ['variable_name', 'variable_value'] + + def to_representation(self, value): + treeRep = { + "type": "variable", + "name": value.variable_name, + "value": value.variable_value, + "children": FeatureTreeSerializer(value.in_use, many=True).data + } + return treeRep + +class VariablesTreeNoFeatureSerializer(serializers.ModelSerializer, VariablesMixin): + class Meta: + model = Variable + fields = ['variable_name', 'variable_value'] + + def to_representation(self, value): + treeRep = { + "type": "variable", + "name": value.variable_name, + "value": value.variable_value, + } + return treeRep ########################################### # Invites model serializers # ########################################### diff --git a/backend/src/backend/templatetags/humanize.py b/backend/src/backend/templatetags/humanize.py index dc5271c9..cb30c3bc 100644 --- a/backend/src/backend/templatetags/humanize.py +++ b/backend/src/backend/templatetags/humanize.py @@ -5,6 +5,22 @@ @register.filter def Humanize(milliseconds): + return _humanize(milliseconds) + +@register.filter +def NormalizeDownloadName(path): + filename = path.split('/')[1] + filename, extension = os.path.splitext(filename) + if len(filename) > pdfDownloadNameLimit: + filename = filename[:pdfDownloadNameLimit] + '(...)' + return filename + extension + +@register.filter +def FormatNumber(number): + return '{:,}'.format(number) + + +def _humanize(milliseconds): milliseconds = int(milliseconds) if milliseconds < 1000: return "%dms" % milliseconds @@ -14,12 +30,4 @@ def Humanize(milliseconds): hDisplay = (str(hours) + 'h' if hours > 0 else '') + (':' if minutes > 0 and hours > 0 else '') mDisplay = (str(minutes) + 'm' if minutes > 0 else '') + (':' if seconds > 0 and minutes > 0 else '') sDisplay = str(seconds) + 's' if seconds > 0 else '' - return hDisplay + mDisplay + sDisplay - -@register.filter -def NormalizeDownloadName(path): - filename = path.split('/')[1] - filename, extension = os.path.splitext(filename) - if len(filename) > pdfDownloadNameLimit: - filename = filename[:pdfDownloadNameLimit] + '(...)' - return filename + extension \ No newline at end of file + return hDisplay + mDisplay + sDisplay \ No newline at end of file diff --git a/backend/src/backend/utility/cometa_logger.py b/backend/src/backend/utility/cometa_logger.py new file mode 100644 index 00000000..09002e81 --- /dev/null +++ b/backend/src/backend/utility/cometa_logger.py @@ -0,0 +1,30 @@ +import logging, threading, re, os + + +class CometaLogger(logging.Logger): + + def __init__(self, name, level = logging.NOTSET): + self._mask_words = [] + self._mask_words_lock = threading.Lock() + return super(CometaLogger, self).__init__(name, level) + + def updateMaskWords(self, word_to_mask): + self._mask_words_lock.acquire() + self._mask_words.append(word_to_mask) + self._mask_words_lock.release() + + return self._mask_words + + def mask_values(self, msg): + words_to_mask = re.escape('@@'.join(self._mask_words)) + if words_to_mask: + words_to_mask = words_to_mask.replace('@@', '|') + msg = re.sub(rf"(?:{words_to_mask})\b", '[MASKED]', str(msg)) + return msg + + def _log(self, level, msg, args, exc_info = None, extra = {}, stack_info = False, stacklevel = 1): + msg = self.mask_values(msg) + extra['feature_id'] = os.environ.get('feature_id', "n/a") + extra['current_step'] = os.environ.get('current_step', "?") + extra['total_steps'] = os.environ.get('total_steps', "?") + return super()._log(level, msg, args, exc_info, extra, stack_info, stacklevel) \ No newline at end of file diff --git a/backend/src/backend/utility/decorators.py b/backend/src/backend/utility/decorators.py index 213e64ff..67a93eb4 100644 --- a/backend/src/backend/utility/decorators.py +++ b/backend/src/backend/utility/decorators.py @@ -75,7 +75,7 @@ def decorated(*args, **kwargs): elif usersOwn(user, **require_permissions_kwargs): # user owns the object kwargs['usersOwn'] = True return fn(*args, **kwargs) - return JsonResponse({"success": False, "error": "permission_denied"}, status=200) + return JsonResponse({"success": False, "error": "Missing permission '%s'." % permission}, status=200) return decorated return decorator diff --git a/backend/src/backend/utility/encryption.py b/backend/src/backend/utility/encryption.py index 4dc690d9..c914067c 100644 --- a/backend/src/backend/utility/encryption.py +++ b/backend/src/backend/utility/encryption.py @@ -1,10 +1,17 @@ # pycrypto imports +import sys from Crypto import Random from Crypto.Cipher import AES -import base64 +import base64, sys from hashlib import md5 -passphrase = "4c9ac091fd7aff4eb727a64d4f523e3b76af91eef0dd1f3b94f44502016fd9e3".encode() +# just to import secrets +sys.path.append("/code") +import secret_variables + +# encrypt and decrypt password encrypted in Angular using Crypto-JS +ENCRYPTION_PASSPHRASE = getattr(secret_variables, 'COMETA_ENCRYPTION_PASSPHRASE', '').encode() +ENCRYPTION_START = getattr(secret_variables, 'COMETA_ENCRYPTION_START', '') BLOCK_SIZE = 16 def pad(data): @@ -27,7 +34,7 @@ def bytes_to_key(data, salt, output=48): def encrypt(message): salt = Random.new().read(8) - key_iv = bytes_to_key(passphrase, salt, 32+16) + key_iv = bytes_to_key(ENCRYPTION_PASSPHRASE, salt, 32+16) key = key_iv[:32] iv = key_iv[32:] aes = AES.new(key, AES.MODE_CBC, iv) @@ -37,7 +44,7 @@ def decrypt(encrypted): encrypted = base64.b64decode(encrypted) assert encrypted[0:8] == b"Salted__" salt = encrypted[8:16] - key_iv = bytes_to_key(passphrase, salt, 32+16) + key_iv = bytes_to_key(ENCRYPTION_PASSPHRASE, salt, 32+16) key = key_iv[:32] iv = key_iv[32:] aes = AES.new(key, AES.MODE_CBC, iv) diff --git a/backend/src/backend/utility/uploadFile.py b/backend/src/backend/utility/uploadFile.py new file mode 100644 index 00000000..fa74252b --- /dev/null +++ b/backend/src/backend/utility/uploadFile.py @@ -0,0 +1,261 @@ +from django.http import JsonResponse +from django.views.decorators.csrf import csrf_exempt +from django.core.files.uploadhandler import TemporaryFileUploadHandler +from django.core.files.uploadedfile import UploadedFile +from django.core.files import temp as tempfile +from django.conf import settings +import subprocess, magic, os, sys, requests, re +from backend.common import UPLOADS_FOLDER +from backend.models import File +from backend.serializers import FileSerializer +from backend.utility.functions import getLogger +sys.path.append("/code") +from secret_variables import COMETA_UPLOAD_ENCRYPTION_PASSPHRASE + +# logger information +logger = getLogger() + +""" +Docs: https://docs.djangoproject.com/en/4.1/_modules/django/core/files/uploadedfile/#TemporaryUploadedFile +""" +class TempUploadedFile(UploadedFile): + """ + A file uploaded to a temporary location (i.e. stream-to-disk). + """ + + def __init__(self, name, content_type, size, charset, content_type_extra=None): + _, ext = os.path.splitext(name) + file = tempfile.NamedTemporaryFile( + suffix=".cometa.upload" + ext, dir=settings.FILE_UPLOAD_TEMP_DIR, delete=False + ) + super().__init__(file, name, content_type, size, charset, content_type_extra) + + def temporary_file_path(self): + """Return the full path of this file.""" + return self.file.name + + + def close(self): + try: + return self.file.close() + except FileNotFoundError: + # The file was moved or deleted before the tempfile could unlink + # it. Still sets self.file.close_called and calls + # self.file.file.close() before the exception. + pass + +""" +Docs: https://docs.djangoproject.com/en/4.1/_modules/django/core/files/uploadhandler/#TemporaryFileUploadHandler +""" +class TempFileUploadHandler(TemporaryFileUploadHandler): + def new_file(self, *args, **kwargs): + """ + Create the file object to append to as data is coming in. + """ + super().new_file(*args, **kwargs) + self.file = TempUploadedFile( + self.file_name, self.content_type, 0, self.charset, self.content_type_extra + ) + +""" + +""" +class UploadFile(): + + """ + + """ + def __init__(self, file: TempUploadedFile, department_id: int, uploaded_by: int): + logger.debug(f"Upload file request: {file.name} for department id {department_id} and uploaded by {uploaded_by}.") + self.tempFile: TempUploadedFile = file + self.department_id: int = department_id + self.uploaded_by: int = uploaded_by + self.filename = self.sanitize(self.tempFile.name) + self.finalPath = self.generateFinalPath() + self.uploadPath = f"uploads/{self.filename}" + logger.debug(f"Final path for the file generated: {self.finalPath}") + self.file: File = self.generateTempObject(*self.getFileProperties()) + + def proccessUploadFile(self) -> File: + self.file.status = "Processing" + logger.debug(f"Processing {self.tempFile.name}...") + # send a websocket about the processing being done. + self.sendWebsocket({ + "type": "[Files] Processing", + "file": FileSerializer(self.file, many=False).data + }) + + # check if file already exists + if os.path.exists(self.finalPath): + logger.error("File already exists ... will not save.") + self.deleteFile(self.tempFile.temporary_file_path()) + # send a websocket about the processing being done. + self.file.status = "Error" + self.sendWebsocket({ + "type": "[Files] Error", + "file": FileSerializer(self.file, many=False).data, + "error": { + "status": f"FILE_ALREADY_EXIST", + "description": f"File already exists with the same name, please try removing old file or rename the file." + } + }) + return None + + # check for virus + try: + self.virusScan() + self.encrypt() + except Exception as err: + return None + + # finally save the object if everything went well + self.file.status = "Done" + try: + self.file.save() + except Exception as err: + logger.error("Exception occured while trying to save file to database.") + logger.exception(err) + # send a websocket about the processing being done. + self.file.status = "Error" + self.sendWebsocket({ + "type": "[Files] Error", + "file": FileSerializer(self.file, many=False).data, + "error": { + "status": f"UNABLE_TO_SAVE_FILE", + "description": f"Error occurred when trying to save the file to database, please try again later or contact an administrator." + } + }) + self.deleteFile(self.finalPath) + return None + # send a websocket about the processing being done. + self.sendWebsocket({ + "type": "[Files] Done", + "file": FileSerializer(self.file, many=False).data + }) + # delete the temporary file that was generated + self.deleteFile(self.tempFile.temporary_file_path()) + + def virusScan(self): + self.file.status = "Scanning" + logger.debug(f"Scanning for virus {self.tempFile.name}...") + # send a websocket about the processing being done. + self.sendWebsocket({ + "type": "[Files] Scanning", + "file": FileSerializer(self.file, many=False).data + }) + + # start with scanning + tempFilePath = self.tempFile.temporary_file_path() + result = subprocess.run(["bash", "-c", f"clamscan {tempFilePath} | grep {tempFilePath}"], stdout=subprocess.PIPE, stderr=subprocess.PIPE) + if result.returncode > 0: + # get the error + errOutput = result.stderr.decode('utf-8') + logger.error(errOutput) + raise Exception('Failed to scan the file, please contact an administrator.') + + output = result.stdout.decode("utf-8") + file, virus = output.split(": ") + + if not virus.startswith("OK"): + error = f"{self.tempFile.name} contains some sort of virus: {virus}." + self.deleteFile(self.tempFile.temporary_file_path()) + logger.error(error) + # send a websocket about the processing being done. + self.file.status = "Error" + self.sendWebsocket({ + "type": "[Files] Error", + "file": FileSerializer(self.file, many=False).data, + "error": { + "status": f"FILE_VIRUS_DETECTED", + "description": error + } + }) + raise Exception(error) + + def getFileProperties(self): + fileText = magic.from_file(self.tempFile.temporary_file_path()) + fileMime = magic.from_file(self.tempFile.temporary_file_path(), mime=True) + fileSize = self.tempFile.size + result = subprocess.run(["bash", "-c", f"md5sum \"{self.tempFile.temporary_file_path()}\""], stdout=subprocess.PIPE) + output = result.stdout.decode("utf-8") + fileMD5Sum = output.split(" ")[0] + + return fileText, fileMime, fileSize, fileMD5Sum + + def generateFinalPath(self): + finalDir = f"{UPLOADS_FOLDER}/{self.department_id}" + # make sure finalDir exists + os.makedirs(finalDir, 0o755, True) + + return f'{finalDir}/{self.filename}' + + def encrypt(self): + self.file.status = "Encrypting" + logger.debug(f"Encrypting {self.tempFile.name}...") + # send a websocket about the processing being done. + self.sendWebsocket({ + "type": "[Files] Encrypting", + "file": FileSerializer(self.file, many=False).data + }) + + # start encryption + source = self.tempFile.temporary_file_path() + target = self.finalPath + logger.debug(f"Moving {source} to {target}.") + try: + result = subprocess.run(["bash", "-c", f"gpg --output {target} --batch --passphrase {COMETA_UPLOAD_ENCRYPTION_PASSPHRASE} --symmetric --cipher-algo AES256 {source}"], stdout=subprocess.PIPE, stderr=subprocess.PIPE) + if result.returncode > 0: + # get the error + errOutput = result.stderr.decode('utf-8') + logger.error(errOutput) + raise Exception('Failed to encrypt the file, please contact an administrator.') + except Exception as err: + logger.error(f"Unable to move the file from source to target.", err) + self.deleteFile(self.tempFile.temporary_file_path()) + # send a websocket about the processing being done. + self.file.status = "Error" + self.sendWebsocket({ + "type": "[Files] Error", + "file": FileSerializer(self.file, many=False).data, + "error": { + "status": f"FILE_ENCRYPTION_FAILED", + "description": f"Unable to encrypt the file ... please contact an administrator." + } + }) + raise Exception(str(err)) + + def generateTempObject(self, text, mime, size, md5sum): + logger.debug(f"Generating temporary File object with name {self.tempFile.name}.") + try: + file: File = File( + name = self.tempFile.name, + path = self.finalPath, + uploadPath = self.uploadPath, + size = size, + type = text, + mime = mime, + md5sum = md5sum, + department_id = self.department_id, + uploaded_by_id = self.uploaded_by, + status = "Unknown" + ) + except Exception as err: + logger.error("Exception occured while trying to create an object...", err) + file = None + return file + + def deleteFile(self, file): + try: + os.remove(file) + return True + except Exception as err: + logger.error(f"Unable to remove file ({file}).") + logger.exception(err) + return False + + def sendWebsocket(self, payload): + response = requests.post('http://cometa_socket:3001/sendAction', json=payload) + return response + + def sanitize(self, filename: str): + return re.sub(r'[^A-Za-z0-9\.]', '-', filename) diff --git a/backend/src/backend/views.py b/backend/src/backend/views.py index a45ee04e..ac8905c5 100755 --- a/backend/src/backend/views.py +++ b/backend/src/backend/views.py @@ -1,4 +1,5 @@ # Import all models and all the utility methods +from itertools import islice from backend.models import * # Import all serializers from backend.serializers import * @@ -6,6 +7,7 @@ from django.core.exceptions import * # Import permissions related methods from backend.utility.decorators import require_permissions, hasPermission, require_subscription +from backend.templatetags.humanize import _humanize # Needed rest_framework import from rest_framework.renderers import JSONRenderer from rest_framework.response import Response @@ -13,6 +15,7 @@ # Django Imports from django.conf import settings from django.core.exceptions import ValidationError +from django.db.utils import IntegrityError from django.core.mail import send_mail, EmailMessage, EmailMultiAlternatives from django.core.validators import validate_email from django.core import serializers @@ -51,10 +54,12 @@ sys.path.append("/code") import secret_variables from threading import Thread -from concurrent.futures import ThreadPoolExecutor +from concurrent.futures import ThreadPoolExecutor, as_completed # import humanize for time conversion from backend.templatetags.humanize import * from sentry_sdk import capture_exception +from backend.utility.uploadFile import UploadFile +# from silk.profiling.profiler import silk_profile SCREENSHOT_PREFIX = getattr(secret_variables, 'COMETA_SCREENSHOT_PREFIX', '') BROWSERSTACK_USERNAME = getattr(secret_variables, 'COMETA_BROWSERSTACK_USERNAME', '') @@ -64,6 +69,18 @@ logger = getLogger() +def timediff(method): + def wrapper(*args, **kwargs): + method_name = method.__qualname__ or method.__name__ + logger.debug("Mesuring time differance for " + method_name + "...") + start = time.time() + result = method(*args, **kwargs) + end = time.time() + logger.debug(method_name + " took: " + str(end-start) + " seconds") + return result + return wrapper + + def bytesToMegaBytes(bytes): kilobytes = bytes/1024 megabytes = kilobytes/1024 @@ -94,7 +111,7 @@ def UserRelatedDepartments(user_email, type="id"): departmentsList.append(d.department_id if type == "id" else Department.objects.all().filter(department_id=d.department_id)[0].department_name) return departmentsList -def GetBrowserStackBrowsers(request): +def browser_stack_request(): # set default value for results to empty array results = [] # get browserstack cloud from database @@ -105,11 +122,19 @@ def GetBrowserStackBrowsers(request): if browsers.status_code == 200: results = browsers.json() else: - logger.error(browsers.text) - return JsonResponse({ - "success": False, - "msg": "Failed to make request to Browserstack, maybe provided credentials are incorrect?" - }) + raise Exception(browsers.text) + return results + +def GetBrowserStackBrowsers(request): + try: + results = browser_stack_request() + except Exception as err: + logger.exception(err) + return JsonResponse({ + "success": False, + "msg": "Failed to make request to Browserstack, maybe provided credentials are incorrect?", + "results": [] + }) # send a response back return JsonResponse({ "success": True, @@ -118,17 +143,17 @@ def GetBrowserStackBrowsers(request): def GetUserDepartments(request): - user = request.session['user'] # Get an array of the departments ids owned by the current logged user - try: - departmentsOwning = Department.objects.filter(owners__user_id=request.session['user']['user_id']).values_list('department_id', flat=True) - except FieldError: - print('DepartmentOwners not implemented yet') - departmentsOwning = [] + # try: + # departmentsOwning = Department.objects.filter(owners__user_id=request.session['user']['user_id']).values_list('department_id', flat=True) + # except FieldError: + # print('DepartmentOwners not implemented yet') + # departmentsOwning = [] # Get an array of department ids which the user has access to from the OIDCAccount info userDepartments = [x['department_id'] for x in request.session['user']['departments']] # Merge arrays of departments and owning, and return - return userDepartments + list(set(departmentsOwning) - set(userDepartments)) + # return userDepartments + list(set(departmentsOwning) - set(userDepartments)) + return userDepartments + list(set(userDepartments)) # function that recieves feature_run are removes all feature_results not # marked as archived. @@ -160,6 +185,13 @@ def DepartmentsRelatedToAccountView(request): "results": json.loads(d) }) +def departmentExists(department_id) -> int: + try: + department = Department.objects.get(department_id=department_id) + return department.department_id + except Exception as err: + return -1 + @csrf_exempt def CreateOIDCAccount(request): return JsonResponse(request.session['user'], status=200) @@ -490,30 +522,243 @@ def downloadFeatureFiles(request, filepath, *args, **kwargs): # Sends the request to behave without waiting def startFeatureRun(data): - requests.post('http://behave:8001/run_test/', data=data) + result = requests.post('http://behave:8001/run_test/', data=data) @csrf_exempt -@require_subscription() -@require_permissions("run_feature") -def runTest(request, *args, **kwargs): - # Verify body can be parsed as JSON +def viewRunStatus(request, feature_id): + # Verify feature id exists try: - data = json.loads(request.body) - except json.JSONDecodeError: - return JsonResponse({ 'success': False, 'error': 'Unable to parse request body.' }) + feature = Feature.objects.get(feature_id=feature_id) + except Feature.DoesNotExist: + return JsonResponse({ 'success': False, 'error': 'Provided Feature ID does not exist.' }, status=404) + + onlyProgress = request.GET.get('onlyProgress', False) + logger.debug(f"OnlyProgress? - {onlyProgress}") + + # check if user belong to the department + userDepartments = GetUserDepartments(request) + if feature.department_id not in userDepartments: + return JsonResponse({ 'success': False, 'error': 'Provided Feature ID does not exist.' }) + + # check if it feature is currently running + request_response = requests.get(f'http://cometa_socket:3001/featureStatus/{feature_id}') + + # result that will be returned + result = "" + + try: + data = request_response.json() + + # if only progress is set + if onlyProgress and data.get('running', False): + return HttpResponse("waiting....\n", status=206) + + # get the last result from the feature + try: + last_feature_result = Feature_result.objects.filter(feature_id=feature_id).order_by('-result_date')[0] + result += f"""Feature: {feature.feature_name} ({feature.feature_id}) +Feature Result ID: {last_feature_result.feature_result_id} + +""" + except: + return JsonResponse({ + 'success': False, + 'error': 'Unable to retrieve the last feature result.' + }, status=503) + # get all the steps related to the last result + try: + row = ["N", "Step Name", "Execution Time", "Success"] + result += "{:>4}º {:<100} | {:^14} | {:^7} \n".format(*row) + step_results = Step_result.objects.filter(feature_result_id=last_feature_result.feature_result_id) + if len(step_results) == 0: + result += "No Step Results Yet.\n" + else: + i = 1 + for step_result in step_results: + sn = step_result.step_name if len(step_result.step_name) < 97 else step_result.step_name[:97] + '...' + row = [i, sn, _humanize(step_result.execution_time), '🗸' if step_result.success else '✖'] + result += "{:>4}) {:<100} | {:^14} | {:^7} \n".format(*row) + i += 1 + if not data.get('running', False): + result += f""" +Overall Status: {last_feature_result.status} +Total Execution Time: {_humanize(last_feature_result.execution_time)} +""" + return HttpResponse(result, status=(206 if data.get('running', False) else 200)) + except: + return JsonResponse({ + 'success': False, + 'error': 'Unable to retrieve the steps from the last feature result.' + }, status=503) + except Exception as err: + return JsonResponse({ + 'success': False, + 'error': 'Feature is not running.' + }, status=404) + +def features_sql(department_id=None, folder_id=None, recursive=False): + + if department_id is None and folder_id is None: + raise Exception("Either department_id or folder_id must be provided.") + + if department_id is not None and folder_id is not None: + raise Exception("Please provide either department_id or folder_id. Both properties are not allowed.") + + query = """ + SELECT + feature.feature_id + FROM + backend_feature feature + LEFT JOIN + backend_folder_feature bff + ON bff.feature_id = feature.feature_id + LEFT JOIN backend_folder folder + ON folder.folder_id = bff.folder_id + WHERE + feature.depends_on_others IS NOT TRUE + AND + """ + + if department_id: + query += f"feature.department_id = {department_id}" + + if department_id and not recursive: + query += f" AND bff.folder_id is null" + + if folder_id and not recursive: + query += f"folder.folder_id = {folder_id}" + + if folder_id and recursive: + query = f""" + WITH RECURSIVE recursive_folders AS ( + SELECT + bf.folder_id, 1 AS LVL + FROM + backend_folder bf + WHERE + bf.parent_id_id = {folder_id} OR bf.folder_id = {folder_id} + UNION + SELECT + bf.folder_id, rf.LVL + 1 + FROM + backend_folder bf + JOIN recursive_folders rf + ON bf.parent_id_id = rf.folder_id AND rf.LVL < {MAX_FOLDER_HIERARCHY} + ) + SELECT DISTINCT + bff.feature_id + FROM + backend_folder_feature bff + RIGHT JOIN recursive_folders rf ON rf.folder_id = bff.folder_id + LEFT JOIN backend_feature feature ON bff.feature_id = feature.feature_id + WHERE + feature.depends_on_others IS NOT TRUE + """ + + results = Feature.objects.raw(query) + features = [result for result in results] + + return features + +# Checks if the selected browser is valid using the array of available browsers +# for the selected cloud, it also tries to repair the selected browser object +def checkBrowser(selected, local_browsers, cloud_browsers): + # Retrieve all browsers from either local or browserstack + browsers = local_browsers if selected.get('cloud', 'browserstack') == 'local' else cloud_browsers + if selected.get('cloud', '') == 'local' and selected.get('mobile_emulation', False): + # In that case just assign the latest version of Chrome for the emulated browser + # Exclude non stable versions by checking if version can be numeric (without dots) + browsers = [ x for x in browsers if x.get('browser_version').replace('.', '').isnumeric() and x.get('browser') == 'chrome' ] + # Sort browsers by browser_version + browsers = sorted(browsers, key=lambda k: int(k.get('browser_version').replace('.', '')), reverse=True) + version = browsers[0].get('browser_version') + selected['browser_version'] = version + selected['mobile_user_agent'] = selected['mobile_user_agent'].replace('$version', version) + selected['browser'] = 'chrome' + return selected + # Set common browser properties + fields = ['browser', 'device', 'os', 'os_version', 'real_mobile'] + # Filter corresponding browsers with properties from selected browser + # If the resulting array contains 0 elements, it means the selected browser + # is not found, and therefore is invalid + for field in fields: + browsers = [ x for x in browsers if x.get(field) == selected.get(field) ] + if len(browsers) == 0: + raise Exception('Found invalid browser object') + # Filter browsers by the version in the selected browser + # If the resulting array contains 0 elements, it means the selected browser + # has an outdated browser version or invalid, in that case + browsers_versions = [ x for x in browsers if x.get('browser_version') == selected.get('browser_version') ] + # Handle exception of "latest" version, it is handled later + if selected.get('browser_version', '') == "latest" or len(browsers_versions) == 0: + # Exclude non stable versions by checking if version can be numeric (without dots) + browsers = [ x for x in browsers if x.get('browser_version').replace('.', '').isnumeric() ] + # Sort browsers by browser_version + browsers = sorted(browsers, key=lambda k: int(k.get('browser_version').replace('.', '')), reverse=True) + try: + # Assign the version of the first sorted browser + selected['browser_version'] = browsers[0].get('browser_version') + except: + raise Exception('Unable to find latest version for the selected browser') + return selected + +def getFeatureBrowsers(feature: Feature): + feature_browsers = feature.browsers + local_browsers = [] + cloud_browsers = [] + browsers = [] + + for browser in feature_browsers: + #3013: Fix missing cloud property in outdated favourited browsers + if 'cloud' not in browser and browser.get("os","").lower() == "generic": + browser['cloud'] = "local" + # Check selected cloud is local and we don't have it yet + if browser.get('cloud') == 'local' and len(local_browsers) == 0: + logger.debug("Found 'local' as cloud for browser. Getting local browsers.") + local_browsers = list(Browser.objects.all().values_list('browser_json', flat=True)) + # Check selected cloud is browserstack and we don't have it yet + elif len(cloud_browsers) == 0: + logger.debug("Found 'cloud' as cloud for browser. Getting BrowserStack browsers.") + try: + cloud_browsers = browser_stack_request() + except Exception as err: + logger.exception(err) + + browsers.append(checkBrowser(browser, local_browsers, cloud_browsers)) + + return browsers + + +def runFeature(request, feature_id, data={}, additional_variables=list): + + # set default value for additional_variables + if additional_variables == list: + additional_variables = [] # Verify feature id exists try: - feature = Feature.objects.get(feature_id=data['feature_id']) + logger.info(f"Getting feature with id {feature_id} to run.") + feature = Feature.objects.get(pk=feature_id) except Feature.DoesNotExist: - return JsonResponse({ 'success': False, 'error': 'Provided Feature ID does not exist.' }) + return { 'success': False, 'error': 'Provided Feature ID does not exist.' } + + # check if user belong to the department + userDepartments = GetUserDepartments(request) + if feature.department_id not in userDepartments: + return { 'success': False, 'error': 'Provided Feature ID does not exist.' } + + if len(feature.browsers) == 0: + return JsonResponse({ + 'success': False, + 'error': 'No browsers selected.' + }, status=400) # Verify access to submitted browsers try: subscriptions = get_subscriptions_from_request(request) check_browser_access(feature.browsers, subscriptions) except Exception as err: - return JsonResponse({ 'success': False, 'error': str(err) }) + return { 'success': False, 'error': str(err) } # Get user session user = request.session['user'] @@ -526,7 +771,7 @@ def runTest(request, *args, **kwargs): # Prevent continue if user will exceed quota and has marked the option to prevent schedules from running scheduled_behavior = user['settings'].get('budget_schedule_behavior', '') if check_user_will_exceed_budget(user['user_id'], feature.feature_id) and scheduled_behavior == 'prevent': - return JsonResponse({ 'success': False, 'error': 'Execution will exceed budget and user has `prevent` option configured.' }) + return { 'success': False, 'error': 'Execution will exceed budget and user has `prevent` option configured.' } else: # Request coming from front user # Retrieve confirm parameter from body JSON @@ -536,29 +781,61 @@ def runTest(request, *args, **kwargs): budget_exceeded = check_user_will_exceed_budget(user['user_id'], feature.feature_id) if budget_exceeded: # Budget will or has already exceeded the budget - return JsonResponse({ 'success': False, 'action': 'confirm_exceeded' }) + return { 'success': False, 'action': 'confirm_exceeded' } except BudgetAhead as err: # Budget is very close to be reached - return JsonResponse({ 'success': False, 'action': 'confirm_ahead' }) + return { 'success': False, 'action': 'confirm_ahead' } except Exception as err: # An unkown error occurred capture_exception(err) logger.error(str(err)) - return JsonResponse({ 'success': False, 'error': str(err) }) + return { 'success': False, 'error': str(err) } # create a run id for the executed test date_time = datetime.datetime.utcnow() fRun = Feature_Runs(feature=feature, date_time=date_time) fRun.save() + try: + get_feature_browsers = getFeatureBrowsers(feature) + except Exception as err: + return JsonResponse({ + 'success': False, + 'error': str(err) + }, status=400) + # update feature info feature.info = fRun # Make sure feature files exists - steps = Step.objects.filter(feature_id=data['feature_id']).order_by('id').values() + steps = Step.objects.filter(feature_id=feature_id).order_by('id').values() feature.save(steps=list(steps)) json_path = get_feature_path(feature)['fullPath']+'_meta.json' + executions = [] + frs = [] + for browser in get_feature_browsers: + # Generate random hash for image url obfuscating + run_hash = secrets.token_hex(nbytes=8) + # create a feature_result + feature_result = Feature_result( + feature_id_id=feature.feature_id, + result_date=datetime.datetime.utcnow(), + run_hash=run_hash, + running=True, + browser=browser, + executed_by_id=user['user_id'] + ) + feature_result.save() + frs.append(feature_result) + + executions.append({ + "feature_result_id": feature_result.feature_result_id, + "run_hash": run_hash, + "browser": browser + }) + fRun.feature_results.add(*frs) + # check if job exists jobParameters = {} if "jobId" in data: @@ -567,14 +844,25 @@ def runTest(request, *args, **kwargs): job.parameters['jobId'] = job.id jobParameters = job.parameters except Exception as err: - return JsonResponse({'success': False, 'error': str(err)}) + return {'success': False, 'error': str(err)} + + # get keynames from the additional variables + additional_variables_names = [v['variable_name'] for v in additional_variables] # get environment variables env = Environment.objects.filter(environment_name=feature.environment_name)[0] dep = Department.objects.filter(department_name=feature.department_name)[0] - env_variabels = EnvironmentVariables.objects.filter(environment=env, department=dep) - seri = EnvironmentVariablesSerializer(env_variabels, many=True).data - + env_variables = Variable.objects.filter( + Q(department=dep, based='department') | + Q(department=dep, environment=env, based='environment') | + Q(feature=feature, based='feature') + ).exclude( + variable_name__in=additional_variables_names + ).order_by('variable_name', '-based').distinct('variable_name') + seri = VariablesSerializer(env_variables, many=True).data + + additional_variables.extend(list(seri)) + # user data user = request.session['user'] @@ -583,8 +871,8 @@ def runTest(request, *args, **kwargs): 'feature_run': fRun.run_id, 'HTTP_PROXY_USER': json.dumps(user), 'HTTP_X_SERVER': request.META.get("HTTP_X_SERVER","none"), - "variables": json.dumps(seri), - "browsers": json.dumps(feature.browsers), + "variables": json.dumps(additional_variables), + "browsers": json.dumps(executions), "feature_id": feature.feature_id, "department": json.dumps(model_to_dict(dep), default=str), "parameters": json.dumps(jobParameters) @@ -594,10 +882,165 @@ def runTest(request, *args, **kwargs): # Spawn thread to launch feature run t = Thread(target=startFeatureRun, args=(datum, )) t.start() - return JsonResponse({ 'success': True }) + + return { + 'success': True, + 'feature_result_ids': [fr.feature_result_id for fr in frs], + } + except Exception as e: + return { 'success': False, 'error': str(e) } + +@csrf_exempt +@require_subscription() +@require_permissions("run_feature") +def runBatch(request, *args, **kwargs): + # Verify body can be parsed as JSON + try: + data = json.loads(request.body) + except json.JSONDecodeError: + return JsonResponse({ 'success': False, 'error': 'Unable to parse request body.' }) + + try: + features = features_sql(data.get('department_id', None), data.get('folder_id', None), data.get('recursive', False)) + if features: + feature_results = {feature.pk : runFeature(request, feature.pk) for feature in features} + print(feature_results) + return JsonResponse({ + 'success': True, + 'results': feature_results + }) + else: + return JsonResponse({ 'success': False, 'error': 'No features found.' }) + except Exception as err: + return JsonResponse({ 'success': False, 'error': str(err) }) + +def dataDrivenExecution(request, row, ddr: DataDriven_Runs): + data = row.data + feature_id = data.get('feature_id', None) + feature_name = data.get('feature_name', None) + + if not feature_id and not feature_name: + raise Exception("Missing 'feature_id' and 'feature_name' in row.") + + try: + feature = Feature.objects.get(pk=feature_id) + except Feature.DoesNotExist: + feature = Feature.objects.get(feature_name=feature_name) + except: + raise Exception("Feature not found.") + + additional_variables = [ + { + 'variable_name': k, + 'variable_value': v, + 'scope': 'data-driven' + } for k,v in data.items() + ] + + result = runFeature(request, feature.feature_id, additional_variables=additional_variables) + + if not result['success']: + raise Exception("Feature execution failed: %s" % result['error']) + + feature_result_ids = result['feature_result_ids'] + ddr.feature_results.add(*feature_result_ids) + + MAX_EXECUTION_TIMEOUT=7500 + start = time.time() + + for feature_result_id in feature_result_ids: + while True: + try: + fr = Feature_result.objects.get(pk=feature_result_id) + if fr.running and (time.time() - start) < MAX_EXECUTION_TIMEOUT: + logger.debug(f"Feature Result {fr.feature_result_id} is still running, will wait for it.") + time.sleep(10) + else: + break + except Feature_result.DoesNotExist: + raise Exception(f'Feature Result with id {feature_result_id} probably failed.') + + # add feature result data to the ddr + if fr: + ddr.total += fr.total + ddr.ok += fr.ok + ddr.fails += fr.fails + ddr.skipped += fr.skipped + ddr.pixel_diff += fr.pixel_diff + ddr.execution_time += fr.execution_time + ddr.save() + +def startDataDrivenRun(request, rows: list[FileData], ddr: DataDriven_Runs): + user = request.session['user'] + logger.info(f"Starting Data Driven Test {user['name']}") + with ThreadPoolExecutor(max_workers=1) as executor: + futures = [] + for row in rows: + logger.info(row) + futures.append(executor.submit(dataDrivenExecution, request, row, ddr)) + + for future in as_completed(futures): + try: + logger.info(future.result()) + except Exception as err: + logger.exception(err) + + ddr.status = 'Failed' if ddr.fails > 0 else 'Passed' + ddr.save() + +@csrf_exempt +@require_subscription() +@require_permissions("run_feature") +def runDataDriven(request, *args, **kwargs): + # Verify body can be parsed as JSON + try: + data = json.loads(request.body) + except json.JSONDecodeError: + return JsonResponse({ 'success': False, 'error': 'Unable to parse request body.' }, status=400) + + file_id = data.get('file_id', None) + if not file_id: + return JsonResponse({ 'success': False, 'error': 'Missing \'file_id\' parameter.' }, status=400) + + try: + file = File.objects.prefetch_related('file').get(pk=file_id) + except File.DoesNotExist: + return JsonResponse({ + "success": False, + "error": "File not found." + }, status=404) + + file_data = file.file.all() + + if len(file_data) == 0: + return JsonResponse({ + "success": False, + "error": "No rows found for selected data driven file." + }, status=400) + try: + ddr = DataDriven_Runs(file_id=file_id, status="Running") + ddr.save() + # Spawn thread to launch feature run + t = Thread(target=startDataDrivenRun, args=(request, file_data, ddr)) + t.start() + + return JsonResponse({ 'success': True, 'run_id': ddr.pk }) except Exception as e: return JsonResponse({ 'success': False, 'error': str(e) }) +@csrf_exempt +@require_subscription() +@require_permissions("run_feature") +def runTest(request, *args, **kwargs): + # Verify body can be parsed as JSON + try: + data = json.loads(request.body) + except json.JSONDecodeError: + return JsonResponse({ 'success': False, 'error': 'Unable to parse request body.' }) + + return JsonResponse(runFeature(request, data.get('feature_id', None), data)) + + @csrf_exempt def GetSteps(request, feature_id): queryset = Step.objects.filter(feature_id=feature_id) @@ -856,6 +1299,8 @@ def parseActions(request): if action.startswith("@step"): regex = r"\@(.*)\((u|)'(.*)'\)" matches = re.findall(regex,action) + if matches[0][2] == "{step}": + continue actionsParsed.append(matches[0][2]) actionObject = Action( action_name = matches[0][2], @@ -948,6 +1393,281 @@ def UpdateSchedule(request, feature_id, *args, **kwargs): features.update(schedule = schedule) return JsonResponse({ 'success': True }) +def uploadFilesThread(files, department_id, uploaded_by): + for file in files: + uploadFile = UploadFile(file, department_id, uploaded_by) + print(uploadFile.proccessUploadFile()) + +def decryptFile(source): + import tempfile + target = "/tmp/%s" % next(tempfile._get_candidate_names()) + + logger.debug(f"Decrypting source {source}") + + try: + result = subprocess.run(["bash", "-c", f"gpg --output {target} --batch --passphrase {secret_variables.COMETA_UPLOAD_ENCRYPTION_PASSPHRASE} -d {source}"], stdout=subprocess.PIPE, stderr=subprocess.PIPE) + if result.returncode > 0: + # get the error + errOutput = result.stderr.decode('utf-8') + logger.error(errOutput) + raise Exception('Failed to decrypt the file, please contact an administrator.') + return target + except Exception as err: + raise Exception(str(err)) + +class DataDrivenResultsViewset(viewsets.ModelViewSet): + queryset = DataDriven_Runs.objects.none() + serializer_class = FeatureResultSerializer + renderer_classes = (JSONRenderer, ) + + def list(self, request, *args, **kwargs): + run_id = kwargs.get('run_id', None) + user_departments = GetUserDepartments(request) + + if not run_id: + return JsonResponse({ + 'success': False, + 'error': 'No \'run_id\' provided.' + }, status=400) + try: + ddr = DataDriven_Runs.objects.prefetch_related('feature_results').get(pk=run_id, file__department_id__in=user_departments) + except DataDriven_Runs.DoesNotExist: + return JsonResponse({ + 'success': False, + 'error': 'Data Driven Run not found.' + }, status=404) + + page = self.paginate_queryset(ddr.feature_results.all()) + # serialize the data + serialized_data = self.serializer_class(page, many=True).data + # return the data with count, next and previous pages. + return self.get_paginated_response(serialized_data) + + +class DataDrivenViewset(viewsets.ModelViewSet): + queryset = DataDriven_Runs.objects.none() + serializer_class = DataDrivenRunsSerializer + renderer_classes = (JSONRenderer, ) + + def list(self, request, *args, **kwargs): + run_id = kwargs.get('run_id', None) + user_departments = GetUserDepartments(request) + + if run_id: + try: + ddr = DataDriven_Runs.objects.get(pk=run_id, file__department_id__in=user_departments) + except DataDriven_Runs.DoesNotExist: + return JsonResponse({ + 'success': False, + 'error': 'Data Driven Run not found.' + }, status=404) + # get the data related to the run + return JsonResponse({ + 'success': True, + 'result': DataDrivenRunsSerializer(ddr, many=False).data + }) + + ddrs = DataDriven_Runs.objects.filter(file__department_id__in=user_departments).order_by('-date_time', '-run_id') + # get the amount of data per page using the queryset + page = self.paginate_queryset(DataDrivenRunsSerializer.setup_eager_loading(ddrs)) + # serialize the data + serialized_data = DataDrivenRunsSerializer(page, many=True).data + # return the data with count, next and previous pages. + return self.get_paginated_response(serialized_data) + +class DataDrivenFileViewset(viewsets.ModelViewSet): + queryset = File.objects.none() + serializer_class = FileSerializer + renderer_classes = (JSONRenderer, ) + + def getFileContent(self, file: File): + # decrypt file + targetPath = decryptFile(file.path) + + import pandas as pd + try: + df = pd.read_csv(targetPath, header=0, skipinitialspace=True, skip_blank_lines=True) + except ValueError: + df = pd.read_excel(targetPath, header=0) + except: + raise Exception("Unable to parse excel or csv file.") + + # replace " " with "_" and to lower the column names + df.columns = df.columns.str.replace(" ", "_").str.lower() + + # check if feature id or feature name column is present + if 'feature_id' not in df.columns and 'feature_name' not in df.columns: + raise Exception("Missing 'Feature id' or 'Feature Name' columns, please add one and re-try.") + + # convert row to json + json_data = df.to_json(orient='records', lines=True).splitlines() + + # add all the lines to the FileData + rows = (FileData(file=file, data=json.loads(data)) for data in json_data) + + # how many row we want to save in single query + batch_size = 100 + file_data = [] + while True: + batch = list(islice(rows, batch_size)) + if not batch: + break + file_data.extend(FileData.objects.bulk_create(batch, batch_size)) + + return file_data + + def list(self, request, *args, **kwargs): + file_id = kwargs.get('file_id', None) + reparse = request.GET.get('reparse', None) + + try: + file = File.objects.prefetch_related('file').get(pk=file_id) + except File.DoesNotExist: + return JsonResponse({ + "success": False, + "error": "File not found." + }, status=404) + + # get user departments + user_departments = GetUserDepartments(request) + if file.department.department_id not in user_departments: + return JsonResponse({ + "success": False, + "error": "You do not have access to this object." + }, status=403) + + # file.all() comes from the reverse relation between FileData and File model. + file_data = file.file.all() + + if reparse or not file_data: + # delete any old data only works incase of reparse + file_data.delete() + try: + # parse file data + file_data = self.getFileContent(file) + except Exception as err: + return JsonResponse({ + "success": False, + "error": str(err) + }, status=400) + + # paginate the queryset + page = self.paginate_queryset(file_data) + # serialize the paginated data + serialized_data = FileDataSerializer(page, many=True).data + # return data to the user + return self.get_paginated_response(serialized_data) + +class UploadViewSet(viewsets.ModelViewSet): + queryset = File.objects.none() + serializer_class = FileSerializer + renderer_classes = (JSONRenderer, ) + + def list(self, request, *args, **kwargs): + file_id = kwargs.get('file_id', None) + XHR = request.headers.get('X-Requested-With', None) + + # check if file id was passed. + if file_id is None: + return JsonResponse({ 'success': False, 'error': 'Missing file id.' }) + + try: + # get the file from file id + file = File.objects.get(id=file_id) + + # check if user has access to the file + self.userHasAccess(request, file) + + # decrypt file + targetPath = decryptFile(file.path) + # check if fullpath exists + if os.path.exists(targetPath): + # open the file + with open(targetPath, 'rb') as fh: + data = fh.read() + # check if request was made using XHR + if XHR is not None and XHR == 'XMLHttpRequest': + data = base64.b64encode(data) + response = HttpResponse(data, content_type=file.mime) + response['Content-Disposition'] = 'attachment; filename="%s"' % file.name + os.remove(targetPath) + return response + return JsonResponse({ 'success': True }) + except Exception as err: + logger.error(f"Error occured while trying to download the file: {file_id}.") + logger.exception(err) + return JsonResponse({ 'success': False, 'error': 'File does not exists.' }, status=404) + + def put(self, request, *args, **kwargs): + file_id = kwargs.get('file_id', None) + restore = request.POST.get('restore', False) + + # check if file id was passed. + if file_id is None: + return JsonResponse({ 'success': False, 'error': 'Missing file id.' }) + + try: + # get the file from file id + file = File.all_objects.get(id=file_id) + + # check if user has access to the file + self.userHasAccess(request, file) + + if restore == "true": + file.restore() + return JsonResponse({ 'success': True }) + except Exception as err: + logger.error(f"Error occured while trying to update the file: {file_id}.") + logger.exception(err) + return JsonResponse({ 'success': False, 'error': 'File does not exists.' }) + + def create(self, request): + # get the department_id from the request + department_id = request.POST.get('department_id', -1) + if department_id == -1 or departmentExists(department_id) == -1: + return JsonResponse({ 'success': False, 'error': "Department does not exist." }) + + try: + # Spawn thread to upload files in background + t = Thread(target=uploadFilesThread, args=(request.FILES.getlist('files'), department_id, request.session['user']['user_id'])) + t.start() + return JsonResponse({ 'success': True }) + except Exception as e: + return JsonResponse({ 'success': False, 'error': str(e) }) + + def delete(self, request, *args, **kwargs): + file_id = kwargs.get('file_id', None) + + # check if file id was passed. + if file_id is None: + return JsonResponse({ 'success': False, 'error': 'Missing file id.' }) + + try: + # get the file from file id + file = File.objects.get(id=file_id) + + # check if user has access to the file + self.userHasAccess(request, file) + + file.delete() + return JsonResponse({ 'success': True }) + except Exception as err: + logger.error(f"Error occured while trying to delete the file: {file_id}.") + logger.exception(err) + return JsonResponse({ 'success': False, 'error': 'File does not exists.' }) + + def userHasAccess(self, request, file): + # get department id from the userb + department_id = file.department_id + # get user departments + user_departments = GetUserDepartments(request) + + if department_id not in user_departments: + raise Exception('User trying to access file with no access to the department.') + + return True + + class AccountViewset(viewsets.ModelViewSet): """ API endpoint to view or edit accounts @@ -983,24 +1703,28 @@ def patch(self, request, *args, **kwargs): name = data['name'], email = data['email'] ) + user = OIDCAccount.objects.filter(user_id=user_id) if 'favourite_browsers' in data: - OIDCAccount.objects.filter(user_id=user_id).update( favourite_browsers = json.loads(data['favourite_browsers']) ) + user.update( favourite_browsers = json.loads(data['favourite_browsers']) ) if 'settings' in data: - OIDCAccount.objects.filter(user_id=user_id).update( settings = data['settings'] ) + user.update( settings = data['settings'] ) + if 'permission_name' in data: + user_permission = Permissions.objects.filter(permission_name=data['permission_name']) + if user_permission.exists() and request.session['user']['user_permissions']['permission_power'] >= user_permission[0].permission_power: + user.update( + user_permissions = user_permission[0] + ) + else: + return JsonResponse({ 'success': False, 'error': 'You do not have permissions to set higher role than your current role.'}, status=403) if 'departments' in data and not kwargs['usersOwn']: Account_role.objects.filter(user=user_id).delete() - user = OIDCAccount.objects.filter(user_id=user_id) for department in data['departments']: department = Department.objects.filter(department_id = department)[0] Account_role.objects.create( user = user[0], department = department ) - user_permission = Permissions.objects.filter(permission_name=data['permission_name']) - if user_permission.exists(): - user.update( - user_permissions = user_permission[0] - ) + # get user thats been updated user = OIDCAccount.objects.filter(user_id=user_id)[0] # send a websocket to front about the creation @@ -1051,6 +1775,95 @@ def list(self, request): 'results': ActionSerializer(queryset, many=True).data }) +# # FeatureResultByFeatureIdViewSet +# # ... takes parameter FeatureId and returns all results for that ID from featureResults +# # ... this enables showing all results without grouping them by featureRun +# class FeatureResultByFeatureIdViewSet(viewsets.ModelViewSet): +# """ +# API endpoint that allow returns featureResults for a featureID. +# """ +# queryset = Feature_result.available_objects.all() +# serializer_class = FeatureResultSerializer +# renderer_classes = (JSONRenderer, ) +# filter_backends = (filters.OrderingFilter,) +# ordering_fields = ('result_date',) + +# def list(self, request, *args, **kwargs): + +# data = {} + +# # +# # If featureId in request, then return the featureResults for that ID +# # ... this does not take into account the "archived/saved" feature-runs +# # ... TODO/FIXME/XXX ... retrieve also the archived flag from featureRuns +# # ... or set the archived Flag on FeatureResult _and_ featureRun +# # +# if "feature_id" in kwargs: +# # query feature_results for the id and order by date and result_id +# feature_result = self.queryset.filter(feature_id=kwargs['feature_id']).order_by('-result_date', '-feature_result_id') + +# # Check on number of results and return data with success=false or results +# if len(feature_result) > 0: +# logger.debug("Found "+str(len(feature_result))+" results") +# data["result"] = FeatureResultSerializer(feature_result,many=True).data +# else: +# data['success'] = False +# data['error'] = "No feature_result found with id " + kwargs['feature_id'] +# return Response(data) + +# # check if feature_id in GET parameters +# feature_id = request.GET.get('feature_id', False) +# # get if user want only archived runs +# archived = request.GET.get('archived', False) == 'true' +# if feature_id and feature_id.isnumeric(): +# # get all the feature runs for specific run +# feature_runs = Feature_Runs.available_objects.filter(feature=feature_id, archived=archived).order_by('-date_time', '-run_id') +# # get the amount of data per page using the queryset +# page = self.paginate_queryset(FeatureRunsSerializer.fast_loader(feature_runs)) +# # serialize the data +# serialized_data = FeatureRunsSerializer(page, many=True).data +# # return the data with count, next and previous pages. +# return self.get_paginated_response(serialized_data) + +# return JsonResponse({'success': False, 'error': 'No feature_result_id nor feature_id specified...'}, status=400) + + + +# FeatureResultByFeatureIdViewSet +# ... takes parameter FeatureId and returns all results for that ID from featureResults +# ... this enables showing all results without grouping them by featureRun +class FeatureResultByFeatureIdViewSet(viewsets.ModelViewSet): + """ + API endpoint that allow returns featureResults for a featureID. + """ + queryset = Feature_result.available_objects.all() + serializer_class = FeatureResultSerializer + renderer_classes = (JSONRenderer, ) + filter_backends = (filters.OrderingFilter,) + ordering_fields = ('result_date',) + + # @silk_profile(name="FeatureResultByFeatureId") + def list(self, request, *args, **kwargs): + # check if feature_id in GET parameters + feature_id = request.GET.get('feature_id', False) + # get if user want only archived runs + archived = request.GET.get('archived', False) == 'true' + if feature_id and feature_id.isnumeric(): + + # get all the feature runs for specific run + feature_result = self.queryset.filter(feature_id=feature_id, archived=archived).order_by('-result_date', '-feature_result_id') + + # get the amount of data per page using the queryset + page = self.paginate_queryset(feature_result) + serializer = self.get_serializer(page, many=True) + # return the data with count, next and previous pages. + return self.get_paginated_response(serializer.data) + + return JsonResponse({'success': False, 'error': 'No feature_result_id nor feature_id specified...'}, status=400) + + + + class FeatureResultViewSet(viewsets.ModelViewSet): """ API endpoint that allows users to be viewed or edited. @@ -1123,6 +1936,7 @@ def patch(self, request, *args, **kwargs): return JsonResponse({'success': True}, status=202) return JsonResponse({'success': False, "error": 'No feature_result_id specified'}, status=406) + @timediff def list(self, request, *args, **kwargs): data = {} @@ -1144,7 +1958,7 @@ def list(self, request, *args, **kwargs): # get all the feature runs for specific run feature_runs = Feature_Runs.available_objects.filter(feature=feature_id, archived=archived).order_by('-date_time', '-run_id') # get the amount of data per page using the queryset - page = self.paginate_queryset(FeatureRunsSerializer.fast_loader(feature_runs)) + page = self.paginate_queryset(FeatureRunsSerializer.setup_eager_loading(feature_runs)) # serialize the data serialized_data = FeatureRunsSerializer(page, many=True).data # return the data with count, next and previous pages. @@ -1317,12 +2131,12 @@ def delete(self, request, *args, **kwargs): environment_id = self.kwargs['environment_id'] environments = Environment.objects.filter(environment_id=environment_id) if not environments.exists(): - return JsonResponse({"success": False , "error": "Browser_id invalid or doesn't exist."}, status=400) + return JsonResponse({"success": False , "error": "Environment id invalid or doesn't exist."}, status=400) environments.delete() # send a websocket to front about the creation response = requests.post('http://cometa_socket:3001/sendAction', json={ 'type': '[Environments] Remove Environment', - 'environment': IEnvironment(environments[0], many=False).data + 'environment_id': environment_id }) return JsonResponse({"success": True }, status=200) @@ -1406,17 +2220,89 @@ def delete(self, request, *args, **kwargs): return JsonResponse({"success": True }, status=200) +@csrf_exempt +@require_permissions("edit_department") +def UpdateStepTimeout(request, department_id, *args, **kwargs): + + def checkParameter(object, property, valueType): + if property not in object: + raise ValueError(f"Missing mandatory parameter '{property}' from the request body.") + elif not isinstance(object[property], valueType): + raise ValueError(f"'{property}' should be of type '{valueType.__name__}'.") + + return object[property] + + # get body from request + body = json.loads(request.body) + try: + # check if there are required parameters + step_timeout_from = checkParameter(body, 'step_timeout_from', int) + step_timeout_to = checkParameter(body, 'step_timeout_to', int) + except ValueError as err: + return JsonResponse({ + 'success': False, + 'error': str(err) + }, status=400) + + try: + # get department from the db + department = Department.objects.get(pk=department_id) + # get all the features related to the department + features = Feature.objects.filter(department_id=department_id).values_list('feature_id', flat=True) + # get all the steps in these features + steps = Step.objects.filter(feature_id__in=features, timeout=step_timeout_from) + # get total features that will be updated + total_features_updated = len(steps.order_by('feature_id').distinct('feature_id').values_list('feature_id', flat=True)) + # update steps to their new timeout + steps_updated = steps.update(timeout=step_timeout_to) + return JsonResponse({ + "success": True, + "total_features_updated": total_features_updated, + "total_steps_updated": steps_updated + }) + except Department.DoesNotExist: + return JsonResponse({ + "success": False, + "error": "Department does not exists." + }, status=404) + class DepartmentViewSet(viewsets.ModelViewSet): """ API endpoint that allows users to be viewed or edited. """ - queryset = Department.objects.all() + queryset = Department.objects.prefetch_related('files').all() serializer_class = DepartmentSerializer renderer_classes = (JSONRenderer, ) def list(self, request, *args, **kwargs): + # set default value for qs + qs = Department.objects.none() + # check if user is superuser + superuser = request.session['user']['user_permissions']['permission_name'] == "SUPERUSER" + # get show_all_departments permission + show_all_departments = request.session['user']['user_permissions']['show_all_departments'] + # if superuser or has show_all_departments permission show all departments + if superuser or show_all_departments: + qs = Department.objects.prefetch_related( + Prefetch('files', queryset=File.all_objects.all()) + ).all() + else: + # get logged in user departments + user_departments = [x['department_id'] for x in request.session['user']['departments']] + qs = Department.objects.prefetch_related( + Prefetch('files', queryset=File.all_objects.all()) + ).filter(department_id__in=user_departments) + + # get show_department_users permission + show_department_users = request.session['user']['user_permissions']['show_department_users'] + # if user has show_department_users permission the show users aswell + if show_department_users: + results = DepartmentWithUsersSerializer(qs, many=True).data + else: + results = DepartmentSerializer(qs, many=True).data + # return results return Response({ - 'results': DepartmentSerializer(Department.objects.all(), many=True).data + 'results': results }) @require_permissions("create_department") @@ -1433,7 +2319,7 @@ def create(self, request, *args, **kwards): # send a websocket to front about the creation response = requests.post('http://cometa_socket:3001/sendAction', json={ 'type': '[Departments] Add Admin Department', - 'department': IDepartment(department, many=False).data + 'department': DepartmentWithUsersSerializer(department, many=False).data }) # update the user who has been added to the created department @@ -1646,6 +2532,24 @@ def patch(self, request, *args, **kwargs): feature.last_edited_id = request.session['user']['user_id'] feature.last_edited_date = datetime.datetime.utcnow() + """ + Process schedule if requested + """ + # Set schedule of feature if provided in data, if schedule is empty will be removed + set_schedule = False + if 'schedule' in data: + schedule = data['schedule'] + logger.debug("Saveing schedule: "+str(schedule) ) + # Check if schedule is not 'now' + if schedule != '' and schedule != 'now': + # Validate cron format before sending to Behave + if schedule != "" and not CronSlices.is_valid(schedule): + return JsonResponse({ 'success': False, "error": 'Schedule format is invalid.' }, status=200) + set_schedule = True + # Save schedule, at this point is 100% valid and saved + logger.debug("Adding schedule to database") + feature.schedule = schedule + """ Save submitted feature steps """ @@ -1659,26 +2563,14 @@ def patch(self, request, *args, **kwargs): # Save without steps result = feature.save() - """ - Process schedule if requested - """ - # Set schedule of feature if provided in data, if schedule is empty will be removed - if 'schedule' in data: - schedule = data['schedule'] - # Check if schedule is not 'now' - if schedule != 'now': - # Validate cron format before sending to Behave - if schedule != "" and not CronSlices.is_valid(schedule): - return JsonResponse({ 'success': False, "error": 'Schedule format is invalid.' }, status=200) - # Save schedule in Behave docker Crontab - response = set_test_schedule(feature.feature_id, schedule, request.session['user']['user_id']) - if response.status_code != 200: - # Oops, something went wrong while saving schedule - json_data = response.json() - return JsonResponse({ 'success': False, "error": json_data.get('error', 'Something went wrong while saving schedule.') }, status=200) - # Save schedule, at this point is 100% valid and saved - feature.schedule = schedule - feature.save() + if set_schedule: + # Save schedule in Behave docker Crontab + response = set_test_schedule(feature.feature_id, schedule, request.session['user']['user_id']) + if response.status_code != 200: + # Oops, something went wrong while saving schedule + logger.debug("Ooops - something went wrong saveing the schedule. You should probably check the crontab file mounted into docker to be a file and not a directory.") + json_data = response.json() + return JsonResponse({ 'success': False, "error": json_data.get('error', 'Something went wrong while saving schedule. Check crontab directory of docker.') }, status=200) """ Send WebSockets @@ -1783,6 +2675,127 @@ class FolderViewset(viewsets.ModelViewSet): serializer_class = FolderSerializer renderer_classes = (JSONRenderer, ) + ''' + Function find_in_dict + Finds key with specified value inside dictionaries + + Parameters: + @objects Dictionaries on which @key, @value pair are searched + @key Key name that will be searched in @objects + @value If @key name is found then match the value. + + Return: + @result Dict that contains the @key, @value pair. + ''' + def find_in_dict(self, objects, key, value): + for obj in objects.values() if isinstance(objects, dict) else objects: + if obj[key] == value: return obj + result = self.find_in_dict(obj['folders'], key, value) + if result is not None: + return result + + def find_in_dict_for_tree(self, objects, key, value): + for obj in objects.values() if isinstance(objects, dict) else objects: + if obj['type'] != 'folder': continue + if obj[key] == value: return obj + result = self.find_in_dict_for_tree(obj['children'], key, value) + if result is not None: + return result + + def serializeResultsFromRawQueryForTree(self, departments, max_lvl): + logger.debug("Preparing query for recursive folder, feature lookup for tree visualisation.") + query = """ + WITH RECURSIVE recursive_folders AS ( + SELECT bf.*, 1 AS LVL + FROM backend_folder bf + WHERE bf.parent_id_id is null + + UNION + + SELECT bf.*, rf.LVL + 1 + FROM backend_folder bf + + JOIN recursive_folders rf ON bf.parent_id_id = rf.folder_id and rf.LVL < %s + ) + SELECT rf.*, bf.feature_id, bf.feature_name, bd.department_id as d_id, bd.department_name + FROM (recursive_folders rf + LEFT JOIN backend_folder_feature bff + ON rf.folder_id = bff.folder_id) + FULL OUTER JOIN backend_feature bf + ON bf.feature_id = bff.feature_id + JOIN backend_department bd + ON bf.department_id = bd.department_id or rf.department_id = bd.department_id + WHERE + ( + ( rf.department_id IN %s and ( bf.department_id IN %s or bf.department_id is null) ) + or + ( rf.department_id is null and bf.department_id IN %s ) + ) + ORDER BY bd.department_name, bf.depends_on_others, bf.feature_name + """ + + # make a raw query to folders table + results = Folder.objects.raw(query, [max_lvl, departments, departments, departments]) + objectsCreated = { + "departments": {}, + "folders": {} + } + + # loop over table formatted data + for result in results: + # if folder does not already exist in folders variable add a new folder + if result.d_id not in objectsCreated["departments"]: + objectsCreated["departments"][result.d_id] = { + 'id': result.d_id, + 'name': result.department_name, + 'type': 'department', + 'children': [ + { + "name": "Variables", + "type": "variables", + "children": VariablesTreeSerializer(Variable.objects.filter(department_id=result.d_id), many=True).data + } + ] + } + if result.folder_id is not None and result.folder_id not in objectsCreated["folders"]: + objectsCreated["folders"][result.folder_id] = { + 'id': result.folder_id, + 'parent_id': result.parent_id_id, + 'department': result.d_id, + 'name': result.name, + 'type': 'folder', + 'children': [] + } + # if feature_id exists that means feature belongs to folder_id + if result.feature_id is not None: + feature_object = FeatureHasSubFeatureSerializer(Feature.objects.get(feature_id=result.feature_id), many=False).data + if result.folder_id is not None: + objectsCreated["folders"][result.folder_id]['children'].append(feature_object) + else: + objectsCreated['departments'][result.d_id]['children'].append(feature_object) + + # loop over all the folders with parent_id not None + not_none_folders = [v for k, v in objectsCreated["folders"].items() if v['parent_id'] is not None] + for folder in not_none_folders: + obj = self.find_in_dict_for_tree(objectsCreated["folders"], 'id', folder['parent_id']) + if obj is not None: + obj['children'].append(folder) + del objectsCreated["folders"][folder['id']] + + # add all top level folders to the department childrens + folders = [v for k, v in objectsCreated["folders"].items() if v['parent_id'] is None] + for folder in folders: + objectsCreated['departments'][folder['department']]['children'].append(folder) + del objectsCreated["folders"][folder['id']] + + logger.debug("Finished recursive lookup") + + return { + "name": "Home", + "type": "home", + "children": objectsCreated['departments'].values() + } + ''' Function serializeResultsFromRawQuery Turns results in table form to JSON form. @@ -1794,25 +2807,6 @@ class FolderViewset(viewsets.ModelViewSet): @folders JSON formatted data ''' def serializeResultsFromRawQuery(self, results): - ''' - Function find_in_dict - Finds key with specified value inside dictionaries - - Parameters: - @objects Dictionaries on which @key, @value pair are searched - @key Key name that will be searched in @objects - @value If @key name is found then match the value. - - Return: - @result Dict that contains the @key, @value pair. - ''' - def find_in_dict(objects, key, value): - for obj in objects.values() if isinstance(objects, dict) else objects: - if obj[key] == value: return obj - result = find_in_dict(obj['folders'], key, value) - if result is not None: - return result - # save all the folders in this variable folders = {} # loop over table formatted data @@ -1835,7 +2829,7 @@ def find_in_dict(objects, key, value): # loop over all the folders with parent_id not None not_none_folders = [v for k, v in folders.items() if v['parent_id'] is not None] for folder in not_none_folders: - obj = find_in_dict(folders, 'folder_id', folder['parent_id']) + obj = self.find_in_dict(folders, 'folder_id', folder['parent_id']) if obj is not None: obj['folders'].append(folder) del folders[folder['folder_id']] @@ -1877,6 +2871,9 @@ def list(self, request, *args, **kwargs): # get the departments from the session user departments = tuple([x['department_id'] for x in request.session['user']['departments']]) + logger.debug("Departments for FolderViewset %s", departments ) + logger.debug("User object %s", request.session['user']) + logger.debug("UserID: %s", request.session['user']['user_id']) # return empty array if user does not belong to any department if len(departments) == 0: @@ -1884,35 +2881,88 @@ def list(self, request, *args, **kwargs): "folders": [], "features": [] }) + + if request.query_params.get('tree') is not None: + return Response(self.serializeResultsFromRawQueryForTree(departments, MAX_FOLDER_HIERARCHY)) + + # this query does not take into account that user has selected departments in account_role table + # query = ''' + # WITH RECURSIVE recursive_folders AS ( + # SELECT bf.*, 1 AS LVL + # FROM backend_folder bf + # WHERE bf.parent_id_id is null + + # UNION + + # SELECT bf.*, rf.LVL + 1 + # FROM backend_folder bf JOIN recursive_folders rf ON bf.parent_id_id = rf.folder_id and rf.LVL < %s + # ) + # SELECT rf.*, bf.feature_id + # FROM (recursive_folders rf + # LEFT JOIN backend_folder_feature bff + # ON rf.folder_id = bff.folder_id) + # FULL OUTER JOIN backend_feature bf + # ON bf.feature_id = bff.feature_id + # WHERE + # rf.department_id IN %s + # OR + # bf.department_id IN %s + # ORDER BY rf.folder_id; + # ''' + + + # + # give me all folders and features inside recursively in a flat table from my selected departments, + # so folder department must match and feature deparment must match + # folder_id | name | owner_id | parent_id_id | department_id | created_on | lvl | feature_id | userid + # ----------+------------------+----------+--------------+---------------+-------------------------------+-----+------------+-------- + # 83 | MIX | 2 | | 2 | 2021-09-09 22:28:01.274756+00 | 1 | 38 | 320 + # 83 | MIX | 2 | | 2 | 2021-09-09 22:28:01.274756+00 | 1 | 31 | 320 + + # Testcases: + # I have access to department number 2. + # 1. There is a folder with department number 2 and inside are two features belonging to department number 2 and 3. I will see the folder and one feature. + # 2. There is a folder with department number 3 and inside are two features belonging to department number 2 and 3. I will not see the folder. I will not see the features. + # 3. Moving a new feature only gives me access to department 2 + + # I change my access to department number 2 + 3 + # in case 1: I can see both features. + # in case 2: I can see the folder and both features. + # in case 3: I can move features between departments. query = ''' - WITH RECURSIVE recursive_folders AS ( - SELECT bf.*, 1 AS LVL - FROM backend_folder bf - WHERE bf.parent_id_id is null + WITH RECURSIVE recursive_folders AS ( + SELECT bf.*, 1 AS LVL + FROM backend_folder bf + WHERE bf.parent_id_id is null - UNION + UNION - SELECT bf.*, rf.LVL + 1 - FROM backend_folder bf JOIN recursive_folders rf ON bf.parent_id_id = rf.folder_id and rf.LVL < %s - ) - SELECT rf.*, bf.feature_id - FROM (recursive_folders rf - LEFT JOIN backend_folder_feature bff - ON rf.folder_id = bff.folder_id) - FULL OUTER JOIN backend_feature bf - ON bf.feature_id = bff.feature_id - WHERE - rf.department_id IN %s - OR - bf.department_id IN %s - ORDER BY rf.folder_id; + SELECT bf.*, rf.LVL + 1 + FROM backend_folder bf + + JOIN recursive_folders rf ON bf.parent_id_id = rf.folder_id and rf.LVL < %s + ) + SELECT rf.*, bf.feature_id + FROM (recursive_folders rf + LEFT JOIN backend_folder_feature bff + ON rf.folder_id = bff.folder_id) + FULL OUTER JOIN backend_feature bf + ON bf.feature_id = bff.feature_id + WHERE + ( rf.department_id IN %s and ( bf.department_id IN %s or bf.department_id is null) ) + or + ( rf.department_id is null and bf.department_id IN %s ) + ORDER BY rf.folder_id ''' + # make a raw query to folders table - results = Folder.objects.raw(query, [MAX_FOLDER_HIERARCHY, departments, departments]) + results = Folder.objects.raw(query, [MAX_FOLDER_HIERARCHY, departments, departments, departments]) + logger.debug("Query: %s" % results) # serialize raw data to JSON folders = self.serializeResultsFromRawQuery(results) + logger.debug("Folders: %s" % folders) # add features with parent none to final_dict.features if None in folders: @@ -2048,54 +3098,20 @@ def patch(self, request, *args, **kwargs): 'feature': FeatureSerializer(feat, many=False).data, 'exclude': [request.session['user']['user_id']] }) - if new_folder == old_folder: - # Send folder websockets - requests.post('http://cometa_socket:3001/updatedObjects/folders') - return JsonResponse({"success": True}, status=200) - - if old_folder == new_folder: - error = "You can't move the feature to the same folder..." - return JsonResponse({"success": False , "error": error}, status=400) - - # if old_folder is None that means the instance does not exist # if new_folder is None that means we need to delete instance if new_folder is not None: newFolder = Folder.objects.filter(folder_id=new_folder) if str(new_folder).isnumeric() else Folder.objects.filter(name=new_folder) - if not newFolder.exists() and newFolder.department.department_id != new_folder: + if not newFolder.exists(): error = "No folder found with id or name %s...." % str(new_folder) return JsonResponse({"success": False , "error": error}, status=400) newFolder = newFolder[0] - # As the user now can move testcases between departments, there is no need to check if the destination folder pertains to the same department as the origin - # check if newFolder belongs to in the feature department - # if newFolder.department.department_id != feat.department_id: - # error = "Requested feature can not be moved to a folder that does not belong to the feature department." - # return JsonResponse({"success": False , "error": error}, status=200) - - if old_folder is not None: - oldFolder = Folder.objects.filter(folder_id=old_folder) if str(old_folder).isnumeric() else Folder.objects.filter(name=old_folder) - if not oldFolder.exists(): - error = "No folder found with id or name %s...." % str(old_folder) - return JsonResponse({"success": False , "error": error}, status=400) - oldFolder = oldFolder[0] + Folder_Feature.objects.filter(feature=feat).delete() - # do some stuff - if old_folder is not None and new_folder is not None: - obj = Folder_Feature.objects.filter(folder=oldFolder, feature=feature) - if not obj.exists(): - error = "No instance found with folder_id %d and feature_id %d combination..." % (old_folder, feature) - return JsonResponse({"success": False , "error": error}, status=400) - obj.update(folder=new_folder) - elif new_folder is None: - obj = Folder_Feature.objects.filter(folder=oldFolder, feature=feature) - if not obj.exists(): - error = "No instance found with folder_id %d and feature_id %d combination..." % (old_folder, feature) - return JsonResponse({"success": False , "error": error}, status=400) - obj.delete() - elif old_folder is None: + if new_folder is not None: obj = Folder_Feature(folder=newFolder, feature=feat) - obj.save(); + obj.save() # update websockets requests.post('http://cometa_socket:3001/updatedObjects/folders') @@ -2187,94 +3203,151 @@ def Encrypt(request): result = decrypt(text) return JsonResponse({ 'result': result }, status=200) -class EnvironmentVariablesViewSet(viewsets.ModelViewSet): +class VariablesViewSet(viewsets.ModelViewSet): - queryset = EnvironmentVariables.objects.all() - serializer_class = EnvironmentVariablesSerializer + queryset = Variable.objects.all() + serializer_class = VariablesSerializer renderer_classes = (JSONRenderer, ) def list(self, request, *args, **kwargs): - result = EnvironmentVariables.objects.all() - data = EnvironmentVariablesSerializer(EnvironmentVariablesSerializer.fast_loader(result), many=True).data + user_departments = GetUserDepartments(request) + result = Variable.objects.filter(department__department_id__in=user_departments) + data = VariablesSerializer(VariablesSerializer.fast_loader(result), many=True).data return JsonResponse(data, safe=False) + def validator(self, data, key, obj=None): + logger.debug("-------------------------------------------------") + logger.debug("Checking key: %s " % key) + if key not in data: + raise Exception(f"{key} is required.") + + value = data[key] + if obj is not None: + # try to validate for the rest + try: + # on feature we except null values + if key == 'feature' and value is None: + logger.debug("Found value=null for FeatureID, which is ok for new variables.") + else: + value = obj.objects.get(pk=data[key]) + logger.debug("Found value: %s " % value) + except: + raise Exception(f"{key} does not exists.") + logger.debug("Using value: %s " % value) + return value + + def optionalValidator(self, data, key, default): + return data.get(key, default) + + def optionalValidatorWithObject(self, data, key, default, obj=None): + value = data.get(key, default) + if obj is not None and isinstance(value, int): + try: + return obj.objects.get(pk=value) + except: + raise Exception(f"{key} does not exists.") + return value + + @require_permissions("create_variable") def create(self, request, *args, **kwargs): - # Get parameters from request body - data=json.loads(request.body) - department_id = data['department_id'] - environment_id = data['environment_id'] - env_variables = data['variables'] - - # Get deparment of current variables - department = Department.objects.filter(department_id=department_id) - if not department.exists(): - return JsonResponse({"success": False, "error": "The department you are looking for does not exist." }, status=200) - - # Get environment of current variables - environment = Environment.objects.filter(environment_id=environment_id) - if not environment.exists(): - return JsonResponse({"success": False, "error": "The environment you are looking for does not exist." }, status=200) - - # Delete previous variables for current department and environment - EnvironmentVariables.objects.filter(environment=environment[0], department=department[0]).delete() - - # Recreate variables - for var in env_variables: - variable_name = var['variable_name'] - variable_value = var['variable_value'] - encrypted = var.get('encrypted', False) - # Only encrypt value if user checked the encrypt checkbox and it isn't already encrypted - if encrypted and not variable_value.startswith(ENCRYPTION_START): - variable_value = encrypt(variable_value) - # Create variable pair - environment_variable = EnvironmentVariables( - department=department[0], - environment=environment[0], - variable_name=variable_name, - variable_value=variable_value, - encrypted=encrypted - ) - # Save variable - environment_variable.save() - - return JsonResponse({"success": True }, status=200) + data = json.loads(request.body) + logger.debug("=================================================") + logger.debug("Received request to save variables.") + logger.debug("=================================================") + logger.debug("Will try validating the data: %s" % data) + try: + valid_data = { + "department": self.validator(data, 'department', Department), + "environment": self.validator(data, 'environment', Environment), + "feature": self.validator(data, 'feature', Feature), + "variable_name": self.validator(data, 'variable_name'), + "variable_value": self.validator(data, 'variable_value'), + "encrypted": self.optionalValidator(data, 'encrypted', False), + "based": self.optionalValidator(data, 'based', 'feature'), + "created_by": self.optionalValidatorWithObject(data, 'created_by', request.session['user']['user_id'], OIDCAccount), + "updated_by": self.optionalValidatorWithObject(data, 'updated_by', request.session['user']['user_id'], OIDCAccount) + } + # encrypt the value if needed + if valid_data['encrypted'] and not valid_data['variable_value'].startswith(ENCRYPTION_START): + valid_data['variable_value'] = encrypt(valid_data['variable_value']) + # check if feature belongs to the department, only if feature is not null + if valid_data['feature'] is not None and valid_data['feature'].department_id != None and valid_data['feature'].department_id != valid_data['department'].department_id: + raise Exception(f"Feature does not belong the same department id, please check.") + logger.debug("Validation done. I think, we should validate that variable name is unique in relation to department or environment or feature ... FIXME??") + except Exception as err: + logger.debug("Exception while creating variable: %s" % err) + return JsonResponse({ + 'success': False, + 'error': str(err) + }, status=400) + logger.debug("Will try creating the variable with data: %s" % valid_data) + try: + # create the new variable + variable = Variable.objects.create(**valid_data) + logger.debug("Data has been saved to database.") + logger.debug("=================================================") + except IntegrityError as err: + logger.error("IntegrityError occured ... unique variable already exists.") + logger.exception(err) + return JsonResponse({ + 'success': False, + 'error': 'Variable already exists with specified requirements, please update the variable.' + }, status=400) + return JsonResponse({ + "success": True, + "data": VariablesSerializer(variable, many=False).data + }, status=201) + @require_permissions("edit_variable") def patch(self, request, *args, **kwargs): - data = json.loads(request.body) - - if 'variable_id' in kwargs: - env_var = EnvironmentVariables.objects.filter(id=kwargs['variable_id']) - else: - return JsonResponse({"success": False, "error": "No variable id found in request." }, status=405) - - if not env_var.exists(): - return JsonResponse({"success": False, "error": "The variable you are looking for does not exist." }, status=404) - - env_var.update(variable_name=data['variable_name'], variable_value=data['variable_value']) - + try: + # get the variable + variable = self.validator(kwargs, 'id', Variable) + variable.department = self.optionalValidatorWithObject(data, 'department', variable.department, Department) + variable.environment = self.optionalValidatorWithObject(data, 'environment', variable.environment, Environment) + variable.feature = self.optionalValidatorWithObject(data, 'feature', variable.feature, Feature) + variable.variable_name = self.optionalValidator(data, 'variable_name', variable.variable_name) + variable.variable_value = self.optionalValidator(data, 'variable_value', variable.variable_value) + variable.encrypted = self.optionalValidator(data, 'encrypted', variable.encrypted) + variable.based = self.optionalValidator(data, 'based', variable.based) + variable.updated_by = self.optionalValidatorWithObject(data, 'updated_by', request.session['user']['user_id'], OIDCAccount) + # encrypt the value if needed + if variable.encrypted and not variable.variable_value.startswith(ENCRYPTION_START): + variable.variable_value = encrypt(variable.variable_value) + + # check if feature belongs to the department + # commented for now + # this condition raises exception if variable.feature coming from front is null, which prevents user from patching old variables + # if variable.feature.department_id != variable.department.department_id: + # raise Exception(f"Feature does not belong the same department id, please check.") + variable.save() + except Exception as err: + return JsonResponse({ + 'success': False, + 'error': str(err) + }, status=400) return JsonResponse({ "success": True, + "data": VariablesSerializer(variable, many=False).data }, status=200) @require_permissions("delete_variable") def delete(self, request, *args, **kwargs): - - if 'variable_id' in kwargs: - env_var = EnvironmentVariables.objects.filter(id=kwargs['variable_id']) - else: - return JsonResponse({"success": False, "error": "No variable id found in request." }, status=404) - - if not env_var.exists(): - return JsonResponse({"success": False, "error": "The variable you are looking for does not exist." }, status=404) - - env_var.delete() - + try: + # get the variable + variable = self.validator(kwargs, 'id', Variable) + variable.delete() + except Exception as err: + return JsonResponse({ + 'success': False, + 'error': str(err) + }, status=400) return JsonResponse({ - "success": True, - }, status=200); + "success": True + }, status=200) class FeatureRunViewSet(viewsets.ModelViewSet): diff --git a/backend/src/cometa_pj/settings.py b/backend/src/cometa_pj/settings.py index a803d3c3..f6fe10ae 100755 --- a/backend/src/cometa_pj/settings.py +++ b/backend/src/cometa_pj/settings.py @@ -169,6 +169,9 @@ }, ] +FILE_UPLOAD_HANDLERS = [ + 'backend.utility.uploadFile.TempFileUploadHandler', +] # Internationalization # https://docs.djangoproject.com/en/2.0/topics/i18n/ @@ -199,39 +202,67 @@ # LOGGER CONFIGURATION # From: https://stackoverflow.com/questions/52004910/django-and-docker-outputting-information-to-console LOGGING = { - "version": 1, - "disable_existing_loggers": False, - "formatters": { - "verbose": { - "format": "[{asctime}] {levelname} {name}:{funcName} [{threadName}({thread}):{process}] - {message}", - "style": "{", + 'version': 1, + 'disable_existing_loggers': False, + 'filters': { + 'require_debug_false': { + '()': 'django.utils.log.RequireDebugFalse', }, - "simple": { - "format": "[{asctime}] {levelname} - {message}", - "style": "{", + 'require_debug_true': { + '()': 'django.utils.log.RequireDebugTrue', }, }, - "handlers": { - "console": { - "class": "logging.StreamHandler", - "formatter": "simple", - "level": "DEBUG" + 'formatters': { + 'verbose': { + "format": "[%(asctime)s][%(levelname)s][%(process)d:%(processName)s][%(thread)d:%(threadName)s] - %(message)s" }, - "errorFile": { - "class": "logging.handlers.RotatingFileHandler", - "formatter": "verbose", - "level": "ERROR", - "filename": "/opt/code/logs/error.log", - "maxBytes": 1000000, # 1MB - "backupCount": 4, - "encoding": "utf-8" + 'django.server': { + '()': 'django.utils.log.ServerFormatter', + 'format': '[%(server_time)s] %(message)s', } }, - "loggers": { - "*": { - "handlers": ["errorFile", "console"], - "level": "DEBUG", - "propagate": True + 'handlers': { + 'console': { + 'level': 'INFO', + 'filters': ['require_debug_true'], + 'class': 'logging.StreamHandler', + }, + # Custom handler which we will use with logger 'django'. + # We want errors/warnings to be logged when DEBUG=False + 'console_on_not_debug': { + 'level': 'WARNING', + 'filters': ['require_debug_false'], + 'class': 'logging.StreamHandler', + 'formatter': 'verbose' + }, + 'django.server': { + 'level': 'INFO', + 'class': 'logging.StreamHandler', + 'formatter': 'django.server', + }, + 'mail_admins': { + 'level': 'ERROR', + 'filters': ['require_debug_false'], + 'class': 'django.utils.log.AdminEmailHandler' + }, + 'error_file': { + 'level': 'WARNING', + 'class': 'logging.handlers.RotatingFileHandler', + 'filename': "logs/error.log", + 'maxBytes': 1000000, + 'backupCount': 7, + 'formatter': 'verbose' } + }, + 'loggers': { + 'django': { + 'handlers': ['console', 'mail_admins', 'console_on_not_debug', 'error_file'], + 'level': 'INFO', + }, + 'django.server': { + 'handlers': ['django.server'], + 'level': 'INFO', + 'propagate': False, + }, } -} +} \ No newline at end of file diff --git a/backend/src/cometa_pj/urls.py b/backend/src/cometa_pj/urls.py index fb6deed7..8fb044e1 100755 --- a/backend/src/cometa_pj/urls.py +++ b/backend/src/cometa_pj/urls.py @@ -26,6 +26,8 @@ router.register(r'feature_results/(?P[0-9]+)', views.FeatureResultViewSet) router.register(r'feature_results', views.FeatureResultViewSet) router.register(r'feature_results/(?P[0-9]+)/step_results/(?P[0-9]+)', views.StepResultViewSet) +# ask for all feature results by feature_id +router.register(r'feature_results_by_featureid', views.FeatureResultByFeatureIdViewSet) router.register(r'step_result/(?P[0-9]+)', views.StepResultViewSet) router.register(r'environments/(?P[0-9]+)/(?P[0-9a-zA-Z -_.]+)', views.EnvironmentViewSet) router.register(r'environments/(?P[0-9]+)', views.EnvironmentViewSet) @@ -49,14 +51,20 @@ router.register(r'features/(?P[0-9]+)', views.FeatureViewSet) router.register(r'features', views.FeatureViewSet) router.register(r'actions', views.ActionViewSet) -router.register(r'variables/(?P[0-9]+)', views.EnvironmentVariablesViewSet) -router.register(r'variables', views.EnvironmentVariablesViewSet) +router.register(r'variables/(?P[0-9]+)', views.VariablesViewSet) +router.register(r'variables', views.VariablesViewSet) router.register(r'invite', views.InviteViewSet) router.register(r'feature_run/(?P[0-9]+)', views.FeatureRunViewSet) router.register(r'feature_run', views.FeatureRunViewSet) router.register(r'authproviders', views.AuthenticationProviderViewSet) router.register(r'schedule', views.ScheduleViewSet) router.register(r'subscriptions', views.SubscriptionsViewSet) +router.register(r'uploads/(?P[0-9]+)', views.UploadViewSet) +router.register(r'uploads', views.UploadViewSet) +router.register(r'data_driven/results/(?P[0-9]+)', views.DataDrivenResultsViewset) +router.register(r'data_driven/file/(?P[0-9]+)', views.DataDrivenFileViewset) +router.register(r'data_driven/(?P[0-9]+)', views.DataDrivenViewset) +router.register(r'data_driven', views.DataDrivenViewset) # provides numbers of system usage router.register(r'cometausage', views.CometaUsageViewSet) @@ -88,6 +96,8 @@ def static(prefix, view=serve, **kwargs): url(r'^stepsByName/', views.GetStepsByName), url(r'^schedule/(?P.+)/', views.UpdateSchedule), url(r'^exectest/', views.runTest), + url(r'^exec_batch/', views.runBatch), + url(r'^exec_data_driven/', views.runDataDriven), url(r'^info/', views.GetInfo), url(r'^migrateScreenshots', views.MigrateScreenshots), url(r'^checkBrowserstackVideo', views.CheckBrowserstackVideo), @@ -104,6 +114,7 @@ def static(prefix, view=serve, **kwargs): url(r'^userDetails/', views.userDetails), url(r'^isFeatureRunning/(?P[0-9]+)/', views.featureRunning), url(r'^noVNC/(?P[0-9]+)/', views.noVNCProxy), + url(r'featureStatus/(?P[0-9]+)/', views.viewRunStatus), # Payments API url(r'^invoices/(?P[0-9]+)/', payments.getInvoices), url(r'^invoices/', payments.getInvoices), @@ -111,6 +122,8 @@ def static(prefix, view=serve, **kwargs): url(r'^updatePayment/', payments.updatePayment), url(r'^customerPortal', payments.getCustomerPortal), url(r'^createDonation/', payments.createDonation), + # Additional department updates + url(r'^departments/(?P[0-9]+)/updateStepTimeout/', views.UpdateStepTimeout), # Reporting url(r'^cometausage/', views.CometaUsage), ] + static('/static/', document_root=STATIC_ADMIN_FILES) diff --git a/backend/src/defaults/permissions.json b/backend/src/defaults/permissions.json index 6af86403..ee1cddd6 100644 --- a/backend/src/defaults/permissions.json +++ b/backend/src/defaults/permissions.json @@ -4,6 +4,7 @@ "pk": 1, "fields": { "permission_name": "SUPERUSER", + "permission_power": 100, "create_account": true, "edit_account": true, "delete_account": true, @@ -46,6 +47,7 @@ "pk": 2, "fields": { "permission_name": "DEVOPS", + "permission_power": 20, "create_feature": true, "edit_feature": true, "run_feature": true, @@ -60,6 +62,7 @@ "pk": 3, "fields": { "permission_name": "ANALYSIS", + "permission_power": 0, "create_feature": true, "run_feature": true, "view_feature": true, @@ -71,6 +74,7 @@ "pk": 4, "fields": { "permission_name": "ANONYMOUS", + "permission_power": 10, "create_feature": true, "edit_feature": true, "run_feature": true, diff --git a/backend/src/defaults/superusers.json b/backend/src/defaults/superusers.json index 5fe1f84d..60ec4759 100644 --- a/backend/src/defaults/superusers.json +++ b/backend/src/defaults/superusers.json @@ -3,30 +3,8 @@ "model": "auth.user", "pk": 1, "fields": { - "username": "rroeber", - "password": "pbkdf2_sha256$216000$Sx2HHs0mG6h6$2SSA9Er6S7jeY89IoVFHVJUPBSiM+x3mNJiU8cXNkRM=", - "is_superuser": true, - "is_staff": true, - "is_active": true - } - }, - { - "model": "auth.user", - "pk": 2, - "fields": { - "username": "alex", - "password": "pbkdf2_sha256$216000$Sx2HHs0mG6h6$2SSA9Er6S7jeY89IoVFHVJUPBSiM+x3mNJiU8cXNkRM=", - "is_superuser": true, - "is_staff": true, - "is_active": true - } - }, - { - "model": "auth.user", - "pk": 3, - "fields": { - "username": "arslansb", - "password": "pbkdf2_sha256$216000$75OUMHFs4bDb$xKWevyZxtnIGKHSVFoR5DfkSWPxGlFKTiJfPbEoTT/w=", + "username": "admin", + "password": "***REMOVED***", "is_superuser": true, "is_staff": true, "is_active": true diff --git a/backend/src/poetry.lock b/backend/src/poetry.lock index 0e72416b..524e6fa8 100644 --- a/backend/src/poetry.lock +++ b/backend/src/poetry.lock @@ -1,35 +1,56 @@ +# This file is automatically @generated by Poetry 1.6.1 and should not be changed by hand. + [[package]] name = "arabic-reshaper" -version = "2.1.3" -description = "Reconstruct Arabic sentences to be used in applications that don't support Arabic" -category = "main" +version = "3.0.0" +description = "Reconstruct Arabic sentences to be used in applications that do not support Arabic" optional = false python-versions = "*" - -[package.dependencies] -future = "*" +files = [ + {file = "arabic_reshaper-3.0.0-py3-none-any.whl", hash = "sha256:3f71d5034bb694204a239a6f1ebcf323ac3c5b059de02259235e2016a1a5e2dc"}, + {file = "arabic_reshaper-3.0.0.tar.gz", hash = "sha256:ffcd13ba5ec007db71c072f5b23f420da92ac7f268512065d49e790e62237099"}, +] [package.extras] -with-fonttools = ["fonttools (>=3.0)", "fonttools (>=4.0)"] +with-fonttools = ["fonttools (>=4.0)"] [[package]] name = "asgiref" -version = "3.3.4" +version = "3.7.2" description = "ASGI specs, helper code, and adapters" -category = "main" optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" +files = [ + {file = "asgiref-3.7.2-py3-none-any.whl", hash = "sha256:89b2ef2247e3b562a16eef663bc0e2e703ec6468e2fa8a5cd61cd449786d4f6e"}, + {file = "asgiref-3.7.2.tar.gz", hash = "sha256:9e0ce3aa93a819ba5b45120216b23878cf6e8525eb3848653452b4192b92afed"}, +] + +[package.dependencies] +typing-extensions = {version = ">=4", markers = "python_version < \"3.11\""} [package.extras] -tests = ["pytest", "pytest-asyncio", "mypy (>=0.800)"] +tests = ["mypy (>=0.800)", "pytest", "pytest-asyncio"] + +[[package]] +name = "asn1crypto" +version = "1.5.1" +description = "Fast ASN.1 parser and serializer with definitions for private keys, public keys, certificates, CRL, OCSP, CMS, PKCS#3, PKCS#7, PKCS#8, PKCS#12, PKCS#5, X.509 and TSP" +optional = false +python-versions = "*" +files = [ + {file = "asn1crypto-1.5.1-py2.py3-none-any.whl", hash = "sha256:db4e40728b728508912cbb3d44f19ce188f218e9eba635821bb4b68564f8fd67"}, + {file = "asn1crypto-1.5.1.tar.gz", hash = "sha256:13ae38502be632115abf8a24cbe5f4da52e3b5231990aff31123c805306ccb9c"}, +] [[package]] name = "awesome-slugify" version = "1.6.5" description = "Python flexible slugify function" -category = "main" optional = false python-versions = "*" +files = [ + {file = "awesome-slugify-1.6.5.tar.gz", hash = "sha256:bbdec3fa2187917473a2efad092b57f7125a55f841a7cf6a1773178d32ccfd71"}, +] [package.dependencies] regex = "*" @@ -37,15 +58,26 @@ Unidecode = ">=0.04.14,<0.05" [[package]] name = "bcrypt" -version = "3.2.0" +version = "3.2.2" description = "Modern password hashing for your software and your servers" -category = "main" optional = false python-versions = ">=3.6" +files = [ + {file = "bcrypt-3.2.2-cp36-abi3-macosx_10_10_universal2.whl", hash = "sha256:7180d98a96f00b1050e93f5b0f556e658605dd9f524d0b0e68ae7944673f525e"}, + {file = "bcrypt-3.2.2-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:61bae49580dce88095d669226d5076d0b9d927754cedbdf76c6c9f5099ad6f26"}, + {file = "bcrypt-3.2.2-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:88273d806ab3a50d06bc6a2fc7c87d737dd669b76ad955f449c43095389bc8fb"}, + {file = "bcrypt-3.2.2-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:6d2cb9d969bfca5bc08e45864137276e4c3d3d7de2b162171def3d188bf9d34a"}, + {file = "bcrypt-3.2.2-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2b02d6bfc6336d1094276f3f588aa1225a598e27f8e3388f4db9948cb707b521"}, + {file = "bcrypt-3.2.2-cp36-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:a2c46100e315c3a5b90fdc53e429c006c5f962529bc27e1dfd656292c20ccc40"}, + {file = "bcrypt-3.2.2-cp36-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:7d9ba2e41e330d2af4af6b1b6ec9e6128e91343d0b4afb9282e54e5508f31baa"}, + {file = "bcrypt-3.2.2-cp36-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:cd43303d6b8a165c29ec6756afd169faba9396a9472cdff753fe9f19b96ce2fa"}, + {file = "bcrypt-3.2.2-cp36-abi3-win32.whl", hash = "sha256:4e029cef560967fb0cf4a802bcf4d562d3d6b4b1bf81de5ec1abbe0f1adb027e"}, + {file = "bcrypt-3.2.2-cp36-abi3-win_amd64.whl", hash = "sha256:7ff2069240c6bbe49109fe84ca80508773a904f5a8cb960e02a977f7f519b129"}, + {file = "bcrypt-3.2.2.tar.gz", hash = "sha256:433c410c2177057705da2a9f2cd01dd157493b2a7ac14c8593a16b3dab6b6bfb"}, +] [package.dependencies] cffi = ">=1.1" -six = ">=1.4.1" [package.extras] tests = ["pytest (>=3.2.1,!=3.3.0)"] @@ -53,19 +85,87 @@ typecheck = ["mypy"] [[package]] name = "certifi" -version = "2020.12.5" +version = "2023.7.22" description = "Python package for providing Mozilla's CA Bundle." -category = "main" optional = false -python-versions = "*" +python-versions = ">=3.6" +files = [ + {file = "certifi-2023.7.22-py3-none-any.whl", hash = "sha256:92d6037539857d8206b8f6ae472e8b77db8058fec5937a1ef3f54304089edbb9"}, + {file = "certifi-2023.7.22.tar.gz", hash = "sha256:539cc1d13202e33ca466e88b2807e29f4c13049d6d87031a3c110744495cb082"}, +] [[package]] name = "cffi" -version = "1.14.5" +version = "1.15.1" description = "Foreign Function Interface for Python calling C code." -category = "main" optional = false python-versions = "*" +files = [ + {file = "cffi-1.15.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:a66d3508133af6e8548451b25058d5812812ec3798c886bf38ed24a98216fab2"}, + {file = "cffi-1.15.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:470c103ae716238bbe698d67ad020e1db9d9dba34fa5a899b5e21577e6d52ed2"}, + {file = "cffi-1.15.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:9ad5db27f9cabae298d151c85cf2bad1d359a1b9c686a275df03385758e2f914"}, + {file = "cffi-1.15.1-cp27-cp27m-win32.whl", hash = "sha256:b3bbeb01c2b273cca1e1e0c5df57f12dce9a4dd331b4fa1635b8bec26350bde3"}, + {file = "cffi-1.15.1-cp27-cp27m-win_amd64.whl", hash = "sha256:e00b098126fd45523dd056d2efba6c5a63b71ffe9f2bbe1a4fe1716e1d0c331e"}, + {file = "cffi-1.15.1-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:d61f4695e6c866a23a21acab0509af1cdfd2c013cf256bbf5b6b5e2695827162"}, + {file = "cffi-1.15.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:ed9cb427ba5504c1dc15ede7d516b84757c3e3d7868ccc85121d9310d27eed0b"}, + {file = "cffi-1.15.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:39d39875251ca8f612b6f33e6b1195af86d1b3e60086068be9cc053aa4376e21"}, + {file = "cffi-1.15.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:285d29981935eb726a4399badae8f0ffdff4f5050eaa6d0cfc3f64b857b77185"}, + {file = "cffi-1.15.1-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3eb6971dcff08619f8d91607cfc726518b6fa2a9eba42856be181c6d0d9515fd"}, + {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:21157295583fe8943475029ed5abdcf71eb3911894724e360acff1d61c1d54bc"}, + {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5635bd9cb9731e6d4a1132a498dd34f764034a8ce60cef4f5319c0541159392f"}, + {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2012c72d854c2d03e45d06ae57f40d78e5770d252f195b93f581acf3ba44496e"}, + {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd86c085fae2efd48ac91dd7ccffcfc0571387fe1193d33b6394db7ef31fe2a4"}, + {file = "cffi-1.15.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:fa6693661a4c91757f4412306191b6dc88c1703f780c8234035eac011922bc01"}, + {file = "cffi-1.15.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:59c0b02d0a6c384d453fece7566d1c7e6b7bae4fc5874ef2ef46d56776d61c9e"}, + {file = "cffi-1.15.1-cp310-cp310-win32.whl", hash = "sha256:cba9d6b9a7d64d4bd46167096fc9d2f835e25d7e4c121fb2ddfc6528fb0413b2"}, + {file = "cffi-1.15.1-cp310-cp310-win_amd64.whl", hash = "sha256:ce4bcc037df4fc5e3d184794f27bdaab018943698f4ca31630bc7f84a7b69c6d"}, + {file = "cffi-1.15.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3d08afd128ddaa624a48cf2b859afef385b720bb4b43df214f85616922e6a5ac"}, + {file = "cffi-1.15.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3799aecf2e17cf585d977b780ce79ff0dc9b78d799fc694221ce814c2c19db83"}, + {file = "cffi-1.15.1-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a591fe9e525846e4d154205572a029f653ada1a78b93697f3b5a8f1f2bc055b9"}, + {file = "cffi-1.15.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3548db281cd7d2561c9ad9984681c95f7b0e38881201e157833a2342c30d5e8c"}, + {file = "cffi-1.15.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:91fc98adde3d7881af9b59ed0294046f3806221863722ba7d8d120c575314325"}, + {file = "cffi-1.15.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:94411f22c3985acaec6f83c6df553f2dbe17b698cc7f8ae751ff2237d96b9e3c"}, + {file = "cffi-1.15.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:03425bdae262c76aad70202debd780501fabeaca237cdfddc008987c0e0f59ef"}, + {file = "cffi-1.15.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:cc4d65aeeaa04136a12677d3dd0b1c0c94dc43abac5860ab33cceb42b801c1e8"}, + {file = "cffi-1.15.1-cp311-cp311-win32.whl", hash = "sha256:a0f100c8912c114ff53e1202d0078b425bee3649ae34d7b070e9697f93c5d52d"}, + {file = "cffi-1.15.1-cp311-cp311-win_amd64.whl", hash = "sha256:04ed324bda3cda42b9b695d51bb7d54b680b9719cfab04227cdd1e04e5de3104"}, + {file = "cffi-1.15.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50a74364d85fd319352182ef59c5c790484a336f6db772c1a9231f1c3ed0cbd7"}, + {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e263d77ee3dd201c3a142934a086a4450861778baaeeb45db4591ef65550b0a6"}, + {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cec7d9412a9102bdc577382c3929b337320c4c4c4849f2c5cdd14d7368c5562d"}, + {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4289fc34b2f5316fbb762d75362931e351941fa95fa18789191b33fc4cf9504a"}, + {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:173379135477dc8cac4bc58f45db08ab45d228b3363adb7af79436135d028405"}, + {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:6975a3fac6bc83c4a65c9f9fcab9e47019a11d3d2cf7f3c0d03431bf145a941e"}, + {file = "cffi-1.15.1-cp36-cp36m-win32.whl", hash = "sha256:2470043b93ff09bf8fb1d46d1cb756ce6132c54826661a32d4e4d132e1977adf"}, + {file = "cffi-1.15.1-cp36-cp36m-win_amd64.whl", hash = "sha256:30d78fbc8ebf9c92c9b7823ee18eb92f2e6ef79b45ac84db507f52fbe3ec4497"}, + {file = "cffi-1.15.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:198caafb44239b60e252492445da556afafc7d1e3ab7a1fb3f0584ef6d742375"}, + {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5ef34d190326c3b1f822a5b7a45f6c4535e2f47ed06fec77d3d799c450b2651e"}, + {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8102eaf27e1e448db915d08afa8b41d6c7ca7a04b7d73af6514df10a3e74bd82"}, + {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5df2768244d19ab7f60546d0c7c63ce1581f7af8b5de3eb3004b9b6fc8a9f84b"}, + {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a8c4917bd7ad33e8eb21e9a5bbba979b49d9a97acb3a803092cbc1133e20343c"}, + {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0e2642fe3142e4cc4af0799748233ad6da94c62a8bec3a6648bf8ee68b1c7426"}, + {file = "cffi-1.15.1-cp37-cp37m-win32.whl", hash = "sha256:e229a521186c75c8ad9490854fd8bbdd9a0c9aa3a524326b55be83b54d4e0ad9"}, + {file = "cffi-1.15.1-cp37-cp37m-win_amd64.whl", hash = "sha256:a0b71b1b8fbf2b96e41c4d990244165e2c9be83d54962a9a1d118fd8657d2045"}, + {file = "cffi-1.15.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:320dab6e7cb2eacdf0e658569d2575c4dad258c0fcc794f46215e1e39f90f2c3"}, + {file = "cffi-1.15.1-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e74c6b51a9ed6589199c787bf5f9875612ca4a8a0785fb2d4a84429badaf22a"}, + {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5c84c68147988265e60416b57fc83425a78058853509c1b0629c180094904a5"}, + {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3b926aa83d1edb5aa5b427b4053dc420ec295a08e40911296b9eb1b6170f6cca"}, + {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:87c450779d0914f2861b8526e035c5e6da0a3199d8f1add1a665e1cbc6fc6d02"}, + {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4f2c9f67e9821cad2e5f480bc8d83b8742896f1242dba247911072d4fa94c192"}, + {file = "cffi-1.15.1-cp38-cp38-win32.whl", hash = "sha256:8b7ee99e510d7b66cdb6c593f21c043c248537a32e0bedf02e01e9553a172314"}, + {file = "cffi-1.15.1-cp38-cp38-win_amd64.whl", hash = "sha256:00a9ed42e88df81ffae7a8ab6d9356b371399b91dbdf0c3cb1e84c03a13aceb5"}, + {file = "cffi-1.15.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:54a2db7b78338edd780e7ef7f9f6c442500fb0d41a5a4ea24fff1c929d5af585"}, + {file = "cffi-1.15.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:fcd131dd944808b5bdb38e6f5b53013c5aa4f334c5cad0c72742f6eba4b73db0"}, + {file = "cffi-1.15.1-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7473e861101c9e72452f9bf8acb984947aa1661a7704553a9f6e4baa5ba64415"}, + {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6c9a799e985904922a4d207a94eae35c78ebae90e128f0c4e521ce339396be9d"}, + {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3bcde07039e586f91b45c88f8583ea7cf7a0770df3a1649627bf598332cb6984"}, + {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:33ab79603146aace82c2427da5ca6e58f2b3f2fb5da893ceac0c42218a40be35"}, + {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5d598b938678ebf3c67377cdd45e09d431369c3b1a5b331058c338e201f12b27"}, + {file = "cffi-1.15.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:db0fbb9c62743ce59a9ff687eb5f4afbe77e5e8403d6697f7446e5f609976f76"}, + {file = "cffi-1.15.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:98d85c6a2bef81588d9227dde12db8a7f47f639f4a17c9ae08e773aa9c697bf3"}, + {file = "cffi-1.15.1-cp39-cp39-win32.whl", hash = "sha256:40f4774f5a9d4f5e344f31a32b5096977b5d48560c5592e2f3d2c4374bd543ee"}, + {file = "cffi-1.15.1-cp39-cp39-win_amd64.whl", hash = "sha256:70df4e3b545a17496c9b3f41f5115e69a4f2e77e94e1d2a8e1070bc0c38c8a3c"}, + {file = "cffi-1.15.1.tar.gz", hash = "sha256:d400bfb9a37b1351253cb402671cea7e89bdecc294e8016a707f6d1d8ac934f9"}, +] [package.dependencies] pycparser = "*" @@ -74,17 +174,48 @@ pycparser = "*" name = "chardet" version = "4.0.0" description = "Universal encoding detector for Python 2 and 3" -category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +files = [ + {file = "chardet-4.0.0-py2.py3-none-any.whl", hash = "sha256:f864054d66fd9118f2e67044ac8981a54775ec5b67aed0441892edb553d21da5"}, + {file = "chardet-4.0.0.tar.gz", hash = "sha256:0d6f53a15db4120f2b08c94f11e7d93d2c911ee118b6b30a04ec3ee8310179fa"}, +] + +[[package]] +name = "click" +version = "8.1.6" +description = "Composable command line interface toolkit" +optional = false +python-versions = ">=3.7" +files = [ + {file = "click-8.1.6-py3-none-any.whl", hash = "sha256:fa244bb30b3b5ee2cae3da8f55c9e5e0c0e86093306301fb418eb9dc40fbded5"}, + {file = "click-8.1.6.tar.gz", hash = "sha256:48ee849951919527a045bfe3bf7baa8a959c423134e1a5b98c05c20ba75a1cbd"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "platform_system == \"Windows\""} + +[[package]] +name = "colorama" +version = "0.4.6" +description = "Cross-platform colored terminal text." +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +files = [ + {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, + {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, +] [[package]] name = "coreapi" version = "2.3.3" description = "Python client library for Core API." -category = "main" optional = false python-versions = "*" +files = [ + {file = "coreapi-2.3.3-py2.py3-none-any.whl", hash = "sha256:bf39d118d6d3e171f10df9ede5666f63ad80bba9a29a8ec17726a66cf52ee6f3"}, + {file = "coreapi-2.3.3.tar.gz", hash = "sha256:46145fcc1f7017c076a2ef684969b641d18a2991051fddec9458ad3f78ffc1cb"}, +] [package.dependencies] coreschema = "*" @@ -96,20 +227,101 @@ uritemplate = "*" name = "coreschema" version = "0.0.4" description = "Core Schema." -category = "main" optional = false python-versions = "*" +files = [ + {file = "coreschema-0.0.4-py2-none-any.whl", hash = "sha256:5e6ef7bf38c1525d5e55a895934ab4273548629f16aed5c0a6caa74ebf45551f"}, + {file = "coreschema-0.0.4.tar.gz", hash = "sha256:9503506007d482ab0867ba14724b93c18a33b22b6d19fb419ef2d239dd4a1607"}, +] [package.dependencies] jinja2 = "*" +[[package]] +name = "cryptography" +version = "41.0.2" +description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." +optional = false +python-versions = ">=3.7" +files = [ + {file = "cryptography-41.0.2-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:01f1d9e537f9a15b037d5d9ee442b8c22e3ae11ce65ea1f3316a41c78756b711"}, + {file = "cryptography-41.0.2-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:079347de771f9282fbfe0e0236c716686950c19dee1b76240ab09ce1624d76d7"}, + {file = "cryptography-41.0.2-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:439c3cc4c0d42fa999b83ded80a9a1fb54d53c58d6e59234cfe97f241e6c781d"}, + {file = "cryptography-41.0.2-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f14ad275364c8b4e525d018f6716537ae7b6d369c094805cae45300847e0894f"}, + {file = "cryptography-41.0.2-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:84609ade00a6ec59a89729e87a503c6e36af98ddcd566d5f3be52e29ba993182"}, + {file = "cryptography-41.0.2-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:49c3222bb8f8e800aead2e376cbef687bc9e3cb9b58b29a261210456a7783d83"}, + {file = "cryptography-41.0.2-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:d73f419a56d74fef257955f51b18d046f3506270a5fd2ac5febbfa259d6c0fa5"}, + {file = "cryptography-41.0.2-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:2a034bf7d9ca894720f2ec1d8b7b5832d7e363571828037f9e0c4f18c1b58a58"}, + {file = "cryptography-41.0.2-cp37-abi3-win32.whl", hash = "sha256:d124682c7a23c9764e54ca9ab5b308b14b18eba02722b8659fb238546de83a76"}, + {file = "cryptography-41.0.2-cp37-abi3-win_amd64.whl", hash = "sha256:9c3fe6534d59d071ee82081ca3d71eed3210f76ebd0361798c74abc2bcf347d4"}, + {file = "cryptography-41.0.2-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:a719399b99377b218dac6cf547b6ec54e6ef20207b6165126a280b0ce97e0d2a"}, + {file = "cryptography-41.0.2-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:182be4171f9332b6741ee818ec27daff9fb00349f706629f5cbf417bd50e66fd"}, + {file = "cryptography-41.0.2-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:7a9a3bced53b7f09da251685224d6a260c3cb291768f54954e28f03ef14e3766"}, + {file = "cryptography-41.0.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:f0dc40e6f7aa37af01aba07277d3d64d5a03dc66d682097541ec4da03cc140ee"}, + {file = "cryptography-41.0.2-pp38-pypy38_pp73-macosx_10_12_x86_64.whl", hash = "sha256:674b669d5daa64206c38e507808aae49904c988fa0a71c935e7006a3e1e83831"}, + {file = "cryptography-41.0.2-pp38-pypy38_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:7af244b012711a26196450d34f483357e42aeddb04128885d95a69bd8b14b69b"}, + {file = "cryptography-41.0.2-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:9b6d717393dbae53d4e52684ef4f022444fc1cce3c48c38cb74fca29e1f08eaa"}, + {file = "cryptography-41.0.2-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:192255f539d7a89f2102d07d7375b1e0a81f7478925b3bc2e0549ebf739dae0e"}, + {file = "cryptography-41.0.2-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:f772610fe364372de33d76edcd313636a25684edb94cee53fd790195f5989d14"}, + {file = "cryptography-41.0.2-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:b332cba64d99a70c1e0836902720887fb4529ea49ea7f5462cf6640e095e11d2"}, + {file = "cryptography-41.0.2-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:9a6673c1828db6270b76b22cc696f40cde9043eb90373da5c2f8f2158957f42f"}, + {file = "cryptography-41.0.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:342f3767e25876751e14f8459ad85e77e660537ca0a066e10e75df9c9e9099f0"}, + {file = "cryptography-41.0.2.tar.gz", hash = "sha256:7d230bf856164de164ecb615ccc14c7fc6de6906ddd5b491f3af90d3514c925c"}, +] + +[package.dependencies] +cffi = ">=1.12" + +[package.extras] +docs = ["sphinx (>=5.3.0)", "sphinx-rtd-theme (>=1.1.1)"] +docstest = ["pyenchant (>=1.6.11)", "sphinxcontrib-spelling (>=4.0.1)", "twine (>=1.12.0)"] +nox = ["nox"] +pep8test = ["black", "check-sdist", "mypy", "ruff"] +sdist = ["build"] +ssh = ["bcrypt (>=3.1.5)"] +test = ["pretend", "pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-xdist"] +test-randomorder = ["pytest-randomly"] + +[[package]] +name = "cssselect2" +version = "0.7.0" +description = "CSS selectors for Python ElementTree" +optional = false +python-versions = ">=3.7" +files = [ + {file = "cssselect2-0.7.0-py3-none-any.whl", hash = "sha256:fd23a65bfd444595913f02fc71f6b286c29261e354c41d722ca7a261a49b5969"}, + {file = "cssselect2-0.7.0.tar.gz", hash = "sha256:1ccd984dab89fc68955043aca4e1b03e0cf29cad9880f6e28e3ba7a74b14aa5a"}, +] + +[package.dependencies] +tinycss2 = "*" +webencodings = "*" + +[package.extras] +doc = ["sphinx", "sphinx_rtd_theme"] +test = ["flake8", "isort", "pytest"] + +[[package]] +name = "defusedxml" +version = "0.7.1" +description = "XML bomb protection for Python stdlib modules" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +files = [ + {file = "defusedxml-0.7.1-py2.py3-none-any.whl", hash = "sha256:a352e7e428770286cc899e2542b6cdaedb2b4953ff269a210103ec58f6198a61"}, + {file = "defusedxml-0.7.1.tar.gz", hash = "sha256:1bb3032db185915b62d7c6209c5a8792be6a32ab2fedacc84e01b52c51aa3e69"}, +] + [[package]] name = "django" -version = "3.2.3" +version = "3.2.20" description = "A high-level Python Web framework that encourages rapid development and clean, pragmatic design." -category = "main" optional = false python-versions = ">=3.6" +files = [ + {file = "Django-3.2.20-py3-none-any.whl", hash = "sha256:a477ab326ae7d8807dc25c186b951ab8c7648a3a23f9497763c37307a2b5ef87"}, + {file = "Django-3.2.20.tar.gz", hash = "sha256:dec2a116787b8e14962014bf78e120bba454135108e1af9e9b91ade7b2964c40"}, +] [package.dependencies] asgiref = ">=3.3.2,<4" @@ -122,52 +334,71 @@ bcrypt = ["bcrypt"] [[package]] name = "django-cors-headers" -version = "3.7.0" +version = "3.14.0" description = "django-cors-headers is a Django application for handling the server headers required for Cross-Origin Resource Sharing (CORS)." -category = "main" optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" +files = [ + {file = "django_cors_headers-3.14.0-py3-none-any.whl", hash = "sha256:684180013cc7277bdd8702b80a3c5a4b3fcae4abb2bf134dceb9f5dfe300228e"}, + {file = "django_cors_headers-3.14.0.tar.gz", hash = "sha256:5fbd58a6fb4119d975754b2bc090f35ec160a8373f276612c675b00e8a138739"}, +] [package.dependencies] -Django = ">=2.2" +Django = ">=3.2" [[package]] name = "django-filter" version = "2.4.0" description = "Django-filter is a reusable Django application for allowing users to filter querysets dynamically." -category = "main" optional = false python-versions = ">=3.5" +files = [ + {file = "django-filter-2.4.0.tar.gz", hash = "sha256:84e9d5bb93f237e451db814ed422a3a625751cbc9968b484ecc74964a8696b06"}, + {file = "django_filter-2.4.0-py3-none-any.whl", hash = "sha256:e00d32cebdb3d54273c48f4f878f898dced8d5dfaad009438fe61ebdf535ace1"}, +] [package.dependencies] Django = ">=2.2" [[package]] name = "djangorestframework" -version = "3.12.4" +version = "3.14.0" description = "Web APIs for Django, made easy." -category = "main" optional = false -python-versions = ">=3.5" +python-versions = ">=3.6" +files = [ + {file = "djangorestframework-3.14.0-py3-none-any.whl", hash = "sha256:eb63f58c9f218e1a7d064d17a70751f528ed4e1d35547fdade9aaf4cd103fd08"}, + {file = "djangorestframework-3.14.0.tar.gz", hash = "sha256:579a333e6256b09489cbe0a067e66abe55c6595d8926be6b99423786334350c8"}, +] [package.dependencies] -django = ">=2.2" +django = ">=3.0" +pytz = "*" [[package]] -name = "future" -version = "0.18.2" -description = "Clean single-source support for Python 3 and 2" -category = "main" +name = "et-xmlfile" +version = "1.1.0" +description = "An implementation of lxml.xmlfile for the standard library" optional = false -python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" +python-versions = ">=3.6" +files = [ + {file = "et_xmlfile-1.1.0-py3-none-any.whl", hash = "sha256:a2ba85d1d6a74ef63837eed693bcb89c3f752169b0e3e7ae5b16ca5e1b3deada"}, + {file = "et_xmlfile-1.1.0.tar.gz", hash = "sha256:8eb9e2bc2f8c97e37a2dc85a09ecdcdec9d8a396530a6d5a33b30b9a92da0c5c"}, +] [[package]] name = "gunicorn" version = "20.1.0" description = "WSGI HTTP Server for UNIX" -category = "main" optional = false python-versions = ">=3.5" +files = [ + {file = "gunicorn-20.1.0-py3-none-any.whl", hash = "sha256:9dcc4547dbb1cb284accfb15ab5667a0e5d1881cc443e0677b4882a4067a807e"}, + {file = "gunicorn-20.1.0.tar.gz", hash = "sha256:e0a968b5ba15f8a328fdfd7ab1fcb5af4470c28aaf7e55df02a99bc13138e6e8"}, +] + +[package.dependencies] +setuptools = ">=3.0" [package.extras] eventlet = ["eventlet (>=0.24.1)"] @@ -179,16 +410,19 @@ tornado = ["tornado (>=0.2)"] name = "html5lib" version = "1.1" description = "HTML parser based on the WHATWG HTML specification" -category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +files = [ + {file = "html5lib-1.1-py2.py3-none-any.whl", hash = "sha256:0d78f8fde1c230e99fe37986a60526d7049ed4bf8a9fadbad5f00e22e58e041d"}, + {file = "html5lib-1.1.tar.gz", hash = "sha256:b2e5b40261e20f354d198eae92afc10d750afb487ed5e50f9c4eaf07c184146f"}, +] [package.dependencies] six = ">=1.9" webencodings = "*" [package.extras] -all = ["genshi", "chardet (>=2.2)", "lxml"] +all = ["chardet (>=2.2)", "genshi", "lxml"] chardet = ["chardet (>=2.2)"] genshi = ["genshi"] lxml = ["lxml"] @@ -197,114 +431,718 @@ lxml = ["lxml"] name = "idna" version = "2.10" description = "Internationalized Domain Names in Applications (IDNA)" -category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +files = [ + {file = "idna-2.10-py2.py3-none-any.whl", hash = "sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0"}, + {file = "idna-2.10.tar.gz", hash = "sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6"}, +] [[package]] name = "itypes" version = "1.2.0" description = "Simple immutable types for python." -category = "main" optional = false python-versions = "*" +files = [ + {file = "itypes-1.2.0-py2.py3-none-any.whl", hash = "sha256:03da6872ca89d29aef62773672b2d408f490f80db48b23079a4b194c86dd04c6"}, + {file = "itypes-1.2.0.tar.gz", hash = "sha256:af886f129dea4a2a1e3d36595a2d139589e4dd287f5cab0b40e799ee81570ff1"}, +] [[package]] name = "jinja2" -version = "3.0.0" +version = "3.1.2" description = "A very fast and expressive template engine." -category = "main" optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" +files = [ + {file = "Jinja2-3.1.2-py3-none-any.whl", hash = "sha256:6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61"}, + {file = "Jinja2-3.1.2.tar.gz", hash = "sha256:31351a702a408a9e7595a8fc6150fc3f43bb6bf7e319770cbc0db9df9437e852"}, +] [package.dependencies] -MarkupSafe = ">=2.0.0rc2" +MarkupSafe = ">=2.0" [package.extras] i18n = ["Babel (>=2.7)"] +[[package]] +name = "lxml" +version = "4.9.3" +description = "Powerful and Pythonic XML processing library combining libxml2/libxslt with the ElementTree API." +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, != 3.4.*" +files = [ + {file = "lxml-4.9.3-cp27-cp27m-macosx_11_0_x86_64.whl", hash = "sha256:b0a545b46b526d418eb91754565ba5b63b1c0b12f9bd2f808c852d9b4b2f9b5c"}, + {file = "lxml-4.9.3-cp27-cp27m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:075b731ddd9e7f68ad24c635374211376aa05a281673ede86cbe1d1b3455279d"}, + {file = "lxml-4.9.3-cp27-cp27m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:1e224d5755dba2f4a9498e150c43792392ac9b5380aa1b845f98a1618c94eeef"}, + {file = "lxml-4.9.3-cp27-cp27m-win32.whl", hash = "sha256:2c74524e179f2ad6d2a4f7caf70e2d96639c0954c943ad601a9e146c76408ed7"}, + {file = "lxml-4.9.3-cp27-cp27m-win_amd64.whl", hash = "sha256:4f1026bc732b6a7f96369f7bfe1a4f2290fb34dce00d8644bc3036fb351a4ca1"}, + {file = "lxml-4.9.3-cp27-cp27mu-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c0781a98ff5e6586926293e59480b64ddd46282953203c76ae15dbbbf302e8bb"}, + {file = "lxml-4.9.3-cp27-cp27mu-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:cef2502e7e8a96fe5ad686d60b49e1ab03e438bd9123987994528febd569868e"}, + {file = "lxml-4.9.3-cp310-cp310-macosx_11_0_x86_64.whl", hash = "sha256:b86164d2cff4d3aaa1f04a14685cbc072efd0b4f99ca5708b2ad1b9b5988a991"}, + {file = "lxml-4.9.3-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:42871176e7896d5d45138f6d28751053c711ed4d48d8e30b498da155af39aebd"}, + {file = "lxml-4.9.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:ae8b9c6deb1e634ba4f1930eb67ef6e6bf6a44b6eb5ad605642b2d6d5ed9ce3c"}, + {file = "lxml-4.9.3-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:411007c0d88188d9f621b11d252cce90c4a2d1a49db6c068e3c16422f306eab8"}, + {file = "lxml-4.9.3-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:cd47b4a0d41d2afa3e58e5bf1f62069255aa2fd6ff5ee41604418ca925911d76"}, + {file = "lxml-4.9.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:0e2cb47860da1f7e9a5256254b74ae331687b9672dfa780eed355c4c9c3dbd23"}, + {file = "lxml-4.9.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:1247694b26342a7bf47c02e513d32225ededd18045264d40758abeb3c838a51f"}, + {file = "lxml-4.9.3-cp310-cp310-win32.whl", hash = "sha256:cdb650fc86227eba20de1a29d4b2c1bfe139dc75a0669270033cb2ea3d391b85"}, + {file = "lxml-4.9.3-cp310-cp310-win_amd64.whl", hash = "sha256:97047f0d25cd4bcae81f9ec9dc290ca3e15927c192df17331b53bebe0e3ff96d"}, + {file = "lxml-4.9.3-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:1f447ea5429b54f9582d4b955f5f1985f278ce5cf169f72eea8afd9502973dd5"}, + {file = "lxml-4.9.3-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:57d6ba0ca2b0c462f339640d22882acc711de224d769edf29962b09f77129cbf"}, + {file = "lxml-4.9.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:9767e79108424fb6c3edf8f81e6730666a50feb01a328f4a016464a5893f835a"}, + {file = "lxml-4.9.3-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:71c52db65e4b56b8ddc5bb89fb2e66c558ed9d1a74a45ceb7dcb20c191c3df2f"}, + {file = "lxml-4.9.3-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:d73d8ecf8ecf10a3bd007f2192725a34bd62898e8da27eb9d32a58084f93962b"}, + {file = "lxml-4.9.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:0a3d3487f07c1d7f150894c238299934a2a074ef590b583103a45002035be120"}, + {file = "lxml-4.9.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:9e28c51fa0ce5674be9f560c6761c1b441631901993f76700b1b30ca6c8378d6"}, + {file = "lxml-4.9.3-cp311-cp311-win32.whl", hash = "sha256:0bfd0767c5c1de2551a120673b72e5d4b628737cb05414f03c3277bf9bed3305"}, + {file = "lxml-4.9.3-cp311-cp311-win_amd64.whl", hash = "sha256:25f32acefac14ef7bd53e4218fe93b804ef6f6b92ffdb4322bb6d49d94cad2bc"}, + {file = "lxml-4.9.3-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:d3ff32724f98fbbbfa9f49d82852b159e9784d6094983d9a8b7f2ddaebb063d4"}, + {file = "lxml-4.9.3-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:48d6ed886b343d11493129e019da91d4039826794a3e3027321c56d9e71505be"}, + {file = "lxml-4.9.3-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:9a92d3faef50658dd2c5470af249985782bf754c4e18e15afb67d3ab06233f13"}, + {file = "lxml-4.9.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:b4e4bc18382088514ebde9328da057775055940a1f2e18f6ad2d78aa0f3ec5b9"}, + {file = "lxml-4.9.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:fc9b106a1bf918db68619fdcd6d5ad4f972fdd19c01d19bdb6bf63f3589a9ec5"}, + {file = "lxml-4.9.3-cp312-cp312-win_amd64.whl", hash = "sha256:d37017287a7adb6ab77e1c5bee9bcf9660f90ff445042b790402a654d2ad81d8"}, + {file = "lxml-4.9.3-cp35-cp35m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:56dc1f1ebccc656d1b3ed288f11e27172a01503fc016bcabdcbc0978b19352b7"}, + {file = "lxml-4.9.3-cp35-cp35m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:578695735c5a3f51569810dfebd05dd6f888147a34f0f98d4bb27e92b76e05c2"}, + {file = "lxml-4.9.3-cp35-cp35m-win32.whl", hash = "sha256:704f61ba8c1283c71b16135caf697557f5ecf3e74d9e453233e4771d68a1f42d"}, + {file = "lxml-4.9.3-cp35-cp35m-win_amd64.whl", hash = "sha256:c41bfca0bd3532d53d16fd34d20806d5c2b1ace22a2f2e4c0008570bf2c58833"}, + {file = "lxml-4.9.3-cp36-cp36m-macosx_11_0_x86_64.whl", hash = "sha256:64f479d719dc9f4c813ad9bb6b28f8390360660b73b2e4beb4cb0ae7104f1c12"}, + {file = "lxml-4.9.3-cp36-cp36m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:dd708cf4ee4408cf46a48b108fb9427bfa00b9b85812a9262b5c668af2533ea5"}, + {file = "lxml-4.9.3-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5c31c7462abdf8f2ac0577d9f05279727e698f97ecbb02f17939ea99ae8daa98"}, + {file = "lxml-4.9.3-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:e3cd95e10c2610c360154afdc2f1480aea394f4a4f1ea0a5eacce49640c9b190"}, + {file = "lxml-4.9.3-cp36-cp36m-manylinux_2_28_x86_64.whl", hash = "sha256:4930be26af26ac545c3dffb662521d4e6268352866956672231887d18f0eaab2"}, + {file = "lxml-4.9.3-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4aec80cde9197340bc353d2768e2a75f5f60bacda2bab72ab1dc499589b3878c"}, + {file = "lxml-4.9.3-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:14e019fd83b831b2e61baed40cab76222139926b1fb5ed0e79225bc0cae14584"}, + {file = "lxml-4.9.3-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:0c0850c8b02c298d3c7006b23e98249515ac57430e16a166873fc47a5d549287"}, + {file = "lxml-4.9.3-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:aca086dc5f9ef98c512bac8efea4483eb84abbf926eaeedf7b91479feb092458"}, + {file = "lxml-4.9.3-cp36-cp36m-win32.whl", hash = "sha256:50baa9c1c47efcaef189f31e3d00d697c6d4afda5c3cde0302d063492ff9b477"}, + {file = "lxml-4.9.3-cp36-cp36m-win_amd64.whl", hash = "sha256:bef4e656f7d98aaa3486d2627e7d2df1157d7e88e7efd43a65aa5dd4714916cf"}, + {file = "lxml-4.9.3-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:46f409a2d60f634fe550f7133ed30ad5321ae2e6630f13657fb9479506b00601"}, + {file = "lxml-4.9.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:4c28a9144688aef80d6ea666c809b4b0e50010a2aca784c97f5e6bf143d9f129"}, + {file = "lxml-4.9.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:141f1d1a9b663c679dc524af3ea1773e618907e96075262726c7612c02b149a4"}, + {file = "lxml-4.9.3-cp37-cp37m-manylinux_2_28_x86_64.whl", hash = "sha256:53ace1c1fd5a74ef662f844a0413446c0629d151055340e9893da958a374f70d"}, + {file = "lxml-4.9.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:17a753023436a18e27dd7769e798ce302963c236bc4114ceee5b25c18c52c693"}, + {file = "lxml-4.9.3-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:7d298a1bd60c067ea75d9f684f5f3992c9d6766fadbc0bcedd39750bf344c2f4"}, + {file = "lxml-4.9.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:081d32421db5df44c41b7f08a334a090a545c54ba977e47fd7cc2deece78809a"}, + {file = "lxml-4.9.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:23eed6d7b1a3336ad92d8e39d4bfe09073c31bfe502f20ca5116b2a334f8ec02"}, + {file = "lxml-4.9.3-cp37-cp37m-win32.whl", hash = "sha256:1509dd12b773c02acd154582088820893109f6ca27ef7291b003d0e81666109f"}, + {file = "lxml-4.9.3-cp37-cp37m-win_amd64.whl", hash = "sha256:120fa9349a24c7043854c53cae8cec227e1f79195a7493e09e0c12e29f918e52"}, + {file = "lxml-4.9.3-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:4d2d1edbca80b510443f51afd8496be95529db04a509bc8faee49c7b0fb6d2cc"}, + {file = "lxml-4.9.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:8d7e43bd40f65f7d97ad8ef5c9b1778943d02f04febef12def25f7583d19baac"}, + {file = "lxml-4.9.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:71d66ee82e7417828af6ecd7db817913cb0cf9d4e61aa0ac1fde0583d84358db"}, + {file = "lxml-4.9.3-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:6fc3c450eaa0b56f815c7b62f2b7fba7266c4779adcf1cece9e6deb1de7305ce"}, + {file = "lxml-4.9.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:65299ea57d82fb91c7f019300d24050c4ddeb7c5a190e076b5f48a2b43d19c42"}, + {file = "lxml-4.9.3-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:eadfbbbfb41b44034a4c757fd5d70baccd43296fb894dba0295606a7cf3124aa"}, + {file = "lxml-4.9.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:3e9bdd30efde2b9ccfa9cb5768ba04fe71b018a25ea093379c857c9dad262c40"}, + {file = "lxml-4.9.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:fcdd00edfd0a3001e0181eab3e63bd5c74ad3e67152c84f93f13769a40e073a7"}, + {file = "lxml-4.9.3-cp38-cp38-win32.whl", hash = "sha256:57aba1bbdf450b726d58b2aea5fe47c7875f5afb2c4a23784ed78f19a0462574"}, + {file = "lxml-4.9.3-cp38-cp38-win_amd64.whl", hash = "sha256:92af161ecbdb2883c4593d5ed4815ea71b31fafd7fd05789b23100d081ecac96"}, + {file = "lxml-4.9.3-cp39-cp39-macosx_11_0_x86_64.whl", hash = "sha256:9bb6ad405121241e99a86efff22d3ef469024ce22875a7ae045896ad23ba2340"}, + {file = "lxml-4.9.3-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:8ed74706b26ad100433da4b9d807eae371efaa266ffc3e9191ea436087a9d6a7"}, + {file = "lxml-4.9.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:fbf521479bcac1e25a663df882c46a641a9bff6b56dc8b0fafaebd2f66fb231b"}, + {file = "lxml-4.9.3-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:303bf1edce6ced16bf67a18a1cf8339d0db79577eec5d9a6d4a80f0fb10aa2da"}, + {file = "lxml-4.9.3-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:5515edd2a6d1a5a70bfcdee23b42ec33425e405c5b351478ab7dc9347228f96e"}, + {file = "lxml-4.9.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:690dafd0b187ed38583a648076865d8c229661ed20e48f2335d68e2cf7dc829d"}, + {file = "lxml-4.9.3-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:b6420a005548ad52154c8ceab4a1290ff78d757f9e5cbc68f8c77089acd3c432"}, + {file = "lxml-4.9.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:bb3bb49c7a6ad9d981d734ef7c7193bc349ac338776a0360cc671eaee89bcf69"}, + {file = "lxml-4.9.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:d27be7405547d1f958b60837dc4c1007da90b8b23f54ba1f8b728c78fdb19d50"}, + {file = "lxml-4.9.3-cp39-cp39-win32.whl", hash = "sha256:8df133a2ea5e74eef5e8fc6f19b9e085f758768a16e9877a60aec455ed2609b2"}, + {file = "lxml-4.9.3-cp39-cp39-win_amd64.whl", hash = "sha256:4dd9a263e845a72eacb60d12401e37c616438ea2e5442885f65082c276dfb2b2"}, + {file = "lxml-4.9.3-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:6689a3d7fd13dc687e9102a27e98ef33730ac4fe37795d5036d18b4d527abd35"}, + {file = "lxml-4.9.3-pp37-pypy37_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:f6bdac493b949141b733c5345b6ba8f87a226029cbabc7e9e121a413e49441e0"}, + {file = "lxml-4.9.3-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:05186a0f1346ae12553d66df1cfce6f251589fea3ad3da4f3ef4e34b2d58c6a3"}, + {file = "lxml-4.9.3-pp37-pypy37_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:c2006f5c8d28dee289f7020f721354362fa304acbaaf9745751ac4006650254b"}, + {file = "lxml-4.9.3-pp38-pypy38_pp73-macosx_11_0_x86_64.whl", hash = "sha256:5c245b783db29c4e4fbbbfc9c5a78be496c9fea25517f90606aa1f6b2b3d5f7b"}, + {file = "lxml-4.9.3-pp38-pypy38_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:4fb960a632a49f2f089d522f70496640fdf1218f1243889da3822e0a9f5f3ba7"}, + {file = "lxml-4.9.3-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:50670615eaf97227d5dc60de2dc99fb134a7130d310d783314e7724bf163f75d"}, + {file = "lxml-4.9.3-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:9719fe17307a9e814580af1f5c6e05ca593b12fb7e44fe62450a5384dbf61b4b"}, + {file = "lxml-4.9.3-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:3331bece23c9ee066e0fb3f96c61322b9e0f54d775fccefff4c38ca488de283a"}, + {file = "lxml-4.9.3-pp39-pypy39_pp73-macosx_11_0_x86_64.whl", hash = "sha256:ed667f49b11360951e201453fc3967344d0d0263aa415e1619e85ae7fd17b4e0"}, + {file = "lxml-4.9.3-pp39-pypy39_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:8b77946fd508cbf0fccd8e400a7f71d4ac0e1595812e66025bac475a8e811694"}, + {file = "lxml-4.9.3-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:e4da8ca0c0c0aea88fd46be8e44bd49716772358d648cce45fe387f7b92374a7"}, + {file = "lxml-4.9.3-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:fe4bda6bd4340caa6e5cf95e73f8fea5c4bfc55763dd42f1b50a94c1b4a2fbd4"}, + {file = "lxml-4.9.3-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:f3df3db1d336b9356dd3112eae5f5c2b8b377f3bc826848567f10bfddfee77e9"}, + {file = "lxml-4.9.3.tar.gz", hash = "sha256:48628bd53a426c9eb9bc066a923acaa0878d1e86129fd5359aee99285f4eed9c"}, +] + +[package.extras] +cssselect = ["cssselect (>=0.7)"] +html5 = ["html5lib"] +htmlsoup = ["BeautifulSoup4"] +source = ["Cython (>=0.29.35)"] + [[package]] name = "markupsafe" -version = "2.0.0" +version = "2.1.3" description = "Safely add untrusted strings to HTML/XML markup." -category = "main" +optional = false +python-versions = ">=3.7" +files = [ + {file = "MarkupSafe-2.1.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:cd0f502fe016460680cd20aaa5a76d241d6f35a1c3350c474bac1273803893fa"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e09031c87a1e51556fdcb46e5bd4f59dfb743061cf93c4d6831bf894f125eb57"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:68e78619a61ecf91e76aa3e6e8e33fc4894a2bebe93410754bd28fce0a8a4f9f"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:65c1a9bcdadc6c28eecee2c119465aebff8f7a584dd719facdd9e825ec61ab52"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:525808b8019e36eb524b8c68acdd63a37e75714eac50e988180b169d64480a00"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:962f82a3086483f5e5f64dbad880d31038b698494799b097bc59c2edf392fce6"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:aa7bd130efab1c280bed0f45501b7c8795f9fdbeb02e965371bbef3523627779"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c9c804664ebe8f83a211cace637506669e7890fec1b4195b505c214e50dd4eb7"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-win32.whl", hash = "sha256:10bbfe99883db80bdbaff2dcf681dfc6533a614f700da1287707e8a5d78a8431"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-win_amd64.whl", hash = "sha256:1577735524cdad32f9f694208aa75e422adba74f1baee7551620e43a3141f559"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ad9e82fb8f09ade1c3e1b996a6337afac2b8b9e365f926f5a61aacc71adc5b3c"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3c0fae6c3be832a0a0473ac912810b2877c8cb9d76ca48de1ed31e1c68386575"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b076b6226fb84157e3f7c971a47ff3a679d837cf338547532ab866c57930dbee"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bfce63a9e7834b12b87c64d6b155fdd9b3b96191b6bd334bf37db7ff1fe457f2"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:338ae27d6b8745585f87218a3f23f1512dbf52c26c28e322dbe54bcede54ccb9"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e4dd52d80b8c83fdce44e12478ad2e85c64ea965e75d66dbeafb0a3e77308fcc"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:df0be2b576a7abbf737b1575f048c23fb1d769f267ec4358296f31c2479db8f9"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5bbe06f8eeafd38e5d0a4894ffec89378b6c6a625ff57e3028921f8ff59318ac"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-win32.whl", hash = "sha256:dd15ff04ffd7e05ffcb7fe79f1b98041b8ea30ae9234aed2a9168b5797c3effb"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-win_amd64.whl", hash = "sha256:134da1eca9ec0ae528110ccc9e48041e0828d79f24121a1a146161103c76e686"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:f698de3fd0c4e6972b92290a45bd9b1536bffe8c6759c62471efaa8acb4c37bc"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:aa57bd9cf8ae831a362185ee444e15a93ecb2e344c8e52e4d721ea3ab6ef1823"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ffcc3f7c66b5f5b7931a5aa68fc9cecc51e685ef90282f4a82f0f5e9b704ad11"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47d4f1c5f80fc62fdd7777d0d40a2e9dda0a05883ab11374334f6c4de38adffd"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1f67c7038d560d92149c060157d623c542173016c4babc0c1913cca0564b9939"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:9aad3c1755095ce347e26488214ef77e0485a3c34a50c5a5e2471dff60b9dd9c"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:14ff806850827afd6b07a5f32bd917fb7f45b046ba40c57abdb636674a8b559c"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8f9293864fe09b8149f0cc42ce56e3f0e54de883a9de90cd427f191c346eb2e1"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-win32.whl", hash = "sha256:715d3562f79d540f251b99ebd6d8baa547118974341db04f5ad06d5ea3eb8007"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-win_amd64.whl", hash = "sha256:1b8dd8c3fd14349433c79fa8abeb573a55fc0fdd769133baac1f5e07abf54aeb"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8e254ae696c88d98da6555f5ace2279cf7cd5b3f52be2b5cf97feafe883b58d2"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cb0932dc158471523c9637e807d9bfb93e06a95cbf010f1a38b98623b929ef2b"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9402b03f1a1b4dc4c19845e5c749e3ab82d5078d16a2a4c2cd2df62d57bb0707"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ca379055a47383d02a5400cb0d110cef0a776fc644cda797db0c5696cfd7e18e"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:b7ff0f54cb4ff66dd38bebd335a38e2c22c41a8ee45aa608efc890ac3e3931bc"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:c011a4149cfbcf9f03994ec2edffcb8b1dc2d2aede7ca243746df97a5d41ce48"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:56d9f2ecac662ca1611d183feb03a3fa4406469dafe241673d521dd5ae92a155"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-win32.whl", hash = "sha256:8758846a7e80910096950b67071243da3e5a20ed2546e6392603c096778d48e0"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-win_amd64.whl", hash = "sha256:787003c0ddb00500e49a10f2844fac87aa6ce977b90b0feaaf9de23c22508b24"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:2ef12179d3a291be237280175b542c07a36e7f60718296278d8593d21ca937d4"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2c1b19b3aaacc6e57b7e25710ff571c24d6c3613a45e905b1fde04d691b98ee0"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8afafd99945ead6e075b973fefa56379c5b5c53fd8937dad92c662da5d8fd5ee"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8c41976a29d078bb235fea9b2ecd3da465df42a562910f9022f1a03107bd02be"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d080e0a5eb2529460b30190fcfcc4199bd7f827663f858a226a81bc27beaa97e"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:69c0f17e9f5a7afdf2cc9fb2d1ce6aabdb3bafb7f38017c0b77862bcec2bbad8"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:504b320cd4b7eff6f968eddf81127112db685e81f7e36e75f9f84f0df46041c3"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:42de32b22b6b804f42c5d98be4f7e5e977ecdd9ee9b660fda1a3edf03b11792d"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-win32.whl", hash = "sha256:ceb01949af7121f9fc39f7d27f91be8546f3fb112c608bc4029aef0bab86a2a5"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-win_amd64.whl", hash = "sha256:1b40069d487e7edb2676d3fbdb2b0829ffa2cd63a2ec26c4938b2d34391b4ecc"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:8023faf4e01efadfa183e863fefde0046de576c6f14659e8782065bcece22198"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6b2b56950d93e41f33b4223ead100ea0fe11f8e6ee5f641eb753ce4b77a7042b"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9dcdfd0eaf283af041973bff14a2e143b8bd64e069f4c383416ecd79a81aab58"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:05fb21170423db021895e1ea1e1f3ab3adb85d1c2333cbc2310f2a26bc77272e"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:282c2cb35b5b673bbcadb33a585408104df04f14b2d9b01d4c345a3b92861c2c"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:ab4a0df41e7c16a1392727727e7998a467472d0ad65f3ad5e6e765015df08636"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:7ef3cb2ebbf91e330e3bb937efada0edd9003683db6b57bb108c4001f37a02ea"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:0a4e4a1aff6c7ac4cd55792abf96c915634c2b97e3cc1c7129578aa68ebd754e"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-win32.whl", hash = "sha256:fec21693218efe39aa7f8599346e90c705afa52c5b31ae019b2e57e8f6542bb2"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-win_amd64.whl", hash = "sha256:3fd4abcb888d15a94f32b75d8fd18ee162ca0c064f35b11134be77050296d6ba"}, + {file = "MarkupSafe-2.1.3.tar.gz", hash = "sha256:af598ed32d6ae86f1b747b82783958b1a4ab8f617b06fe68795c7f026abbdcad"}, +] + +[[package]] +name = "numpy" +version = "1.26.1" +description = "Fundamental package for array computing in Python" +optional = false +python-versions = "<3.13,>=3.9" +files = [ + {file = "numpy-1.26.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:82e871307a6331b5f09efda3c22e03c095d957f04bf6bc1804f30048d0e5e7af"}, + {file = "numpy-1.26.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:cdd9ec98f0063d93baeb01aad472a1a0840dee302842a2746a7a8e92968f9575"}, + {file = "numpy-1.26.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d78f269e0c4fd365fc2992c00353e4530d274ba68f15e968d8bc3c69ce5f5244"}, + {file = "numpy-1.26.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8ab9163ca8aeb7fd32fe93866490654d2f7dda4e61bc6297bf72ce07fdc02f67"}, + {file = "numpy-1.26.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:78ca54b2f9daffa5f323f34cdf21e1d9779a54073f0018a3094ab907938331a2"}, + {file = "numpy-1.26.1-cp310-cp310-win32.whl", hash = "sha256:d1cfc92db6af1fd37a7bb58e55c8383b4aa1ba23d012bdbba26b4bcca45ac297"}, + {file = "numpy-1.26.1-cp310-cp310-win_amd64.whl", hash = "sha256:d2984cb6caaf05294b8466966627e80bf6c7afd273279077679cb010acb0e5ab"}, + {file = "numpy-1.26.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cd7837b2b734ca72959a1caf3309457a318c934abef7a43a14bb984e574bbb9a"}, + {file = "numpy-1.26.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1c59c046c31a43310ad0199d6299e59f57a289e22f0f36951ced1c9eac3665b9"}, + {file = "numpy-1.26.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d58e8c51a7cf43090d124d5073bc29ab2755822181fcad978b12e144e5e5a4b3"}, + {file = "numpy-1.26.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6081aed64714a18c72b168a9276095ef9155dd7888b9e74b5987808f0dd0a974"}, + {file = "numpy-1.26.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:97e5d6a9f0702c2863aaabf19f0d1b6c2628fbe476438ce0b5ce06e83085064c"}, + {file = "numpy-1.26.1-cp311-cp311-win32.whl", hash = "sha256:b9d45d1dbb9de84894cc50efece5b09939752a2d75aab3a8b0cef6f3a35ecd6b"}, + {file = "numpy-1.26.1-cp311-cp311-win_amd64.whl", hash = "sha256:3649d566e2fc067597125428db15d60eb42a4e0897fc48d28cb75dc2e0454e53"}, + {file = "numpy-1.26.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:1d1bd82d539607951cac963388534da3b7ea0e18b149a53cf883d8f699178c0f"}, + {file = "numpy-1.26.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:afd5ced4e5a96dac6725daeb5242a35494243f2239244fad10a90ce58b071d24"}, + {file = "numpy-1.26.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a03fb25610ef560a6201ff06df4f8105292ba56e7cdd196ea350d123fc32e24e"}, + {file = "numpy-1.26.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dcfaf015b79d1f9f9c9fd0731a907407dc3e45769262d657d754c3a028586124"}, + {file = "numpy-1.26.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:e509cbc488c735b43b5ffea175235cec24bbc57b227ef1acc691725beb230d1c"}, + {file = "numpy-1.26.1-cp312-cp312-win32.whl", hash = "sha256:af22f3d8e228d84d1c0c44c1fbdeb80f97a15a0abe4f080960393a00db733b66"}, + {file = "numpy-1.26.1-cp312-cp312-win_amd64.whl", hash = "sha256:9f42284ebf91bdf32fafac29d29d4c07e5e9d1af862ea73686581773ef9e73a7"}, + {file = "numpy-1.26.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:bb894accfd16b867d8643fc2ba6c8617c78ba2828051e9a69511644ce86ce83e"}, + {file = "numpy-1.26.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e44ccb93f30c75dfc0c3aa3ce38f33486a75ec9abadabd4e59f114994a9c4617"}, + {file = "numpy-1.26.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9696aa2e35cc41e398a6d42d147cf326f8f9d81befcb399bc1ed7ffea339b64e"}, + {file = "numpy-1.26.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a5b411040beead47a228bde3b2241100454a6abde9df139ed087bd73fc0a4908"}, + {file = "numpy-1.26.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:1e11668d6f756ca5ef534b5be8653d16c5352cbb210a5c2a79ff288e937010d5"}, + {file = "numpy-1.26.1-cp39-cp39-win32.whl", hash = "sha256:d1d2c6b7dd618c41e202c59c1413ef9b2c8e8a15f5039e344af64195459e3104"}, + {file = "numpy-1.26.1-cp39-cp39-win_amd64.whl", hash = "sha256:59227c981d43425ca5e5c01094d59eb14e8772ce6975d4b2fc1e106a833d5ae2"}, + {file = "numpy-1.26.1-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:06934e1a22c54636a059215d6da99e23286424f316fddd979f5071093b648668"}, + {file = "numpy-1.26.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:76ff661a867d9272cd2a99eed002470f46dbe0943a5ffd140f49be84f68ffc42"}, + {file = "numpy-1.26.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:6965888d65d2848e8768824ca8288db0a81263c1efccec881cb35a0d805fcd2f"}, + {file = "numpy-1.26.1.tar.gz", hash = "sha256:c8c6c72d4a9f831f328efb1312642a1cafafaa88981d9ab76368d50d07d93cbe"}, +] + +[[package]] +name = "odfpy" +version = "1.4.1" +description = "Python API and tools to manipulate OpenDocument files" +optional = false +python-versions = "*" +files = [ + {file = "odfpy-1.4.1.tar.gz", hash = "sha256:db766a6e59c5103212f3cc92ec8dd50a0f3a02790233ed0b52148b70d3c438ec"}, +] + +[package.dependencies] +defusedxml = "*" + +[[package]] +name = "openpyxl" +version = "3.1.2" +description = "A Python library to read/write Excel 2010 xlsx/xlsm files" optional = false python-versions = ">=3.6" +files = [ + {file = "openpyxl-3.1.2-py2.py3-none-any.whl", hash = "sha256:f91456ead12ab3c6c2e9491cf33ba6d08357d802192379bb482f1033ade496f5"}, + {file = "openpyxl-3.1.2.tar.gz", hash = "sha256:a6f5977418eff3b2d5500d54d9db50c8277a368436f4e4f8ddb1be3422870184"}, +] + +[package.dependencies] +et-xmlfile = "*" + +[[package]] +name = "oscrypto" +version = "1.3.0" +description = "TLS (SSL) sockets, key generation, encryption, decryption, signing, verification and KDFs using the OS crypto libraries. Does not require a compiler, and relies on the OS for patching. Works on Windows, OS X and Linux/BSD." +optional = false +python-versions = "*" +files = [ + {file = "oscrypto-1.3.0-py2.py3-none-any.whl", hash = "sha256:2b2f1d2d42ec152ca90ccb5682f3e051fb55986e1b170ebde472b133713e7085"}, + {file = "oscrypto-1.3.0.tar.gz", hash = "sha256:6f5fef59cb5b3708321db7cca56aed8ad7e662853351e7991fcf60ec606d47a4"}, +] + +[package.dependencies] +asn1crypto = ">=1.5.1" + +[[package]] +name = "pandas" +version = "2.1.1" +description = "Powerful data structures for data analysis, time series, and statistics" +optional = false +python-versions = ">=3.9" +files = [ + {file = "pandas-2.1.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:58d997dbee0d4b64f3cb881a24f918b5f25dd64ddf31f467bb9b67ae4c63a1e4"}, + {file = "pandas-2.1.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:02304e11582c5d090e5a52aec726f31fe3f42895d6bfc1f28738f9b64b6f0614"}, + {file = "pandas-2.1.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ffa8f0966de2c22de408d0e322db2faed6f6e74265aa0856f3824813cf124363"}, + {file = "pandas-2.1.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c1f84c144dee086fe4f04a472b5cd51e680f061adf75c1ae4fc3a9275560f8f4"}, + {file = "pandas-2.1.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:75ce97667d06d69396d72be074f0556698c7f662029322027c226fd7a26965cb"}, + {file = "pandas-2.1.1-cp310-cp310-win_amd64.whl", hash = "sha256:4c3f32fd7c4dccd035f71734df39231ac1a6ff95e8bdab8d891167197b7018d2"}, + {file = "pandas-2.1.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:9e2959720b70e106bb1d8b6eadd8ecd7c8e99ccdbe03ee03260877184bb2877d"}, + {file = "pandas-2.1.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:25e8474a8eb258e391e30c288eecec565bfed3e026f312b0cbd709a63906b6f8"}, + {file = "pandas-2.1.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b8bd1685556f3374520466998929bade3076aeae77c3e67ada5ed2b90b4de7f0"}, + {file = "pandas-2.1.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dc3657869c7902810f32bd072f0740487f9e030c1a3ab03e0af093db35a9d14e"}, + {file = "pandas-2.1.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:05674536bd477af36aa2effd4ec8f71b92234ce0cc174de34fd21e2ee99adbc2"}, + {file = "pandas-2.1.1-cp311-cp311-win_amd64.whl", hash = "sha256:b407381258a667df49d58a1b637be33e514b07f9285feb27769cedb3ab3d0b3a"}, + {file = "pandas-2.1.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c747793c4e9dcece7bb20156179529898abf505fe32cb40c4052107a3c620b49"}, + {file = "pandas-2.1.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3bcad1e6fb34b727b016775bea407311f7721db87e5b409e6542f4546a4951ea"}, + {file = "pandas-2.1.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f5ec7740f9ccb90aec64edd71434711f58ee0ea7f5ed4ac48be11cfa9abf7317"}, + {file = "pandas-2.1.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:29deb61de5a8a93bdd033df328441a79fcf8dd3c12d5ed0b41a395eef9cd76f0"}, + {file = "pandas-2.1.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4f99bebf19b7e03cf80a4e770a3e65eee9dd4e2679039f542d7c1ace7b7b1daa"}, + {file = "pandas-2.1.1-cp312-cp312-win_amd64.whl", hash = "sha256:84e7e910096416adec68075dc87b986ff202920fb8704e6d9c8c9897fe7332d6"}, + {file = "pandas-2.1.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:366da7b0e540d1b908886d4feb3d951f2f1e572e655c1160f5fde28ad4abb750"}, + {file = "pandas-2.1.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:9e50e72b667415a816ac27dfcfe686dc5a0b02202e06196b943d54c4f9c7693e"}, + {file = "pandas-2.1.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cc1ab6a25da197f03ebe6d8fa17273126120874386b4ac11c1d687df288542dd"}, + {file = "pandas-2.1.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a0dbfea0dd3901ad4ce2306575c54348d98499c95be01b8d885a2737fe4d7a98"}, + {file = "pandas-2.1.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:0489b0e6aa3d907e909aef92975edae89b1ee1654db5eafb9be633b0124abe97"}, + {file = "pandas-2.1.1-cp39-cp39-win_amd64.whl", hash = "sha256:4cdb0fab0400c2cb46dafcf1a0fe084c8bb2480a1fa8d81e19d15e12e6d4ded2"}, + {file = "pandas-2.1.1.tar.gz", hash = "sha256:fecb198dc389429be557cde50a2d46da8434a17fe37d7d41ff102e3987fd947b"}, +] + +[package.dependencies] +numpy = [ + {version = ">=1.22.4", markers = "python_version < \"3.11\""}, + {version = ">=1.23.2", markers = "python_version == \"3.11\""}, + {version = ">=1.26.0", markers = "python_version >= \"3.12\""}, +] +python-dateutil = ">=2.8.2" +pytz = ">=2020.1" +tzdata = ">=2022.1" + +[package.extras] +all = ["PyQt5 (>=5.15.6)", "SQLAlchemy (>=1.4.36)", "beautifulsoup4 (>=4.11.1)", "bottleneck (>=1.3.4)", "dataframe-api-compat (>=0.1.7)", "fastparquet (>=0.8.1)", "fsspec (>=2022.05.0)", "gcsfs (>=2022.05.0)", "html5lib (>=1.1)", "hypothesis (>=6.46.1)", "jinja2 (>=3.1.2)", "lxml (>=4.8.0)", "matplotlib (>=3.6.1)", "numba (>=0.55.2)", "numexpr (>=2.8.0)", "odfpy (>=1.4.1)", "openpyxl (>=3.0.10)", "pandas-gbq (>=0.17.5)", "psycopg2 (>=2.9.3)", "pyarrow (>=7.0.0)", "pymysql (>=1.0.2)", "pyreadstat (>=1.1.5)", "pytest (>=7.3.2)", "pytest-asyncio (>=0.17.0)", "pytest-xdist (>=2.2.0)", "pyxlsb (>=1.0.9)", "qtpy (>=2.2.0)", "s3fs (>=2022.05.0)", "scipy (>=1.8.1)", "tables (>=3.7.0)", "tabulate (>=0.8.10)", "xarray (>=2022.03.0)", "xlrd (>=2.0.1)", "xlsxwriter (>=3.0.3)", "zstandard (>=0.17.0)"] +aws = ["s3fs (>=2022.05.0)"] +clipboard = ["PyQt5 (>=5.15.6)", "qtpy (>=2.2.0)"] +compression = ["zstandard (>=0.17.0)"] +computation = ["scipy (>=1.8.1)", "xarray (>=2022.03.0)"] +consortium-standard = ["dataframe-api-compat (>=0.1.7)"] +excel = ["odfpy (>=1.4.1)", "openpyxl (>=3.0.10)", "pyxlsb (>=1.0.9)", "xlrd (>=2.0.1)", "xlsxwriter (>=3.0.3)"] +feather = ["pyarrow (>=7.0.0)"] +fss = ["fsspec (>=2022.05.0)"] +gcp = ["gcsfs (>=2022.05.0)", "pandas-gbq (>=0.17.5)"] +hdf5 = ["tables (>=3.7.0)"] +html = ["beautifulsoup4 (>=4.11.1)", "html5lib (>=1.1)", "lxml (>=4.8.0)"] +mysql = ["SQLAlchemy (>=1.4.36)", "pymysql (>=1.0.2)"] +output-formatting = ["jinja2 (>=3.1.2)", "tabulate (>=0.8.10)"] +parquet = ["pyarrow (>=7.0.0)"] +performance = ["bottleneck (>=1.3.4)", "numba (>=0.55.2)", "numexpr (>=2.8.0)"] +plot = ["matplotlib (>=3.6.1)"] +postgresql = ["SQLAlchemy (>=1.4.36)", "psycopg2 (>=2.9.3)"] +spss = ["pyreadstat (>=1.1.5)"] +sql-other = ["SQLAlchemy (>=1.4.36)"] +test = ["hypothesis (>=6.46.1)", "pytest (>=7.3.2)", "pytest-asyncio (>=0.17.0)", "pytest-xdist (>=2.2.0)"] +xml = ["lxml (>=4.8.0)"] [[package]] name = "pillow" -version = "8.2.0" +version = "9.5.0" description = "Python Imaging Library (Fork)" -category = "main" optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" +files = [ + {file = "Pillow-9.5.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:ace6ca218308447b9077c14ea4ef381ba0b67ee78d64046b3f19cf4e1139ad16"}, + {file = "Pillow-9.5.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d3d403753c9d5adc04d4694d35cf0391f0f3d57c8e0030aac09d7678fa8030aa"}, + {file = "Pillow-9.5.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5ba1b81ee69573fe7124881762bb4cd2e4b6ed9dd28c9c60a632902fe8db8b38"}, + {file = "Pillow-9.5.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fe7e1c262d3392afcf5071df9afa574544f28eac825284596ac6db56e6d11062"}, + {file = "Pillow-9.5.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f36397bf3f7d7c6a3abdea815ecf6fd14e7fcd4418ab24bae01008d8d8ca15e"}, + {file = "Pillow-9.5.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:252a03f1bdddce077eff2354c3861bf437c892fb1832f75ce813ee94347aa9b5"}, + {file = "Pillow-9.5.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:85ec677246533e27770b0de5cf0f9d6e4ec0c212a1f89dfc941b64b21226009d"}, + {file = "Pillow-9.5.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:b416f03d37d27290cb93597335a2f85ed446731200705b22bb927405320de903"}, + {file = "Pillow-9.5.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:1781a624c229cb35a2ac31cc4a77e28cafc8900733a864870c49bfeedacd106a"}, + {file = "Pillow-9.5.0-cp310-cp310-win32.whl", hash = "sha256:8507eda3cd0608a1f94f58c64817e83ec12fa93a9436938b191b80d9e4c0fc44"}, + {file = "Pillow-9.5.0-cp310-cp310-win_amd64.whl", hash = "sha256:d3c6b54e304c60c4181da1c9dadf83e4a54fd266a99c70ba646a9baa626819eb"}, + {file = "Pillow-9.5.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:7ec6f6ce99dab90b52da21cf0dc519e21095e332ff3b399a357c187b1a5eee32"}, + {file = "Pillow-9.5.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:560737e70cb9c6255d6dcba3de6578a9e2ec4b573659943a5e7e4af13f298f5c"}, + {file = "Pillow-9.5.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:96e88745a55b88a7c64fa49bceff363a1a27d9a64e04019c2281049444a571e3"}, + {file = "Pillow-9.5.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d9c206c29b46cfd343ea7cdfe1232443072bbb270d6a46f59c259460db76779a"}, + {file = "Pillow-9.5.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cfcc2c53c06f2ccb8976fb5c71d448bdd0a07d26d8e07e321c103416444c7ad1"}, + {file = "Pillow-9.5.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:a0f9bb6c80e6efcde93ffc51256d5cfb2155ff8f78292f074f60f9e70b942d99"}, + {file = "Pillow-9.5.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:8d935f924bbab8f0a9a28404422da8af4904e36d5c33fc6f677e4c4485515625"}, + {file = "Pillow-9.5.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:fed1e1cf6a42577953abbe8e6cf2fe2f566daebde7c34724ec8803c4c0cda579"}, + {file = "Pillow-9.5.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:c1170d6b195555644f0616fd6ed929dfcf6333b8675fcca044ae5ab110ded296"}, + {file = "Pillow-9.5.0-cp311-cp311-win32.whl", hash = "sha256:54f7102ad31a3de5666827526e248c3530b3a33539dbda27c6843d19d72644ec"}, + {file = "Pillow-9.5.0-cp311-cp311-win_amd64.whl", hash = "sha256:cfa4561277f677ecf651e2b22dc43e8f5368b74a25a8f7d1d4a3a243e573f2d4"}, + {file = "Pillow-9.5.0-cp311-cp311-win_arm64.whl", hash = "sha256:965e4a05ef364e7b973dd17fc765f42233415974d773e82144c9bbaaaea5d089"}, + {file = "Pillow-9.5.0-cp312-cp312-win32.whl", hash = "sha256:22baf0c3cf0c7f26e82d6e1adf118027afb325e703922c8dfc1d5d0156bb2eeb"}, + {file = "Pillow-9.5.0-cp312-cp312-win_amd64.whl", hash = "sha256:432b975c009cf649420615388561c0ce7cc31ce9b2e374db659ee4f7d57a1f8b"}, + {file = "Pillow-9.5.0-cp37-cp37m-macosx_10_10_x86_64.whl", hash = "sha256:5d4ebf8e1db4441a55c509c4baa7a0587a0210f7cd25fcfe74dbbce7a4bd1906"}, + {file = "Pillow-9.5.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:375f6e5ee9620a271acb6820b3d1e94ffa8e741c0601db4c0c4d3cb0a9c224bf"}, + {file = "Pillow-9.5.0-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:99eb6cafb6ba90e436684e08dad8be1637efb71c4f2180ee6b8f940739406e78"}, + {file = "Pillow-9.5.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2dfaaf10b6172697b9bceb9a3bd7b951819d1ca339a5ef294d1f1ac6d7f63270"}, + {file = "Pillow-9.5.0-cp37-cp37m-manylinux_2_28_aarch64.whl", hash = "sha256:763782b2e03e45e2c77d7779875f4432e25121ef002a41829d8868700d119392"}, + {file = "Pillow-9.5.0-cp37-cp37m-manylinux_2_28_x86_64.whl", hash = "sha256:35f6e77122a0c0762268216315bf239cf52b88865bba522999dc38f1c52b9b47"}, + {file = "Pillow-9.5.0-cp37-cp37m-win32.whl", hash = "sha256:aca1c196f407ec7cf04dcbb15d19a43c507a81f7ffc45b690899d6a76ac9fda7"}, + {file = "Pillow-9.5.0-cp37-cp37m-win_amd64.whl", hash = "sha256:322724c0032af6692456cd6ed554bb85f8149214d97398bb80613b04e33769f6"}, + {file = "Pillow-9.5.0-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:a0aa9417994d91301056f3d0038af1199eb7adc86e646a36b9e050b06f526597"}, + {file = "Pillow-9.5.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:f8286396b351785801a976b1e85ea88e937712ee2c3ac653710a4a57a8da5d9c"}, + {file = "Pillow-9.5.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c830a02caeb789633863b466b9de10c015bded434deb3ec87c768e53752ad22a"}, + {file = "Pillow-9.5.0-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fbd359831c1657d69bb81f0db962905ee05e5e9451913b18b831febfe0519082"}, + {file = "Pillow-9.5.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f8fc330c3370a81bbf3f88557097d1ea26cd8b019d6433aa59f71195f5ddebbf"}, + {file = "Pillow-9.5.0-cp38-cp38-manylinux_2_28_aarch64.whl", hash = "sha256:7002d0797a3e4193c7cdee3198d7c14f92c0836d6b4a3f3046a64bd1ce8df2bf"}, + {file = "Pillow-9.5.0-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:229e2c79c00e85989a34b5981a2b67aa079fd08c903f0aaead522a1d68d79e51"}, + {file = "Pillow-9.5.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:9adf58f5d64e474bed00d69bcd86ec4bcaa4123bfa70a65ce72e424bfb88ed96"}, + {file = "Pillow-9.5.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:662da1f3f89a302cc22faa9f14a262c2e3951f9dbc9617609a47521c69dd9f8f"}, + {file = "Pillow-9.5.0-cp38-cp38-win32.whl", hash = "sha256:6608ff3bf781eee0cd14d0901a2b9cc3d3834516532e3bd673a0a204dc8615fc"}, + {file = "Pillow-9.5.0-cp38-cp38-win_amd64.whl", hash = "sha256:e49eb4e95ff6fd7c0c402508894b1ef0e01b99a44320ba7d8ecbabefddcc5569"}, + {file = "Pillow-9.5.0-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:482877592e927fd263028c105b36272398e3e1be3269efda09f6ba21fd83ec66"}, + {file = "Pillow-9.5.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3ded42b9ad70e5f1754fb7c2e2d6465a9c842e41d178f262e08b8c85ed8a1d8e"}, + {file = "Pillow-9.5.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c446d2245ba29820d405315083d55299a796695d747efceb5717a8b450324115"}, + {file = "Pillow-9.5.0-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8aca1152d93dcc27dc55395604dcfc55bed5f25ef4c98716a928bacba90d33a3"}, + {file = "Pillow-9.5.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:608488bdcbdb4ba7837461442b90ea6f3079397ddc968c31265c1e056964f1ef"}, + {file = "Pillow-9.5.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:60037a8db8750e474af7ffc9faa9b5859e6c6d0a50e55c45576bf28be7419705"}, + {file = "Pillow-9.5.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:07999f5834bdc404c442146942a2ecadd1cb6292f5229f4ed3b31e0a108746b1"}, + {file = "Pillow-9.5.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:a127ae76092974abfbfa38ca2d12cbeddcdeac0fb71f9627cc1135bedaf9d51a"}, + {file = "Pillow-9.5.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:489f8389261e5ed43ac8ff7b453162af39c3e8abd730af8363587ba64bb2e865"}, + {file = "Pillow-9.5.0-cp39-cp39-win32.whl", hash = "sha256:9b1af95c3a967bf1da94f253e56b6286b50af23392a886720f563c547e48e964"}, + {file = "Pillow-9.5.0-cp39-cp39-win_amd64.whl", hash = "sha256:77165c4a5e7d5a284f10a6efaa39a0ae8ba839da344f20b111d62cc932fa4e5d"}, + {file = "Pillow-9.5.0-pp38-pypy38_pp73-macosx_10_10_x86_64.whl", hash = "sha256:833b86a98e0ede388fa29363159c9b1a294b0905b5128baf01db683672f230f5"}, + {file = "Pillow-9.5.0-pp38-pypy38_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:aaf305d6d40bd9632198c766fb64f0c1a83ca5b667f16c1e79e1661ab5060140"}, + {file = "Pillow-9.5.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0852ddb76d85f127c135b6dd1f0bb88dbb9ee990d2cd9aa9e28526c93e794fba"}, + {file = "Pillow-9.5.0-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:91ec6fe47b5eb5a9968c79ad9ed78c342b1f97a091677ba0e012701add857829"}, + {file = "Pillow-9.5.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:cb841572862f629b99725ebaec3287fc6d275be9b14443ea746c1dd325053cbd"}, + {file = "Pillow-9.5.0-pp39-pypy39_pp73-macosx_10_10_x86_64.whl", hash = "sha256:c380b27d041209b849ed246b111b7c166ba36d7933ec6e41175fd15ab9eb1572"}, + {file = "Pillow-9.5.0-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7c9af5a3b406a50e313467e3565fc99929717f780164fe6fbb7704edba0cebbe"}, + {file = "Pillow-9.5.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5671583eab84af046a397d6d0ba25343c00cd50bce03787948e0fff01d4fd9b1"}, + {file = "Pillow-9.5.0-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:84a6f19ce086c1bf894644b43cd129702f781ba5751ca8572f08aa40ef0ab7b7"}, + {file = "Pillow-9.5.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:1e7723bd90ef94eda669a3c2c19d549874dd5badaeefabefd26053304abe5799"}, + {file = "Pillow-9.5.0.tar.gz", hash = "sha256:bf548479d336726d7a0eceb6e767e179fbde37833ae42794602631a070d630f1"}, +] + +[package.extras] +docs = ["furo", "olefile", "sphinx (>=2.4)", "sphinx-copybutton", "sphinx-inline-tabs", "sphinx-removed-in", "sphinxext-opengraph"] +tests = ["check-manifest", "coverage", "defusedxml", "markdown2", "olefile", "packaging", "pyroma", "pytest", "pytest-cov", "pytest-timeout"] [[package]] name = "psycopg2" -version = "2.8.6" +version = "2.9.6" description = "psycopg2 - Python-PostgreSQL Database Adapter" -category = "main" optional = false -python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*" +python-versions = ">=3.6" +files = [ + {file = "psycopg2-2.9.6-cp310-cp310-win32.whl", hash = "sha256:f7a7a5ee78ba7dc74265ba69e010ae89dae635eea0e97b055fb641a01a31d2b1"}, + {file = "psycopg2-2.9.6-cp310-cp310-win_amd64.whl", hash = "sha256:f75001a1cbbe523e00b0ef896a5a1ada2da93ccd752b7636db5a99bc57c44494"}, + {file = "psycopg2-2.9.6-cp311-cp311-win32.whl", hash = "sha256:53f4ad0a3988f983e9b49a5d9765d663bbe84f508ed655affdb810af9d0972ad"}, + {file = "psycopg2-2.9.6-cp311-cp311-win_amd64.whl", hash = "sha256:b81fcb9ecfc584f661b71c889edeae70bae30d3ef74fa0ca388ecda50b1222b7"}, + {file = "psycopg2-2.9.6-cp36-cp36m-win32.whl", hash = "sha256:11aca705ec888e4f4cea97289a0bf0f22a067a32614f6ef64fcf7b8bfbc53744"}, + {file = "psycopg2-2.9.6-cp36-cp36m-win_amd64.whl", hash = "sha256:36c941a767341d11549c0fbdbb2bf5be2eda4caf87f65dfcd7d146828bd27f39"}, + {file = "psycopg2-2.9.6-cp37-cp37m-win32.whl", hash = "sha256:869776630c04f335d4124f120b7fb377fe44b0a7645ab3c34b4ba42516951889"}, + {file = "psycopg2-2.9.6-cp37-cp37m-win_amd64.whl", hash = "sha256:a8ad4a47f42aa6aec8d061fdae21eaed8d864d4bb0f0cade5ad32ca16fcd6258"}, + {file = "psycopg2-2.9.6-cp38-cp38-win32.whl", hash = "sha256:2362ee4d07ac85ff0ad93e22c693d0f37ff63e28f0615a16b6635a645f4b9214"}, + {file = "psycopg2-2.9.6-cp38-cp38-win_amd64.whl", hash = "sha256:d24ead3716a7d093b90b27b3d73459fe8cd90fd7065cf43b3c40966221d8c394"}, + {file = "psycopg2-2.9.6-cp39-cp39-win32.whl", hash = "sha256:1861a53a6a0fd248e42ea37c957d36950da00266378746588eab4f4b5649e95f"}, + {file = "psycopg2-2.9.6-cp39-cp39-win_amd64.whl", hash = "sha256:ded2faa2e6dfb430af7713d87ab4abbfc764d8d7fb73eafe96a24155f906ebf5"}, + {file = "psycopg2-2.9.6.tar.gz", hash = "sha256:f15158418fd826831b28585e2ab48ed8df2d0d98f502a2b4fe619e7d5ca29011"}, +] [[package]] name = "psycopg2-binary" -version = "2.8.6" +version = "2.9.6" description = "psycopg2 - Python-PostgreSQL Database Adapter" -category = "main" optional = false -python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*" +python-versions = ">=3.6" +files = [ + {file = "psycopg2-binary-2.9.6.tar.gz", hash = "sha256:1f64dcfb8f6e0c014c7f55e51c9759f024f70ea572fbdef123f85318c297947c"}, + {file = "psycopg2_binary-2.9.6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d26e0342183c762de3276cca7a530d574d4e25121ca7d6e4a98e4f05cb8e4df7"}, + {file = "psycopg2_binary-2.9.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c48d8f2db17f27d41fb0e2ecd703ea41984ee19362cbce52c097963b3a1b4365"}, + {file = "psycopg2_binary-2.9.6-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ffe9dc0a884a8848075e576c1de0290d85a533a9f6e9c4e564f19adf8f6e54a7"}, + {file = "psycopg2_binary-2.9.6-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8a76e027f87753f9bd1ab5f7c9cb8c7628d1077ef927f5e2446477153a602f2c"}, + {file = "psycopg2_binary-2.9.6-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6460c7a99fc939b849431f1e73e013d54aa54293f30f1109019c56a0b2b2ec2f"}, + {file = "psycopg2_binary-2.9.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ae102a98c547ee2288637af07393dd33f440c25e5cd79556b04e3fca13325e5f"}, + {file = "psycopg2_binary-2.9.6-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:9972aad21f965599ed0106f65334230ce826e5ae69fda7cbd688d24fa922415e"}, + {file = "psycopg2_binary-2.9.6-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:7a40c00dbe17c0af5bdd55aafd6ff6679f94a9be9513a4c7e071baf3d7d22a70"}, + {file = "psycopg2_binary-2.9.6-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:cacbdc5839bdff804dfebc058fe25684cae322987f7a38b0168bc1b2df703fb1"}, + {file = "psycopg2_binary-2.9.6-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:7f0438fa20fb6c7e202863e0d5ab02c246d35efb1d164e052f2f3bfe2b152bd0"}, + {file = "psycopg2_binary-2.9.6-cp310-cp310-win32.whl", hash = "sha256:b6c8288bb8a84b47e07013bb4850f50538aa913d487579e1921724631d02ea1b"}, + {file = "psycopg2_binary-2.9.6-cp310-cp310-win_amd64.whl", hash = "sha256:61b047a0537bbc3afae10f134dc6393823882eb263088c271331602b672e52e9"}, + {file = "psycopg2_binary-2.9.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:964b4dfb7c1c1965ac4c1978b0f755cc4bd698e8aa2b7667c575fb5f04ebe06b"}, + {file = "psycopg2_binary-2.9.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:afe64e9b8ea66866a771996f6ff14447e8082ea26e675a295ad3bdbffdd72afb"}, + {file = "psycopg2_binary-2.9.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:15e2ee79e7cf29582ef770de7dab3d286431b01c3bb598f8e05e09601b890081"}, + {file = "psycopg2_binary-2.9.6-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dfa74c903a3c1f0d9b1c7e7b53ed2d929a4910e272add6700c38f365a6002820"}, + {file = "psycopg2_binary-2.9.6-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b83456c2d4979e08ff56180a76429263ea254c3f6552cd14ada95cff1dec9bb8"}, + {file = "psycopg2_binary-2.9.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0645376d399bfd64da57148694d78e1f431b1e1ee1054872a5713125681cf1be"}, + {file = "psycopg2_binary-2.9.6-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e99e34c82309dd78959ba3c1590975b5d3c862d6f279f843d47d26ff89d7d7e1"}, + {file = "psycopg2_binary-2.9.6-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:4ea29fc3ad9d91162c52b578f211ff1c931d8a38e1f58e684c45aa470adf19e2"}, + {file = "psycopg2_binary-2.9.6-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:4ac30da8b4f57187dbf449294d23b808f8f53cad6b1fc3623fa8a6c11d176dd0"}, + {file = "psycopg2_binary-2.9.6-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e78e6e2a00c223e164c417628572a90093c031ed724492c763721c2e0bc2a8df"}, + {file = "psycopg2_binary-2.9.6-cp311-cp311-win32.whl", hash = "sha256:1876843d8e31c89c399e31b97d4b9725a3575bb9c2af92038464231ec40f9edb"}, + {file = "psycopg2_binary-2.9.6-cp311-cp311-win_amd64.whl", hash = "sha256:b4b24f75d16a89cc6b4cdff0eb6a910a966ecd476d1e73f7ce5985ff1328e9a6"}, + {file = "psycopg2_binary-2.9.6-cp36-cp36m-win32.whl", hash = "sha256:498807b927ca2510baea1b05cc91d7da4718a0f53cb766c154c417a39f1820a0"}, + {file = "psycopg2_binary-2.9.6-cp36-cp36m-win_amd64.whl", hash = "sha256:0d236c2825fa656a2d98bbb0e52370a2e852e5a0ec45fc4f402977313329174d"}, + {file = "psycopg2_binary-2.9.6-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:34b9ccdf210cbbb1303c7c4db2905fa0319391bd5904d32689e6dd5c963d2ea8"}, + {file = "psycopg2_binary-2.9.6-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:84d2222e61f313c4848ff05353653bf5f5cf6ce34df540e4274516880d9c3763"}, + {file = "psycopg2_binary-2.9.6-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:30637a20623e2a2eacc420059be11527f4458ef54352d870b8181a4c3020ae6b"}, + {file = "psycopg2_binary-2.9.6-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8122cfc7cae0da9a3077216528b8bb3629c43b25053284cc868744bfe71eb141"}, + {file = "psycopg2_binary-2.9.6-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:38601cbbfe600362c43714482f43b7c110b20cb0f8172422c616b09b85a750c5"}, + {file = "psycopg2_binary-2.9.6-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:c7e62ab8b332147a7593a385d4f368874d5fe4ad4e341770d4983442d89603e3"}, + {file = "psycopg2_binary-2.9.6-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:2ab652e729ff4ad76d400df2624d223d6e265ef81bb8aa17fbd63607878ecbee"}, + {file = "psycopg2_binary-2.9.6-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:c83a74b68270028dc8ee74d38ecfaf9c90eed23c8959fca95bd703d25b82c88e"}, + {file = "psycopg2_binary-2.9.6-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:d4e6036decf4b72d6425d5b29bbd3e8f0ff1059cda7ac7b96d6ac5ed34ffbacd"}, + {file = "psycopg2_binary-2.9.6-cp37-cp37m-win32.whl", hash = "sha256:a8c28fd40a4226b4a84bdf2d2b5b37d2c7bd49486b5adcc200e8c7ec991dfa7e"}, + {file = "psycopg2_binary-2.9.6-cp37-cp37m-win_amd64.whl", hash = "sha256:51537e3d299be0db9137b321dfb6a5022caaab275775680e0c3d281feefaca6b"}, + {file = "psycopg2_binary-2.9.6-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:cf4499e0a83b7b7edcb8dabecbd8501d0d3a5ef66457200f77bde3d210d5debb"}, + {file = "psycopg2_binary-2.9.6-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7e13a5a2c01151f1208d5207e42f33ba86d561b7a89fca67c700b9486a06d0e2"}, + {file = "psycopg2_binary-2.9.6-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0e0f754d27fddcfd74006455b6e04e6705d6c31a612ec69ddc040a5468e44b4e"}, + {file = "psycopg2_binary-2.9.6-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d57c3fd55d9058645d26ae37d76e61156a27722097229d32a9e73ed54819982a"}, + {file = "psycopg2_binary-2.9.6-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:71f14375d6f73b62800530b581aed3ada394039877818b2d5f7fc77e3bb6894d"}, + {file = "psycopg2_binary-2.9.6-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:441cc2f8869a4f0f4bb408475e5ae0ee1f3b55b33f350406150277f7f35384fc"}, + {file = "psycopg2_binary-2.9.6-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:65bee1e49fa6f9cf327ce0e01c4c10f39165ee76d35c846ade7cb0ec6683e303"}, + {file = "psycopg2_binary-2.9.6-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:af335bac6b666cc6aea16f11d486c3b794029d9df029967f9938a4bed59b6a19"}, + {file = "psycopg2_binary-2.9.6-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:cfec476887aa231b8548ece2e06d28edc87c1397ebd83922299af2e051cf2827"}, + {file = "psycopg2_binary-2.9.6-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:65c07febd1936d63bfde78948b76cd4c2a411572a44ac50719ead41947d0f26b"}, + {file = "psycopg2_binary-2.9.6-cp38-cp38-win32.whl", hash = "sha256:4dfb4be774c4436a4526d0c554af0cc2e02082c38303852a36f6456ece7b3503"}, + {file = "psycopg2_binary-2.9.6-cp38-cp38-win_amd64.whl", hash = "sha256:02c6e3cf3439e213e4ee930308dc122d6fb4d4bea9aef4a12535fbd605d1a2fe"}, + {file = "psycopg2_binary-2.9.6-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:e9182eb20f41417ea1dd8e8f7888c4d7c6e805f8a7c98c1081778a3da2bee3e4"}, + {file = "psycopg2_binary-2.9.6-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:8a6979cf527e2603d349a91060f428bcb135aea2be3201dff794813256c274f1"}, + {file = "psycopg2_binary-2.9.6-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8338a271cb71d8da40b023a35d9c1e919eba6cbd8fa20a54b748a332c355d896"}, + {file = "psycopg2_binary-2.9.6-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e3ed340d2b858d6e6fb5083f87c09996506af483227735de6964a6100b4e6a54"}, + {file = "psycopg2_binary-2.9.6-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f81e65376e52f03422e1fb475c9514185669943798ed019ac50410fb4c4df232"}, + {file = "psycopg2_binary-2.9.6-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bfb13af3c5dd3a9588000910178de17010ebcccd37b4f9794b00595e3a8ddad3"}, + {file = "psycopg2_binary-2.9.6-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:4c727b597c6444a16e9119386b59388f8a424223302d0c06c676ec8b4bc1f963"}, + {file = "psycopg2_binary-2.9.6-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:4d67fbdaf177da06374473ef6f7ed8cc0a9dc640b01abfe9e8a2ccb1b1402c1f"}, + {file = "psycopg2_binary-2.9.6-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:0892ef645c2fabb0c75ec32d79f4252542d0caec1d5d949630e7d242ca4681a3"}, + {file = "psycopg2_binary-2.9.6-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:02c0f3757a4300cf379eb49f543fb7ac527fb00144d39246ee40e1df684ab514"}, + {file = "psycopg2_binary-2.9.6-cp39-cp39-win32.whl", hash = "sha256:c3dba7dab16709a33a847e5cd756767271697041fbe3fe97c215b1fc1f5c9848"}, + {file = "psycopg2_binary-2.9.6-cp39-cp39-win_amd64.whl", hash = "sha256:f6a88f384335bb27812293fdb11ac6aee2ca3f51d3c7820fe03de0a304ab6249"}, +] [[package]] name = "pycparser" -version = "2.20" +version = "2.21" description = "C parser in Python" -category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +files = [ + {file = "pycparser-2.21-py2.py3-none-any.whl", hash = "sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9"}, + {file = "pycparser-2.21.tar.gz", hash = "sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206"}, +] [[package]] name = "pycryptodome" -version = "3.10.1" +version = "3.18.0" description = "Cryptographic library for Python" -category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +files = [ + {file = "pycryptodome-3.18.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:d1497a8cd4728db0e0da3c304856cb37c0c4e3d0b36fcbabcc1600f18504fc54"}, + {file = "pycryptodome-3.18.0-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:928078c530da78ff08e10eb6cada6e0dff386bf3d9fa9871b4bbc9fbc1efe024"}, + {file = "pycryptodome-3.18.0-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:157c9b5ba5e21b375f052ca78152dd309a09ed04703fd3721dce3ff8ecced148"}, + {file = "pycryptodome-3.18.0-cp27-cp27m-manylinux2014_aarch64.whl", hash = "sha256:d20082bdac9218649f6abe0b885927be25a917e29ae0502eaf2b53f1233ce0c2"}, + {file = "pycryptodome-3.18.0-cp27-cp27m-musllinux_1_1_aarch64.whl", hash = "sha256:e8ad74044e5f5d2456c11ed4cfd3e34b8d4898c0cb201c4038fe41458a82ea27"}, + {file = "pycryptodome-3.18.0-cp27-cp27m-win32.whl", hash = "sha256:62a1e8847fabb5213ccde38915563140a5b338f0d0a0d363f996b51e4a6165cf"}, + {file = "pycryptodome-3.18.0-cp27-cp27m-win_amd64.whl", hash = "sha256:16bfd98dbe472c263ed2821284118d899c76968db1a6665ade0c46805e6b29a4"}, + {file = "pycryptodome-3.18.0-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:7a3d22c8ee63de22336679e021c7f2386f7fc465477d59675caa0e5706387944"}, + {file = "pycryptodome-3.18.0-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:78d863476e6bad2a592645072cc489bb90320972115d8995bcfbee2f8b209918"}, + {file = "pycryptodome-3.18.0-cp27-cp27mu-manylinux2014_aarch64.whl", hash = "sha256:b6a610f8bfe67eab980d6236fdc73bfcdae23c9ed5548192bb2d530e8a92780e"}, + {file = "pycryptodome-3.18.0-cp27-cp27mu-musllinux_1_1_aarch64.whl", hash = "sha256:422c89fd8df8a3bee09fb8d52aaa1e996120eafa565437392b781abec2a56e14"}, + {file = "pycryptodome-3.18.0-cp35-abi3-macosx_10_9_universal2.whl", hash = "sha256:9ad6f09f670c466aac94a40798e0e8d1ef2aa04589c29faa5b9b97566611d1d1"}, + {file = "pycryptodome-3.18.0-cp35-abi3-macosx_10_9_x86_64.whl", hash = "sha256:53aee6be8b9b6da25ccd9028caf17dcdce3604f2c7862f5167777b707fbfb6cb"}, + {file = "pycryptodome-3.18.0-cp35-abi3-manylinux2014_aarch64.whl", hash = "sha256:10da29526a2a927c7d64b8f34592f461d92ae55fc97981aab5bbcde8cb465bb6"}, + {file = "pycryptodome-3.18.0-cp35-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f21efb8438971aa16924790e1c3dba3a33164eb4000106a55baaed522c261acf"}, + {file = "pycryptodome-3.18.0-cp35-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4944defabe2ace4803f99543445c27dd1edbe86d7d4edb87b256476a91e9ffa4"}, + {file = "pycryptodome-3.18.0-cp35-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:51eae079ddb9c5f10376b4131be9589a6554f6fd84f7f655180937f611cd99a2"}, + {file = "pycryptodome-3.18.0-cp35-abi3-musllinux_1_1_i686.whl", hash = "sha256:83c75952dcf4a4cebaa850fa257d7a860644c70a7cd54262c237c9f2be26f76e"}, + {file = "pycryptodome-3.18.0-cp35-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:957b221d062d5752716923d14e0926f47670e95fead9d240fa4d4862214b9b2f"}, + {file = "pycryptodome-3.18.0-cp35-abi3-win32.whl", hash = "sha256:795bd1e4258a2c689c0b1f13ce9684fa0dd4c0e08680dcf597cf9516ed6bc0f3"}, + {file = "pycryptodome-3.18.0-cp35-abi3-win_amd64.whl", hash = "sha256:b1d9701d10303eec8d0bd33fa54d44e67b8be74ab449052a8372f12a66f93fb9"}, + {file = "pycryptodome-3.18.0-pp27-pypy_73-manylinux2010_x86_64.whl", hash = "sha256:cb1be4d5af7f355e7d41d36d8eec156ef1382a88638e8032215c215b82a4b8ec"}, + {file = "pycryptodome-3.18.0-pp27-pypy_73-win32.whl", hash = "sha256:fc0a73f4db1e31d4a6d71b672a48f3af458f548059aa05e83022d5f61aac9c08"}, + {file = "pycryptodome-3.18.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:f022a4fd2a5263a5c483a2bb165f9cb27f2be06f2f477113783efe3fe2ad887b"}, + {file = "pycryptodome-3.18.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:363dd6f21f848301c2dcdeb3c8ae5f0dee2286a5e952a0f04954b82076f23825"}, + {file = "pycryptodome-3.18.0-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:12600268763e6fec3cefe4c2dcdf79bde08d0b6dc1813887e789e495cb9f3403"}, + {file = "pycryptodome-3.18.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:4604816adebd4faf8810782f137f8426bf45fee97d8427fa8e1e49ea78a52e2c"}, + {file = "pycryptodome-3.18.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:01489bbdf709d993f3058e2996f8f40fee3f0ea4d995002e5968965fa2fe89fb"}, + {file = "pycryptodome-3.18.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3811e31e1ac3069988f7a1c9ee7331b942e605dfc0f27330a9ea5997e965efb2"}, + {file = "pycryptodome-3.18.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6f4b967bb11baea9128ec88c3d02f55a3e338361f5e4934f5240afcb667fdaec"}, + {file = "pycryptodome-3.18.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:9c8eda4f260072f7dbe42f473906c659dcbadd5ae6159dfb49af4da1293ae380"}, + {file = "pycryptodome-3.18.0.tar.gz", hash = "sha256:c9adee653fc882d98956e33ca2c1fb582e23a8af7ac82fee75bd6113c55a0413"}, +] [[package]] name = "pygments" -version = "2.9.0" +version = "2.15.1" description = "Pygments is a syntax highlighting package written in Python." -category = "main" optional = false -python-versions = ">=3.5" +python-versions = ">=3.7" +files = [ + {file = "Pygments-2.15.1-py3-none-any.whl", hash = "sha256:db2db3deb4b4179f399a09054b023b6a586b76499d36965813c71aa8ed7b5fd1"}, + {file = "Pygments-2.15.1.tar.gz", hash = "sha256:8ace4d3c1dd481894b2005f560ead0f9f19ee64fe983366be1a21e171d12775c"}, +] + +[package.extras] +plugins = ["importlib-metadata"] + +[[package]] +name = "pyhanko" +version = "0.19.0" +description = "Tools for stamping and signing PDF files" +optional = false +python-versions = ">=3.7" +files = [ + {file = "pyHanko-0.19.0-py3-none-any.whl", hash = "sha256:853fd237d90d76da9b5a0f8aaddb48331d18623e755ab06be4665e02ac603f4c"}, + {file = "pyHanko-0.19.0.tar.gz", hash = "sha256:9cca53a633784bf7ad9ba41f2d7a8abdafca722b9f23f02add9d4150e316d6db"}, +] + +[package.dependencies] +asn1crypto = ">=1.5.1" +click = ">=7.1.2" +cryptography = ">=3.3.1" +pyhanko-certvalidator = "==0.23.*" +pyyaml = ">=5.3.1" +qrcode = ">=6.1" +requests = ">=2.24.0" +tzlocal = ">=4.3" + +[package.extras] +async-http = ["aiohttp (>=3.8.0,<3.9.0)"] +docs = ["sphinx", "sphinx-rtd-theme"] +extra-pubkey-algs = ["oscrypto (>=1.2.1)"] +image-support = ["Pillow (>=7.2.0)", "python-barcode (==0.14.0)"] +live-test = ["certomancer-csc-dummy (==0.2.2)", "certomancer[pkcs12,web-api] (>=0.11.0,<0.12.0)", "pyHanko[async-http,extra-pubkey-algs,testing-basic,xmp]", "pytest-aiohttp (>=1.0.4,<1.1.0)", "pytest-cov (>=4.0.0,<4.1.0)"] +mypy = ["pyHanko[async-http,extra-pubkey-algs,image-support,opentype,pkcs11,xmp]", "types-PyYAML", "types-python-dateutil", "types-requests", "types-tzlocal"] +opentype = ["fonttools (>=4.33.3)", "uharfbuzz (>=0.25.0,<0.36.0)"] +pkcs11 = ["python-pkcs11 (>=0.7.0,<0.8.0)"] +testing = ["certomancer-csc-dummy (==0.2.2)", "pyHanko[async-http,extra-pubkey-algs,image-support,opentype,pkcs11,testing-basic,xmp]", "pytest-aiohttp (>=1.0.4,<1.1.0)"] +testing-basic = ["backports.zoneinfo[tzdata]", "certomancer (==0.11.*)", "freezegun (>=1.1.0)", "pytest (>=6.1.1)", "pytest-asyncio (==0.21.0)", "pytest-cov (>=4.0.0,<4.1.0)", "requests-mock (>=1.8.0)"] +xmp = ["defusedxml (>=0.7.1,<0.8.0)"] + +[[package]] +name = "pyhanko-certvalidator" +version = "0.23.0" +description = "Validates X.509 certificates and paths; forked from wbond/certvalidator" +optional = false +python-versions = ">=3.7" +files = [ + {file = "pyhanko-certvalidator-0.23.0.tar.gz", hash = "sha256:424d9070e92096ab088ae1bf5b027a0fd6adb48a3b5ff446d53b14d1dbc00338"}, + {file = "pyhanko_certvalidator-0.23.0-py3-none-any.whl", hash = "sha256:287d7c51d8ff4a1f8d9b8909f9d26f435f00d4cdd37351745b444768a668a6a5"}, +] + +[package.dependencies] +asn1crypto = ">=1.5.1" +cryptography = ">=3.3.1" +oscrypto = ">=1.1.0" +requests = ">=2.24.0" +uritools = ">=3.0.1" + +[package.extras] +async-http = ["aiohttp (>=3.8.0,<3.9.0)"] +mypy = ["pyhanko-certvalidator[testing]", "types-requests"] +testing = ["aiohttp (>=3.8.0,<3.9.0)", "freezegun (>=1.1.0)", "pyhanko-certvalidator[async-http]", "pytest (>=6.1.1)", "pytest-aiohttp (>=1.0.4,<1.1.0)", "pytest-cov (>=4.0.0,<4.1.0)"] [[package]] -name = "pypdf2" -version = "1.26.0" -description = "PDF toolkit" -category = "main" +name = "pypdf" +version = "3.12.2" +description = "A pure-python PDF library capable of splitting, merging, cropping, and transforming PDF files" +optional = false +python-versions = ">=3.6" +files = [ + {file = "pypdf-3.12.2-py3-none-any.whl", hash = "sha256:75c56b5e8ac8c61f6d8189d6bc839c1c2d1fe64626f4bab5d954e82c38652162"}, + {file = "pypdf-3.12.2.tar.gz", hash = "sha256:8657d56fd4f64540b9a1e5285845543534321484f1276af893eead7bd00598e6"}, +] + +[package.dependencies] +typing_extensions = {version = ">=3.10.0.0", markers = "python_version < \"3.10\""} + +[package.extras] +crypto = ["PyCryptodome"] +dev = ["black", "flit", "pip-tools", "pre-commit (<2.18.0)", "pytest-cov", "pytest-socket", "wheel"] +docs = ["myst_parser", "sphinx", "sphinx_rtd_theme"] +full = ["Pillow", "PyCryptodome"] +image = ["Pillow"] + +[[package]] +name = "pypng" +version = "0.20220715.0" +description = "Pure Python library for saving and loading PNG images" optional = false python-versions = "*" +files = [ + {file = "pypng-0.20220715.0-py3-none-any.whl", hash = "sha256:4a43e969b8f5aaafb2a415536c1a8ec7e341cd6a3f957fd5b5f32a4cfeed902c"}, + {file = "pypng-0.20220715.0.tar.gz", hash = "sha256:739c433ba96f078315de54c0db975aee537cbc3e1d0ae4ed9aab0ca1e427e2c1"}, +] [[package]] name = "python-bidi" version = "0.4.2" description = "Pure python implementation of the BiDi layout algorithm" -category = "main" optional = false python-versions = "*" +files = [ + {file = "python-bidi-0.4.2.tar.gz", hash = "sha256:5347f71e82b3e9976dc657f09ded2bfe39ba8d6777ca81a5b2c56c30121c496e"}, + {file = "python_bidi-0.4.2-py2.py3-none-any.whl", hash = "sha256:50eef6f6a0bbdd685f9e8c207f3c9050f5b578d0a46e37c76a9c4baea2cc2e13"}, +] [package.dependencies] six = "*" [[package]] name = "python-crontab" -version = "2.5.1" +version = "2.7.1" description = "Python Crontab API" -category = "main" optional = false python-versions = "*" +files = [ + {file = "python-crontab-2.7.1.tar.gz", hash = "sha256:b21af4647c7bbb848fef2f020616c6b0289dcb9f94b4f991a55310ff9bec5749"}, + {file = "python_crontab-2.7.1-py3-none-any.whl", hash = "sha256:9c374d1c9d401afdd8dd958f20077f74c158ab3fffb9604296802715e887fe48"}, +] [package.dependencies] python-dateutil = "*" @@ -317,50 +1155,288 @@ cron-schedule = ["croniter"] name = "python-dateutil" version = "2.8.2" description = "Extensions to the standard Python datetime module" -category = "main" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" +files = [ + {file = "python-dateutil-2.8.2.tar.gz", hash = "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86"}, + {file = "python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"}, +] [package.dependencies] six = ">=1.5" +[[package]] +name = "python-magic" +version = "0.4.27" +description = "File type identification using libmagic" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +files = [ + {file = "python-magic-0.4.27.tar.gz", hash = "sha256:c1ba14b08e4a5f5c31a302b7721239695b2f0f058d125bd5ce1ee36b9d9d3c3b"}, + {file = "python_magic-0.4.27-py2.py3-none-any.whl", hash = "sha256:c212960ad306f700aa0d01e5d7a325d20548ff97eb9920dcd29513174f0294d3"}, +] + [[package]] name = "pytz" -version = "2021.1" +version = "2023.3" description = "World timezone definitions, modern and historical" -category = "main" optional = false python-versions = "*" +files = [ + {file = "pytz-2023.3-py2.py3-none-any.whl", hash = "sha256:a151b3abb88eda1d4e34a9814df37de2a80e301e68ba0fd856fb9b46bfbbbffb"}, + {file = "pytz-2023.3.tar.gz", hash = "sha256:1d8ce29db189191fb55338ee6d0387d82ab59f3d00eac103412d64e0ebd0c588"}, +] + +[[package]] +name = "pyyaml" +version = "6.0.1" +description = "YAML parser and emitter for Python" +optional = false +python-versions = ">=3.6" +files = [ + {file = "PyYAML-6.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d858aa552c999bc8a8d57426ed01e40bef403cd8ccdd0fc5f6f04a00414cac2a"}, + {file = "PyYAML-6.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fd66fc5d0da6d9815ba2cebeb4205f95818ff4b79c3ebe268e75d961704af52f"}, + {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938"}, + {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d"}, + {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515"}, + {file = "PyYAML-6.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:326c013efe8048858a6d312ddd31d56e468118ad4cdeda36c719bf5bb6192290"}, + {file = "PyYAML-6.0.1-cp310-cp310-win32.whl", hash = "sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924"}, + {file = "PyYAML-6.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d"}, + {file = "PyYAML-6.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007"}, + {file = "PyYAML-6.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f003ed9ad21d6a4713f0a9b5a7a0a79e08dd0f221aff4525a2be4c346ee60aab"}, + {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d"}, + {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc"}, + {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673"}, + {file = "PyYAML-6.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e7d73685e87afe9f3b36c799222440d6cf362062f78be1013661b00c5c6f678b"}, + {file = "PyYAML-6.0.1-cp311-cp311-win32.whl", hash = "sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741"}, + {file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"}, + {file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"}, + {file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"}, + {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"}, + {file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"}, + {file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"}, + {file = "PyYAML-6.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:0d3304d8c0adc42be59c5f8a4d9e3d7379e6955ad754aa9d6ab7a398b59dd1df"}, + {file = "PyYAML-6.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47"}, + {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98"}, + {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c"}, + {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:afd7e57eddb1a54f0f1a974bc4391af8bcce0b444685d936840f125cf046d5bd"}, + {file = "PyYAML-6.0.1-cp36-cp36m-win32.whl", hash = "sha256:fca0e3a251908a499833aa292323f32437106001d436eca0e6e7833256674585"}, + {file = "PyYAML-6.0.1-cp36-cp36m-win_amd64.whl", hash = "sha256:f22ac1c3cac4dbc50079e965eba2c1058622631e526bd9afd45fedd49ba781fa"}, + {file = "PyYAML-6.0.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b1275ad35a5d18c62a7220633c913e1b42d44b46ee12554e5fd39c70a243d6a3"}, + {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:18aeb1bf9a78867dc38b259769503436b7c72f7a1f1f4c93ff9a17de54319b27"}, + {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:596106435fa6ad000c2991a98fa58eeb8656ef2325d7e158344fb33864ed87e3"}, + {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:baa90d3f661d43131ca170712d903e6295d1f7a0f595074f151c0aed377c9b9c"}, + {file = "PyYAML-6.0.1-cp37-cp37m-win32.whl", hash = "sha256:9046c58c4395dff28dd494285c82ba00b546adfc7ef001486fbf0324bc174fba"}, + {file = "PyYAML-6.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:4fb147e7a67ef577a588a0e2c17b6db51dda102c71de36f8549b6816a96e1867"}, + {file = "PyYAML-6.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1d4c7e777c441b20e32f52bd377e0c409713e8bb1386e1099c2415f26e479595"}, + {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5"}, + {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696"}, + {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735"}, + {file = "PyYAML-6.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:49a183be227561de579b4a36efbb21b3eab9651dd81b1858589f796549873dd6"}, + {file = "PyYAML-6.0.1-cp38-cp38-win32.whl", hash = "sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206"}, + {file = "PyYAML-6.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62"}, + {file = "PyYAML-6.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8"}, + {file = "PyYAML-6.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c8098ddcc2a85b61647b2590f825f3db38891662cfc2fc776415143f599bb859"}, + {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6"}, + {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0"}, + {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c"}, + {file = "PyYAML-6.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:04ac92ad1925b2cff1db0cfebffb6ffc43457495c9b3c39d3fcae417d7125dc5"}, + {file = "PyYAML-6.0.1-cp39-cp39-win32.whl", hash = "sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c"}, + {file = "PyYAML-6.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486"}, + {file = "PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43"}, +] + +[[package]] +name = "qrcode" +version = "7.4.2" +description = "QR Code image generator" +optional = false +python-versions = ">=3.7" +files = [ + {file = "qrcode-7.4.2-py3-none-any.whl", hash = "sha256:581dca7a029bcb2deef5d01068e39093e80ef00b4a61098a2182eac59d01643a"}, + {file = "qrcode-7.4.2.tar.gz", hash = "sha256:9dd969454827e127dbd93696b20747239e6d540e082937c90f14ac95b30f5845"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "platform_system == \"Windows\""} +pypng = "*" +typing-extensions = "*" + +[package.extras] +all = ["pillow (>=9.1.0)", "pytest", "pytest-cov", "tox", "zest.releaser[recommended]"] +dev = ["pytest", "pytest-cov", "tox"] +maintainer = ["zest.releaser[recommended]"] +pil = ["pillow (>=9.1.0)"] +test = ["coverage", "pytest"] [[package]] name = "regex" -version = "2021.4.4" +version = "2023.6.3" description = "Alternative regular expression module, to replace re." -category = "main" optional = false -python-versions = "*" +python-versions = ">=3.6" +files = [ + {file = "regex-2023.6.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:824bf3ac11001849aec3fa1d69abcb67aac3e150a933963fb12bda5151fe1bfd"}, + {file = "regex-2023.6.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:05ed27acdf4465c95826962528f9e8d41dbf9b1aa8531a387dee6ed215a3e9ef"}, + {file = "regex-2023.6.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0b49c764f88a79160fa64f9a7b425620e87c9f46095ef9c9920542ab2495c8bc"}, + {file = "regex-2023.6.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8e3f1316c2293e5469f8f09dc2d76efb6c3982d3da91ba95061a7e69489a14ef"}, + {file = "regex-2023.6.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:43e1dd9d12df9004246bacb79a0e5886b3b6071b32e41f83b0acbf293f820ee8"}, + {file = "regex-2023.6.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4959e8bcbfda5146477d21c3a8ad81b185cd252f3d0d6e4724a5ef11c012fb06"}, + {file = "regex-2023.6.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:af4dd387354dc83a3bff67127a124c21116feb0d2ef536805c454721c5d7993d"}, + {file = "regex-2023.6.3-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:2239d95d8e243658b8dbb36b12bd10c33ad6e6933a54d36ff053713f129aa536"}, + {file = "regex-2023.6.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:890e5a11c97cf0d0c550eb661b937a1e45431ffa79803b942a057c4fb12a2da2"}, + {file = "regex-2023.6.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:a8105e9af3b029f243ab11ad47c19b566482c150c754e4c717900a798806b222"}, + {file = "regex-2023.6.3-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:25be746a8ec7bc7b082783216de8e9473803706723b3f6bef34b3d0ed03d57e2"}, + {file = "regex-2023.6.3-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:3676f1dd082be28b1266c93f618ee07741b704ab7b68501a173ce7d8d0d0ca18"}, + {file = "regex-2023.6.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:10cb847aeb1728412c666ab2e2000ba6f174f25b2bdc7292e7dd71b16db07568"}, + {file = "regex-2023.6.3-cp310-cp310-win32.whl", hash = "sha256:dbbbfce33cd98f97f6bffb17801b0576e653f4fdb1d399b2ea89638bc8d08ae1"}, + {file = "regex-2023.6.3-cp310-cp310-win_amd64.whl", hash = "sha256:c5f8037000eb21e4823aa485149f2299eb589f8d1fe4b448036d230c3f4e68e0"}, + {file = "regex-2023.6.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c123f662be8ec5ab4ea72ea300359023a5d1df095b7ead76fedcd8babbedf969"}, + {file = "regex-2023.6.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9edcbad1f8a407e450fbac88d89e04e0b99a08473f666a3f3de0fd292badb6aa"}, + {file = "regex-2023.6.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dcba6dae7de533c876255317c11f3abe4907ba7d9aa15d13e3d9710d4315ec0e"}, + {file = "regex-2023.6.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:29cdd471ebf9e0f2fb3cac165efedc3c58db841d83a518b082077e612d3ee5df"}, + {file = "regex-2023.6.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:12b74fbbf6cbbf9dbce20eb9b5879469e97aeeaa874145517563cca4029db65c"}, + {file = "regex-2023.6.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0c29ca1bd61b16b67be247be87390ef1d1ef702800f91fbd1991f5c4421ebae8"}, + {file = "regex-2023.6.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d77f09bc4b55d4bf7cc5eba785d87001d6757b7c9eec237fe2af57aba1a071d9"}, + {file = "regex-2023.6.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ea353ecb6ab5f7e7d2f4372b1e779796ebd7b37352d290096978fea83c4dba0c"}, + {file = "regex-2023.6.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:10590510780b7541969287512d1b43f19f965c2ece6c9b1c00fc367b29d8dce7"}, + {file = "regex-2023.6.3-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:e2fbd6236aae3b7f9d514312cdb58e6494ee1c76a9948adde6eba33eb1c4264f"}, + {file = "regex-2023.6.3-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:6b2675068c8b56f6bfd5a2bda55b8accbb96c02fd563704732fd1c95e2083461"}, + {file = "regex-2023.6.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:74419d2b50ecb98360cfaa2974da8689cb3b45b9deff0dcf489c0d333bcc1477"}, + {file = "regex-2023.6.3-cp311-cp311-win32.whl", hash = "sha256:fb5ec16523dc573a4b277663a2b5a364e2099902d3944c9419a40ebd56a118f9"}, + {file = "regex-2023.6.3-cp311-cp311-win_amd64.whl", hash = "sha256:09e4a1a6acc39294a36b7338819b10baceb227f7f7dbbea0506d419b5a1dd8af"}, + {file = "regex-2023.6.3-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:0654bca0cdf28a5956c83839162692725159f4cda8d63e0911a2c0dc76166525"}, + {file = "regex-2023.6.3-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:463b6a3ceb5ca952e66550a4532cef94c9a0c80dc156c4cc343041951aec1697"}, + {file = "regex-2023.6.3-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:87b2a5bb5e78ee0ad1de71c664d6eb536dc3947a46a69182a90f4410f5e3f7dd"}, + {file = "regex-2023.6.3-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6343c6928282c1f6a9db41f5fd551662310e8774c0e5ebccb767002fcf663ca9"}, + {file = "regex-2023.6.3-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b6192d5af2ccd2a38877bfef086d35e6659566a335b1492786ff254c168b1693"}, + {file = "regex-2023.6.3-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:74390d18c75054947e4194019077e243c06fbb62e541d8817a0fa822ea310c14"}, + {file = "regex-2023.6.3-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:742e19a90d9bb2f4a6cf2862b8b06dea5e09b96c9f2df1779e53432d7275331f"}, + {file = "regex-2023.6.3-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:8abbc5d54ea0ee80e37fef009e3cec5dafd722ed3c829126253d3e22f3846f1e"}, + {file = "regex-2023.6.3-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:c2b867c17a7a7ae44c43ebbeb1b5ff406b3e8d5b3e14662683e5e66e6cc868d3"}, + {file = "regex-2023.6.3-cp36-cp36m-musllinux_1_1_ppc64le.whl", hash = "sha256:d831c2f8ff278179705ca59f7e8524069c1a989e716a1874d6d1aab6119d91d1"}, + {file = "regex-2023.6.3-cp36-cp36m-musllinux_1_1_s390x.whl", hash = "sha256:ee2d1a9a253b1729bb2de27d41f696ae893507c7db224436abe83ee25356f5c1"}, + {file = "regex-2023.6.3-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:61474f0b41fe1a80e8dfa70f70ea1e047387b7cd01c85ec88fa44f5d7561d787"}, + {file = "regex-2023.6.3-cp36-cp36m-win32.whl", hash = "sha256:0b71e63226e393b534105fcbdd8740410dc6b0854c2bfa39bbda6b0d40e59a54"}, + {file = "regex-2023.6.3-cp36-cp36m-win_amd64.whl", hash = "sha256:bbb02fd4462f37060122e5acacec78e49c0fbb303c30dd49c7f493cf21fc5b27"}, + {file = "regex-2023.6.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b862c2b9d5ae38a68b92e215b93f98d4c5e9454fa36aae4450f61dd33ff48487"}, + {file = "regex-2023.6.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:976d7a304b59ede34ca2921305b57356694f9e6879db323fd90a80f865d355a3"}, + {file = "regex-2023.6.3-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:83320a09188e0e6c39088355d423aa9d056ad57a0b6c6381b300ec1a04ec3d16"}, + {file = "regex-2023.6.3-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9427a399501818a7564f8c90eced1e9e20709ece36be701f394ada99890ea4b3"}, + {file = "regex-2023.6.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7178bbc1b2ec40eaca599d13c092079bf529679bf0371c602edaa555e10b41c3"}, + {file = "regex-2023.6.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:837328d14cde912af625d5f303ec29f7e28cdab588674897baafaf505341f2fc"}, + {file = "regex-2023.6.3-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:2d44dc13229905ae96dd2ae2dd7cebf824ee92bc52e8cf03dcead37d926da019"}, + {file = "regex-2023.6.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:d54af539295392611e7efbe94e827311eb8b29668e2b3f4cadcfe6f46df9c777"}, + {file = "regex-2023.6.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:7117d10690c38a622e54c432dfbbd3cbd92f09401d622902c32f6d377e2300ee"}, + {file = "regex-2023.6.3-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:bb60b503ec8a6e4e3e03a681072fa3a5adcbfa5479fa2d898ae2b4a8e24c4591"}, + {file = "regex-2023.6.3-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:65ba8603753cec91c71de423a943ba506363b0e5c3fdb913ef8f9caa14b2c7e0"}, + {file = "regex-2023.6.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:271f0bdba3c70b58e6f500b205d10a36fb4b58bd06ac61381b68de66442efddb"}, + {file = "regex-2023.6.3-cp37-cp37m-win32.whl", hash = "sha256:9beb322958aaca059f34975b0df135181f2e5d7a13b84d3e0e45434749cb20f7"}, + {file = "regex-2023.6.3-cp37-cp37m-win_amd64.whl", hash = "sha256:fea75c3710d4f31389eed3c02f62d0b66a9da282521075061ce875eb5300cf23"}, + {file = "regex-2023.6.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8f56fcb7ff7bf7404becdfc60b1e81a6d0561807051fd2f1860b0d0348156a07"}, + {file = "regex-2023.6.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:d2da3abc88711bce7557412310dfa50327d5769a31d1c894b58eb256459dc289"}, + {file = "regex-2023.6.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a99b50300df5add73d307cf66abea093304a07eb017bce94f01e795090dea87c"}, + {file = "regex-2023.6.3-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5708089ed5b40a7b2dc561e0c8baa9535b77771b64a8330b684823cfd5116036"}, + {file = "regex-2023.6.3-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:687ea9d78a4b1cf82f8479cab23678aff723108df3edeac098e5b2498879f4a7"}, + {file = "regex-2023.6.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4d3850beab9f527f06ccc94b446c864059c57651b3f911fddb8d9d3ec1d1b25d"}, + {file = "regex-2023.6.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e8915cc96abeb8983cea1df3c939e3c6e1ac778340c17732eb63bb96247b91d2"}, + {file = "regex-2023.6.3-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:841d6e0e5663d4c7b4c8099c9997be748677d46cbf43f9f471150e560791f7ff"}, + {file = "regex-2023.6.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:9edce5281f965cf135e19840f4d93d55b3835122aa76ccacfd389e880ba4cf82"}, + {file = "regex-2023.6.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:b956231ebdc45f5b7a2e1f90f66a12be9610ce775fe1b1d50414aac1e9206c06"}, + {file = "regex-2023.6.3-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:36efeba71c6539d23c4643be88295ce8c82c88bbd7c65e8a24081d2ca123da3f"}, + {file = "regex-2023.6.3-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:cf67ca618b4fd34aee78740bea954d7c69fdda419eb208c2c0c7060bb822d747"}, + {file = "regex-2023.6.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:b4598b1897837067a57b08147a68ac026c1e73b31ef6e36deeeb1fa60b2933c9"}, + {file = "regex-2023.6.3-cp38-cp38-win32.whl", hash = "sha256:f415f802fbcafed5dcc694c13b1292f07fe0befdb94aa8a52905bd115ff41e88"}, + {file = "regex-2023.6.3-cp38-cp38-win_amd64.whl", hash = "sha256:d4f03bb71d482f979bda92e1427f3ec9b220e62a7dd337af0aa6b47bf4498f72"}, + {file = "regex-2023.6.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:ccf91346b7bd20c790310c4147eee6ed495a54ddb6737162a36ce9dbef3e4751"}, + {file = "regex-2023.6.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b28f5024a3a041009eb4c333863d7894d191215b39576535c6734cd88b0fcb68"}, + {file = "regex-2023.6.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e0bb18053dfcfed432cc3ac632b5e5e5c5b7e55fb3f8090e867bfd9b054dbcbf"}, + {file = "regex-2023.6.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9a5bfb3004f2144a084a16ce19ca56b8ac46e6fd0651f54269fc9e230edb5e4a"}, + {file = "regex-2023.6.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5c6b48d0fa50d8f4df3daf451be7f9689c2bde1a52b1225c5926e3f54b6a9ed1"}, + {file = "regex-2023.6.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:051da80e6eeb6e239e394ae60704d2b566aa6a7aed6f2890a7967307267a5dc6"}, + {file = "regex-2023.6.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a4c3b7fa4cdaa69268748665a1a6ff70c014d39bb69c50fda64b396c9116cf77"}, + {file = "regex-2023.6.3-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:457b6cce21bee41ac292d6753d5e94dcbc5c9e3e3a834da285b0bde7aa4a11e9"}, + {file = "regex-2023.6.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:aad51907d74fc183033ad796dd4c2e080d1adcc4fd3c0fd4fd499f30c03011cd"}, + {file = "regex-2023.6.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:0385e73da22363778ef2324950e08b689abdf0b108a7d8decb403ad7f5191938"}, + {file = "regex-2023.6.3-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:c6a57b742133830eec44d9b2290daf5cbe0a2f1d6acee1b3c7b1c7b2f3606df7"}, + {file = "regex-2023.6.3-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:3e5219bf9e75993d73ab3d25985c857c77e614525fac9ae02b1bebd92f7cecac"}, + {file = "regex-2023.6.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:e5087a3c59eef624a4591ef9eaa6e9a8d8a94c779dade95d27c0bc24650261cd"}, + {file = "regex-2023.6.3-cp39-cp39-win32.whl", hash = "sha256:20326216cc2afe69b6e98528160b225d72f85ab080cbdf0b11528cbbaba2248f"}, + {file = "regex-2023.6.3-cp39-cp39-win_amd64.whl", hash = "sha256:bdff5eab10e59cf26bc479f565e25ed71a7d041d1ded04ccf9aee1d9f208487a"}, + {file = "regex-2023.6.3.tar.gz", hash = "sha256:72d1a25bf36d2050ceb35b517afe13864865268dfb45910e2e17a84be6cbfeb0"}, +] [[package]] name = "reportlab" -version = "3.5.67" +version = "3.6.13" description = "The Reportlab Toolkit" -category = "main" optional = false -python-versions = ">=2.7, >=3.6, <4" +python-versions = ">=3.7,<4" +files = [ + {file = "reportlab-3.6.13-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a0330322c6c8123745ac7667fcc6ae3e0de3b73c15bdfaa28c788a9eaa0f50da"}, + {file = "reportlab-3.6.13-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:753485bb2b18cbd11340e227e4aaf9bde3bb64f83406dfa011e92ad0231a42c9"}, + {file = "reportlab-3.6.13-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:58ea3471b9b4b8e7952bd357e8487789da11213470be328ffb3e5b7d7690c2c7"}, + {file = "reportlab-3.6.13-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f1993a68c0edc45895d3df350d01b0456efe79aaf309cef777762742be501f2a"}, + {file = "reportlab-3.6.13-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ca8eb7a6607f8a664187a330bab9f8d11c9f81ed885e063dfbb29a130944a72a"}, + {file = "reportlab-3.6.13-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:57add04824bca89a130f9d428ace1b003cce4061386e0ec2a1b45b554ffe7aa3"}, + {file = "reportlab-3.6.13-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e98965c6e60d76ff63989d9400ae8e65efd67c665d785b377f438f166a57c053"}, + {file = "reportlab-3.6.13-cp310-cp310-win32.whl", hash = "sha256:3cb0da4975dbade6cc2ea6b0b0b17578af266dc3f669e959648f3306af993369"}, + {file = "reportlab-3.6.13-cp310-cp310-win_amd64.whl", hash = "sha256:65b441e22d8fe93154567a30662d8539e639b78142815afcaf92b388846eb3c1"}, + {file = "reportlab-3.6.13-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d59e62faa03003be81aa14d37ac34ea110e5ac59c8678fd4c0daa7d8b8f42096"}, + {file = "reportlab-3.6.13-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:afb418409e0d323c6cb5e3be7ea4d14dfbf8a07eb03ab0b0062904cacf819878"}, + {file = "reportlab-3.6.13-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a477f652e6c417ad40387a8498d9ad827421006f156aab16f67adc9b81699a72"}, + {file = "reportlab-3.6.13-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0b94e4f65a5f77a631cc010c9a7892d69e33f3251b760639dcc76420e138ce95"}, + {file = "reportlab-3.6.13-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7efdf68e97e8fea8683bfc17f25747fefbda729b9018bc2e3221658ac41ee0bd"}, + {file = "reportlab-3.6.13-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e28a8d9cf462e2b4c9e71abd0630f9ec245d88b976b283b0dbb4602c9ddb3938"}, + {file = "reportlab-3.6.13-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d95fc8bc177a009053548c6d851a513b2147c465a5e8fea82287ea22d6825c4e"}, + {file = "reportlab-3.6.13-cp311-cp311-win32.whl", hash = "sha256:48eadd93237c7e2739525c74cf6615dd6c1a767c839f4b0d7c12167dc0b09911"}, + {file = "reportlab-3.6.13-cp311-cp311-win_amd64.whl", hash = "sha256:cca2d4c783f985b91b98e80d09ac79b6ed3f317a729cba5ba86edfe5eb9a2d9c"}, + {file = "reportlab-3.6.13-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:149718c3eaee937f28094325f0dd9ae1add3172c2dacbb93ff5403f37c9d3c57"}, + {file = "reportlab-3.6.13-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8260c002e4845a5af65908d5ee2099bcc25a16c7646c5c417fa27f1e4b844bc1"}, + {file = "reportlab-3.6.13-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8e4983486d419daa45cade40874bb869976e27ba11f77fb4b9ae32417284ade7"}, + {file = "reportlab-3.6.13-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6ea46fef07c588fef84d1164d4788fef322b39feb2bfb2df0a0706181dff79b8"}, + {file = "reportlab-3.6.13-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e5949f3b4e207fa7901c0cc3b49470b2a3372617a47dfbc892db31c2b56af296"}, + {file = "reportlab-3.6.13-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b0d91663d450c11404ec189ebc5a4abdf20f7c4eca5954a920427cdbf5601525"}, + {file = "reportlab-3.6.13-cp37-cp37m-win32.whl", hash = "sha256:269c59e508df08be498ab9e5278addb2cc16989677a03f800b17f8a31f8c5cc7"}, + {file = "reportlab-3.6.13-cp37-cp37m-win_amd64.whl", hash = "sha256:21d6b6bcdecee9c7ce047156d0553a30d736b8172629e4c0fcacab35ba261f3b"}, + {file = "reportlab-3.6.13-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:36568d3cb4101a210c4d821d9101635c2ef6e06bd649335938c01eb197f50c5d"}, + {file = "reportlab-3.6.13-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:a043cff1781ddb2a0ba0e8e760a79fc5be2430957c4f2a1f51bd4528cc53178f"}, + {file = "reportlab-3.6.13-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dbddadca6f08212732e83a60e30a42cfc7d2695892cedea208b3c3e7131c9993"}, + {file = "reportlab-3.6.13-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:faeebde62f0f6ad86985bec5685411260393d2eb7ba907972da56af586b644e8"}, + {file = "reportlab-3.6.13-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ff09a0a1e5cef05309ac09dfc5185e8151d927bcf45470d2f540c96260f8a355"}, + {file = "reportlab-3.6.13-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bbdbba1ec3498b17eefca14d424ee90bb95b53e1423ecb22f1c17733c3406559"}, + {file = "reportlab-3.6.13-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba6f533b262f4ee1636b754992bb2fb349df0500d765ac9be014a375c047f4db"}, + {file = "reportlab-3.6.13-cp38-cp38-win32.whl", hash = "sha256:7ff89011b5ee30209b3106641e3b7b4959f10aa6e9d6f3030205123c178f605d"}, + {file = "reportlab-3.6.13-cp38-cp38-win_amd64.whl", hash = "sha256:8f00175f8e12e6f7d3a01309de6d7008fac94a2cdce6837ad066f0961472c9e5"}, + {file = "reportlab-3.6.13-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a4dbc28fede7f504b9ac65ce9cbea35585e999d63f9fa68bc73f5a75b4929302"}, + {file = "reportlab-3.6.13-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:9f869286fcefa7f8e89e38448309891ff110ad74f58a7317ec204f3d4b8ad5f5"}, + {file = "reportlab-3.6.13-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e13a4e81761636591f5b60104f6e1eec70832ffd9aa781db68d7ebb576970d4b"}, + {file = "reportlab-3.6.13-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6fdac930dfdc6227720545ec44fdb396e92d53ec227a6f5ae58cc8cb9a6cbe89"}, + {file = "reportlab-3.6.13-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:701290747662d2b3be49fc0de33898ecc9ce3fafe0e2887d406e24693465e5ae"}, + {file = "reportlab-3.6.13-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7b690bc30f58931b0abd47635d93a43a82d67972e83a6511cc8adbcd7da25310"}, + {file = "reportlab-3.6.13-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6172481e8acffcf72042653e977281fbd807a41705a39456d92d2606d8b8c5e2"}, + {file = "reportlab-3.6.13-cp39-cp39-win32.whl", hash = "sha256:5a460f4c0c30bdf9d7bef46a816671a4386a9253670a53d35c694c666544261f"}, + {file = "reportlab-3.6.13-cp39-cp39-win_amd64.whl", hash = "sha256:11a71c314183532d889ad4b3941f61c3fe4bfdda769c768a7f02d93cb69dd1bb"}, + {file = "reportlab-3.6.13.tar.gz", hash = "sha256:6f75d33f7a3720cf47371ab63ced0f0ebd1aeb6db19386ae92f8977a09be9611"}, +] [package.dependencies] -pillow = ">=4.0.0" +pillow = ">=9.0.0" [package.extras] -rlpycairo = ["rlPyCairo (>=0.0.5)"] +fttextpath = ["freetype-py (>=2.3.0,<2.4)"] +rlpycairo = ["rlPyCairo (>=0.1.0)"] [[package]] name = "requests" version = "2.25.1" description = "Python HTTP for Humans." -category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +files = [ + {file = "requests-2.25.1-py2.py3-none-any.whl", hash = "sha256:c210084e36a42ae6b9219e00e48287def368a26d03a048ddad7bfee44f75871e"}, + {file = "requests-2.25.1.tar.gz", hash = "sha256:27973dd4a904a4f13b263a19c866c13b92a39ed1c964655f025f3f8d3d75b804"}, +] [package.dependencies] certifi = ">=2017.4.17" @@ -369,562 +1445,283 @@ idna = ">=2.5,<3" urllib3 = ">=1.21.1,<1.27" [package.extras] -security = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)"] +security = ["cryptography (>=1.3.4)", "pyOpenSSL (>=0.14)"] socks = ["PySocks (>=1.5.6,!=1.5.7)", "win-inet-pton"] [[package]] name = "sentry-sdk" -version = "1.1.0" +version = "1.28.1" description = "Python client for Sentry (https://sentry.io)" -category = "main" optional = false python-versions = "*" +files = [ + {file = "sentry-sdk-1.28.1.tar.gz", hash = "sha256:dcd88c68aa64dae715311b5ede6502fd684f70d00a7cd4858118f0ba3153a3ae"}, + {file = "sentry_sdk-1.28.1-py2.py3-none-any.whl", hash = "sha256:6bdb25bd9092478d3a817cb0d01fa99e296aea34d404eac3ca0037faa5c2aa0a"}, +] [package.dependencies] certifi = "*" -urllib3 = ">=1.10.0" +urllib3 = {version = ">=1.26.11", markers = "python_version >= \"3.6\""} [package.extras] aiohttp = ["aiohttp (>=3.5)"] +arq = ["arq (>=0.23)"] beam = ["apache-beam (>=2.12)"] bottle = ["bottle (>=0.12.13)"] celery = ["celery (>=3)"] chalice = ["chalice (>=1.16.0)"] django = ["django (>=1.8)"] falcon = ["falcon (>=1.4)"] -flask = ["flask (>=0.11)", "blinker (>=1.1)"] -pure_eval = ["pure-eval", "executing", "asttokens"] +fastapi = ["fastapi (>=0.79.0)"] +flask = ["blinker (>=1.1)", "flask (>=0.11)", "markupsafe"] +grpcio = ["grpcio (>=1.21.1)"] +httpx = ["httpx (>=0.16.0)"] +huey = ["huey (>=2)"] +loguru = ["loguru (>=0.5)"] +opentelemetry = ["opentelemetry-distro (>=0.35b0)"] +pure-eval = ["asttokens", "executing", "pure-eval"] +pymongo = ["pymongo (>=3.1)"] pyspark = ["pyspark (>=2.4.4)"] +quart = ["blinker (>=1.1)", "quart (>=0.16.1)"] rq = ["rq (>=0.6)"] sanic = ["sanic (>=0.8)"] sqlalchemy = ["sqlalchemy (>=1.2)"] +starlette = ["starlette (>=0.19.1)"] +starlite = ["starlite (>=1.48)"] tornado = ["tornado (>=5)"] +[[package]] +name = "setuptools" +version = "68.0.0" +description = "Easily download, build, install, upgrade, and uninstall Python packages" +optional = false +python-versions = ">=3.7" +files = [ + {file = "setuptools-68.0.0-py3-none-any.whl", hash = "sha256:11e52c67415a381d10d6b462ced9cfb97066179f0e871399e006c4ab101fc85f"}, + {file = "setuptools-68.0.0.tar.gz", hash = "sha256:baf1fdb41c6da4cd2eae722e135500da913332ab3f2f5c7d33af9b492acb5235"}, +] + +[package.extras] +docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-hoverxref (<2)", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (==0.8.3)", "sphinx-reredirects", "sphinxcontrib-towncrier"] +testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pip (>=19.1)", "pip-run (>=8.8)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-ruff", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] +testing-integration = ["build[virtualenv]", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"] + [[package]] name = "six" version = "1.16.0" description = "Python 2 and 3 compatibility utilities" -category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" +files = [ + {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, + {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, +] [[package]] name = "sqlparse" -version = "0.4.1" +version = "0.4.4" description = "A non-validating SQL parser." -category = "main" optional = false python-versions = ">=3.5" +files = [ + {file = "sqlparse-0.4.4-py3-none-any.whl", hash = "sha256:5430a4fe2ac7d0f93e66f1efc6e1338a41884b7ddf2a350cedd20ccc4d9d28f3"}, + {file = "sqlparse-0.4.4.tar.gz", hash = "sha256:d446183e84b8349fa3061f0fe7f06ca94ba65b426946ffebe6e3e8295332420c"}, +] + +[package.extras] +dev = ["build", "flake8"] +doc = ["sphinx"] +test = ["pytest", "pytest-cov"] [[package]] name = "stripe" -version = "2.56.0" +version = "2.76.0" description = "Python bindings for the Stripe API" -category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +files = [ + {file = "stripe-2.76.0-py2.py3-none-any.whl", hash = "sha256:756bf6c1206f438d1fa23bb90cdf1233c9383478f854f2720a8a3e1eaf1f715b"}, + {file = "stripe-2.76.0.tar.gz", hash = "sha256:fd3fc6935c3b6189967191607b6f38ebe490005a590b4d0d43fbe3aba45deca8"}, +] [package.dependencies] requests = {version = ">=2.20", markers = "python_version >= \"3.0\""} +[[package]] +name = "svglib" +version = "1.5.1" +description = "A pure-Python library for reading and converting SVG" +optional = false +python-versions = ">=3.7" +files = [ + {file = "svglib-1.5.1.tar.gz", hash = "sha256:3ae765d3a9409ee60c0fb4d24c2deb6a80617aa927054f5bcd7fc98f0695e587"}, +] + +[package.dependencies] +cssselect2 = ">=0.2.0" +lxml = "*" +reportlab = "*" +tinycss2 = ">=0.6.0" + +[[package]] +name = "tinycss2" +version = "1.2.1" +description = "A tiny CSS parser" +optional = false +python-versions = ">=3.7" +files = [ + {file = "tinycss2-1.2.1-py3-none-any.whl", hash = "sha256:2b80a96d41e7c3914b8cda8bc7f705a4d9c49275616e886103dd839dfc847847"}, + {file = "tinycss2-1.2.1.tar.gz", hash = "sha256:8cff3a8f066c2ec677c06dbc7b45619804a6938478d9d73c284b29d14ecb0627"}, +] + +[package.dependencies] +webencodings = ">=0.4" + +[package.extras] +doc = ["sphinx", "sphinx_rtd_theme"] +test = ["flake8", "isort", "pytest"] + [[package]] name = "tqdm" -version = "4.60.0" +version = "4.65.0" description = "Fast, Extensible Progress Meter" -category = "main" optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7" +python-versions = ">=3.7" +files = [ + {file = "tqdm-4.65.0-py3-none-any.whl", hash = "sha256:c4f53a17fe37e132815abceec022631be8ffe1b9381c2e6e30aa70edc99e9671"}, + {file = "tqdm-4.65.0.tar.gz", hash = "sha256:1871fb68a86b8fb3b59ca4cdd3dcccbc7e6d613eeed31f4c332531977b89beb5"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "platform_system == \"Windows\""} [package.extras] dev = ["py-make (>=0.1.0)", "twine", "wheel"] notebook = ["ipywidgets (>=6)"] +slack = ["slack-sdk"] telegram = ["requests"] +[[package]] +name = "typing-extensions" +version = "4.7.1" +description = "Backported and Experimental Type Hints for Python 3.7+" +optional = false +python-versions = ">=3.7" +files = [ + {file = "typing_extensions-4.7.1-py3-none-any.whl", hash = "sha256:440d5dd3af93b060174bf433bccd69b0babc3b15b1a8dca43789fd7f61514b36"}, + {file = "typing_extensions-4.7.1.tar.gz", hash = "sha256:b75ddc264f0ba5615db7ba217daeb99701ad295353c45f9e95963337ceeeffb2"}, +] + +[[package]] +name = "tzdata" +version = "2023.3" +description = "Provider of IANA time zone data" +optional = false +python-versions = ">=2" +files = [ + {file = "tzdata-2023.3-py2.py3-none-any.whl", hash = "sha256:7e65763eef3120314099b6939b5546db7adce1e7d6f2e179e3df563c70511eda"}, + {file = "tzdata-2023.3.tar.gz", hash = "sha256:11ef1e08e54acb0d4f95bdb1be05da659673de4acbd21bf9c69e94cc5e907a3a"}, +] + +[[package]] +name = "tzlocal" +version = "5.0.1" +description = "tzinfo object for the local timezone" +optional = false +python-versions = ">=3.7" +files = [ + {file = "tzlocal-5.0.1-py3-none-any.whl", hash = "sha256:f3596e180296aaf2dbd97d124fe76ae3a0e3d32b258447de7b939b3fd4be992f"}, + {file = "tzlocal-5.0.1.tar.gz", hash = "sha256:46eb99ad4bdb71f3f72b7d24f4267753e240944ecfc16f25d2719ba89827a803"}, +] + +[package.dependencies] +tzdata = {version = "*", markers = "platform_system == \"Windows\""} + +[package.extras] +devenv = ["black", "check-manifest", "flake8", "pyroma", "pytest (>=4.3)", "pytest-cov", "pytest-mock (>=3.3)", "zest.releaser"] + [[package]] name = "unidecode" version = "0.04.21" description = "ASCII transliterations of Unicode text" -category = "main" optional = false python-versions = "*" +files = [ + {file = "Unidecode-0.04.21-py2.py3-none-any.whl", hash = "sha256:61f807220eda0203a774a09f84b4304a3f93b5944110cc132af29ddb81366883"}, + {file = "Unidecode-0.04.21.tar.gz", hash = "sha256:280a6ab88e1f2eb5af79edff450021a0d3f0448952847cd79677e55e58bad051"}, +] [[package]] name = "uritemplate" -version = "3.0.1" -description = "URI templates" -category = "main" +version = "4.1.1" +description = "Implementation of RFC 6570 URI Templates" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +python-versions = ">=3.6" +files = [ + {file = "uritemplate-4.1.1-py2.py3-none-any.whl", hash = "sha256:830c08b8d99bdd312ea4ead05994a38e8936266f84b9a7878232db50b044e02e"}, + {file = "uritemplate-4.1.1.tar.gz", hash = "sha256:4346edfc5c3b79f694bccd6d6099a322bbeb628dbf2cd86eea55a456ce5124f0"}, +] + +[[package]] +name = "uritools" +version = "4.0.1" +description = "URI parsing, classification and composition" +optional = false +python-versions = "~=3.7" +files = [ + {file = "uritools-4.0.1-py3-none-any.whl", hash = "sha256:d122d394ed6e6e15ac0fddba6a5b19e9fa204e7797507815cbfb0e1455ac0475"}, + {file = "uritools-4.0.1.tar.gz", hash = "sha256:efc5c3a6de05404850685a8d3f34da8476b56aa3516fbf8eff5c8704c7a2826f"}, +] [[package]] name = "urllib3" -version = "1.26.4" +version = "1.26.16" description = "HTTP library with thread-safe connection pooling, file post, and more." -category = "main" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4" +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" +files = [ + {file = "urllib3-1.26.16-py2.py3-none-any.whl", hash = "sha256:8d36afa7616d8ab714608411b4a3b13e58f463aee519024578e062e141dce20f"}, + {file = "urllib3-1.26.16.tar.gz", hash = "sha256:8f135f6502756bde6b2a9b28989df5fbe87c9970cecaa69041edcce7f0589b14"}, +] [package.extras] -secure = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "certifi", "ipaddress"] +brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)", "brotlipy (>=0.6.0)"] +secure = ["certifi", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "ipaddress", "pyOpenSSL (>=0.14)", "urllib3-secure-extra"] socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] -brotli = ["brotlipy (>=0.6.0)"] [[package]] name = "webencodings" version = "0.5.1" description = "Character encoding aliases for legacy web content" -category = "main" optional = false python-versions = "*" +files = [ + {file = "webencodings-0.5.1-py2.py3-none-any.whl", hash = "sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78"}, + {file = "webencodings-0.5.1.tar.gz", hash = "sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923"}, +] [[package]] name = "xhtml2pdf" -version = "0.2.5" +version = "0.2.11" description = "PDF generator using HTML and CSS" -category = "main" optional = false python-versions = "*" +files = [ + {file = "xhtml2pdf-0.2.11.tar.gz", hash = "sha256:1b81630a10eba833ddeda16933fd6625471f16b93c352d954f552e5a608e7465"}, +] [package.dependencies] -arabic-reshaper = ">=2.1.0" -html5lib = ">=1.0" -Pillow = "*" -pyPdf2 = "*" +arabic-reshaper = ">=3.0.0" +html5lib = ">=1.0.1" +Pillow = ">=8.1.1" +pyHanko = ">=0.12.1" +pyhanko-certvalidator = ">=0.19.5" +pypdf = ">=3.1.0" python-bidi = ">=0.4.2" -reportlab = ">=3.3.0" -six = "*" +reportlab = ">=3.5.53,<4" +svglib = ">=1.2.1" [metadata] -lock-version = "1.1" -python-versions = "^3.8" -content-hash = "8576b9e6e6a9343b033a888034604f841eb600359fccc4b3a068cf04bf330b12" - -[metadata.files] -arabic-reshaper = [ - {file = "arabic_reshaper-2.1.3-py2-none-any.whl", hash = "sha256:43f58136dbbfecab54ce0434e556c75689e46b0f11010578b893f72c504cd27b"}, - {file = "arabic_reshaper-2.1.3-py3-none-any.whl", hash = "sha256:15078431d8f45eaca0a1710100aabc87abba13759c67eeb4538cca22fe167da1"}, - {file = "arabic_reshaper-2.1.3.tar.gz", hash = "sha256:a236fc6e9dde2a61cc6a5ca962b522e42694e1bb2a2d86894ed7a4eba4ce1890"}, -] -asgiref = [ - {file = "asgiref-3.3.4-py3-none-any.whl", hash = "sha256:92906c611ce6c967347bbfea733f13d6313901d54dcca88195eaeb52b2a8e8ee"}, - {file = "asgiref-3.3.4.tar.gz", hash = "sha256:d1216dfbdfb63826470995d31caed36225dcaf34f182e0fa257a4dd9e86f1b78"}, -] -awesome-slugify = [ - {file = "awesome-slugify-1.6.5.tar.gz", hash = "sha256:bbdec3fa2187917473a2efad092b57f7125a55f841a7cf6a1773178d32ccfd71"}, -] -bcrypt = [ - {file = "bcrypt-3.2.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:c95d4cbebffafcdd28bd28bb4e25b31c50f6da605c81ffd9ad8a3d1b2ab7b1b6"}, - {file = "bcrypt-3.2.0-cp36-abi3-manylinux1_x86_64.whl", hash = "sha256:63d4e3ff96188e5898779b6057878fecf3f11cfe6ec3b313ea09955d587ec7a7"}, - {file = "bcrypt-3.2.0-cp36-abi3-manylinux2010_x86_64.whl", hash = "sha256:cd1ea2ff3038509ea95f687256c46b79f5fc382ad0aa3664d200047546d511d1"}, - {file = "bcrypt-3.2.0-cp36-abi3-manylinux2014_aarch64.whl", hash = "sha256:cdcdcb3972027f83fe24a48b1e90ea4b584d35f1cc279d76de6fc4b13376239d"}, - {file = "bcrypt-3.2.0-cp36-abi3-win32.whl", hash = "sha256:a67fb841b35c28a59cebed05fbd3e80eea26e6d75851f0574a9273c80f3e9b55"}, - {file = "bcrypt-3.2.0-cp36-abi3-win_amd64.whl", hash = "sha256:81fec756feff5b6818ea7ab031205e1d323d8943d237303baca2c5f9c7846f34"}, - {file = "bcrypt-3.2.0.tar.gz", hash = "sha256:5b93c1726e50a93a033c36e5ca7fdcd29a5c7395af50a6892f5d9e7c6cfbfb29"}, -] -certifi = [ - {file = "certifi-2020.12.5-py2.py3-none-any.whl", hash = "sha256:719a74fb9e33b9bd44cc7f3a8d94bc35e4049deebe19ba7d8e108280cfd59830"}, - {file = "certifi-2020.12.5.tar.gz", hash = "sha256:1a4995114262bffbc2413b159f2a1a480c969de6e6eb13ee966d470af86af59c"}, -] -cffi = [ - {file = "cffi-1.14.5-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:bb89f306e5da99f4d922728ddcd6f7fcebb3241fc40edebcb7284d7514741991"}, - {file = "cffi-1.14.5-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:34eff4b97f3d982fb93e2831e6750127d1355a923ebaeeb565407b3d2f8d41a1"}, - {file = "cffi-1.14.5-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:99cd03ae7988a93dd00bcd9d0b75e1f6c426063d6f03d2f90b89e29b25b82dfa"}, - {file = "cffi-1.14.5-cp27-cp27m-win32.whl", hash = "sha256:65fa59693c62cf06e45ddbb822165394a288edce9e276647f0046e1ec26920f3"}, - {file = "cffi-1.14.5-cp27-cp27m-win_amd64.whl", hash = "sha256:51182f8927c5af975fece87b1b369f722c570fe169f9880764b1ee3bca8347b5"}, - {file = "cffi-1.14.5-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:43e0b9d9e2c9e5d152946b9c5fe062c151614b262fda2e7b201204de0b99e482"}, - {file = "cffi-1.14.5-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:cbde590d4faaa07c72bf979734738f328d239913ba3e043b1e98fe9a39f8b2b6"}, - {file = "cffi-1.14.5-cp35-cp35m-macosx_10_9_x86_64.whl", hash = "sha256:5de7970188bb46b7bf9858eb6890aad302577a5f6f75091fd7cdd3ef13ef3045"}, - {file = "cffi-1.14.5-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:a465da611f6fa124963b91bf432d960a555563efe4ed1cc403ba5077b15370aa"}, - {file = "cffi-1.14.5-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:d42b11d692e11b6634f7613ad8df5d6d5f8875f5d48939520d351007b3c13406"}, - {file = "cffi-1.14.5-cp35-cp35m-win32.whl", hash = "sha256:72d8d3ef52c208ee1c7b2e341f7d71c6fd3157138abf1a95166e6165dd5d4369"}, - {file = "cffi-1.14.5-cp35-cp35m-win_amd64.whl", hash = "sha256:29314480e958fd8aab22e4a58b355b629c59bf5f2ac2492b61e3dc06d8c7a315"}, - {file = "cffi-1.14.5-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:3d3dd4c9e559eb172ecf00a2a7517e97d1e96de2a5e610bd9b68cea3925b4892"}, - {file = "cffi-1.14.5-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:48e1c69bbacfc3d932221851b39d49e81567a4d4aac3b21258d9c24578280058"}, - {file = "cffi-1.14.5-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:69e395c24fc60aad6bb4fa7e583698ea6cc684648e1ffb7fe85e3c1ca131a7d5"}, - {file = "cffi-1.14.5-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:9e93e79c2551ff263400e1e4be085a1210e12073a31c2011dbbda14bda0c6132"}, - {file = "cffi-1.14.5-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:24ec4ff2c5c0c8f9c6b87d5bb53555bf267e1e6f70e52e5a9740d32861d36b6f"}, - {file = "cffi-1.14.5-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3c3f39fa737542161d8b0d680df2ec249334cd70a8f420f71c9304bd83c3cbed"}, - {file = "cffi-1.14.5-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:681d07b0d1e3c462dd15585ef5e33cb021321588bebd910124ef4f4fb71aef55"}, - {file = "cffi-1.14.5-cp36-cp36m-win32.whl", hash = "sha256:58e3f59d583d413809d60779492342801d6e82fefb89c86a38e040c16883be53"}, - {file = "cffi-1.14.5-cp36-cp36m-win_amd64.whl", hash = "sha256:005a36f41773e148deac64b08f233873a4d0c18b053d37da83f6af4d9087b813"}, - {file = "cffi-1.14.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:2894f2df484ff56d717bead0a5c2abb6b9d2bf26d6960c4604d5c48bbc30ee73"}, - {file = "cffi-1.14.5-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:0857f0ae312d855239a55c81ef453ee8fd24136eaba8e87a2eceba644c0d4c06"}, - {file = "cffi-1.14.5-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:cd2868886d547469123fadc46eac7ea5253ea7fcb139f12e1dfc2bbd406427d1"}, - {file = "cffi-1.14.5-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:35f27e6eb43380fa080dccf676dece30bef72e4a67617ffda586641cd4508d49"}, - {file = "cffi-1.14.5-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:06d7cd1abac2ffd92e65c0609661866709b4b2d82dd15f611e602b9b188b0b69"}, - {file = "cffi-1.14.5-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0f861a89e0043afec2a51fd177a567005847973be86f709bbb044d7f42fc4e05"}, - {file = "cffi-1.14.5-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cc5a8e069b9ebfa22e26d0e6b97d6f9781302fe7f4f2b8776c3e1daea35f1adc"}, - {file = "cffi-1.14.5-cp37-cp37m-win32.whl", hash = "sha256:9ff227395193126d82e60319a673a037d5de84633f11279e336f9c0f189ecc62"}, - {file = "cffi-1.14.5-cp37-cp37m-win_amd64.whl", hash = "sha256:9cf8022fb8d07a97c178b02327b284521c7708d7c71a9c9c355c178ac4bbd3d4"}, - {file = "cffi-1.14.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8b198cec6c72df5289c05b05b8b0969819783f9418e0409865dac47288d2a053"}, - {file = "cffi-1.14.5-cp38-cp38-manylinux1_i686.whl", hash = "sha256:ad17025d226ee5beec591b52800c11680fca3df50b8b29fe51d882576e039ee0"}, - {file = "cffi-1.14.5-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:6c97d7350133666fbb5cf4abdc1178c812cb205dc6f41d174a7b0f18fb93337e"}, - {file = "cffi-1.14.5-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:8ae6299f6c68de06f136f1f9e69458eae58f1dacf10af5c17353eae03aa0d827"}, - {file = "cffi-1.14.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:04c468b622ed31d408fea2346bec5bbffba2cc44226302a0de1ade9f5ea3d373"}, - {file = "cffi-1.14.5-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:06db6321b7a68b2bd6df96d08a5adadc1fa0e8f419226e25b2a5fbf6ccc7350f"}, - {file = "cffi-1.14.5-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:293e7ea41280cb28c6fcaaa0b1aa1f533b8ce060b9e701d78511e1e6c4a1de76"}, - {file = "cffi-1.14.5-cp38-cp38-win32.whl", hash = "sha256:b85eb46a81787c50650f2392b9b4ef23e1f126313b9e0e9013b35c15e4288e2e"}, - {file = "cffi-1.14.5-cp38-cp38-win_amd64.whl", hash = "sha256:1f436816fc868b098b0d63b8920de7d208c90a67212546d02f84fe78a9c26396"}, - {file = "cffi-1.14.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:1071534bbbf8cbb31b498d5d9db0f274f2f7a865adca4ae429e147ba40f73dea"}, - {file = "cffi-1.14.5-cp39-cp39-manylinux1_i686.whl", hash = "sha256:9de2e279153a443c656f2defd67769e6d1e4163952b3c622dcea5b08a6405322"}, - {file = "cffi-1.14.5-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:6e4714cc64f474e4d6e37cfff31a814b509a35cb17de4fb1999907575684479c"}, - {file = "cffi-1.14.5-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:158d0d15119b4b7ff6b926536763dc0714313aa59e320ddf787502c70c4d4bee"}, - {file = "cffi-1.14.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1bf1ac1984eaa7675ca8d5745a8cb87ef7abecb5592178406e55858d411eadc0"}, - {file = "cffi-1.14.5-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:df5052c5d867c1ea0b311fb7c3cd28b19df469c056f7fdcfe88c7473aa63e333"}, - {file = "cffi-1.14.5-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:24a570cd11895b60829e941f2613a4f79df1a27344cbbb82164ef2e0116f09c7"}, - {file = "cffi-1.14.5-cp39-cp39-win32.whl", hash = "sha256:afb29c1ba2e5a3736f1c301d9d0abe3ec8b86957d04ddfa9d7a6a42b9367e396"}, - {file = "cffi-1.14.5-cp39-cp39-win_amd64.whl", hash = "sha256:f2d45f97ab6bb54753eab54fffe75aaf3de4ff2341c9daee1987ee1837636f1d"}, - {file = "cffi-1.14.5.tar.gz", hash = "sha256:fd78e5fee591709f32ef6edb9a015b4aa1a5022598e36227500c8f4e02328d9c"}, -] -chardet = [ - {file = "chardet-4.0.0-py2.py3-none-any.whl", hash = "sha256:f864054d66fd9118f2e67044ac8981a54775ec5b67aed0441892edb553d21da5"}, - {file = "chardet-4.0.0.tar.gz", hash = "sha256:0d6f53a15db4120f2b08c94f11e7d93d2c911ee118b6b30a04ec3ee8310179fa"}, -] -coreapi = [ - {file = "coreapi-2.3.3-py2.py3-none-any.whl", hash = "sha256:bf39d118d6d3e171f10df9ede5666f63ad80bba9a29a8ec17726a66cf52ee6f3"}, - {file = "coreapi-2.3.3.tar.gz", hash = "sha256:46145fcc1f7017c076a2ef684969b641d18a2991051fddec9458ad3f78ffc1cb"}, -] -coreschema = [ - {file = "coreschema-0.0.4-py2-none-any.whl", hash = "sha256:5e6ef7bf38c1525d5e55a895934ab4273548629f16aed5c0a6caa74ebf45551f"}, - {file = "coreschema-0.0.4.tar.gz", hash = "sha256:9503506007d482ab0867ba14724b93c18a33b22b6d19fb419ef2d239dd4a1607"}, -] -django = [ - {file = "Django-3.2.3-py3-none-any.whl", hash = "sha256:7e0a1393d18c16b503663752a8b6790880c5084412618990ce8a81cc908b4962"}, - {file = "Django-3.2.3.tar.gz", hash = "sha256:13ac78dbfd189532cad8f383a27e58e18b3d33f80009ceb476d7fcbfc5dcebd8"}, -] -django-cors-headers = [ - {file = "django-cors-headers-3.7.0.tar.gz", hash = "sha256:96069c4aaacace786a34ee7894ff680780ec2644e4268b31181044410fecd12e"}, - {file = "django_cors_headers-3.7.0-py3-none-any.whl", hash = "sha256:1ac2b1213de75a251e2ba04448da15f99bcfcbe164288ae6b5ff929dc49b372f"}, -] -django-filter = [ - {file = "django-filter-2.4.0.tar.gz", hash = "sha256:84e9d5bb93f237e451db814ed422a3a625751cbc9968b484ecc74964a8696b06"}, - {file = "django_filter-2.4.0-py3-none-any.whl", hash = "sha256:e00d32cebdb3d54273c48f4f878f898dced8d5dfaad009438fe61ebdf535ace1"}, -] -djangorestframework = [ - {file = "djangorestframework-3.12.4-py3-none-any.whl", hash = "sha256:6d1d59f623a5ad0509fe0d6bfe93cbdfe17b8116ebc8eda86d45f6e16e819aaf"}, - {file = "djangorestframework-3.12.4.tar.gz", hash = "sha256:f747949a8ddac876e879190df194b925c177cdeb725a099db1460872f7c0a7f2"}, -] -future = [ - {file = "future-0.18.2.tar.gz", hash = "sha256:b1bead90b70cf6ec3f0710ae53a525360fa360d306a86583adc6bf83a4db537d"}, -] -gunicorn = [ - {file = "gunicorn-20.1.0-py3-none-any.whl", hash = "sha256:9dcc4547dbb1cb284accfb15ab5667a0e5d1881cc443e0677b4882a4067a807e"}, - {file = "gunicorn-20.1.0.tar.gz", hash = "sha256:e0a968b5ba15f8a328fdfd7ab1fcb5af4470c28aaf7e55df02a99bc13138e6e8"}, -] -html5lib = [ - {file = "html5lib-1.1-py2.py3-none-any.whl", hash = "sha256:0d78f8fde1c230e99fe37986a60526d7049ed4bf8a9fadbad5f00e22e58e041d"}, - {file = "html5lib-1.1.tar.gz", hash = "sha256:b2e5b40261e20f354d198eae92afc10d750afb487ed5e50f9c4eaf07c184146f"}, -] -idna = [ - {file = "idna-2.10-py2.py3-none-any.whl", hash = "sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0"}, - {file = "idna-2.10.tar.gz", hash = "sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6"}, -] -itypes = [ - {file = "itypes-1.2.0-py2.py3-none-any.whl", hash = "sha256:03da6872ca89d29aef62773672b2d408f490f80db48b23079a4b194c86dd04c6"}, - {file = "itypes-1.2.0.tar.gz", hash = "sha256:af886f129dea4a2a1e3d36595a2d139589e4dd287f5cab0b40e799ee81570ff1"}, -] -jinja2 = [ - {file = "Jinja2-3.0.0-py3-none-any.whl", hash = "sha256:2f2de5285cf37f33d33ecd4a9080b75c87cd0c1994d5a9c6df17131ea1f049c6"}, - {file = "Jinja2-3.0.0.tar.gz", hash = "sha256:ea8d7dd814ce9df6de6a761ec7f1cac98afe305b8cdc4aaae4e114b8d8ce24c5"}, -] -markupsafe = [ - {file = "MarkupSafe-2.0.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:2efaeb1baff547063bad2b2893a8f5e9c459c4624e1a96644bbba08910ae34e0"}, - {file = "MarkupSafe-2.0.0-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:441ce2a8c17683d97e06447fcbccbdb057cbf587c78eb75ae43ea7858042fe2c"}, - {file = "MarkupSafe-2.0.0-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:45535241baa0fc0ba2a43961a1ac7562ca3257f46c4c3e9c0de38b722be41bd1"}, - {file = "MarkupSafe-2.0.0-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:90053234a6479738fd40d155268af631c7fca33365f964f2208867da1349294b"}, - {file = "MarkupSafe-2.0.0-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:3b54a9c68995ef4164567e2cd1a5e16db5dac30b2a50c39c82db8d4afaf14f63"}, - {file = "MarkupSafe-2.0.0-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:f58b5ba13a5689ca8317b98439fccfbcc673acaaf8241c1869ceea40f5d585bf"}, - {file = "MarkupSafe-2.0.0-cp36-cp36m-win32.whl", hash = "sha256:a00dce2d96587651ef4fa192c17e039e8cfab63087c67e7d263a5533c7dad715"}, - {file = "MarkupSafe-2.0.0-cp36-cp36m-win_amd64.whl", hash = "sha256:007dc055dbce5b1104876acee177dbfd18757e19d562cd440182e1f492e96b95"}, - {file = "MarkupSafe-2.0.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:a08cd07d3c3c17cd33d9e66ea9dee8f8fc1c48e2d11bd88fd2dc515a602c709b"}, - {file = "MarkupSafe-2.0.0-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:3c352ff634e289061711608f5e474ec38dbaa21e3e168820d53d5f4015e5b91b"}, - {file = "MarkupSafe-2.0.0-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:32200f562daaab472921a11cbb63780f1654552ae49518196fc361ed8e12e901"}, - {file = "MarkupSafe-2.0.0-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:fef86115fdad7ae774720d7103aa776144cf9b66673b4afa9bcaa7af990ed07b"}, - {file = "MarkupSafe-2.0.0-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:e79212d09fc0e224d20b43ad44bb0a0a3416d1e04cf6b45fed265114a5d43d20"}, - {file = "MarkupSafe-2.0.0-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:79b2ae94fa991be023832e6bcc00f41dbc8e5fe9d997a02db965831402551730"}, - {file = "MarkupSafe-2.0.0-cp37-cp37m-win32.whl", hash = "sha256:3261fae28155e5c8634dd7710635fe540a05b58f160cef7713c7700cb9980e66"}, - {file = "MarkupSafe-2.0.0-cp37-cp37m-win_amd64.whl", hash = "sha256:e4570d16f88c7f3032ed909dc9e905a17da14a1c4cfd92608e3fda4cb1208bbd"}, - {file = "MarkupSafe-2.0.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8f806bfd0f218477d7c46a11d3e52dc7f5fdfaa981b18202b7dc84bbc287463b"}, - {file = "MarkupSafe-2.0.0-cp38-cp38-manylinux1_i686.whl", hash = "sha256:e77e4b983e2441aff0c0d07ee711110c106b625f440292dfe02a2f60c8218bd6"}, - {file = "MarkupSafe-2.0.0-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:031bf79a27d1c42f69c276d6221172417b47cb4b31cdc73d362a9bf5a1889b9f"}, - {file = "MarkupSafe-2.0.0-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:83cf0228b2f694dcdba1374d5312f2277269d798e65f40344964f642935feac1"}, - {file = "MarkupSafe-2.0.0-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:4cc563836f13c57f1473bc02d1e01fc37bab70ad4ee6be297d58c1d66bc819bf"}, - {file = "MarkupSafe-2.0.0-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:d00a669e4a5bec3ee6dbeeeedd82a405ced19f8aeefb109a012ea88a45afff96"}, - {file = "MarkupSafe-2.0.0-cp38-cp38-win32.whl", hash = "sha256:161d575fa49395860b75da5135162481768b11208490d5a2143ae6785123e77d"}, - {file = "MarkupSafe-2.0.0-cp38-cp38-win_amd64.whl", hash = "sha256:58bc9fce3e1557d463ef5cee05391a05745fd95ed660f23c1742c711712c0abb"}, - {file = "MarkupSafe-2.0.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:3fb47f97f1d338b943126e90b79cad50d4fcfa0b80637b5a9f468941dbbd9ce5"}, - {file = "MarkupSafe-2.0.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:dab0c685f21f4a6c95bfc2afd1e7eae0033b403dd3d8c1b6d13a652ada75b348"}, - {file = "MarkupSafe-2.0.0-cp39-cp39-manylinux1_i686.whl", hash = "sha256:664832fb88b8162268928df233f4b12a144a0c78b01d38b81bdcf0fc96668ecb"}, - {file = "MarkupSafe-2.0.0-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:df561f65049ed3556e5b52541669310e88713fdae2934845ec3606f283337958"}, - {file = "MarkupSafe-2.0.0-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:24bbc3507fb6dfff663af7900a631f2aca90d5a445f272db5fc84999fa5718bc"}, - {file = "MarkupSafe-2.0.0-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:87de598edfa2230ff274c4de7fcf24c73ffd96208c8e1912d5d0fee459767d75"}, - {file = "MarkupSafe-2.0.0-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:a19d39b02a24d3082856a5b06490b714a9d4179321225bbf22809ff1e1887cc8"}, - {file = "MarkupSafe-2.0.0-cp39-cp39-win32.whl", hash = "sha256:4aca81a687975b35e3e80bcf9aa93fe10cd57fac37bf18b2314c186095f57e05"}, - {file = "MarkupSafe-2.0.0-cp39-cp39-win_amd64.whl", hash = "sha256:70820a1c96311e02449591cbdf5cd1c6a34d5194d5b55094ab725364375c9eb2"}, - {file = "MarkupSafe-2.0.0.tar.gz", hash = "sha256:4fae0677f712ee090721d8b17f412f1cbceefbf0dc180fe91bab3232f38b4527"}, -] -pillow = [ - {file = "Pillow-8.2.0-cp36-cp36m-macosx_10_10_x86_64.whl", hash = "sha256:dc38f57d8f20f06dd7c3161c59ca2c86893632623f33a42d592f097b00f720a9"}, - {file = "Pillow-8.2.0-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:a013cbe25d20c2e0c4e85a9daf438f85121a4d0344ddc76e33fd7e3965d9af4b"}, - {file = "Pillow-8.2.0-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:8bb1e155a74e1bfbacd84555ea62fa21c58e0b4e7e6b20e4447b8d07990ac78b"}, - {file = "Pillow-8.2.0-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:c5236606e8570542ed424849f7852a0ff0bce2c4c8d0ba05cc202a5a9c97dee9"}, - {file = "Pillow-8.2.0-cp36-cp36m-win32.whl", hash = "sha256:12e5e7471f9b637762453da74e390e56cc43e486a88289995c1f4c1dc0bfe727"}, - {file = "Pillow-8.2.0-cp36-cp36m-win_amd64.whl", hash = "sha256:5afe6b237a0b81bd54b53f835a153770802f164c5570bab5e005aad693dab87f"}, - {file = "Pillow-8.2.0-cp37-cp37m-macosx_10_10_x86_64.whl", hash = "sha256:cb7a09e173903541fa888ba010c345893cd9fc1b5891aaf060f6ca77b6a3722d"}, - {file = "Pillow-8.2.0-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:0d19d70ee7c2ba97631bae1e7d4725cdb2ecf238178096e8c82ee481e189168a"}, - {file = "Pillow-8.2.0-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:083781abd261bdabf090ad07bb69f8f5599943ddb539d64497ed021b2a67e5a9"}, - {file = "Pillow-8.2.0-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:c6b39294464b03457f9064e98c124e09008b35a62e3189d3513e5148611c9388"}, - {file = "Pillow-8.2.0-cp37-cp37m-win32.whl", hash = "sha256:01425106e4e8cee195a411f729cff2a7d61813b0b11737c12bd5991f5f14bcd5"}, - {file = "Pillow-8.2.0-cp37-cp37m-win_amd64.whl", hash = "sha256:3b570f84a6161cf8865c4e08adf629441f56e32f180f7aa4ccbd2e0a5a02cba2"}, - {file = "Pillow-8.2.0-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:031a6c88c77d08aab84fecc05c3cde8414cd6f8406f4d2b16fed1e97634cc8a4"}, - {file = "Pillow-8.2.0-cp38-cp38-manylinux1_i686.whl", hash = "sha256:66cc56579fd91f517290ab02c51e3a80f581aba45fd924fcdee01fa06e635812"}, - {file = "Pillow-8.2.0-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:6c32cc3145928c4305d142ebec682419a6c0a8ce9e33db900027ddca1ec39178"}, - {file = "Pillow-8.2.0-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:624b977355cde8b065f6d51b98497d6cd5fbdd4f36405f7a8790e3376125e2bb"}, - {file = "Pillow-8.2.0-cp38-cp38-win32.whl", hash = "sha256:5cbf3e3b1014dddc45496e8cf38b9f099c95a326275885199f427825c6522232"}, - {file = "Pillow-8.2.0-cp38-cp38-win_amd64.whl", hash = "sha256:463822e2f0d81459e113372a168f2ff59723e78528f91f0bd25680ac185cf797"}, - {file = "Pillow-8.2.0-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:95d5ef984eff897850f3a83883363da64aae1000e79cb3c321915468e8c6add5"}, - {file = "Pillow-8.2.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b91c36492a4bbb1ee855b7d16fe51379e5f96b85692dc8210831fbb24c43e484"}, - {file = "Pillow-8.2.0-cp39-cp39-manylinux1_i686.whl", hash = "sha256:d68cb92c408261f806b15923834203f024110a2e2872ecb0bd2a110f89d3c602"}, - {file = "Pillow-8.2.0-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:f217c3954ce5fd88303fc0c317af55d5e0204106d86dea17eb8205700d47dec2"}, - {file = "Pillow-8.2.0-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:5b70110acb39f3aff6b74cf09bb4169b167e2660dabc304c1e25b6555fa781ef"}, - {file = "Pillow-8.2.0-cp39-cp39-win32.whl", hash = "sha256:a7d5e9fad90eff8f6f6106d3b98b553a88b6f976e51fce287192a5d2d5363713"}, - {file = "Pillow-8.2.0-cp39-cp39-win_amd64.whl", hash = "sha256:238c197fc275b475e87c1453b05b467d2d02c2915fdfdd4af126145ff2e4610c"}, - {file = "Pillow-8.2.0-pp36-pypy36_pp73-macosx_10_10_x86_64.whl", hash = "sha256:0e04d61f0064b545b989126197930807c86bcbd4534d39168f4aa5fda39bb8f9"}, - {file = "Pillow-8.2.0-pp36-pypy36_pp73-manylinux2010_i686.whl", hash = "sha256:63728564c1410d99e6d1ae8e3b810fe012bc440952168af0a2877e8ff5ab96b9"}, - {file = "Pillow-8.2.0-pp36-pypy36_pp73-manylinux2010_x86_64.whl", hash = "sha256:c03c07ed32c5324939b19e36ae5f75c660c81461e312a41aea30acdd46f93a7c"}, - {file = "Pillow-8.2.0-pp37-pypy37_pp73-macosx_10_10_x86_64.whl", hash = "sha256:4d98abdd6b1e3bf1a1cbb14c3895226816e666749ac040c4e2554231068c639b"}, - {file = "Pillow-8.2.0-pp37-pypy37_pp73-manylinux2010_i686.whl", hash = "sha256:aac00e4bc94d1b7813fe882c28990c1bc2f9d0e1aa765a5f2b516e8a6a16a9e4"}, - {file = "Pillow-8.2.0-pp37-pypy37_pp73-manylinux2010_x86_64.whl", hash = "sha256:22fd0f42ad15dfdde6c581347eaa4adb9a6fc4b865f90b23378aa7914895e120"}, - {file = "Pillow-8.2.0-pp37-pypy37_pp73-win32.whl", hash = "sha256:e98eca29a05913e82177b3ba3d198b1728e164869c613d76d0de4bde6768a50e"}, - {file = "Pillow-8.2.0-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:8b56553c0345ad6dcb2e9b433ae47d67f95fc23fe28a0bde15a120f25257e291"}, - {file = "Pillow-8.2.0.tar.gz", hash = "sha256:a787ab10d7bb5494e5f76536ac460741788f1fbce851068d73a87ca7c35fc3e1"}, -] -psycopg2 = [ - {file = "psycopg2-2.8.6-cp27-cp27m-win32.whl", hash = "sha256:068115e13c70dc5982dfc00c5d70437fe37c014c808acce119b5448361c03725"}, - {file = "psycopg2-2.8.6-cp27-cp27m-win_amd64.whl", hash = "sha256:d160744652e81c80627a909a0e808f3c6653a40af435744de037e3172cf277f5"}, - {file = "psycopg2-2.8.6-cp34-cp34m-win32.whl", hash = "sha256:b8cae8b2f022efa1f011cc753adb9cbadfa5a184431d09b273fb49b4167561ad"}, - {file = "psycopg2-2.8.6-cp34-cp34m-win_amd64.whl", hash = "sha256:f22ea9b67aea4f4a1718300908a2fb62b3e4276cf00bd829a97ab5894af42ea3"}, - {file = "psycopg2-2.8.6-cp35-cp35m-win32.whl", hash = "sha256:26e7fd115a6db75267b325de0fba089b911a4a12ebd3d0b5e7acb7028bc46821"}, - {file = "psycopg2-2.8.6-cp35-cp35m-win_amd64.whl", hash = "sha256:00195b5f6832dbf2876b8bf77f12bdce648224c89c880719c745b90515233301"}, - {file = "psycopg2-2.8.6-cp36-cp36m-win32.whl", hash = "sha256:a49833abfdede8985ba3f3ec641f771cca215479f41523e99dace96d5b8cce2a"}, - {file = "psycopg2-2.8.6-cp36-cp36m-win_amd64.whl", hash = "sha256:f974c96fca34ae9e4f49839ba6b78addf0346777b46c4da27a7bf54f48d3057d"}, - {file = "psycopg2-2.8.6-cp37-cp37m-win32.whl", hash = "sha256:6a3d9efb6f36f1fe6aa8dbb5af55e067db802502c55a9defa47c5a1dad41df84"}, - {file = "psycopg2-2.8.6-cp37-cp37m-win_amd64.whl", hash = "sha256:56fee7f818d032f802b8eed81ef0c1232b8b42390df189cab9cfa87573fe52c5"}, - {file = "psycopg2-2.8.6-cp38-cp38-win32.whl", hash = "sha256:ad2fe8a37be669082e61fb001c185ffb58867fdbb3e7a6b0b0d2ffe232353a3e"}, - {file = "psycopg2-2.8.6-cp38-cp38-win_amd64.whl", hash = "sha256:56007a226b8e95aa980ada7abdea6b40b75ce62a433bd27cec7a8178d57f4051"}, - {file = "psycopg2-2.8.6-cp39-cp39-win32.whl", hash = "sha256:2c93d4d16933fea5bbacbe1aaf8fa8c1348740b2e50b3735d1b0bf8154cbf0f3"}, - {file = "psycopg2-2.8.6-cp39-cp39-win_amd64.whl", hash = "sha256:d5062ae50b222da28253059880a871dc87e099c25cb68acf613d9d227413d6f7"}, - {file = "psycopg2-2.8.6.tar.gz", hash = "sha256:fb23f6c71107c37fd667cb4ea363ddeb936b348bbd6449278eb92c189699f543"}, -] -psycopg2-binary = [ - {file = "psycopg2-binary-2.8.6.tar.gz", hash = "sha256:11b9c0ebce097180129e422379b824ae21c8f2a6596b159c7659e2e5a00e1aa0"}, - {file = "psycopg2_binary-2.8.6-cp27-cp27m-macosx_10_6_intel.macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl", hash = "sha256:d14b140a4439d816e3b1229a4a525df917d6ea22a0771a2a78332273fd9528a4"}, - {file = "psycopg2_binary-2.8.6-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:1fabed9ea2acc4efe4671b92c669a213db744d2af8a9fc5d69a8e9bc14b7a9db"}, - {file = "psycopg2_binary-2.8.6-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:f5ab93a2cb2d8338b1674be43b442a7f544a0971da062a5da774ed40587f18f5"}, - {file = "psycopg2_binary-2.8.6-cp27-cp27m-win32.whl", hash = "sha256:b4afc542c0ac0db720cf516dd20c0846f71c248d2b3d21013aa0d4ef9c71ca25"}, - {file = "psycopg2_binary-2.8.6-cp27-cp27m-win_amd64.whl", hash = "sha256:e74a55f6bad0e7d3968399deb50f61f4db1926acf4a6d83beaaa7df986f48b1c"}, - {file = "psycopg2_binary-2.8.6-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:0deac2af1a587ae12836aa07970f5cb91964f05a7c6cdb69d8425ff4c15d4e2c"}, - {file = "psycopg2_binary-2.8.6-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:ad20d2eb875aaa1ea6d0f2916949f5c08a19c74d05b16ce6ebf6d24f2c9f75d1"}, - {file = "psycopg2_binary-2.8.6-cp34-cp34m-win32.whl", hash = "sha256:950bc22bb56ee6ff142a2cb9ee980b571dd0912b0334aa3fe0fe3788d860bea2"}, - {file = "psycopg2_binary-2.8.6-cp34-cp34m-win_amd64.whl", hash = "sha256:b8a3715b3c4e604bcc94c90a825cd7f5635417453b253499664f784fc4da0152"}, - {file = "psycopg2_binary-2.8.6-cp35-cp35m-macosx_10_6_intel.macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl", hash = "sha256:d1b4ab59e02d9008efe10ceabd0b31e79519da6fb67f7d8e8977118832d0f449"}, - {file = "psycopg2_binary-2.8.6-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:ac0c682111fbf404525dfc0f18a8b5f11be52657d4f96e9fcb75daf4f3984859"}, - {file = "psycopg2_binary-2.8.6-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:7d92a09b788cbb1aec325af5fcba9fed7203897bbd9269d5691bb1e3bce29550"}, - {file = "psycopg2_binary-2.8.6-cp35-cp35m-win32.whl", hash = "sha256:aaa4213c862f0ef00022751161df35804127b78adf4a2755b9f991a507e425fd"}, - {file = "psycopg2_binary-2.8.6-cp35-cp35m-win_amd64.whl", hash = "sha256:c2507d796fca339c8fb03216364cca68d87e037c1f774977c8fc377627d01c71"}, - {file = "psycopg2_binary-2.8.6-cp36-cp36m-macosx_10_6_intel.macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl", hash = "sha256:ee69dad2c7155756ad114c02db06002f4cded41132cc51378e57aad79cc8e4f4"}, - {file = "psycopg2_binary-2.8.6-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:e82aba2188b9ba309fd8e271702bd0d0fc9148ae3150532bbb474f4590039ffb"}, - {file = "psycopg2_binary-2.8.6-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:d5227b229005a696cc67676e24c214740efd90b148de5733419ac9aaba3773da"}, - {file = "psycopg2_binary-2.8.6-cp36-cp36m-win32.whl", hash = "sha256:a0eb43a07386c3f1f1ebb4dc7aafb13f67188eab896e7397aa1ee95a9c884eb2"}, - {file = "psycopg2_binary-2.8.6-cp36-cp36m-win_amd64.whl", hash = "sha256:e1f57aa70d3f7cc6947fd88636a481638263ba04a742b4a37dd25c373e41491a"}, - {file = "psycopg2_binary-2.8.6-cp37-cp37m-macosx_10_6_intel.macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl", hash = "sha256:833709a5c66ca52f1d21d41865a637223b368c0ee76ea54ca5bad6f2526c7679"}, - {file = "psycopg2_binary-2.8.6-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:ba28584e6bca48c59eecbf7efb1576ca214b47f05194646b081717fa628dfddf"}, - {file = "psycopg2_binary-2.8.6-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:6a32f3a4cb2f6e1a0b15215f448e8ce2da192fd4ff35084d80d5e39da683e79b"}, - {file = "psycopg2_binary-2.8.6-cp37-cp37m-win32.whl", hash = "sha256:0e4dc3d5996760104746e6cfcdb519d9d2cd27c738296525d5867ea695774e67"}, - {file = "psycopg2_binary-2.8.6-cp37-cp37m-win_amd64.whl", hash = "sha256:cec7e622ebc545dbb4564e483dd20e4e404da17ae07e06f3e780b2dacd5cee66"}, - {file = "psycopg2_binary-2.8.6-cp38-cp38-macosx_10_9_x86_64.macosx_10_9_intel.macosx_10_10_intel.macosx_10_10_x86_64.whl", hash = "sha256:ba381aec3a5dc29634f20692349d73f2d21f17653bda1decf0b52b11d694541f"}, - {file = "psycopg2_binary-2.8.6-cp38-cp38-manylinux1_i686.whl", hash = "sha256:a0c50db33c32594305b0ef9abc0cb7db13de7621d2cadf8392a1d9b3c437ef77"}, - {file = "psycopg2_binary-2.8.6-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:2dac98e85565d5688e8ab7bdea5446674a83a3945a8f416ad0110018d1501b94"}, - {file = "psycopg2_binary-2.8.6-cp38-cp38-win32.whl", hash = "sha256:bd1be66dde2b82f80afb9459fc618216753f67109b859a361cf7def5c7968729"}, - {file = "psycopg2_binary-2.8.6-cp38-cp38-win_amd64.whl", hash = "sha256:8cd0fb36c7412996859cb4606a35969dd01f4ea34d9812a141cd920c3b18be77"}, - {file = "psycopg2_binary-2.8.6-cp39-cp39-macosx_10_9_x86_64.macosx_10_9_intel.macosx_10_10_intel.macosx_10_10_x86_64.whl", hash = "sha256:89705f45ce07b2dfa806ee84439ec67c5d9a0ef20154e0e475e2b2ed392a5b83"}, - {file = "psycopg2_binary-2.8.6-cp39-cp39-manylinux1_i686.whl", hash = "sha256:42ec1035841b389e8cc3692277a0bd81cdfe0b65d575a2c8862cec7a80e62e52"}, - {file = "psycopg2_binary-2.8.6-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:7312e931b90fe14f925729cde58022f5d034241918a5c4f9797cac62f6b3a9dd"}, - {file = "psycopg2_binary-2.8.6-cp39-cp39-win32.whl", hash = "sha256:6422f2ff0919fd720195f64ffd8f924c1395d30f9a495f31e2392c2efafb5056"}, - {file = "psycopg2_binary-2.8.6-cp39-cp39-win_amd64.whl", hash = "sha256:15978a1fbd225583dd8cdaf37e67ccc278b5abecb4caf6b2d6b8e2b948e953f6"}, -] -pycparser = [ - {file = "pycparser-2.20-py2.py3-none-any.whl", hash = "sha256:7582ad22678f0fcd81102833f60ef8d0e57288b6b5fb00323d101be910e35705"}, - {file = "pycparser-2.20.tar.gz", hash = "sha256:2d475327684562c3a96cc71adf7dc8c4f0565175cf86b6d7a404ff4c771f15f0"}, -] -pycryptodome = [ - {file = "pycryptodome-3.10.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:1c5e1ca507de2ad93474be5cfe2bfa76b7cf039a1a32fc196f40935944871a06"}, - {file = "pycryptodome-3.10.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:6260e24d41149268122dd39d4ebd5941e9d107f49463f7e071fd397e29923b0c"}, - {file = "pycryptodome-3.10.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:3f840c49d38986f6e17dbc0673d37947c88bc9d2d9dba1c01b979b36f8447db1"}, - {file = "pycryptodome-3.10.1-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:2dea65df54349cdfa43d6b2e8edb83f5f8d6861e5cf7b1fbc3e34c5694c85e27"}, - {file = "pycryptodome-3.10.1-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:e61e363d9a5d7916f3a4ce984a929514c0df3daf3b1b2eb5e6edbb131ee771cf"}, - {file = "pycryptodome-3.10.1-cp27-cp27m-manylinux2014_aarch64.whl", hash = "sha256:2603c98ae04aac675fefcf71a6c87dc4bb74a75e9071ae3923bbc91a59f08d35"}, - {file = "pycryptodome-3.10.1-cp27-cp27m-win32.whl", hash = "sha256:38661348ecb71476037f1e1f553159b80d256c00f6c0b00502acac891f7116d9"}, - {file = "pycryptodome-3.10.1-cp27-cp27m-win_amd64.whl", hash = "sha256:1723ebee5561628ce96748501cdaa7afaa67329d753933296321f0be55358dce"}, - {file = "pycryptodome-3.10.1-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:77997519d8eb8a4adcd9a47b9cec18f9b323e296986528186c0e9a7a15d6a07e"}, - {file = "pycryptodome-3.10.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:99b2f3fc51d308286071d0953f92055504a6ffe829a832a9fc7a04318a7683dd"}, - {file = "pycryptodome-3.10.1-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:e0a4d5933a88a2c98bbe19c0c722f5483dc628d7a38338ac2cb64a7dbd34064b"}, - {file = "pycryptodome-3.10.1-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:d3d6958d53ad307df5e8469cc44474a75393a434addf20ecd451f38a72fe29b8"}, - {file = "pycryptodome-3.10.1-cp27-cp27mu-manylinux2014_aarch64.whl", hash = "sha256:a8eb8b6ea09ec1c2535bf39914377bc8abcab2c7d30fa9225eb4fe412024e427"}, - {file = "pycryptodome-3.10.1-cp35-abi3-macosx_10_9_x86_64.whl", hash = "sha256:31c1df17b3dc5f39600a4057d7db53ac372f492c955b9b75dd439f5d8b460129"}, - {file = "pycryptodome-3.10.1-cp35-abi3-manylinux1_i686.whl", hash = "sha256:a3105a0eb63eacf98c2ecb0eb4aa03f77f40fbac2bdde22020bb8a536b226bb8"}, - {file = "pycryptodome-3.10.1-cp35-abi3-manylinux1_x86_64.whl", hash = "sha256:a92d5c414e8ee1249e850789052608f582416e82422502dc0ac8c577808a9067"}, - {file = "pycryptodome-3.10.1-cp35-abi3-manylinux2010_i686.whl", hash = "sha256:60386d1d4cfaad299803b45a5bc2089696eaf6cdd56f9fc17479a6f89595cfc8"}, - {file = "pycryptodome-3.10.1-cp35-abi3-manylinux2010_x86_64.whl", hash = "sha256:501ab36aae360e31d0ec370cf5ce8ace6cb4112060d099b993bc02b36ac83fb6"}, - {file = "pycryptodome-3.10.1-cp35-abi3-manylinux2014_aarch64.whl", hash = "sha256:fc7489a50323a0df02378bc2fff86eb69d94cc5639914346c736be981c6a02e7"}, - {file = "pycryptodome-3.10.1-cp35-abi3-win32.whl", hash = "sha256:9b6f711b25e01931f1c61ce0115245a23cdc8b80bf8539ac0363bdcf27d649b6"}, - {file = "pycryptodome-3.10.1-cp35-abi3-win_amd64.whl", hash = "sha256:7fd519b89585abf57bf47d90166903ec7b43af4fe23c92273ea09e6336af5c07"}, - {file = "pycryptodome-3.10.1-pp27-pypy_73-macosx_10_9_x86_64.whl", hash = "sha256:09c1555a3fa450e7eaca41ea11cd00afe7c91fef52353488e65663777d8524e0"}, - {file = "pycryptodome-3.10.1-pp27-pypy_73-manylinux1_x86_64.whl", hash = "sha256:758949ca62690b1540dfb24ad773c6da9cd0e425189e83e39c038bbd52b8e438"}, - {file = "pycryptodome-3.10.1-pp27-pypy_73-manylinux2010_x86_64.whl", hash = "sha256:e3bf558c6aeb49afa9f0c06cee7fb5947ee5a1ff3bd794b653d39926b49077fa"}, - {file = "pycryptodome-3.10.1-pp27-pypy_73-win32.whl", hash = "sha256:f977cdf725b20f6b8229b0c87acb98c7717e742ef9f46b113985303ae12a99da"}, - {file = "pycryptodome-3.10.1-pp36-pypy36_pp73-macosx_10_9_x86_64.whl", hash = "sha256:6d2df5223b12437e644ce0a3be7809471ffa71de44ccd28b02180401982594a6"}, - {file = "pycryptodome-3.10.1-pp36-pypy36_pp73-manylinux1_x86_64.whl", hash = "sha256:98213ac2b18dc1969a47bc65a79a8fca02a414249d0c8635abb081c7f38c91b6"}, - {file = "pycryptodome-3.10.1-pp36-pypy36_pp73-manylinux2010_x86_64.whl", hash = "sha256:12222a5edc9ca4a29de15fbd5339099c4c26c56e13c2ceddf0b920794f26165d"}, - {file = "pycryptodome-3.10.1-pp36-pypy36_pp73-win32.whl", hash = "sha256:6bbf7fee7b7948b29d7e71fcacf48bac0c57fb41332007061a933f2d996f9713"}, - {file = "pycryptodome-3.10.1.tar.gz", hash = "sha256:3e2e3a06580c5f190df843cdb90ea28d61099cf4924334d5297a995de68e4673"}, -] -pygments = [ - {file = "Pygments-2.9.0-py3-none-any.whl", hash = "sha256:d66e804411278594d764fc69ec36ec13d9ae9147193a1740cd34d272ca383b8e"}, - {file = "Pygments-2.9.0.tar.gz", hash = "sha256:a18f47b506a429f6f4b9df81bb02beab9ca21d0a5fee38ed15aef65f0545519f"}, -] -pypdf2 = [ - {file = "PyPDF2-1.26.0.tar.gz", hash = "sha256:e28f902f2f0a1603ea95ebe21dff311ef09be3d0f0ef29a3e44a932729564385"}, -] -python-bidi = [ - {file = "python-bidi-0.4.2.tar.gz", hash = "sha256:5347f71e82b3e9976dc657f09ded2bfe39ba8d6777ca81a5b2c56c30121c496e"}, - {file = "python_bidi-0.4.2-py2.py3-none-any.whl", hash = "sha256:50eef6f6a0bbdd685f9e8c207f3c9050f5b578d0a46e37c76a9c4baea2cc2e13"}, -] -python-crontab = [ - {file = "python-crontab-2.5.1.tar.gz", hash = "sha256:4bbe7e720753a132ca4ca9d4094915f40e9d9dc8a807a4564007651018ce8c31"}, -] -python-dateutil = [ - {file = "python-dateutil-2.8.2.tar.gz", hash = "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86"}, - {file = "python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"}, -] -pytz = [ - {file = "pytz-2021.1-py2.py3-none-any.whl", hash = "sha256:eb10ce3e7736052ed3623d49975ce333bcd712c7bb19a58b9e2089d4057d0798"}, - {file = "pytz-2021.1.tar.gz", hash = "sha256:83a4a90894bf38e243cf052c8b58f381bfe9a7a483f6a9cab140bc7f702ac4da"}, -] -regex = [ - {file = "regex-2021.4.4-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:619d71c59a78b84d7f18891fe914446d07edd48dc8328c8e149cbe0929b4e000"}, - {file = "regex-2021.4.4-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:47bf5bf60cf04d72bf6055ae5927a0bd9016096bf3d742fa50d9bf9f45aa0711"}, - {file = "regex-2021.4.4-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:281d2fd05555079448537fe108d79eb031b403dac622621c78944c235f3fcf11"}, - {file = "regex-2021.4.4-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:bd28bc2e3a772acbb07787c6308e00d9626ff89e3bfcdebe87fa5afbfdedf968"}, - {file = "regex-2021.4.4-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:7c2a1af393fcc09e898beba5dd59196edaa3116191cc7257f9224beaed3e1aa0"}, - {file = "regex-2021.4.4-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:c38c71df845e2aabb7fb0b920d11a1b5ac8526005e533a8920aea97efb8ec6a4"}, - {file = "regex-2021.4.4-cp36-cp36m-manylinux2014_i686.whl", hash = "sha256:96fcd1888ab4d03adfc9303a7b3c0bd78c5412b2bfbe76db5b56d9eae004907a"}, - {file = "regex-2021.4.4-cp36-cp36m-manylinux2014_x86_64.whl", hash = "sha256:ade17eb5d643b7fead300a1641e9f45401c98eee23763e9ed66a43f92f20b4a7"}, - {file = "regex-2021.4.4-cp36-cp36m-win32.whl", hash = "sha256:e8e5b509d5c2ff12f8418006d5a90e9436766133b564db0abaec92fd27fcee29"}, - {file = "regex-2021.4.4-cp36-cp36m-win_amd64.whl", hash = "sha256:11d773d75fa650cd36f68d7ca936e3c7afaae41b863b8c387a22aaa78d3c5c79"}, - {file = "regex-2021.4.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:d3029c340cfbb3ac0a71798100ccc13b97dddf373a4ae56b6a72cf70dfd53bc8"}, - {file = "regex-2021.4.4-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:18c071c3eb09c30a264879f0d310d37fe5d3a3111662438889ae2eb6fc570c31"}, - {file = "regex-2021.4.4-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:4c557a7b470908b1712fe27fb1ef20772b78079808c87d20a90d051660b1d69a"}, - {file = "regex-2021.4.4-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:01afaf2ec48e196ba91b37451aa353cb7eda77efe518e481707e0515025f0cd5"}, - {file = "regex-2021.4.4-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:3a9cd17e6e5c7eb328517969e0cb0c3d31fd329298dd0c04af99ebf42e904f82"}, - {file = "regex-2021.4.4-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:90f11ff637fe8798933fb29f5ae1148c978cccb0452005bf4c69e13db951e765"}, - {file = "regex-2021.4.4-cp37-cp37m-manylinux2014_i686.whl", hash = "sha256:919859aa909429fb5aa9cf8807f6045592c85ef56fdd30a9a3747e513db2536e"}, - {file = "regex-2021.4.4-cp37-cp37m-manylinux2014_x86_64.whl", hash = "sha256:339456e7d8c06dd36a22e451d58ef72cef293112b559010db3d054d5560ef439"}, - {file = "regex-2021.4.4-cp37-cp37m-win32.whl", hash = "sha256:67bdb9702427ceddc6ef3dc382455e90f785af4c13d495f9626861763ee13f9d"}, - {file = "regex-2021.4.4-cp37-cp37m-win_amd64.whl", hash = "sha256:32e65442138b7b76dd8173ffa2cf67356b7bc1768851dded39a7a13bf9223da3"}, - {file = "regex-2021.4.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1e1c20e29358165242928c2de1482fb2cf4ea54a6a6dea2bd7a0e0d8ee321500"}, - {file = "regex-2021.4.4-cp38-cp38-manylinux1_i686.whl", hash = "sha256:314d66636c494ed9c148a42731b3834496cc9a2c4251b1661e40936814542b14"}, - {file = "regex-2021.4.4-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:6d1b01031dedf2503631d0903cb563743f397ccaf6607a5e3b19a3d76fc10480"}, - {file = "regex-2021.4.4-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:741a9647fcf2e45f3a1cf0e24f5e17febf3efe8d4ba1281dcc3aa0459ef424dc"}, - {file = "regex-2021.4.4-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:4c46e22a0933dd783467cf32b3516299fb98cfebd895817d685130cc50cd1093"}, - {file = "regex-2021.4.4-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:e512d8ef5ad7b898cdb2d8ee1cb09a8339e4f8be706d27eaa180c2f177248a10"}, - {file = "regex-2021.4.4-cp38-cp38-manylinux2014_i686.whl", hash = "sha256:980d7be47c84979d9136328d882f67ec5e50008681d94ecc8afa8a65ed1f4a6f"}, - {file = "regex-2021.4.4-cp38-cp38-manylinux2014_x86_64.whl", hash = "sha256:ce15b6d103daff8e9fee13cf7f0add05245a05d866e73926c358e871221eae87"}, - {file = "regex-2021.4.4-cp38-cp38-win32.whl", hash = "sha256:a91aa8619b23b79bcbeb37abe286f2f408d2f2d6f29a17237afda55bb54e7aac"}, - {file = "regex-2021.4.4-cp38-cp38-win_amd64.whl", hash = "sha256:c0502c0fadef0d23b128605d69b58edb2c681c25d44574fc673b0e52dce71ee2"}, - {file = "regex-2021.4.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:598585c9f0af8374c28edd609eb291b5726d7cbce16be6a8b95aa074d252ee17"}, - {file = "regex-2021.4.4-cp39-cp39-manylinux1_i686.whl", hash = "sha256:ee54ff27bf0afaf4c3b3a62bcd016c12c3fdb4ec4f413391a90bd38bc3624605"}, - {file = "regex-2021.4.4-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:7d9884d86dd4dd489e981d94a65cd30d6f07203d90e98f6f657f05170f6324c9"}, - {file = "regex-2021.4.4-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:bf5824bfac591ddb2c1f0a5f4ab72da28994548c708d2191e3b87dd207eb3ad7"}, - {file = "regex-2021.4.4-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:563085e55b0d4fb8f746f6a335893bda5c2cef43b2f0258fe1020ab1dd874df8"}, - {file = "regex-2021.4.4-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:b9c3db21af35e3b3c05764461b262d6f05bbca08a71a7849fd79d47ba7bc33ed"}, - {file = "regex-2021.4.4-cp39-cp39-manylinux2014_i686.whl", hash = "sha256:3916d08be28a1149fb97f7728fca1f7c15d309a9f9682d89d79db75d5e52091c"}, - {file = "regex-2021.4.4-cp39-cp39-manylinux2014_x86_64.whl", hash = "sha256:fd45ff9293d9274c5008a2054ecef86a9bfe819a67c7be1afb65e69b405b3042"}, - {file = "regex-2021.4.4-cp39-cp39-win32.whl", hash = "sha256:fa4537fb4a98fe8fde99626e4681cc644bdcf2a795038533f9f711513a862ae6"}, - {file = "regex-2021.4.4-cp39-cp39-win_amd64.whl", hash = "sha256:97f29f57d5b84e73fbaf99ab3e26134e6687348e95ef6b48cfd2c06807005a07"}, - {file = "regex-2021.4.4.tar.gz", hash = "sha256:52ba3d3f9b942c49d7e4bc105bb28551c44065f139a65062ab7912bef10c9afb"}, -] -reportlab = [ - {file = "reportlab-3.5.67-cp36-cp36m-macosx_10_10_x86_64.whl", hash = "sha256:51a2d5de2c605117cd25dfb3f51d1d14caf1cbed4ef6db582f085eeb0a0c922f"}, - {file = "reportlab-3.5.67-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:34d827c771d6b4d7b45f7fc49a638c97fbd8a0fab6c9d3838ff04d307420b739"}, - {file = "reportlab-3.5.67-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:e4b9b443e88735be4927529d66d9e1164b4fbd6a882e90114967eedc6ad608e7"}, - {file = "reportlab-3.5.67-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:9517f26a512a62d49fc4800222b306e21a14ceec8bd82c93182313ef1eefaa7a"}, - {file = "reportlab-3.5.67-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:5c483c96d4cbeb4919ad9fcf2f262e8e08e34dcbcf8d2bda16263ef002c890d4"}, - {file = "reportlab-3.5.67-cp36-cp36m-win32.whl", hash = "sha256:9989737a409235a734ec783b0545f2966247b26ff555e847f3d0f945e5a11493"}, - {file = "reportlab-3.5.67-cp36-cp36m-win_amd64.whl", hash = "sha256:e2b47a8e0126ec0a3820a2e299a94a6fc29ba132249957dd32c447d380eaae5f"}, - {file = "reportlab-3.5.67-cp37-cp37m-macosx_10_10_x86_64.whl", hash = "sha256:8cd355f8a4c7c126a246f4b4a9803c80498939709bb37d3db4f8dbee1eb7d8f0"}, - {file = "reportlab-3.5.67-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:0d670e119d7f7a68a1136de024464999e8e3d5d1491f23cdd39d5d72481af88f"}, - {file = "reportlab-3.5.67-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:df2784a474028b15a723f6b347625f1f91740de418bed4a0a2694c954de34dd7"}, - {file = "reportlab-3.5.67-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:9c0d71aef4fb5d30dc6ebd08a2bce317a7eaf37d468f85320947eb580daea90a"}, - {file = "reportlab-3.5.67-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:b2b72a0742a493979c348dc3c9a329bd5b87e4243ffecf837b1c8739d58410ba"}, - {file = "reportlab-3.5.67-cp37-cp37m-win32.whl", hash = "sha256:1e41b441542881e007420530bbc028f08c0f546ecaaebdf9f065f901acdac106"}, - {file = "reportlab-3.5.67-cp37-cp37m-win_amd64.whl", hash = "sha256:6a3119d0e985e5c7dadfcf29fb79bbab19806b08ad901622b23f5868c0221fce"}, - {file = "reportlab-3.5.67-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:bda784ebb116d56d3e7133c8e0942cf68cb7fd58bdccf57231dbe56b6430eb01"}, - {file = "reportlab-3.5.67-cp38-cp38-manylinux1_i686.whl", hash = "sha256:55ef4476b2cdecfa643ae4d7591aa157568f903c378c83ea544650b33b2d856d"}, - {file = "reportlab-3.5.67-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:72bb5417f198eb059f01d5a9e1ef80f2fbaf3eaa4cd63e9a681bbbd0ed9fcdf9"}, - {file = "reportlab-3.5.67-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:519ef25d49fe807c6c0402abb5fe4d14b47a8e2358050d8d7673beecfbe116b2"}, - {file = "reportlab-3.5.67-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:9d48fd4a1c2d98ec6686511717f0980d36f5590e038d5afe4e5241f328f06e38"}, - {file = "reportlab-3.5.67-cp38-cp38-win32.whl", hash = "sha256:9945e80a0a6e370f90a23907cc70a0811e808f79420fb9051e26d9c79eb8e26b"}, - {file = "reportlab-3.5.67-cp38-cp38-win_amd64.whl", hash = "sha256:370c5225f0c395a9f1482ac8d4f974d2073548f186eaf49ceb91414f534ad4d8"}, - {file = "reportlab-3.5.67-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:42b90b0cb3556f4d1cc1c538345abc249b6ff58939d3af5e37f5fa8421d9ae07"}, - {file = "reportlab-3.5.67-cp39-cp39-manylinux1_i686.whl", hash = "sha256:5b4acfb15ca028bbc652a6c8d63073dec2a3c8c0db7585d68b96b52940f65899"}, - {file = "reportlab-3.5.67-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:492bd47aabeaa3215cde7a8d3c0d88c909bf7e6b63f0b511a645f1ffc1e948f6"}, - {file = "reportlab-3.5.67-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:af12fbff15a9652ef117456d1d6a4d6fade8fdc02670d6fd31212402e9d03559"}, - {file = "reportlab-3.5.67-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:5c931032aa955431c808e469eb0780ca7d12b39228a02ae7ea09f63d47b1e260"}, - {file = "reportlab-3.5.67-cp39-cp39-win32.whl", hash = "sha256:4c5785b018ed6f48e762737deaa6b7528b0ba43ad67fca566bf10d0337a76dcd"}, - {file = "reportlab-3.5.67-cp39-cp39-win_amd64.whl", hash = "sha256:1656722530b3bbce012b093abf6290ab76dcba39d21f9e703310b008ddc7ffe9"}, - {file = "reportlab-3.5.67.tar.gz", hash = "sha256:0cf2206c73fbca752c8bd39e12bb9ad7f2d01e6fcb2b25b9eaf94ea042fe86c9"}, -] -requests = [ - {file = "requests-2.25.1-py2.py3-none-any.whl", hash = "sha256:c210084e36a42ae6b9219e00e48287def368a26d03a048ddad7bfee44f75871e"}, - {file = "requests-2.25.1.tar.gz", hash = "sha256:27973dd4a904a4f13b263a19c866c13b92a39ed1c964655f025f3f8d3d75b804"}, -] -sentry-sdk = [ - {file = "sentry-sdk-1.1.0.tar.gz", hash = "sha256:c1227d38dca315ba35182373f129c3e2722e8ed999e52584e6aca7d287870739"}, - {file = "sentry_sdk-1.1.0-py2.py3-none-any.whl", hash = "sha256:c7d380a21281e15be3d9f67a3c4fbb4f800c481d88ff8d8931f39486dd7b4ada"}, -] -six = [ - {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, - {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, -] -sqlparse = [ - {file = "sqlparse-0.4.1-py3-none-any.whl", hash = "sha256:017cde379adbd6a1f15a61873f43e8274179378e95ef3fede90b5aa64d304ed0"}, - {file = "sqlparse-0.4.1.tar.gz", hash = "sha256:0f91fd2e829c44362cbcfab3e9ae12e22badaa8a29ad5ff599f9ec109f0454e8"}, -] -stripe = [ - {file = "stripe-2.56.0-py2.py3-none-any.whl", hash = "sha256:6c685eeadf9e3608315b6d84b4f5f2da2909179b65633ce20f296be22ed21a98"}, - {file = "stripe-2.56.0.tar.gz", hash = "sha256:2ff904fb8dee0d25f135059468a876852d24dc8cbe0b45d7aff56a028045777c"}, -] -tqdm = [ - {file = "tqdm-4.60.0-py2.py3-none-any.whl", hash = "sha256:daec693491c52e9498632dfbe9ccfc4882a557f5fa08982db1b4d3adbe0887c3"}, - {file = "tqdm-4.60.0.tar.gz", hash = "sha256:ebdebdb95e3477ceea267decfc0784859aa3df3e27e22d23b83e9b272bf157ae"}, -] -unidecode = [ - {file = "Unidecode-0.04.21-py2.py3-none-any.whl", hash = "sha256:61f807220eda0203a774a09f84b4304a3f93b5944110cc132af29ddb81366883"}, - {file = "Unidecode-0.04.21.tar.gz", hash = "sha256:280a6ab88e1f2eb5af79edff450021a0d3f0448952847cd79677e55e58bad051"}, -] -uritemplate = [ - {file = "uritemplate-3.0.1-py2.py3-none-any.whl", hash = "sha256:07620c3f3f8eed1f12600845892b0e036a2420acf513c53f7de0abd911a5894f"}, - {file = "uritemplate-3.0.1.tar.gz", hash = "sha256:5af8ad10cec94f215e3f48112de2022e1d5a37ed427fbd88652fa908f2ab7cae"}, -] -urllib3 = [ - {file = "urllib3-1.26.4-py2.py3-none-any.whl", hash = "sha256:2f4da4594db7e1e110a944bb1b551fdf4e6c136ad42e4234131391e21eb5b0df"}, - {file = "urllib3-1.26.4.tar.gz", hash = "sha256:e7b021f7241115872f92f43c6508082facffbd1c048e3c6e2bb9c2a157e28937"}, -] -webencodings = [ - {file = "webencodings-0.5.1-py2.py3-none-any.whl", hash = "sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78"}, - {file = "webencodings-0.5.1.tar.gz", hash = "sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923"}, -] -xhtml2pdf = [ - {file = "xhtml2pdf-0.2.5.tar.gz", hash = "sha256:6797e974fac66f0efbe927c1539a2756ca4fe8777eaa5882bac132fc76b39421"}, -] +lock-version = "2.0" +python-versions = ">=3.9,<3.13" +content-hash = "d5bdb84d3339035fe763b04e09c4264dbf3754347ac0e4c6cba51e6c8b8eda41" diff --git a/backend/src/pyproject.toml b/backend/src/pyproject.toml index 3ba18836..e61cb0e7 100644 --- a/backend/src/pyproject.toml +++ b/backend/src/pyproject.toml @@ -6,26 +6,30 @@ authors = ["AMVARA CONSULTING "] license = "MIT" [tool.poetry.dependencies] -python = "^3.8" -Django = "^3.2.3" -psycopg2 = "^2.8.6" -psycopg2-binary = "^2.8.6" -djangorestframework = "^3.12.4" -tqdm = "^4.60.0" +python = ">=3.9,<3.13" +Django = "^3.2.20" +psycopg2 = "^2.9.6" +psycopg2-binary = "^2.9.6" +djangorestframework = "^3.14.0" +tqdm = "^4.65.0" django-filter = "^2.4.0" coreapi = "^2.3.3" -requests = "^2.25.1" -django-cors-headers = "^3.7.0" +requests = "~2.25.1" +django-cors-headers = "^3.14.0" awesome-slugify = "^1.6.5" -bcrypt = "^3.2.0" -xhtml2pdf = "^0.2.5" -pycryptodome = "^3.10.1" -sentry-sdk = "^1.1.0" -Pillow = "^8.2.0" -stripe = "^2.56.0" -Pygments = "^2.9.0" -python-crontab = "^2.5.1" +bcrypt = "^3.2.2" +xhtml2pdf = "^0.2.11" +pycryptodome = "^3.18.0" +sentry-sdk = "^1.28.1" +Pillow = "^9.5.0" +stripe = "^2.76.0" +Pygments = "^2.15.1" +python-crontab = "^2.7.1" gunicorn = "^20.1.0" +python-magic = "^0.4.27" +pandas = "^2.1.1" +openpyxl = "^3.1.2" +odfpy = "^1.4.1" [tool.poetry.dev-dependencies] diff --git a/backend/src/start.sh b/backend/src/start.sh index b7d86574..b6fa992d 100755 --- a/backend/src/start.sh +++ b/backend/src/start.sh @@ -1,5 +1,42 @@ #!/bin/bash +# ####### +# This is the backend entrypoint, which starts the DJANGO Server +# ####### +# +# As deault it will start gunicorn service compiling django, which is the fastest way and saves a lot of resources. +# Only using gunicorn, you will be able to serve hundreds of users and executions on a small server setup. +# +# For development it is recommended to use the python django compiling on each change, so that you can change, see, tests and debug. +# The Django server will be invoked using the parameter "-dev" on this script +# +# ####### + +# set default to prod ... can be overwritten here or with commandline setting or environment variable ENVIRONMENT +# prod executes gunicorn +# dev executes ptyhon manage.py runserver +ENVIRONMENT=${ENVIRONMENT:-prod} + +while [[ $# -gt 0 ]] +do + key="$1" + case $key in + -d|--debug) + set -x + DEBUG=TRUE + shift + ;; + -dev|--development) + echo "###################################################" + echo "==> Setting development option requested from cli " + echo "###################################################" + ENVIRONMENT="dev" + shift + ;; + esac +done + + install_cron() { apt install -y cron touch /etc/cron.d/crontab @@ -14,8 +51,11 @@ create_secret_variables() { echo "Unable to find secret_variables.py will make one..." # make a random encryption passphrase RANDOM_ENCRYPTION_PASSPHRASE=$(openssl rand -base64 46) + RANDOM_UPLOAD_ENCRYPTION_PASSPHRASE=$(openssl rand -base64 46) # make a random secret key for django RANDOM_DJANGO_SECRETKEY=$(openssl rand -base64 31) + # make a random secret key for behave + RANDOM_BEHAVE_SECRETKEY=$(openssl rand -base64 31) # generate a bare minimum secret_variables file to work with cat < /code/secret_variables.py COMETA_STRIPE_CHARGE_AUTOMATICALLY='False' @@ -31,9 +71,11 @@ COMETA_SENTRY_DJANGO='' COMETA_STRIPE_LIVE_KEY='' COMETA_PROD_ENABLE_PAYMENT='False' COMETA_ENCRYPTION_PASSPHRASE='$RANDOM_ENCRYPTION_PASSPHRASE' +COMETA_UPLOAD_ENCRYPTION_PASSPHRASE='$RANDOM_UPLOAD_ENCRYPTION_PASSPHRASE' COMETA_STRIPE_TEST_WEBHOOK_SECRET='' COMETA_STAGE_ENABLE_PAYMENT='False' COMETA_DJANGO_SECRETKEY='$RANDOM_DJANGO_SECRETKEY' +COMETA_BEHAVE_SECRETKEY='$RANDOM_BEHAVE_SECRETKEY' COMETA_STRIPE_LIVE_WEBHOOK_SECRET='' COMETA_SCREENSHOT_PREFIX='AMVARA_' COMETA_EMAIL_ENABLED='False' @@ -54,18 +96,16 @@ EOF # Make sure log folder exists mkdir -p /opt/code/logs || true # Install requirements -apt update && apt install -y rsyslog jq nano vim +apt update && apt install -y rsyslog jq nano vim clamav-daemon service rsyslog start # Install cron install_cron # check and create secret_variables.py create_secret_variables # Install poetry package manager -curl -sSL https://raw.githubusercontent.com/python-poetry/poetry/master/get-poetry.py | python - +curl -sSL https://install.python-poetry.org | python3 - # Create symbolic link to Poetry so it's available as command everywhere -if [ ! -L "/usr/local/bin/poetry" ]; then - ln -s /root/.poetry/bin/poetry /usr/local/bin/poetry -fi +ln -s /root/.local/bin/poetry /usr/local/bin/poetry # Disable creation of virtual env poetry config virtualenvs.create false # Install project dependencies @@ -73,7 +113,7 @@ poetry install --no-interaction --no-ansi # Upgrade PIP pip install -U pip # Run Django migrations -python manage.py makemigrations +python manage.py makemigrations backend python manage.py migrate # if this is the first time initializing co.meta @@ -83,13 +123,43 @@ if [ ! -f "/code/.initiated" ]; then touch /code/.initiated fi -# Start Django server -# python manage.py runserver 0.0.0.0:8000 -# get processor cores -CPUCORES=`getconf _NPROCESSORS_ONLN` -# calculate workers -GUNI_WORKERS=$((($CPUCORES*2+1))) +# update clamav database and start clamav in daemon mode +echo "0" > /tmp/clam_started && freshclam && service clamav-daemon start && echo "1" > /tmp/clam_started -# spin up gunicorn -gunicorn cometa_pj.wsgi:application --workers=${WORKERS:-$GUNI_WORKERS} --threads=${THREADS:-2} --worker-class=gthread --bind 0.0.0.0:8000 +# +# in DEVMODE Start Django server +# +echo "ENVIRONMENT: $ENVIRONMENT" +if [ "$ENVIRONMENT" = "dev" ]; then + echo "###################################################" + echo "# Running in DEV mode #" + echo "###################################################" + echo "Devmode was requested ... starting python manage.py runserver" + python manage.py runserver 0.0.0.0:8000 +fi + +# +# In Production mode start gunicorn +# +if [ "$ENVIRONMENT" != "dev" ]; then + # get processor cores + CPUCORES=`getconf _NPROCESSORS_ONLN` + # calculate workers + # GUNI_WORKERS=$((($CPUCORES*2+1))) + GUNI_WORKERS=$((($CPUCORES+1))) + + # spin up gunicorn + echo "########################################################" + echo "# Running in production mode - starting gunicorn #" + echo "# to enable dev mode, use parameter '-dev' on start.sh #" + echo "########################################################" + gunicorn \ + cometa_pj.wsgi:application \ + --workers=${WORKERS:-$GUNI_WORKERS} \ + --threads=${THREADS:-2} \ + --worker-class=gthread \ + --bind 0.0.0.0:8000 \ + --access-logfile=- \ + --access-logformat='%(t)s %({proxy-user}i)s %({x-forwarded-for}i)s "%(r)s" %(s)s %(b)s "%(f)s" "%(a)s"' +fi diff --git a/backend/src/templates/pdf/generatePDF.html b/backend/src/templates/pdf/generatePDF.html index 4f1ecbcb..63a128c2 100644 --- a/backend/src/templates/pdf/generatePDF.html +++ b/backend/src/templates/pdf/generatePDF.html @@ -95,7 +95,8 @@ .files-step { margin-left: 20px; } - .files { + .files, + .customErrorMessage { padding-left: 20px; font-size: 13px; } @@ -121,7 +122,7 @@

App: {{ featureinfo.app_name }} | Environment: {{ featureinfo.environment_name}} | Test Name: {{featureinfo.feature_name }} [{{featureinfo.feature_id_id}}] | Browser: {{ browserinfo }}

- Executed on {{ date }}
+ Executed on {{ date }} UTC

@@ -141,6 +142,11 @@ {{forloop.counter}}. {{ step.step_name }} + {% if step.success == False and step.error is not None %} +
+   {{ step.error }} +
+ {% endif %}
{% for file in step.files %} @@ -162,7 +168,7 @@ {% if step.pixel_diff == 0 %} - {% else %} - {{ step.pixel_diff }} + {{ step.pixel_diff | FormatNumber }} {% endif %} @@ -175,18 +181,22 @@ -
- -
{% for key, values in screenshots_array.items %} {% if values.4.photo1 is not None %} +
+ +

    {{values.7.count}} - Step Name: {{ values.0.step_name }}

{% if values.4.photo1 is not None and values.5.photo2 is None and values.6.photo3 is None %}

Actual Screenshot

+ {% if values.8.error is not None %} + Error found during the step execution: + {{ values.8.error }} + {% endif %} {% endif %} {% if values.4.photo1 is not None and values.5.photo2 is not None or values.6.photo3 is not None %} diff --git a/backend/ws-server/app/index.js b/backend/ws-server/app/index.js index 41802583..0a00e986 100644 --- a/backend/ws-server/app/index.js +++ b/backend/ws-server/app/index.js @@ -60,6 +60,36 @@ function retrieveMessages(featureId) { /* Setup Logger */ app.use(morgan(':method :url :status :res[content-length] - :response-time ms')); +/* WS Endpoint: Feature has been queued (environment, browserstack, etc) */ +app.get('/feature/:feature_id/queued', (req, res) => { + /** + * Required GET params: + * - feature_id: ID of the Feature + * Required POST params: + * - browser_info: BrowserstackBrowser --> Browser object containing browser_name, browser_version, os, etc + * - feature_result_id: number --> Feature Result ID of the running test + * - feature_info: Feature --> Feature object with info of the running feature + * - run_id: number --> ID of run containing all browser results + * - datetime: string --> Time in which the step started, format: DD-MM-YYYY HH:mm:ss + */ + setRunningStatus(+req.params.feature_id, true); + const payload = { + type: '[WebSockets] Feature Queued', + feature_id: +req.params.feature_id, + run_id: +req.body.run_id, + feature_result_id: +req.body.feature_result_id, + browser_info: JSON.parse(req.body.browser_info), + datetime: req.body.datetime, + pid: parseInt(req.body.pid), + user_id: +req.body.user_id + } + io.emit('message', payload) + // Add message to history + const messages = constructRun(+req.params.feature_id, +req.body.run_id) + messages.push(payload); + res.status(200).json({ success: true }) +}) + /* WS Endpoint: Feature has been begin initializing (environment, browserstack, etc) */ app.get('/feature/:feature_id/initializing', (req, res) => { /** @@ -441,6 +471,18 @@ app.post('/sendAction', (req, res) => { totalSended++; }); break; + case '[Files] Processing started': + case '[Files] Virus scan started': + // filter all clients which are in updated/removed department or have "view_departments_panel" permission + clients_with_in_the_department = Object.keys(clients).filter(client => { + return clients[client].departments.some(dept => dept.department_id == req.body.department_id) || clients[client].user_permissions.view_departments_panel == true; + }); + // send data to only users with in the department + clients_with_in_the_department.forEach(client => { + io.to(client).emit('message', req.body); + totalSended++; + }); + break; case '[Variables] Get All': // filter all clients which are in the same department clients_with_in_the_department = Object.keys(clients).filter(client => { @@ -535,10 +577,20 @@ app.post('/sendActionExample', (req, res) => { }) io.on('connection', function(socket){ - console.log('A client has connected') try { - clients[socket.id] = JSON.parse(socket.handshake.query.user) + const user = socket.handshake.auth.user; + if (user.user_id != undefined && user.email != undefined) { + clients[socket.id] = user + console.log(`[${user.email} (${user.user_id})] has connected successfully.`) + } else { + throw Error("Missing user data. Connection failed.") + } } catch (err) { + socket.emit("error", { + "success": false, + "message": err.message + }) + socket.disconnect(true) console.log('Connection', 'Couldn\'t parse user info.'); } // For testing purposes diff --git a/backend/ws-server/package.json b/backend/ws-server/package.json index 77a90c8d..d8b2fb3a 100644 --- a/backend/ws-server/package.json +++ b/backend/ws-server/package.json @@ -8,15 +8,15 @@ "crypto": "^1.0.1", "express": "^4.17.1", "morgan": "^1.10.0", - "nodemon": "^2.0.8", + "pm2": "^5.2.2", "socket.io": "^4.1.2" }, "devDependencies": { - "socket.io": "^4.1.2", - "nodemon": "^2.0.8" + "socket.io": "^4.1.2" }, "scripts": { - "start": "npm install && npm install -g nodemon && nodemon app/index.js" + "start": "npm install && pm2 start --no-daemon app/index.js", + "test": "npm install && pm2 start --no-daemon app/index.js --watch --ignore-watch=\"node_modules\"" }, "author": "", "license": "ISC" diff --git a/cometa.sh b/cometa.sh new file mode 100755 index 00000000..0f8f1f1d --- /dev/null +++ b/cometa.sh @@ -0,0 +1,206 @@ +#!/bin/bash + +# ################################################## +# First time Cometa Setup Script +# ################################################## +# +# Changelog: +# 2022-10-04 ASO changed sed logic and checking if docker is installed and running. +# 2022-10-03 ASO changing data mount point based on the parameter. +# 2022-09-08 RRO first version +# +VERSION="2022-09-08" + +# +# source our nice logger +# +HELPERS="helpers" +# source logger function if not sourced already +test `command -v log_wfr` || source "${HELPERS}/logger.sh" || exit + +info "This is $0 version ${VERSION} running for your convinience" + +######################################## +# +# RETRY +# Retry for curl or any other command +# $1 {curl command} +# $2 {retries} +# $3 {wait retry} +# +# +######################################## +function retry() { + retries=${2:-60} + wait_retry=${3:-10} + i=0; + exitCode=0; + while [[ "$i" -lt "$retries" ]]; do + eval $1; + exitCode=$? + if [[ "$exitCode" -ne 0 ]]; then + sleep $wait_retry + i=$((i+1)) + else + break + fi + done + return $exitCode +} + +# +# Check if docker is working +# +function checkDocker() { + # check if docker is installed + if [[ ! $(command -v docker) ]]; then + error "Docker not installed, please install Docker." + exit 5; + fi + + # check if docker is running. + if [[ ! $(docker ps -a) ]]; then + error "Either docker daemon is not running or user <${USER}> does not have permissions to use docker." + info "Please start the docker service or ask your server administrator to add user <${USER}> to 'docker' group." + exit 5; + fi +} + +# +# Switches /data to ./data depending on the parameters +# +function switchDataMountPoint() { + # check if first parameter contains root + if [[ "$1" == "root" ]]; then + # change ./data => /data + sed -i_template "s#- \./data#- /data#g" docker-compose.yml + else + # change /data => ./data + sed -i_template "s#- /data#- \./data#g" docker-compose.yml + fi +} + +function get_cometa_up_and_running() { + +# +# Switch mount point based on MOUNTPOINT +# +switchDataMountPoint "${MOUNTPOINT:-local}" + +# +# Create directory schedules +# +if [ ! -d backend/behave/schedules ]; then + mkdir -p backend/behave/schedules && info "Created schedules directory" +fi + +# +# If crontab is a directory ... remove it +# +if [ -d backend/behave/schedules/crontab ]; then + rm -rq backend/behave/schedules/crontab && info "Removed crontab directory" +fi + + +# +# Touch crontab schedules +# +if [ ! -f backend/behave/schedules/crontab ]; then + touch backend/behave/schedules/crontab && info "Created crontab file" +fi + +# +# Touch browsers.json +# +if [ ! -f backend/selenoid/browsers.json ] || [ $(cat backend/selenoid/browsers.json | grep . | wc -l) -eq 0 ]; then + RUNSELENOIDSCRIPT=true + echo "{}" > backend/selenoid/browsers.json && info "Created browsers.json file" +fi + +# +# Replace in docker-compose.yml with "local" +# +sed -i_template "s||local|g" docker-compose.yml && info "Replaced in docker-compose.yml with local" + +# +# Replace in docker-compose.yml with "80" +# +sed -i_template "s||80|g" docker-compose.yml && info "Replaced in docker-compose.yml with 80" + +# +# Check client id has been replaced +# +if grep -Rq "COMETA" "front/apache-conf/metadata/accounts.google.com.client" ; then + warning "Found default string in accounts.google.com.client file - you must replace this before going forward." + read -n 1 -s -r -p "Press any key to continue" + if grep -Rq "GITCLIENTID" "front/apache-conf/metadata/git.amvara.de.client" ; then + warning "Found default string in git.amvara.de.client file - you must replace this before going forward." + warning "If neither Google nor Gitlab is configured, you will not be able to login." + warning "Going forward with installation does not make sense, until SSO is configured. Exiting." + warning "Goto git.amvara.de, create an account. Goto Settings, Applications, add new Application and retrieve your access token." + exit + else + info "The default string in git.amvara.de.client was replaced with something else - hopefully your Gitlab oAuth client credentials"; + fi +else + info "The default string in accounts.google.com.client was replaced with something else - hopefully your google oAuth client credentials"; +fi + + +# +# Bring up the system +# +info "Starting containers" +docker-compose up -d && info "Started docker ... now waiting for container to come alive " || warn "docker-compose command finished with error" + +# +# How to wait for System ready? +# + + +# +# Check selenoid browsers +# +if [ "${RUNSELENOIDSCRIPT:-false}" = "true" ]; then + info "Downloading latest browser versions" + ./backend/selenoid/deploy_selenoid.sh -n 3 || warning "Something went wrong getting the latests browsers for the system" +fi + +# +# parse browsers and actions +# + +# check health status +# Max retries +MAX_RETRIES=60 +# wait between retries +WAIT_RETRY=10 +# Total timeout +TOTAL_TIMEOUT=$(($MAX_RETRIES*$WAIT_RETRY)) + +log_wfr "Waiting for parseBrowsers" +retry "docker exec -it cometa_django curl --fail http://localhost:8000/parseBrowsers/ -o /dev/null -s " && log_res "[done]" || { log_res "[failed]"; warning "Waited for ${TOTAL_TIMEOUT} seconds, docker-container django is not running"; } + +log_wfr "Waiting for parseActions" +retry "docker exec -it cometa_django curl --fail http://localhost:8000/parseActions/ -o /dev/null -s " && log_res "[done]" || { log_res "[failed]"; warning "Waited for ${TOTAL_TIMEOUT} seconds, docker-container django is not running"; } + +log_wfr "Waiting for frontend to compile angular typescript into executable code " +retry "curl --fail --insecure https://localhost/ -o /dev/null -s -L" && log_res "[done] Feeling happy " || { log_res "[failed]"; warning "Waited for ${TOTAL_TIMEOUT} seconds, docker-container front is not running"; } + +} # end of function get_cometA_up_and_running + +while [[ $# -gt 0 ]] +do + case "$1" in + --root-mount-point) + MOUNTPOINT="root" + shift + ;; + esac +done + +checkDocker +get_cometa_up_and_running + +info "The test automation platform is ready to rumble at https://localhost/" +info "Thank you for using the easy peasy setup script." diff --git a/docker-compose-dev.yml b/docker-compose-dev.yml index 06d8024b..e41bff41 100755 --- a/docker-compose-dev.yml +++ b/docker-compose-dev.yml @@ -1,5 +1,11 @@ version: '3' +x-browser-limit: &browser-limit + # get browser limit from environment variables + # if not specified default to 6 + # Idealy ( - 2 ) + BROWSER_LIMIT: ${BROWSER_LIMIT:-6} + services: db: container_name: cometa_postgres @@ -18,7 +24,7 @@ services: image: python:3.9 logging: driver: json-file - command: sh start.sh + command: bash start.sh -dev volumes: - /data/cometa/screenshots:/code/behave/screenshots - /data/cometa/videos:/code/behave/videos @@ -28,6 +34,7 @@ services: working_dir: /opt/code environment: - PYTHONUNBUFFERED=1 + - ENVIRONMENT=dev ports: - "8000:8000" depends_on: @@ -43,8 +50,11 @@ services: image: python:3.9 logging: driver: json-file - expose: - - "8001" + ports: + - "8001:8001" + depends_on: + redis: + condition: service_healthy volumes: - /data/cometa/screenshots:/opt/code/screenshots - /data/cometa/videos:/opt/code/videos @@ -54,7 +64,8 @@ services: command: "/code/docker/build/behave/entry.sh" working_dir: /opt/code/behave_django environment: - - PYTHONUNBUFFERED=1 + PYTHONUNBUFFERED: 1 + <<: *browser-limit networks: - testing restart: always @@ -66,23 +77,20 @@ services: logging: driver: json-file environment: - - NODE_ENV=production + - NODE_ENV=development - NPM_CONFIG_LOGLEVEL=info volumes: - ./backend/ws-server:/home/node/app:rw ports: - "3001:3001" - links: - - behave - - django - command: "npm run-script start" + command: "npm run-script test" networks: - testing restart: always selenoid: # Please use ./selenoid/deploy_selenoid.sh to create necessary files of Selenoid - image: aerokube/selenoid:1.10.4 + image: aerokube/selenoid:1.10.12 container_name: cometa_selenoid logging: driver: json-file @@ -93,12 +101,19 @@ services: - ./backend/selenoid/:/etc/selenoid/:ro - /data/cometa/videos:/opt/selenoid/video environment: - - TZ=Europe/Berlin - - OVERRIDE_VIDEO_OUTPUT_DIR=/data/cometa/videos + TZ: Europe/Berlin + OVERRIDE_VIDEO_OUTPUT_DIR: /data/cometa/videos + <<: *browser-limit networks: - testing restart: always - command: ["-conf", "/etc/selenoid/browsers.json", "-video-output-dir", "/opt/selenoid/video", "-log-output-dir", "/opt/selenoid/logs", "-container-network", "cometa_testing"] + entrypoint: sh -c "/usr/bin/selenoid \ + -listen :4444 \ + -conf /etc/selenoid/browsers.json \ + -video-output-dir /opt/selenoid/video \ + -log-output-dir /opt/selenoid/logs \ + -container-network cometa_testing \ + -limit $${BROWSER_LIMIT}" novnc: image: cometa/novnc:1.0 @@ -108,7 +123,7 @@ services: restart: always apache: - image: "httpd:latest" + image: "httpd:2.4.48" container_name: cometa_front volumes: - ./front/:/code @@ -116,8 +131,6 @@ services: - ./front/apache-conf/openidc.conf_local:/usr/local/apache2/conf/openidc.conf - ./front/apache-conf/paths.conf:/usr/local/apache2/conf/paths.conf - ./front/apache-conf/mod_auth_openidc.so:/usr/local/apache2/modules/mod_auth_openidc.so - - ./front/apache-conf/apache-self-signed.key:/etc/ssl/certs/apache-self-signed.key - - ./front/apache-conf/apache-self-signed.crt:/etc/ssl/certs/apache-self-signed.crt - /data/cometa/screenshots:/screenshots working_dir: /code ports: @@ -127,7 +140,20 @@ services: networks: - testing restart: always + redis: + image: redis:7.2.0-alpine + container_name: cometa_redis + volumes: + - redis_data:/data + networks: + - testing + healthcheck: + test: [ "CMD", "redis-cli", "--raw", "incr", "ping" ] + restart: always networks: testing: driver: "bridge" + +volumes: + redis_data: diff --git a/docker-compose.yml b/docker-compose.yml index ff436b67..f5b27f4a 100755 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,5 +1,11 @@ version: '3' +x-browser-limit: &browser-limit + # get browser limit from environment variables + # if not specified default to 6 + # Idealy ( - 2 ) + BROWSER_LIMIT: ${BROWSER_LIMIT:-6} + services: db: container_name: cometa_postgres @@ -18,10 +24,12 @@ services: image: python:3.9 logging: driver: json-file - command: sh start.sh + command: bash start.sh volumes: - /data/cometa/screenshots:/code/behave/screenshots - /data/cometa/videos:/code/behave/videos + - /data/cometa/pdf:/code/behave/pdf + - /data/cometa/downloads:/code/behave/downloads - ./backend:/code - ./backend/src:/opt/code:rw - "./backend/crontabs/cometa_django_crontab:/etc/cron.d/crontab" @@ -45,16 +53,22 @@ services: driver: json-file expose: - "8001" + depends_on: + redis: + condition: service_healthy volumes: - /data/cometa/screenshots:/opt/code/screenshots - /data/cometa/videos:/opt/code/videos + - /data/cometa/pdf:/code/behave/pdf + - /data/cometa/downloads:/code/behave/downloads - ./backend/behave:/opt/code:rw - ./backend:/code - "./backend/behave/schedules/crontab:/etc/cron.d/crontab" command: "/code/docker/build/behave/entry.sh" working_dir: /opt/code/behave_django environment: - - PYTHONUNBUFFERED=1 + PYTHONUNBUFFERED: 1 + <<: *browser-limit networks: - testing restart: always @@ -72,9 +86,6 @@ services: - ./backend/ws-server:/home/node/app:rw ports: - "3001:3001" - links: - - behave - - django command: "npm run-script start" networks: - testing @@ -82,7 +93,7 @@ services: selenoid: # Please use ./selenoid/deploy_selenoid.sh to create necessary files of Selenoid - image: aerokube/selenoid:1.10.4 + image: aerokube/selenoid:1.10.12 container_name: cometa_selenoid logging: driver: json-file @@ -93,12 +104,19 @@ services: - ./backend/selenoid/:/etc/selenoid/:ro - /data/cometa/videos:/opt/selenoid/video environment: - - TZ=Europe/Berlin - - OVERRIDE_VIDEO_OUTPUT_DIR=/data/cometa/videos + TZ: Europe/Berlin + OVERRIDE_VIDEO_OUTPUT_DIR: /data/cometa/videos + <<: *browser-limit networks: - testing restart: always - command: ["-conf", "/etc/selenoid/browsers.json", "-video-output-dir", "/opt/selenoid/video", "-log-output-dir", "/opt/selenoid/logs", "-container-network", "cometa_testing"] + entrypoint: sh -c "/usr/bin/selenoid \ + -listen :4444 \ + -conf /etc/selenoid/browsers.json \ + -video-output-dir /opt/selenoid/video \ + -log-output-dir /opt/selenoid/logs \ + -container-network cometa_testing \ + -limit $${BROWSER_LIMIT}" novnc: image: cometa/novnc:1.0 @@ -108,7 +126,7 @@ services: restart: always apache: - image: "httpd:latest" + image: "httpd:2.4.48" container_name: cometa_front volumes: - ./front/:/code @@ -116,10 +134,9 @@ services: - ./front/apache-conf/openidc.conf_:/usr/local/apache2/conf/openidc.conf - ./front/apache-conf/paths.conf:/usr/local/apache2/conf/paths.conf - ./front/apache-conf/mod_auth_openidc.so:/usr/local/apache2/modules/mod_auth_openidc.so - - ./front/apache-conf/apache-self-signed.key:/etc/ssl/certs/apache-self-signed.key - - ./front/apache-conf/apache-self-signed.crt:/etc/ssl/certs/apache-self-signed.crt - /data/cometa/screenshots:/screenshots working_dir: /code + privileged: true ports: - ":80" - "443:443" @@ -129,6 +146,20 @@ services: - testing restart: always + redis: + image: redis:7.2.0-alpine + container_name: cometa_redis + volumes: + - redis_data:/data + networks: + - testing + healthcheck: + test: [ "CMD", "redis-cli", "--raw", "incr", "ping" ] + restart: always + networks: testing: driver: "bridge" + +volumes: + redis_data: \ No newline at end of file diff --git a/front/.gitignore b/front/.gitignore index 8dce39f4..29f522dd 100755 --- a/front/.gitignore +++ b/front/.gitignore @@ -27,6 +27,7 @@ node_modules/ !.vscode/extensions.json # misc +/.angular/cache /.sass-cache /connect.lock /coverage @@ -38,7 +39,7 @@ testem.log core* # npm -package-lock.json +# package-lock.json report.* # e2e @@ -52,6 +53,3 @@ Thumbs.db # logs output.log - -# local changes -apache-conf/ diff --git a/front/README.md b/front/README.md index 58552382..bc1e80f1 100755 --- a/front/README.md +++ b/front/README.md @@ -8,9 +8,9 @@ Run `docker-compose -f docker-compose-dev-111.yml up -d --force-recreate` to aut You might need to remove container, if installation is unclean or broken. Execute `docker-compose -f docker-compose-dev-111.yml rm --force` to clean up the docker. -Alternatively run manually `ng serve --host 0.0.0.0 --port 4200 --disable-host-check` for a dev server. Navigate to `http://localhost:4200/`. The app will automatically reload if you change any of the source files. Going directly to server:4200 will most likely produce CORS-errors, as backend is running on another port. +Alternatively run manually `npx ng serve --host 0.0.0.0 --port 4200 --disable-host-check` for a dev server. Navigate to `http://localhost:4200/`. The app will automatically reload if you change any of the source files. Going directly to server:4200 will most likely produce CORS-errors, as backend is running on another port. -The apache `config openidc.conf_local` has mod_rewrite configured to access URL at "/debug" and reverse proxy URLs to server:4200 without problems regarding CORS-header. +The apache `config openidc.conf_local` has mod_rewrite configured to access URL at "/debug" and reverse proxy URLs to server:4200 without problems regarding CORS-header. Use `https://localhost/debug/#/new` for running over SSL to the debug URL of `ng serve`. ## Code scaffolding @@ -24,10 +24,6 @@ Run `ng build` to build the project. The build artifacts will be stored in the ` Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io). -## Running end-to-end tests - -Run `ng e2e` to execute the end-to-end tests via [Protractor](http://www.protractortest.org/). - ## Further help To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI README](https://github.com/angular/angular-cli/blob/master/README.md). diff --git a/front/angular.json b/front/angular.json index 685f21f3..2267e312 100755 --- a/front/angular.json +++ b/front/angular.json @@ -24,7 +24,6 @@ "src/steps_testing.html" ], "styles": [ - "node_modules/c3/c3.min.css", "src/styles.scss" ], "stylePreprocessorOptions": { @@ -36,7 +35,12 @@ "allowedCommonJsDependencies": [ "socket.io-client", "highcharts", - "compare-versions" + "compare-versions", + "photoviewer", + "lodash.keyby", + "nested-property", + "cron-parser", + "memo-decorator" ] }, "configurations": { @@ -44,7 +48,7 @@ "budgets": [ { "type": "anyComponentStyle", - "maximumWarning": "6kb" + "maximumWarning": "10kb" } ], "aot": true, @@ -75,10 +79,8 @@ "serve": { "builder": "@angular-devkit/build-angular:dev-server", "options": { - "baseHref": "/debug/", "host": "0.0.0.0", "disableHostCheck": true, - "proxyConfig": "/code/proxy-config.json", "browserTarget": "cometa:build" }, "configurations": { @@ -106,7 +108,6 @@ "tsConfig": "src/tsconfig.spec.json", "scripts": [], "styles": [ - "node_modules/c3/c3.min.css", "src/styles.scss" ], "assets": [ @@ -114,18 +115,6 @@ "src/manifest.json" ] } - }, - "lint": { - "builder": "@angular-devkit/build-angular:tslint", - "options": { - "tsConfig": [ - "src/tsconfig.app.json", - "src/tsconfig.spec.json" - ], - "exclude": [ - "**/node_modules/**" - ] - } } } }, @@ -135,27 +124,10 @@ "projectType": "application", "architect": { "e2e": { - "builder": "@angular-devkit/build-angular:protractor", - "options": { - "protractorConfig": "./protractor.conf.js", - "devServerTarget": "cometa:serve" - } - }, - "lint": { - "builder": "@angular-devkit/build-angular:tslint", - "options": { - "tsConfig": [ - "e2e/tsconfig.e2e.json" - ], - "exclude": [ - "**/node_modules/**" - ] - } } } } }, - "defaultProject": "cometa", "schematics": { "@schematics/angular:component": { "prefix": "cometa", @@ -168,4 +140,4 @@ "cli": { "analytics": "5f48c63b-3590-4706-84e3-7c79a0c31f0a" } -} \ No newline at end of file +} diff --git a/front/apache-conf/httpd.conf b/front/apache-conf/httpd.conf new file mode 100755 index 00000000..f99738de --- /dev/null +++ b/front/apache-conf/httpd.conf @@ -0,0 +1,590 @@ +# +# This is the main Apache HTTP server configuration file. It contains the +# configuration directives that give the server its instructions. +# See for detailed information. +# In particular, see +# +# for a discussion of each configuration directive. +# +# Do NOT simply read the instructions in here without understanding +# what they do. They're here only as hints or reminders. If you are unsure +# consult the online docs. You have been warned. +# +# Configuration and logfile names: If the filenames you specify for many +# of the server's control files begin with "/" (or "drive:/" for Win32), the +# server will use that explicit path. If the filenames do *not* begin +# with "/", the value of ServerRoot is prepended -- so "logs/access_log" +# with ServerRoot set to "/usr/local/apache2" will be interpreted by the +# server as "/usr/local/apache2/logs/access_log", whereas "/logs/access_log" +# will be interpreted as '/logs/access_log'. + +# +# ServerRoot: The top of the directory tree under which the server's +# configuration, error, and log files are kept. +# +# Do not add a slash at the end of the directory path. If you point +# ServerRoot at a non-local disk, be sure to specify a local disk on the +# Mutex directive, if file-based mutexes are used. If you wish to share the +# same ServerRoot for multiple httpd daemons, you will need to change at +# least PidFile. +# +ServerRoot "/usr/local/apache2" + +# +# Mutex: Allows you to set the mutex mechanism and mutex file directory +# for individual mutexes, or change the global defaults +# +# Uncomment and change the directory if mutexes are file-based and the default +# mutex file directory is not on a local disk or is not appropriate for some +# other reason. +# +# Mutex default:logs + +# +# Listen: Allows you to bind Apache to specific IP addresses and/or +# ports, instead of the default. See also the +# directive. +# +# Change this to Listen on specific IP addresses as shown below to +# prevent Apache from glomming onto all bound IP addresses. +# +#Listen 12.34.56.78:80 +# Listen 80 +# Listen 443 + +# +# Dynamic Shared Object (DSO) Support +# +# To be able to use the functionality of a module which was built as a DSO you +# have to place corresponding `LoadModule' lines at this location so the +# directives contained in it are actually available _before_ they are used. +# Statically compiled modules (those listed by `httpd -l') do not need +# to be loaded here. +# +# Example: +# LoadModule foo_module modules/mod_foo.so +# +LoadModule mpm_event_module modules/mod_mpm_event.so +#LoadModule mpm_prefork_module modules/mod_mpm_prefork.so +#LoadModule mpm_worker_module modules/mod_mpm_worker.so +LoadModule authn_file_module modules/mod_authn_file.so +#LoadModule authn_dbm_module modules/mod_authn_dbm.so +#LoadModule authn_anon_module modules/mod_authn_anon.so +#LoadModule authn_dbd_module modules/mod_authn_dbd.so +#LoadModule authn_socache_module modules/mod_authn_socache.so +LoadModule authn_core_module modules/mod_authn_core.so +LoadModule authz_host_module modules/mod_authz_host.so +LoadModule authz_groupfile_module modules/mod_authz_groupfile.so +LoadModule authz_user_module modules/mod_authz_user.so +#LoadModule authz_dbm_module modules/mod_authz_dbm.so +#LoadModule authz_owner_module modules/mod_authz_owner.so +#LoadModule authz_dbd_module modules/mod_authz_dbd.so +LoadModule authz_core_module modules/mod_authz_core.so +#LoadModule authnz_ldap_module modules/mod_authnz_ldap.so +#LoadModule authnz_fcgi_module modules/mod_authnz_fcgi.so +LoadModule access_compat_module modules/mod_access_compat.so +LoadModule auth_basic_module modules/mod_auth_basic.so +#LoadModule auth_form_module modules/mod_auth_form.so +#LoadModule auth_digest_module modules/mod_auth_digest.so +#LoadModule allowmethods_module modules/mod_allowmethods.so +#LoadModule isapi_module modules/mod_isapi.so +#LoadModule file_cache_module modules/mod_file_cache.so +#LoadModule cache_module modules/mod_cache.so +#LoadModule cache_disk_module modules/mod_cache_disk.so +#LoadModule cache_socache_module modules/mod_cache_socache.so +#LoadModule socache_shmcb_module modules/mod_socache_shmcb.so +#LoadModule socache_dbm_module modules/mod_socache_dbm.so +#LoadModule socache_memcache_module modules/mod_socache_memcache.so +#LoadModule socache_redis_module modules/mod_socache_redis.so +#LoadModule watchdog_module modules/mod_watchdog.so +#LoadModule macro_module modules/mod_macro.so +#LoadModule dbd_module modules/mod_dbd.so +#LoadModule bucketeer_module modules/mod_bucketeer.so +#LoadModule dumpio_module modules/mod_dumpio.so +#LoadModule echo_module modules/mod_echo.so +#LoadModule example_hooks_module modules/mod_example_hooks.so +#LoadModule case_filter_module modules/mod_case_filter.so +#LoadModule case_filter_in_module modules/mod_case_filter_in.so +#LoadModule example_ipc_module modules/mod_example_ipc.so +#LoadModule buffer_module modules/mod_buffer.so +#LoadModule data_module modules/mod_data.so +#LoadModule ratelimit_module modules/mod_ratelimit.so +LoadModule reqtimeout_module modules/mod_reqtimeout.so +#LoadModule ext_filter_module modules/mod_ext_filter.so +#LoadModule request_module modules/mod_request.so +#LoadModule include_module modules/mod_include.so +LoadModule filter_module modules/mod_filter.so +#LoadModule reflector_module modules/mod_reflector.so +#LoadModule substitute_module modules/mod_substitute.so +#LoadModule sed_module modules/mod_sed.so +#LoadModule charset_lite_module modules/mod_charset_lite.so +LoadModule deflate_module modules/mod_deflate.so +#LoadModule xml2enc_module modules/mod_xml2enc.so +#LoadModule proxy_html_module modules/mod_proxy_html.so +#LoadModule brotli_module modules/mod_brotli.so +LoadModule mime_module modules/mod_mime.so +#LoadModule ldap_module modules/mod_ldap.so +LoadModule log_config_module modules/mod_log_config.so +#LoadModule log_debug_module modules/mod_log_debug.so +#LoadModule log_forensic_module modules/mod_log_forensic.so +#LoadModule logio_module modules/mod_logio.so +#LoadModule lua_module modules/mod_lua.so +LoadModule env_module modules/mod_env.so +#LoadModule mime_magic_module modules/mod_mime_magic.so +#LoadModule cern_meta_module modules/mod_cern_meta.so +#LoadModule expires_module modules/mod_expires.so +LoadModule headers_module modules/mod_headers.so +#LoadModule ident_module modules/mod_ident.so +#LoadModule usertrack_module modules/mod_usertrack.so +#LoadModule unique_id_module modules/mod_unique_id.so +LoadModule setenvif_module modules/mod_setenvif.so +LoadModule version_module modules/mod_version.so +#LoadModule remoteip_module modules/mod_remoteip.so +LoadModule proxy_module modules/mod_proxy.so +#LoadModule proxy_connect_module modules/mod_proxy_connect.so +#LoadModule proxy_ftp_module modules/mod_proxy_ftp.so +LoadModule proxy_http_module modules/mod_proxy_http.so +#LoadModule proxy_fcgi_module modules/mod_proxy_fcgi.so +#LoadModule proxy_scgi_module modules/mod_proxy_scgi.so +#LoadModule proxy_uwsgi_module modules/mod_proxy_uwsgi.so +#LoadModule proxy_fdpass_module modules/mod_proxy_fdpass.so +LoadModule proxy_wstunnel_module modules/mod_proxy_wstunnel.so +LoadModule proxy_ajp_module modules/mod_proxy_ajp.so +LoadModule proxy_balancer_module modules/mod_proxy_balancer.so +#LoadModule proxy_express_module modules/mod_proxy_express.so +#LoadModule proxy_hcheck_module modules/mod_proxy_hcheck.so +#LoadModule session_module modules/mod_session.so +#LoadModule session_cookie_module modules/mod_session_cookie.so +#LoadModule session_crypto_module modules/mod_session_crypto.so +#LoadModule session_dbd_module modules/mod_session_dbd.so +LoadModule slotmem_shm_module modules/mod_slotmem_shm.so +#LoadModule slotmem_plain_module modules/mod_slotmem_plain.so +LoadModule ssl_module modules/mod_ssl.so +#LoadModule optional_hook_export_module modules/mod_optional_hook_export.so +#LoadModule optional_hook_import_module modules/mod_optional_hook_import.so +#LoadModule optional_fn_import_module modules/mod_optional_fn_import.so +#LoadModule optional_fn_export_module modules/mod_optional_fn_export.so +#LoadModule dialup_module modules/mod_dialup.so +#LoadModule http2_module modules/mod_http2.so +#LoadModule proxy_http2_module modules/mod_proxy_http2.so +#LoadModule md_module modules/mod_md.so +LoadModule lbmethod_byrequests_module modules/mod_lbmethod_byrequests.so +#LoadModule lbmethod_bytraffic_module modules/mod_lbmethod_bytraffic.so +#LoadModule lbmethod_bybusyness_module modules/mod_lbmethod_bybusyness.so +#LoadModule lbmethod_heartbeat_module modules/mod_lbmethod_heartbeat.so +LoadModule unixd_module modules/mod_unixd.so +#LoadModule heartbeat_module modules/mod_heartbeat.so +#LoadModule heartmonitor_module modules/mod_heartmonitor.so +#LoadModule dav_module modules/mod_dav.so +LoadModule status_module modules/mod_status.so +LoadModule autoindex_module modules/mod_autoindex.so +#LoadModule asis_module modules/mod_asis.so +#LoadModule info_module modules/mod_info.so +#LoadModule suexec_module modules/mod_suexec.so + + LoadModule cgid_module modules/mod_cgid.so + + + LoadModule cgi_module modules/mod_cgi.so + +#LoadModule dav_fs_module modules/mod_dav_fs.so +#LoadModule dav_lock_module modules/mod_dav_lock.so +#LoadModule vhost_alias_module modules/mod_vhost_alias.so +#LoadModule negotiation_module modules/mod_negotiation.so +LoadModule dir_module modules/mod_dir.so +#LoadModule imagemap_module modules/mod_imagemap.so +#LoadModule actions_module modules/mod_actions.so +#LoadModule speling_module modules/mod_speling.so +#LoadModule userdir_module modules/mod_userdir.so +LoadModule alias_module modules/mod_alias.so +LoadModule rewrite_module modules/mod_rewrite.so + + +# +# If you wish httpd to run as a different user or group, you must run +# httpd as root initially and it will switch. +# +# User/Group: The name (or #number) of the user/group to run httpd as. +# It is usually good practice to create a dedicated user and group for +# running httpd, as with most system services. +# +User daemon +Group daemon + + + +# 'Main' server configuration +# +# The directives in this section set up the values used by the 'main' +# server, which responds to any requests that aren't handled by a +# definition. These values also provide defaults for +# any containers you may define later in the file. +# +# All of these directives may appear inside containers, +# in which case these default settings will be overridden for the +# virtual host being defined. +# + +# +# ServerAdmin: Your address, where problems with the server should be +# e-mailed. This address appears on some server-generated pages, such +# as error documents. e.g. admin@your-domain.com +# +ServerAdmin you@example.com + +# +# ServerName gives the name and port that the server uses to identify itself. +# This can often be determined automatically, but we recommend you specify +# it explicitly to prevent problems during startup. +# +# If your host doesn't have a registered DNS name, enter its IP address here. +# +#ServerName www.example.com:80 + +# +# Deny access to the entirety of your server's filesystem. You must +# explicitly permit access to web content directories in other +# blocks below. +# + + AllowOverride none + Require all denied + + +# +# Note that from this point forward you must specifically allow +# particular features to be enabled - so if something's not working as +# you might expect, make sure that you have specifically enabled it +# below. +# + +# +# DocumentRoot: The directory out of which you will serve your +# documents. By default, all requests are taken from this directory, but +# symbolic links and aliases may be used to point to other locations. +# +DocumentRoot "/usr/local/apache2/htdocs" + + # + # Possible values for the Options directive are "None", "All", + # or any combination of: + # Indexes Includes FollowSymLinks SymLinksifOwnerMatch ExecCGI MultiViews + # + # Note that "MultiViews" must be named *explicitly* --- "Options All" + # doesn't give it to you. + # + # The Options directive is both complicated and important. Please see + # http://httpd.apache.org/docs/2.4/mod/core.html#options + # for more information. + # + Options Indexes FollowSymLinks + + # + # AllowOverride controls what directives may be placed in .htaccess files. + # It can be "All", "None", or any combination of the keywords: + # AllowOverride FileInfo AuthConfig Limit + # + AllowOverride None + + # + # Controls who can get stuff from this server. + # + Require all granted + + +# +# DirectoryIndex: sets the file that Apache will serve if a directory +# is requested. +# + + DirectoryIndex index.html + + +# +# The following lines prevent .htaccess and .htpasswd files from being +# viewed by Web clients. +# + + Require all denied + + +# +# ErrorLog: The location of the error log file. +# If you do not specify an ErrorLog directive within a +# container, error messages relating to that virtual host will be +# logged here. If you *do* define an error logfile for a +# container, that host's errors will be logged there and not here. +# +ErrorLog /proc/self/fd/2 + +# +# LogLevel: Control the number of messages logged to the error_log. +# Possible values include: debug, info, notice, warn, error, crit, +# alert, emerg. +# +LogLevel warn + + + # + # The following directives define some format nicknames for use with + # a CustomLog directive (see below). + # + LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"" combined + LogFormat "%h %l %u %t \"%r\" %>s %b" common + + + # You need to enable mod_logio.c to use %I and %O + LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\" %I %O" combinedio + + + # + # The location and format of the access logfile (Common Logfile Format). + # If you do not define any access logfiles within a + # container, they will be logged here. Contrariwise, if you *do* + # define per- access logfiles, transactions will be + # logged therein and *not* in this file. + # + # CustomLog /proc/self/fd/1 common + + CustomLog "|$/usr/bin/tee -a logs/access.log /proc/self/fd/1" common + + # + # If you prefer a logfile with access, agent, and referer information + # (Combined Logfile Format) you can use the following directive. + # + #CustomLog "logs/access_log" combined + + + + # + # Redirect: Allows you to tell clients about documents that used to + # exist in your server's namespace, but do not anymore. The client + # will make a new request for the document at its new location. + # Example: + # Redirect permanent /foo http://www.example.com/bar + + # + # Alias: Maps web paths into filesystem paths and is used to + # access content that does not live under the DocumentRoot. + # Example: + # Alias /webpath /full/filesystem/path + # + # If you include a trailing / on /webpath then the server will + # require it to be present in the URL. You will also likely + # need to provide a section to allow access to + # the filesystem path. + + # + # ScriptAlias: This controls which directories contain server scripts. + # ScriptAliases are essentially the same as Aliases, except that + # documents in the target directory are treated as applications and + # run by the server when requested rather than as documents sent to the + # client. The same rules about trailing "/" apply to ScriptAlias + # directives as to Alias. + # + ScriptAlias /cgi-bin/ "/usr/local/apache2/cgi-bin/" + + + + + # + # ScriptSock: On threaded servers, designate the path to the UNIX + # socket used to communicate with the CGI daemon of mod_cgid. + # + #Scriptsock cgisock + + +# +# "/usr/local/apache2/cgi-bin" should be changed to whatever your ScriptAliased +# CGI directory exists, if you have that configured. +# + + AllowOverride None + Options None + Require all granted + + + + # + # Avoid passing HTTP_PROXY environment to CGI's on this or any proxied + # backend servers which have lingering "httpoxy" defects. + # 'Proxy' request header is undefined by the IETF, not listed by IANA + # + RequestHeader unset Proxy early + + + + # + # TypesConfig points to the file containing the list of mappings from + # filename extension to MIME-type. + # + TypesConfig conf/mime.types + + # + # AddType allows you to add to or override the MIME configuration + # file specified in TypesConfig for specific file types. + # + #AddType application/x-gzip .tgz + # + # AddEncoding allows you to have certain browsers uncompress + # information on the fly. Note: Not all browsers support this. + # + #AddEncoding x-compress .Z + #AddEncoding x-gzip .gz .tgz + # + # If the AddEncoding directives above are commented-out, then you + # probably should define those extensions to indicate media types: + # + AddType application/x-compress .Z + AddType application/x-gzip .gz .tgz + + # + # AddHandler allows you to map certain file extensions to "handlers": + # actions unrelated to filetype. These can be either built into the server + # or added with the Action directive (see below) + # + # To use CGI scripts outside of ScriptAliased directories: + # (You will also need to add "ExecCGI" to the "Options" directive.) + # + #AddHandler cgi-script .cgi + + # For type maps (negotiated resources): + #AddHandler type-map var + + # + # Filters allow you to process content before it is sent to the client. + # + # To parse .shtml files for server-side includes (SSI): + # (You will also need to add "Includes" to the "Options" directive.) + # + #AddType text/html .shtml + #AddOutputFilter INCLUDES .shtml + + +# +# The mod_mime_magic module allows the server to use various hints from the +# contents of the file itself to determine its type. The MIMEMagicFile +# directive tells the module where the hint definitions are located. +# +#MIMEMagicFile conf/magic + +# +# Customizable error responses come in three flavors: +# 1) plain text 2) local redirects 3) external redirects +# +# Some examples: +#ErrorDocument 500 "The server made a boo boo." +#ErrorDocument 404 /missing.html +#ErrorDocument 404 "/cgi-bin/missing_handler.pl" +#ErrorDocument 402 http://www.example.com/subscription_info.html +# + +# +# MaxRanges: Maximum number of Ranges in a request before +# returning the entire resource, or one of the special +# values 'default', 'none' or 'unlimited'. +# Default setting is to accept 200 Ranges. +#MaxRanges unlimited + +# +# EnableMMAP and EnableSendfile: On systems that support it, +# memory-mapping or the sendfile syscall may be used to deliver +# files. This usually improves server performance, but must +# be turned off when serving from networked-mounted +# filesystems or if support for these functions is otherwise +# broken on your system. +# Defaults: EnableMMAP On, EnableSendfile Off +# +#EnableMMAP off +#EnableSendfile on + +# Supplemental configuration +# +# The configuration files in the conf/extra/ directory can be +# included to add extra features or to modify the default configuration of +# the server, or you may simply copy their contents here and change as +# necessary. + +# Server-pool management (MPM specific) +#Include conf/extra/httpd-mpm.conf + +# Multi-language error messages +#Include conf/extra/httpd-multilang-errordoc.conf + +# Fancy directory listings +#Include conf/extra/httpd-autoindex.conf + +# Language settings +#Include conf/extra/httpd-languages.conf + +# User home directories +#Include conf/extra/httpd-userdir.conf + +# Real-time info on requests and configuration +#Include conf/extra/httpd-info.conf + +# Virtual hosts +#Include conf/extra/httpd-vhosts.conf + +# Local access to the Apache HTTP Server Manual +#Include conf/extra/httpd-manual.conf + +# Distributed authoring and versioning (WebDAV) +#Include conf/extra/httpd-dav.conf + +# Various default settings +#Include conf/extra/httpd-default.conf + +# Configure mod_proxy_html to understand HTML4/XHTML1 + +Include conf/extra/proxy-html.conf + + +# Secure (SSL/TLS) connections +#Include conf/extra/httpd-ssl.conf +# +# Note: The following must must be present to support +# starting without SSL on platforms with no /dev/random equivalent +# but a statically compiled-in mod_ssl. +# + +SSLRandomSeed startup builtin +SSLRandomSeed connect builtin + + +ErrorLog logs/error_log + + + # Compress HTML, CSS, JavaScript, Text, XML and fonts + AddOutputFilterByType DEFLATE application/javascript + AddOutputFilterByType DEFLATE application/rss+xml + AddOutputFilterByType DEFLATE application/vnd.ms-fontobject + AddOutputFilterByType DEFLATE application/x-font + AddOutputFilterByType DEFLATE application/x-font-opentype + AddOutputFilterByType DEFLATE application/x-font-otf + AddOutputFilterByType DEFLATE application/x-font-truetype + AddOutputFilterByType DEFLATE application/x-font-ttf + AddOutputFilterByType DEFLATE application/x-javascript + AddOutputFilterByType DEFLATE application/xhtml+xml + AddOutputFilterByType DEFLATE application/xml + AddOutputFilterByType DEFLATE application/json + AddOutputFilterByType DEFLATE font/opentype + AddOutputFilterByType DEFLATE font/otf + AddOutputFilterByType DEFLATE font/ttf + AddOutputFilterByType DEFLATE image/svg+xml + AddOutputFilterByType DEFLATE image/x-icon + AddOutputFilterByType DEFLATE text/css + AddOutputFilterByType DEFLATE text/html + AddOutputFilterByType DEFLATE text/javascript + AddOutputFilterByType DEFLATE text/plain + AddOutputFilterByType DEFLATE text/xml + + # Remove browser bugs (only needed for really old browsers) + BrowserMatch ^Mozilla/4 gzip-only-text/html + BrowserMatch ^Mozilla/4\.0[678] no-gzip + BrowserMatch \bMSIE !no-gzip !gzip-only-text/html + Header append Vary User-Agent + + +Include conf/openidc.conf +Include conf/paths.conf diff --git a/front/apache-conf/metadata/accounts.google.com.client b/front/apache-conf/metadata/accounts.google.com.client new file mode 100644 index 00000000..f8381d00 --- /dev/null +++ b/front/apache-conf/metadata/accounts.google.com.client @@ -0,0 +1,4 @@ +{ + "client_id" : "@@COMETA_GOOGLE_CLIENTID@@", + "client_secret" : "@@COMETA_GOOGLE_SECRETKEY@@" +} \ No newline at end of file diff --git a/front/apache-conf/metadata/accounts.google.com.conf b/front/apache-conf/metadata/accounts.google.com.conf new file mode 100644 index 00000000..9e26dfee --- /dev/null +++ b/front/apache-conf/metadata/accounts.google.com.conf @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/front/apache-conf/metadata/accounts.google.com.provider b/front/apache-conf/metadata/accounts.google.com.provider new file mode 100644 index 00000000..8a7a5ead --- /dev/null +++ b/front/apache-conf/metadata/accounts.google.com.provider @@ -0,0 +1,58 @@ +{ + "issuer": "https://accounts.google.com", + "authorization_endpoint": "https://accounts.google.com/o/oauth2/v2/auth", + "device_authorization_endpoint": "https://oauth2.googleapis.com/device/code", + "token_endpoint": "https://oauth2.googleapis.com/token", + "userinfo_endpoint": "https://openidconnect.googleapis.com/v1/userinfo", + "revocation_endpoint": "https://oauth2.googleapis.com/revoke", + "jwks_uri": "https://www.googleapis.com/oauth2/v3/certs", + "response_types_supported": [ + "code", + "token", + "id_token", + "code token", + "code id_token", + "token id_token", + "code token id_token", + "none" + ], + "subject_types_supported": [ + "public" + ], + "id_token_signing_alg_values_supported": [ + "RS256" + ], + "scopes_supported": [ + "openid", + "email", + "profile" + ], + "token_endpoint_auth_methods_supported": [ + "client_secret_post", + "client_secret_basic" + ], + "claims_supported": [ + "aud", + "email", + "email_verified", + "exp", + "family_name", + "given_name", + "iat", + "iss", + "locale", + "name", + "picture", + "sub" + ], + "code_challenge_methods_supported": [ + "plain", + "S256" + ], + "grant_types_supported": [ + "authorization_code", + "refresh_token", + "urn:ietf:params:oauth:grant-type:device_code", + "urn:ietf:params:oauth:grant-type:jwt-bearer" + ] +} diff --git a/front/apache-conf/metadata/client001.client b/front/apache-conf/metadata/client001.client new file mode 100644 index 00000000..f2b82f79 --- /dev/null +++ b/front/apache-conf/metadata/client001.client @@ -0,0 +1,4 @@ +{ + "client_id" : "@@COMETA_CLIENT01_INT_CLIENTID@@", + "client_secret" : "@@COMETA_CLIENT01_INT_SECRETKEY@@" +} \ No newline at end of file diff --git a/front/apache-conf/metadata/client001.conf b/front/apache-conf/metadata/client001.conf new file mode 100644 index 00000000..9e26dfee --- /dev/null +++ b/front/apache-conf/metadata/client001.conf @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/front/apache-conf/metadata/client001.provider b/front/apache-conf/metadata/client001.provider new file mode 100644 index 00000000..f630e378 --- /dev/null +++ b/front/apache-conf/metadata/client001.provider @@ -0,0 +1,40 @@ +{ + "issuer": "https://@@COMETA_CLIENT01_OIDCHOST@@", + "authorization_endpoint": "https://@@COMETA_CLIENT01_OIDCHOST@@/as/authorization.oauth2", + "token_endpoint": "https://@@COMETA_CLIENT01_OIDCHOST@@/as/token.oauth2", + "introspection_endpoint": "https://@@COMETA_CLIENT01_OIDCHOST@@/as/introspect.oauth2", + "userinfo_endpoint": "https://@@COMETA_CLIENT01_OIDCHOST@@/idp/userinfo.openid", + "revocation_endpoint": "https://@@COMETA_CLIENT01_OIDCHOST@@/as/revoke_token.oauth2", + "end_session_endpoint": "https://@@COMETA_CLIENT01_OIDCHOST@@/idp/startSLO.ping", + "ping_end_session_endpoint": "https://@@COMETA_CLIENT01_OIDCHOST@@/idp/startSLO.ping", + "jwks_uri": "https://@@COMETA_CLIENT01_OIDCHOST@@/pf/JWKS", + "scopes_supported": [ + "openid" + ], + "response_types_supported": [ + "code" + ], + "grant_types_supported": [ + "refresh_token", + "authorization_code" + ], + "acr_values_supported": [ + "daimler:idp:gas:standard", + "daimler:idp:gas:strong" + ], + "subject_types_supported": [ + "public" + ], + "id_token_signing_alg_values_supported": [ + "RS256" + ], + "code_challenge_methods_supported": [ + "S256" + ], + "frontchannel_logout_supported": true, + "token_endpoint_auth_methods_supported": [ + "client_secret_basic", + "client_secret_post" + ], + "ping_revoked_sris_endpoint": "https://@@COMETA_CLIENT01_OIDCHOST@@/pf-ws/rest/sessionMgmt/revokedSris" +} diff --git a/front/apache-conf/metadata/git.amvara.de.client b/front/apache-conf/metadata/git.amvara.de.client new file mode 100644 index 00000000..a6cf0af1 --- /dev/null +++ b/front/apache-conf/metadata/git.amvara.de.client @@ -0,0 +1,5 @@ +{ + "client_id" : "@@GITCLIENTID@@", + "client_secret" : "@@GITCLIENTSECRET@@", + "response_type" : "id_token" +} \ No newline at end of file diff --git a/front/apache-conf/metadata/git.amvara.de.conf b/front/apache-conf/metadata/git.amvara.de.conf new file mode 100644 index 00000000..9e26dfee --- /dev/null +++ b/front/apache-conf/metadata/git.amvara.de.conf @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/front/apache-conf/metadata/git.amvara.de.provider b/front/apache-conf/metadata/git.amvara.de.provider new file mode 100644 index 00000000..c36245c8 --- /dev/null +++ b/front/apache-conf/metadata/git.amvara.de.provider @@ -0,0 +1,57 @@ +{ + "issuer": "https://git.amvara.de", + "authorization_endpoint": "https://git.amvara.de/oauth/authorize", + "token_endpoint": "https://git.amvara.de/oauth/token", + "revocation_endpoint": "https://git.amvara.de/oauth/revoke", + "introspection_endpoint": "https://git.amvara.de/oauth/introspect", + "userinfo_endpoint": "https://git.amvara.de/oauth/userinfo", + "jwks_uri": "https://git.amvara.de/oauth/discovery/keys", + "scopes_supported": [ + "api", + "read_user", + "read_api", + "read_repository", + "write_repository", + "sudo", + "openid", + "profile", + "email" + ], + "response_types_supported": [ + "code", + "token" + ], + "response_modes_supported": [ + "query", + "fragment" + ], + "token_endpoint_auth_methods_supported": [ + "client_secret_basic", + "client_secret_post" + ], + "subject_types_supported": [ + "public" + ], + "id_token_signing_alg_values_supported": [ + "RS256" + ], + "claim_types_supported": [ + "normal" + ], + "claims_supported": [ + "iss", + "sub", + "aud", + "exp", + "iat", + "sub_legacy", + "name", + "nickname", + "email", + "email_verified", + "website", + "profile", + "picture", + "groups" + ] +} \ No newline at end of file diff --git a/front/apache-conf/mod_auth_openidc.so b/front/apache-conf/mod_auth_openidc.so new file mode 100755 index 00000000..56022aaa Binary files /dev/null and b/front/apache-conf/mod_auth_openidc.so differ diff --git a/front/apache-conf/openidc.conf_amvara b/front/apache-conf/openidc.conf_amvara new file mode 100755 index 00000000..655c12f1 --- /dev/null +++ b/front/apache-conf/openidc.conf_amvara @@ -0,0 +1,52 @@ +# listen on port 443 with ssl certificates +Listen 80 +#error_page 404 /404.html; +Header set Access-Control-Allow-Origin "*" +# redirect server error pages to the static page /50x.html +# +ErrorDocument 500 /50x.html +ErrorDocument 502 /50x.html +ErrorDocument 503 /50x.html +ErrorDocument 504 /50x.html + + Redirect 301 / + + +######################################## +#### OIDC CONFIGURATION STARTS HERE #### +######################################## + +# set log level to debug +# LogLevel debug + +# load the openidc module +LoadModule auth_openidc_module modules/mod_auth_openidc.so + +# provide multiple providers data +OIDCMetadataDir /code/apache-conf/metadata +OIDCDiscoverURL /welcome.html + +OIDCXForwardedHeaders X-Forwarded-Proto +RequestHeader set X-Forwarded-Proto "https" early + +# OIDCRedirectURI is a vanity URL that must point to a path protected by this module but must NOT point to any content +OIDCRedirectURI /callback +OIDCCryptoPassphrase @@COMETA_CRYPTO_PASSPHRASE@@ + +# do not validate issuer this results in error if provider does not allow https +OIDCValidateIssuer Off + +OIDCScope "openid email profile" +OIDCInfoHook userinfo + +OIDCOAuthRemoteUserClaim email +OIDCRemoteUserClaim email + +OIDCAuthNHeader REMOTE_USER + +# session timeouts +OIDCSessionInactivityTimeout 86400 +OIDCSessionMaxDuration 86400 + +# SERVER SETTINGS +RequestHeader set X-Server "Amvara" diff --git a/front/apache-conf/openidc.conf_basic b/front/apache-conf/openidc.conf_basic new file mode 100755 index 00000000..ff544db4 --- /dev/null +++ b/front/apache-conf/openidc.conf_basic @@ -0,0 +1,62 @@ +# listen on port 443 with ssl certificates +Listen 80 +Listen 443 +SSLEngine on +SSLCertificateFile /etc/ssl/certs/apache-selfsigned.crt +SSLCertificateKeyFile /etc/ssl/private/apache-selfsigned.key + +#error_page 404 /404.html; +Header set Access-Control-Allow-Origin "*" +Header set X-Forwarded-Host "https" + +# redirect server error pages to the static page /50x.html +# +ErrorDocument 500 /50x.html +ErrorDocument 502 /50x.html +ErrorDocument 503 /50x.html +ErrorDocument 504 /50x.html + + Redirect 301 / + + +######################################## +#### OIDC CONFIGURATION STARTS HERE #### +######################################## + +# PORT 80 to 443 redirect +RewriteEngine on +RewriteCond %{HTTPS} !on +RewriteRule (.*) https://%{HTTP_HOST}%{REQUEST_URI} [R=301,L] + + Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains" + + +# set log level to debug +# LogLevel debug + +# load the openidc module +LoadModule auth_openidc_module modules/mod_auth_openidc.so + +OIDCProviderMetadataURL @@METADATA_URL@@ + +# application client id and client secrect +OIDCClientID @@OAUTH_CLIENTID@@ +OIDCClientSecret @@OAUTH_CLIENT_SECRET@@ + +# OIDCRedirectURI is a vanity URL that must point to a path protected by this module but must NOT point to any content +OIDCRedirectURI @@OAUTH_REDIRECT_URL@@ +OIDCCryptoPassphrase @@CRYPTO_PASSPHRASE@@ + +OIDCScope "openid email profile" +OIDCInfoHook userinfo + +OIDCOAuthRemoteUserClaim email +OIDCRemoteUserClaim email + +OIDCAuthNHeader REMOTE_USER + +# session timeouts +OIDCSessionInactivityTimeout 86400 +OIDCSessionMaxDuration 86400 + +KeepAliveTimeout 600 \ No newline at end of file diff --git a/front/apache-conf/openidc.conf_client01_int b/front/apache-conf/openidc.conf_client01_int new file mode 100755 index 00000000..ef87aebf --- /dev/null +++ b/front/apache-conf/openidc.conf_client01_int @@ -0,0 +1,71 @@ +# listen on port 80 & 443 +Listen 80 +Listen 443 + +# Create virtual hosts for 443 just for SSL Certificate + + SSLEngine on + SSLCertificateFile /etc/ssl/certs/apache-self-signed.crt + SSLCertificateKeyFile /etc/ssl/certs/apache-self-signed.key + + +#error_page 404 /404.html; +Header set Access-Control-Allow-Origin "*" +Header set X-Forwarded-Host "https" + +# redirect server error pages to the static page /50x.html +# +ErrorDocument 500 /50x.html +ErrorDocument 502 /50x.html +ErrorDocument 503 /50x.html +ErrorDocument 504 /50x.html + + Redirect 301 / + + +######################################## +#### OIDC CONFIGURATION STARTS HERE #### +######################################## + +# set log level to debug +# LogLevel debug + +# load the openidc module +LoadModule auth_openidc_module modules/mod_auth_openidc.so + +# provide multiple providers data +OIDCMetadataDir /code/apache-conf/metadata +OIDCDiscoverURL /welcome.html + +OIDCXForwardedHeaders X-Forwarded-Proto +RequestHeader set X-Forwarded-Proto "https" early + +# OIDCRedirectURI is a vanity URL that must point to a path protected by this module but must NOT point to any content +OIDCRedirectURI /callback +OIDCCryptoPassphrase @@COMETA_CRYPTO_PASSPHRASE@@ + +OIDCScope "openid email profile" +OIDCInfoHook userinfo + +OIDCOAuthRemoteUserClaim email +OIDCRemoteUserClaim email + +OIDCAuthNHeader REMOTE_USER + +# session timeouts +OIDCSessionInactivityTimeout 86400 +OIDCSessionMaxDuration 86400 + + +# Specify the maximum number of state cookies i.e. the maximum number of parallel outstanding +# authentication requests. See: https://github.com/zmartzone/mod_auth_openidc/issues/331 +# Setting this to 0 means unlimited, until the browser or server gives up which is the +# behavior of mod_auth_openidc < 2.3.8, which did not have this configuration option. +# +# The optional second boolean parameter if the oldest state cookie(s) will be deleted, +# even if still valid; see #399. +# +# When not defined, the default is 7 and "false", thus the oldest cookie(s) will not be deleted. +OIDCStateMaxNumberOfCookies 8 true + +KeepAliveTimeout 600 diff --git a/front/apache-conf/openidc.conf_local b/front/apache-conf/openidc.conf_local new file mode 100755 index 00000000..a388474a --- /dev/null +++ b/front/apache-conf/openidc.conf_local @@ -0,0 +1,86 @@ +# listen on port 443 with ssl certificates +Listen 80 +Listen 443 +SSLEngine on +SSLCertificateFile /etc/ssl/certs/apache-self-signed.crt +SSLCertificateKeyFile /etc/ssl/certs/apache-self-signed.key + +#error_page 404 /404.html; +Header set Access-Control-Allow-Origin "*" +# redirect server error pages to the static page /50x.html +# +ErrorDocument 500 /50x.html +ErrorDocument 502 /50x.html +ErrorDocument 503 /50x.html +ErrorDocument 504 /50x.html + + Redirect 301 / + + +# PORT 80 to 443 redirect +RewriteEngine on +RewriteCond %{HTTPS} !on +RewriteRule (.*) https://%{HTTP_HOST}%{REQUEST_URI} [R=301,L] + + Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains" + + +# +# if request contains @@BRANCH@@ rewrite that DEV +# .. on stage and prod this would be removed in the deployment process +# with .gitlab-ci.yml +# +RewriteRule ^(.+)/@@BRANCH@@/(.+)$ https://%{HTTP_HOST}/$1/dev/$2 [R=301,L] + + + +######################################## +#### OIDC CONFIGURATION STARTS HERE #### +######################################## + +# set log level to debug +# LogLevel debug + +# load the openidc module +LoadModule auth_openidc_module modules/mod_auth_openidc.so + +# provide multiple providers data +OIDCMetadataDir /code/apache-conf/metadata +OIDCDiscoverURL /welcome.html + +OIDCXForwardedHeaders X-Forwarded-Proto +RequestHeader set X-Forwarded-Proto "https" early + +# OIDCRedirectURI is a vanity URL that must point to a path protected by this module but must NOT point to any content +OIDCRedirectURI /callback +OIDCCryptoPassphrase thisIsCo.Meta + +# do not validate issuer this results in error if provider does not allow https +OIDCValidateIssuer Off + +OIDCScope "openid email profile" +OIDCInfoHook userinfo + +OIDCOAuthRemoteUserClaim email +OIDCRemoteUserClaim email + +OIDCAuthNHeader REMOTE_USER + +# session timeouts +OIDCSessionInactivityTimeout 86400 +OIDCSessionMaxDuration 86400 + +# SERVER SETTINGS +RequestHeader set X-Server "Local" + +# ### +# DEBUG SETTINGS +# LEAVE IT COMMENTED IF NOT USING +# ### + +ProxyPassMatch "^/debug/(.*)" "http://localhost:4200/$1" +ProxyPassReverse "^/debug/(.*)" "http://localhost:4200/$1" +ProxyPassMatch "^/ws" "ws://localhost:4200" +ProxyPassReverse "^/ws" "ws://localhost:4200" +ProxyPassMatch "^/ng-cli-ws" "ws://localhost:4200" +ProxyPassReverse "^/ng-cli-ws" "ws://localhost:4200" \ No newline at end of file diff --git a/front/apache-conf/openidc.conf_stage b/front/apache-conf/openidc.conf_stage new file mode 100755 index 00000000..3cfcf67f --- /dev/null +++ b/front/apache-conf/openidc.conf_stage @@ -0,0 +1,61 @@ +# listen on port 443 with ssl certificates +Listen 80 +#error_page 404 /404.html; +Header set Access-Control-Allow-Origin "*" +# redirect server error pages to the static page /50x.html +# +ErrorDocument 500 /50x.html +ErrorDocument 502 /50x.html +ErrorDocument 503 /50x.html +ErrorDocument 504 /50x.html + + Redirect 301 / + + +######################################## +#### OIDC CONFIGURATION STARTS HERE #### +######################################## + +# set log level to debug +# LogLevel debug + +# load the openidc module +LoadModule auth_openidc_module modules/mod_auth_openidc.so + +# provide multiple providers data +OIDCMetadataDir /code/apache-conf/metadata +OIDCDiscoverURL /welcome.html + +OIDCXForwardedHeaders X-Forwarded-Proto +RequestHeader set X-Forwarded-Proto "https" early + +# OIDCRedirectURI is a vanity URL that must point to a path protected by this module but must NOT point to any content +OIDCRedirectURI /callback +OIDCCryptoPassphrase @@COMETA_CRYPTO_PASSPHRASE@@ + +# do not validate issuer this results in error if provider does not allow https +OIDCValidateIssuer Off + +OIDCScope "openid email profile" +OIDCInfoHook userinfo + +OIDCOAuthRemoteUserClaim email +OIDCRemoteUserClaim email + +OIDCAuthNHeader REMOTE_USER + +# session timeouts +OIDCSessionInactivityTimeout 86400 +OIDCSessionMaxDuration 86400 + +# SERVER SETTINGS +RequestHeader set X-Server "Amvara-Stage" + +# Enable CoreDumps +CoreDumpDirectory /tmp + +# cache in file +OIDCCacheType file + +# PROXY TIMEOUT SETTINGS +ProxyTimeout 300 \ No newline at end of file diff --git a/front/apache-conf/paths.conf b/front/apache-conf/paths.conf new file mode 100644 index 00000000..8aba7678 --- /dev/null +++ b/front/apache-conf/paths.conf @@ -0,0 +1,111 @@ + + AuthType openid-connect + Require valid-user + + +# REVERSE PROXY SETTINGS +RequestHeader set Proxy-User %{OIDC_CLAIM_email}e +RequestHeader set Proxy-Fullname %{OIDC_CLAIM_name}e + +Alias /v2/screenshots /screenshots + +# set URI limit for websockets +# TODO: leave this limit as default and +# fix internal code to not send this much data. +LimitRequestLine 10000 + +# remove sessionid cookie on logout +RewriteEngine on +RewriteCond %{REQUEST_URI} /callback +RewriteCond %{QUERY_STRING} logout +RewriteRule ^(.*)$ /$1 [L,CO=sessionid:INVALID:;:-1] + +ProxyPreserveHost On +ProxyPassMatch "^/backend/(.*)" "http://cometa_django:8000/$1" +ProxyPassReverse "^/backend/(.*)" "http://cometa_django:8000/$1" +ProxyPassMatch "^/api/(.*)" "http://cometa_django:8000/api/$1" +ProxyPassReverse "^/api/(.*)" "http://cometa_django:8000/api/$1" +ProxyPassMatch "^/admin/(.*)" "http://cometa_django:8000/admin/$1" +ProxyPassReverse "^/admin/(.*)" "http://cometa_django:8000/admin/$1" +ProxyPassMatch "^/static/(.*)" "http://cometa_django:8000/static/$1" +ProxyPassReverse "^/static/(.*)" "http://cometa_django:8000/static/$1" +ProxyPassMatch "^/screenshot/(.*)" "http://cometa_django:8000/screenshot/$1" +ProxyPassReverse "^/screenshot/(.*)" "http://cometa_django:8000/screenshot/$1" +ProxyPassMatch "^/screenshots/(.*)" "http://cometa_django:8000/screenshots/$1" +ProxyPassReverse "^/screenshots/(.*)" "http://cometa_django:8000/screenshots/$1" +ProxyPassMatch "^/removeScreenshot/(.*)" "http://cometa_django:8000/removeScreenshot/$1" +ProxyPassReverse "^/removeScreenshot/(.*)" "http://cometa_django:8000/removeScreenshot/$1" +ProxyPassMatch "^/folders" "http://cometa_django:8000/folders" +ProxyPassReverse "^/folders" "http://cometa_django:8000/folders" +ProxyPassMatch "^/folder/(.*)" "http://cometa_django:8000/folder/$1" +ProxyPassReverse "^/folder/(.*)" "http://cometa_django:8000/folder/$1" +ProxyPassMatch "^/videos/(.*).mp4" "http://cometa_selenoid:4444/video/$1.mp4" +ProxyPassReverse "^/videos/(.*).mp4" "http://cometa_selenoid:4444/video/$1.mp4" + +ProxyPassMatch "^/socket.io/(.*)" "ws://cometa_socket:3001/socket.io/$1" +ProxyPassReverse "^/socket.io/(.*)" "ws://cometa_socket:3001/socket.io/$1" + +ProxyPassMatch "^/live-session/(.*)" "http://cometa_novnc/$1" +ProxyPassReverse "^/live-session/(.*)" "http://cometa_novnc/$1" + +ProxyPassMatch "^/vnc/(.*)" "ws://cometa_selenoid:4444/vnc/$1" +ProxyPassReverse "^/vnc/(.*)" "ws://cometa_selenoid:4444/vnc/$1" + +# Exclude payment gateway webhook URL + + Require all granted + + + Require all granted + +# CACHE CONTROL BASED ON LOCATION + + Header set Cache-Control "no-cache" + +# un-protected files + + Require all granted + + + Require all granted + + + Require all granted + + + Require all granted + + + Require all granted + + + Require all granted + + + Require all granted + + + Require all granted + + + Require all granted + + + Require all granted + + + Require all granted + + + Require all granted + + + Require all granted + + + Require all granted + + + + Require all granted + diff --git a/front/docs/index.html b/front/docs/index.html index 046bff94..470eafac 100644 --- a/front/docs/index.html +++ b/front/docs/index.html @@ -81,10 +81,6 @@

Running unit tests

Running end-to-end tests

-

Run ng e2e to execute the end-to-end tests via Protractor.

- -

Further help

-

To get more help on the Angular CLI use ng help or go check out the Angular CLI README.

Loading the app into a container

diff --git a/front/package-lock.json b/front/package-lock.json new file mode 100644 index 00000000..92ba8a93 --- /dev/null +++ b/front/package-lock.json @@ -0,0 +1,13581 @@ +{ + "name": "cometa", + "version": "0.1.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "@ampproject/remapping": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.0.tgz", + "integrity": "sha512-qRmjj8nj9qmLTQXXmaR1cck3UXSRMPrbsLJAasZpF+t3riI71BXed5ebIOYwQntykeZuhjsdweEc9BxH5Jc26w==", + "dev": true, + "requires": { + "@jridgewell/gen-mapping": "^0.1.0", + "@jridgewell/trace-mapping": "^0.3.9" + } + }, + "@angular-devkit/architect": { + "version": "0.1502.8", + "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1502.8.tgz", + "integrity": "sha512-rTltw2ABHrcKc8EGimALvXmrDTP5hlNbEy6nYolJoXEI9EwHgriWrVLVPs3OEF+/ed47dbJi9EGOXUOgzgpB5A==", + "dev": true, + "requires": { + "@angular-devkit/core": "15.2.8", + "rxjs": "6.6.7" + }, + "dependencies": { + "rxjs": { + "version": "6.6.7", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", + "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==", + "dev": true, + "requires": { + "tslib": "^1.9.0" + } + }, + "tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + } + } + }, + "@angular-devkit/build-angular": { + "version": "15.2.8", + "resolved": "https://registry.npmjs.org/@angular-devkit/build-angular/-/build-angular-15.2.8.tgz", + "integrity": "sha512-TGDnXhhOG6h6TOrWWzfnkha7wYBOXi7iJc1o1w1VKCayE3T6TZZdF847aK66vL9KG7AKYVdGhWEGw2WBHUBUpg==", + "dev": true, + "requires": { + "@ampproject/remapping": "2.2.0", + "@angular-devkit/architect": "0.1502.8", + "@angular-devkit/build-webpack": "0.1502.8", + "@angular-devkit/core": "15.2.8", + "@babel/core": "7.20.12", + "@babel/generator": "7.20.14", + "@babel/helper-annotate-as-pure": "7.18.6", + "@babel/helper-split-export-declaration": "7.18.6", + "@babel/plugin-proposal-async-generator-functions": "7.20.7", + "@babel/plugin-transform-async-to-generator": "7.20.7", + "@babel/plugin-transform-runtime": "7.19.6", + "@babel/preset-env": "7.20.2", + "@babel/runtime": "7.20.13", + "@babel/template": "7.20.7", + "@discoveryjs/json-ext": "0.5.7", + "@ngtools/webpack": "15.2.8", + "ansi-colors": "4.1.3", + "autoprefixer": "10.4.13", + "babel-loader": "9.1.2", + "babel-plugin-istanbul": "6.1.1", + "browserslist": "4.21.5", + "cacache": "17.0.4", + "chokidar": "3.5.3", + "copy-webpack-plugin": "11.0.0", + "critters": "0.0.16", + "css-loader": "6.7.3", + "esbuild": "0.17.8", + "esbuild-wasm": "0.17.8", + "glob": "8.1.0", + "https-proxy-agent": "5.0.1", + "inquirer": "8.2.4", + "jsonc-parser": "3.2.0", + "karma-source-map-support": "1.4.0", + "less": "4.1.3", + "less-loader": "11.1.0", + "license-webpack-plugin": "4.0.2", + "loader-utils": "3.2.1", + "magic-string": "0.29.0", + "mini-css-extract-plugin": "2.7.2", + "open": "8.4.1", + "ora": "5.4.1", + "parse5-html-rewriting-stream": "7.0.0", + "piscina": "3.2.0", + "postcss": "8.4.21", + "postcss-loader": "7.0.2", + "resolve-url-loader": "5.0.0", + "rxjs": "6.6.7", + "sass": "1.58.1", + "sass-loader": "13.2.0", + "semver": "7.3.8", + "source-map-loader": "4.0.1", + "source-map-support": "0.5.21", + "terser": "5.16.3", + "text-table": "0.2.0", + "tree-kill": "1.2.2", + "tslib": "2.5.0", + "webpack": "5.76.1", + "webpack-dev-middleware": "6.0.1", + "webpack-dev-server": "4.11.1", + "webpack-merge": "5.8.0", + "webpack-subresource-integrity": "5.1.0" + }, + "dependencies": { + "@ngtools/webpack": { + "version": "15.2.8", + "resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-15.2.8.tgz", + "integrity": "sha512-BJexeT4FxMtToVBGa3wdl6rrkYXgilP0kkSH4Qzu4MPlLPbeBSr4XQalQriewlpC2uzG0r2SJfrAe2eDhtSykA==", + "dev": true + }, + "brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0" + } + }, + "esbuild": { + "version": "0.17.8", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.17.8.tgz", + "integrity": "sha512-g24ybC3fWhZddZK6R3uD2iF/RIPnRpwJAqLov6ouX3hMbY4+tKolP0VMF3zuIYCaXun+yHwS5IPQ91N2BT191g==", + "dev": true, + "optional": true, + "requires": { + "@esbuild/android-arm": "0.17.8", + "@esbuild/android-arm64": "0.17.8", + "@esbuild/android-x64": "0.17.8", + "@esbuild/darwin-arm64": "0.17.8", + "@esbuild/darwin-x64": "0.17.8", + "@esbuild/freebsd-arm64": "0.17.8", + "@esbuild/freebsd-x64": "0.17.8", + "@esbuild/linux-arm": "0.17.8", + "@esbuild/linux-arm64": "0.17.8", + "@esbuild/linux-ia32": "0.17.8", + "@esbuild/linux-loong64": "0.17.8", + "@esbuild/linux-mips64el": "0.17.8", + "@esbuild/linux-ppc64": "0.17.8", + "@esbuild/linux-riscv64": "0.17.8", + "@esbuild/linux-s390x": "0.17.8", + "@esbuild/linux-x64": "0.17.8", + "@esbuild/netbsd-x64": "0.17.8", + "@esbuild/openbsd-x64": "0.17.8", + "@esbuild/sunos-x64": "0.17.8", + "@esbuild/win32-arm64": "0.17.8", + "@esbuild/win32-ia32": "0.17.8", + "@esbuild/win32-x64": "0.17.8" + } + }, + "glob": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", + "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^5.0.1", + "once": "^1.3.0" + } + }, + "minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "dev": true, + "requires": { + "brace-expansion": "^2.0.1" + } + }, + "rxjs": { + "version": "6.6.7", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", + "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==", + "dev": true, + "requires": { + "tslib": "^1.9.0" + }, + "dependencies": { + "tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + } + } + }, + "tslib": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", + "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", + "dev": true + } + } + }, + "@angular-devkit/build-webpack": { + "version": "0.1502.8", + "resolved": "https://registry.npmjs.org/@angular-devkit/build-webpack/-/build-webpack-0.1502.8.tgz", + "integrity": "sha512-jWtNv+S03FFLDe/C8SPCcRvkz3bSb2R+919IT086Q9axIPQ1VowOEwzt2k3qXPSSrC7GSYuASM+X92dB47NTQQ==", + "dev": true, + "requires": { + "@angular-devkit/architect": "0.1502.8", + "rxjs": "6.6.7" + }, + "dependencies": { + "rxjs": { + "version": "6.6.7", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", + "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==", + "dev": true, + "requires": { + "tslib": "^1.9.0" + } + }, + "tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + } + } + }, + "@angular-devkit/core": { + "version": "15.2.8", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-15.2.8.tgz", + "integrity": "sha512-Lo4XrbDMtXarKnMrFgWLmQdSX+3QPNAg4otG8cmp/U4jJyjV4dAYKEAsb1sCNGUSM4h4v09EQU/5ugVjDU29lQ==", + "dev": true, + "requires": { + "ajv": "8.12.0", + "ajv-formats": "2.1.1", + "jsonc-parser": "3.2.0", + "rxjs": "6.6.7", + "source-map": "0.7.4" + }, + "dependencies": { + "ajv": { + "version": "8.12.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", + "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", + "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 + }, + "rxjs": { + "version": "6.6.7", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", + "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==", + "dev": true, + "requires": { + "tslib": "^1.9.0" + } + }, + "tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + } + } + }, + "@angular-devkit/schematics": { + "version": "15.2.8", + "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-15.2.8.tgz", + "integrity": "sha512-w6EUGC96kVsH9f8sEzajzbONMawezyVBiSo+JYp5r25rQArAz/a+KZntbuETWHQ0rQOEsKmUNKxwmr11BaptSQ==", + "dev": true, + "requires": { + "@angular-devkit/core": "15.2.8", + "jsonc-parser": "3.2.0", + "magic-string": "0.29.0", + "ora": "5.4.1", + "rxjs": "6.6.7" + }, + "dependencies": { + "rxjs": { + "version": "6.6.7", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", + "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==", + "dev": true, + "requires": { + "tslib": "^1.9.0" + } + }, + "tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + } + } + }, + "@angular/animations": { + "version": "15.2.9", + "resolved": "https://registry.npmjs.org/@angular/animations/-/animations-15.2.9.tgz", + "integrity": "sha512-GQujLhI0cQFcl4Q8y0oSYKSRnW23GIeSL+Arl4eFufziJ9hGAAQNuesaNs/7i+9UlTHDMkPH3kd5ScXuYYz6wg==", + "requires": { + "tslib": "^2.3.0" + } + }, + "@angular/cdk": { + "version": "15.2.9", + "resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-15.2.9.tgz", + "integrity": "sha512-koaM07N1AIQ5oHU27l0/FoQSSoYAwlAYwVZ4Di3bYrJsTBNCN2Xsby7wI8gZxdepMnV4Fe9si382BDBov+oO4Q==", + "requires": { + "parse5": "^7.1.2", + "tslib": "^2.3.0" + } + }, + "@angular/cli": { + "version": "15.2.8", + "resolved": "https://registry.npmjs.org/@angular/cli/-/cli-15.2.8.tgz", + "integrity": "sha512-3VlTfm6DUZfFHBY43vQSAaqmFTxy3VtRd/iDBCHcEPhHwYLWBvNwReJuJfNja8O105QQ6DBiYVBExEBtPmjQ4w==", + "dev": true, + "requires": { + "@angular-devkit/architect": "0.1502.8", + "@angular-devkit/core": "15.2.8", + "@angular-devkit/schematics": "15.2.8", + "@schematics/angular": "15.2.8", + "@yarnpkg/lockfile": "1.1.0", + "ansi-colors": "4.1.3", + "ini": "3.0.1", + "inquirer": "8.2.4", + "jsonc-parser": "3.2.0", + "npm-package-arg": "10.1.0", + "npm-pick-manifest": "8.0.1", + "open": "8.4.1", + "ora": "5.4.1", + "pacote": "15.1.0", + "resolve": "1.22.1", + "semver": "7.3.8", + "symbol-observable": "4.0.0", + "yargs": "17.6.2" + }, + "dependencies": { + "is-core-module": { + "version": "2.12.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.12.1.tgz", + "integrity": "sha512-Q4ZuBAe2FUsKtyQJoQHlvP8OvBERxO3jEmy1I7hcRXcJBGGHFh/aJBswbXuS9sgrDH2QUO8ilkwNPHvHMd8clg==", + "dev": true, + "requires": { + "has": "^1.0.3" + } + }, + "resolve": { + "version": "1.22.1", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz", + "integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==", + "dev": true, + "requires": { + "is-core-module": "^2.9.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + } + } + } + }, + "@angular/common": { + "version": "15.2.9", + "resolved": "https://registry.npmjs.org/@angular/common/-/common-15.2.9.tgz", + "integrity": "sha512-LM9/UHG2dRrOzlu2KovrFwWIziFMjRxHzSP3Igw6Symw/wIl0kXGq8Fn6RpFP78zmLqnv+IQOoRiby9MCXsI4g==", + "dev": true, + "requires": { + "tslib": "^2.3.0" + } + }, + "@angular/compiler": { + "version": "15.2.9", + "resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-15.2.9.tgz", + "integrity": "sha512-MoKugbjk+E0wRBj12uvIyDLELlVLonnqjA2+XiF+7FxALIeyds3/qQeEoMmYIqAbN3NnTT5pV92RxWwG4tHFwA==", + "requires": { + "tslib": "^2.3.0" + } + }, + "@angular/compiler-cli": { + "version": "15.2.9", + "resolved": "https://registry.npmjs.org/@angular/compiler-cli/-/compiler-cli-15.2.9.tgz", + "integrity": "sha512-zsbI8G2xHOeYWI0hjFzrI//ZhZV9il/uQW5dAimfwJp06KZDeXZ3PdwY9JQslf6F+saLwOObxy6QMrIVvfjy9w==", + "dev": true, + "requires": { + "@babel/core": "7.19.3", + "@jridgewell/sourcemap-codec": "^1.4.14", + "chokidar": "^3.0.0", + "convert-source-map": "^1.5.1", + "dependency-graph": "^0.11.0", + "magic-string": "^0.27.0", + "reflect-metadata": "^0.1.2", + "semver": "^7.0.0", + "tslib": "^2.3.0", + "yargs": "^17.2.1" + }, + "dependencies": { + "@babel/code-frame": { + "version": "7.21.4", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.21.4.tgz", + "integrity": "sha512-LYvhNKfwWSPpocw8GI7gpK2nq3HSDuEPC/uSYaALSJu9xjsalaaYFOq0Pwt5KmVqwEbZlDu81aLXwBOmD/Fv9g==", + "dev": true, + "requires": { + "@babel/highlight": "^7.18.6" + } + }, + "@babel/core": { + "version": "7.19.3", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.19.3.tgz", + "integrity": "sha512-WneDJxdsjEvyKtXKsaBGbDeiyOjR5vYq4HcShxnIbG0qixpoHjI3MqeZM9NDvsojNCEBItQE4juOo/bU6e72gQ==", + "dev": true, + "requires": { + "@ampproject/remapping": "^2.1.0", + "@babel/code-frame": "^7.18.6", + "@babel/generator": "^7.19.3", + "@babel/helper-compilation-targets": "^7.19.3", + "@babel/helper-module-transforms": "^7.19.0", + "@babel/helpers": "^7.19.0", + "@babel/parser": "^7.19.3", + "@babel/template": "^7.18.10", + "@babel/traverse": "^7.19.3", + "@babel/types": "^7.19.3", + "convert-source-map": "^1.7.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.1", + "semver": "^6.3.0" + }, + "dependencies": { + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + } + } + }, + "@babel/helper-validator-identifier": { + "version": "7.19.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz", + "integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==", + "dev": true + }, + "@babel/highlight": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz", + "integrity": "sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.18.6", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + } + }, + "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": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true + }, + "magic-string": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.27.0.tgz", + "integrity": "sha512-8UnnX2PeRAPZuN12svgR9j7M1uWMovg/CEnIwIG0LFkXSJJe4PdfUGiTGl8V9bsBHFUtfVINcSyYxd7q+kx9fA==", + "dev": true, + "requires": { + "@jridgewell/sourcemap-codec": "^1.4.13" + } + }, + "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" + } + } + } + }, + "@angular/core": { + "version": "15.2.9", + "resolved": "https://registry.npmjs.org/@angular/core/-/core-15.2.9.tgz", + "integrity": "sha512-w46Z1yUXCQfKV7XfnamOoLA2VD0MVUUYVrUjO73mHSskDXSXxfZAEHO9kfUS71Cj35PvhP3mbkqWscpea2WeYg==", + "dev": true, + "requires": { + "tslib": "^2.3.0" + } + }, + "@angular/forms": { + "version": "15.2.9", + "resolved": "https://registry.npmjs.org/@angular/forms/-/forms-15.2.9.tgz", + "integrity": "sha512-sk0pC2EFi2Ohg5J0q0NYptbT+2WOkoiERSMYA39ncDvlSZBWsNlxpkbGUSck7NIxjK2QfcVN1ldGbHlZTFvtqg==", + "requires": { + "tslib": "^2.3.0" + } + }, + "@angular/language-service": { + "version": "15.2.9", + "resolved": "https://registry.npmjs.org/@angular/language-service/-/language-service-15.2.9.tgz", + "integrity": "sha512-B7lP4q/eHge2lZezOXS96EYzVf4stMCWfOnz7+pUUi0HbF+A5QCV65SWQddS/M+NM2jj8N2L/j+6UCH8lJjTQA==", + "dev": true + }, + "@angular/material": { + "version": "15.2.9", + "resolved": "https://registry.npmjs.org/@angular/material/-/material-15.2.9.tgz", + "integrity": "sha512-emuFF/7+91Jq+6kVCl3FiVoFLtAZoh+woFQWNuK8nhx0HmD4ckLFI8d9a6ERYR3zRuKhq5deSRE2kYsfpjrrsQ==", + "requires": { + "@material/animation": "15.0.0-canary.684e33d25.0", + "@material/auto-init": "15.0.0-canary.684e33d25.0", + "@material/banner": "15.0.0-canary.684e33d25.0", + "@material/base": "15.0.0-canary.684e33d25.0", + "@material/button": "15.0.0-canary.684e33d25.0", + "@material/card": "15.0.0-canary.684e33d25.0", + "@material/checkbox": "15.0.0-canary.684e33d25.0", + "@material/chips": "15.0.0-canary.684e33d25.0", + "@material/circular-progress": "15.0.0-canary.684e33d25.0", + "@material/data-table": "15.0.0-canary.684e33d25.0", + "@material/density": "15.0.0-canary.684e33d25.0", + "@material/dialog": "15.0.0-canary.684e33d25.0", + "@material/dom": "15.0.0-canary.684e33d25.0", + "@material/drawer": "15.0.0-canary.684e33d25.0", + "@material/elevation": "15.0.0-canary.684e33d25.0", + "@material/fab": "15.0.0-canary.684e33d25.0", + "@material/feature-targeting": "15.0.0-canary.684e33d25.0", + "@material/floating-label": "15.0.0-canary.684e33d25.0", + "@material/form-field": "15.0.0-canary.684e33d25.0", + "@material/icon-button": "15.0.0-canary.684e33d25.0", + "@material/image-list": "15.0.0-canary.684e33d25.0", + "@material/layout-grid": "15.0.0-canary.684e33d25.0", + "@material/line-ripple": "15.0.0-canary.684e33d25.0", + "@material/linear-progress": "15.0.0-canary.684e33d25.0", + "@material/list": "15.0.0-canary.684e33d25.0", + "@material/menu": "15.0.0-canary.684e33d25.0", + "@material/menu-surface": "15.0.0-canary.684e33d25.0", + "@material/notched-outline": "15.0.0-canary.684e33d25.0", + "@material/radio": "15.0.0-canary.684e33d25.0", + "@material/ripple": "15.0.0-canary.684e33d25.0", + "@material/rtl": "15.0.0-canary.684e33d25.0", + "@material/segmented-button": "15.0.0-canary.684e33d25.0", + "@material/select": "15.0.0-canary.684e33d25.0", + "@material/shape": "15.0.0-canary.684e33d25.0", + "@material/slider": "15.0.0-canary.684e33d25.0", + "@material/snackbar": "15.0.0-canary.684e33d25.0", + "@material/switch": "15.0.0-canary.684e33d25.0", + "@material/tab": "15.0.0-canary.684e33d25.0", + "@material/tab-bar": "15.0.0-canary.684e33d25.0", + "@material/tab-indicator": "15.0.0-canary.684e33d25.0", + "@material/tab-scroller": "15.0.0-canary.684e33d25.0", + "@material/textfield": "15.0.0-canary.684e33d25.0", + "@material/theme": "15.0.0-canary.684e33d25.0", + "@material/tooltip": "15.0.0-canary.684e33d25.0", + "@material/top-app-bar": "15.0.0-canary.684e33d25.0", + "@material/touch-target": "15.0.0-canary.684e33d25.0", + "@material/typography": "15.0.0-canary.684e33d25.0", + "tslib": "^2.3.0" + } + }, + "@angular/platform-browser": { + "version": "15.2.9", + "resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-15.2.9.tgz", + "integrity": "sha512-ufCHeSX+U6d43YOMkn3igwfqtlozoCXADcbyfUEG8m2y9XASobqmCKvdSk/zfl62oyiA8msntWBJVBE2l4xKXg==", + "requires": { + "tslib": "^2.3.0" + } + }, + "@angular/platform-browser-dynamic": { + "version": "15.2.9", + "resolved": "https://registry.npmjs.org/@angular/platform-browser-dynamic/-/platform-browser-dynamic-15.2.9.tgz", + "integrity": "sha512-ZIYDM6MShblb8OyV1m4+18lJJ2LCeICmeg2uSbpFYptYBSOClrTiYOOFVDJvn7HLvNzljLs16XPrgyaYVqNpcw==", + "requires": { + "tslib": "^2.3.0" + } + }, + "@angular/router": { + "version": "15.2.9", + "resolved": "https://registry.npmjs.org/@angular/router/-/router-15.2.9.tgz", + "integrity": "sha512-UCbh5DLSDhybv0xKYT7kGQMfOVdyhHIHOZz5EYVebbhste6S+W1LE57vTHq7QtxJsyKBa/WSkaUkCLXD6ntCAg==", + "requires": { + "tslib": "^2.3.0" + } + }, + "@angular/service-worker": { + "version": "15.2.9", + "resolved": "https://registry.npmjs.org/@angular/service-worker/-/service-worker-15.2.9.tgz", + "integrity": "sha512-qM/lcrjaxIfpKj174mMWedtGfLNgLl5m7p9mPNODFjqp5lQj3fTTS643ix5Pr0onwbvbNbXu4g67/WXJqap0eA==", + "requires": { + "tslib": "^2.3.0" + } + }, + "@assemblyscript/loader": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/@assemblyscript/loader/-/loader-0.10.1.tgz", + "integrity": "sha512-H71nDOOL8Y7kWRLqf6Sums+01Q5msqBW2KhDUTemh1tvY04eSkSXrK0uj/4mmY0Xr16/3zyZmsrxN7CKuRbNRg==", + "dev": true + }, + "@babel/code-frame": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.16.7.tgz", + "integrity": "sha512-iAXqUn8IIeBTNd72xsFlgaXHkMBMt6y4HJp1tIaK465CWLT/fG1aqB7ykr95gHHmlBdGbFeWWfyB4NJJ0nmeIg==", + "dev": true, + "requires": { + "@babel/highlight": "^7.16.7" + } + }, + "@babel/compat-data": { + "version": "7.21.9", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.21.9.tgz", + "integrity": "sha512-FUGed8kfhyWvbYug/Un/VPJD41rDIgoVVcR+FuzhzOYyRz5uED+Gd3SLZml0Uw2l2aHFb7ZgdW5mGA3G2cCCnQ==", + "dev": true + }, + "@babel/core": { + "version": "7.20.12", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.20.12.tgz", + "integrity": "sha512-XsMfHovsUYHFMdrIHkZphTN/2Hzzi78R08NuHfDBehym2VsPDL6Zn/JAD/JQdnRvbSsbQc4mVaU1m6JgtTEElg==", + "dev": true, + "requires": { + "@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" + }, + "dependencies": { + "@babel/code-frame": { + "version": "7.21.4", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.21.4.tgz", + "integrity": "sha512-LYvhNKfwWSPpocw8GI7gpK2nq3HSDuEPC/uSYaALSJu9xjsalaaYFOq0Pwt5KmVqwEbZlDu81aLXwBOmD/Fv9g==", + "dev": true, + "requires": { + "@babel/highlight": "^7.18.6" + } + }, + "@babel/helper-validator-identifier": { + "version": "7.19.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz", + "integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==", + "dev": true + }, + "@babel/highlight": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz", + "integrity": "sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.18.6", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + } + }, + "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": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true + }, + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "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/generator": { + "version": "7.20.14", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.20.14.tgz", + "integrity": "sha512-AEmuXHdcD3A52HHXxaTmYlb8q/xMEhoRP67B3T4Oq7lbmSoqroMZzjnGj3+i1io3pdnF8iBYVu4Ilj+c4hBxYg==", + "dev": true, + "requires": { + "@babel/types": "^7.20.7", + "@jridgewell/gen-mapping": "^0.3.2", + "jsesc": "^2.5.1" + }, + "dependencies": { + "@jridgewell/gen-mapping": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", + "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==", + "dev": true, + "requires": { + "@jridgewell/set-array": "^1.0.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.9" + } + } + } + }, + "@babel/helper-annotate-as-pure": { + "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==", + "dev": true, + "requires": { + "@babel/types": "^7.18.6" + } + }, + "@babel/helper-builder-binary-assignment-operator-visitor": { + "version": "7.21.5", + "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.21.5.tgz", + "integrity": "sha512-uNrjKztPLkUk7bpCNC0jEKDJzzkvel/W+HguzbN8krA+LPfC1CEobJEvAvGka2A/M+ViOqXdcRL0GqPUJSjx9g==", + "dev": true, + "requires": { + "@babel/types": "^7.21.5" + } + }, + "@babel/helper-compilation-targets": { + "version": "7.21.5", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.21.5.tgz", + "integrity": "sha512-1RkbFGUKex4lvsB9yhIfWltJM5cZKUftB2eNajaDv3dCMEp49iBG0K14uH8NnX9IPux2+mK7JGEOB0jn48/J6w==", + "dev": true, + "requires": { + "@babel/compat-data": "^7.21.5", + "@babel/helper-validator-option": "^7.21.0", + "browserslist": "^4.21.3", + "lru-cache": "^5.1.1", + "semver": "^6.3.0" + }, + "dependencies": { + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + } + } + }, + "@babel/helper-create-class-features-plugin": { + "version": "7.21.8", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.21.8.tgz", + "integrity": "sha512-+THiN8MqiH2AczyuZrnrKL6cAxFRRQDKW9h1YkBvbgKmAm6mwiacig1qT73DHIWMGo40GRnsEfN3LA+E6NtmSw==", + "dev": true, + "requires": { + "@babel/helper-annotate-as-pure": "^7.18.6", + "@babel/helper-environment-visitor": "^7.21.5", + "@babel/helper-function-name": "^7.21.0", + "@babel/helper-member-expression-to-functions": "^7.21.5", + "@babel/helper-optimise-call-expression": "^7.18.6", + "@babel/helper-replace-supers": "^7.21.5", + "@babel/helper-skip-transparent-expression-wrappers": "^7.20.0", + "@babel/helper-split-export-declaration": "^7.18.6", + "semver": "^6.3.0" + }, + "dependencies": { + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + } + } + }, + "@babel/helper-create-regexp-features-plugin": { + "version": "7.21.8", + "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.21.8.tgz", + "integrity": "sha512-zGuSdedkFtsFHGbexAvNuipg1hbtitDLo2XE8/uf6Y9sOQV1xsYX/2pNbtedp/X0eU1pIt+kGvaqHCowkRbS5g==", + "dev": true, + "requires": { + "@babel/helper-annotate-as-pure": "^7.18.6", + "regexpu-core": "^5.3.1", + "semver": "^6.3.0" + }, + "dependencies": { + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + } + } + }, + "@babel/helper-define-polyfill-provider": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.3.3.tgz", + "integrity": "sha512-z5aQKU4IzbqCC1XH0nAqfsFLMVSo22SBKUc0BxGrLkolTdPTructy0ToNnlO2zA4j9Q/7pjMZf0DSY+DSTYzww==", + "dev": true, + "requires": { + "@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" + }, + "dependencies": { + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + } + } + }, + "@babel/helper-environment-visitor": { + "version": "7.21.5", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.21.5.tgz", + "integrity": "sha512-IYl4gZ3ETsWocUWgsFZLM5i1BYx9SoemminVEXadgLBa9TdeorzgLKm8wWLA6J1N/kT3Kch8XIk1laNzYoHKvQ==", + "dev": true + }, + "@babel/helper-function-name": { + "version": "7.21.0", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.21.0.tgz", + "integrity": "sha512-HfK1aMRanKHpxemaY2gqBmL04iAPOPRj7DxtNbiDOrJK+gdwkiNRVpCpUJYbUT+aZyemKN8brqTOxzCaG6ExRg==", + "dev": true, + "requires": { + "@babel/template": "^7.20.7", + "@babel/types": "^7.21.0" + } + }, + "@babel/helper-hoist-variables": { + "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==", + "dev": true, + "requires": { + "@babel/types": "^7.18.6" + } + }, + "@babel/helper-member-expression-to-functions": { + "version": "7.21.5", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.21.5.tgz", + "integrity": "sha512-nIcGfgwpH2u4n9GG1HpStW5Ogx7x7ekiFHbjjFRKXbn5zUvqO9ZgotCO4x1aNbKn/x/xOUaXEhyNHCwtFCpxWg==", + "dev": true, + "requires": { + "@babel/types": "^7.21.5" + } + }, + "@babel/helper-module-imports": { + "version": "7.21.4", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.21.4.tgz", + "integrity": "sha512-orajc5T2PsRYUN3ZryCEFeMDYwyw09c/pZeaQEZPH0MpKzSvn3e0uXsDBu3k03VI+9DBiRo+l22BfKTpKwa/Wg==", + "dev": true, + "requires": { + "@babel/types": "^7.21.4" + } + }, + "@babel/helper-module-transforms": { + "version": "7.21.5", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.21.5.tgz", + "integrity": "sha512-bI2Z9zBGY2q5yMHoBvJ2a9iX3ZOAzJPm7Q8Yz6YeoUjU/Cvhmi2G4QyTNyPBqqXSgTjUxRg3L0xV45HvkNWWBw==", + "dev": true, + "requires": { + "@babel/helper-environment-visitor": "^7.21.5", + "@babel/helper-module-imports": "^7.21.4", + "@babel/helper-simple-access": "^7.21.5", + "@babel/helper-split-export-declaration": "^7.18.6", + "@babel/helper-validator-identifier": "^7.19.1", + "@babel/template": "^7.20.7", + "@babel/traverse": "^7.21.5", + "@babel/types": "^7.21.5" + }, + "dependencies": { + "@babel/helper-validator-identifier": { + "version": "7.19.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz", + "integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==", + "dev": true + } + } + }, + "@babel/helper-optimise-call-expression": { + "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==", + "dev": true, + "requires": { + "@babel/types": "^7.18.6" + } + }, + "@babel/helper-plugin-utils": { + "version": "7.21.5", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.21.5.tgz", + "integrity": "sha512-0WDaIlXKOX/3KfBK/dwP1oQGiPh6rjMkT7HIRv7i5RR2VUMwrx5ZL0dwBkKx7+SW1zwNdgjHd34IMk5ZjTeHVg==", + "dev": true + }, + "@babel/helper-remap-async-to-generator": { + "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==", + "dev": true, + "requires": { + "@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": { + "version": "7.21.5", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.21.5.tgz", + "integrity": "sha512-/y7vBgsr9Idu4M6MprbOVUfH3vs7tsIfnVWv/Ml2xgwvyH6LTngdfbf5AdsKwkJy4zgy1X/kuNrEKvhhK28Yrg==", + "dev": true, + "requires": { + "@babel/helper-environment-visitor": "^7.21.5", + "@babel/helper-member-expression-to-functions": "^7.21.5", + "@babel/helper-optimise-call-expression": "^7.18.6", + "@babel/template": "^7.20.7", + "@babel/traverse": "^7.21.5", + "@babel/types": "^7.21.5" + } + }, + "@babel/helper-simple-access": { + "version": "7.21.5", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.21.5.tgz", + "integrity": "sha512-ENPDAMC1wAjR0uaCUwliBdiSl1KBJAVnMTzXqi64c2MG8MPR6ii4qf7bSXDqSFbr4W6W028/rf5ivoHop5/mkg==", + "dev": true, + "requires": { + "@babel/types": "^7.21.5" + } + }, + "@babel/helper-skip-transparent-expression-wrappers": { + "version": "7.20.0", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.20.0.tgz", + "integrity": "sha512-5y1JYeNKfvnT8sZcK9DVRtpTbGiomYIHviSP3OQWmDPU3DeH4a1ZlT/N2lyQ5P8egjcRaT/Y9aNqUxK0WsnIIg==", + "dev": true, + "requires": { + "@babel/types": "^7.20.0" + } + }, + "@babel/helper-split-export-declaration": { + "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==", + "dev": true, + "requires": { + "@babel/types": "^7.18.6" + } + }, + "@babel/helper-string-parser": { + "version": "7.21.5", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.21.5.tgz", + "integrity": "sha512-5pTUx3hAJaZIdW99sJ6ZUUgWq/Y+Hja7TowEnLNMm1VivRgZQL3vpBY3qUACVsvw+yQU6+YgfBVmcbLaZtrA1w==", + "dev": true + }, + "@babel/helper-validator-identifier": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.16.7.tgz", + "integrity": "sha512-hsEnFemeiW4D08A5gUAZxLBTXpZ39P+a+DGDsHw1yxqyQ/jzFEnxf5uTEGp+3bzAbNOxU1paTgYS4ECU/IgfDw==", + "dev": true + }, + "@babel/helper-validator-option": { + "version": "7.21.0", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.21.0.tgz", + "integrity": "sha512-rmL/B8/f0mKS2baE9ZpyTcTavvEuWhTTW8amjzXNvYG4AwBsqTLikfXsEofsJEfKHf+HQVQbFOHy6o+4cnC/fQ==", + "dev": true + }, + "@babel/helper-wrap-function": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.20.5.tgz", + "integrity": "sha512-bYMxIWK5mh+TgXGVqAtnu5Yn1un+v8DDZtqyzKRLUzrh70Eal2O3aZ7aPYiMADO4uKlkzOiRiZ6GX5q3qxvW9Q==", + "dev": true, + "requires": { + "@babel/helper-function-name": "^7.19.0", + "@babel/template": "^7.18.10", + "@babel/traverse": "^7.20.5", + "@babel/types": "^7.20.5" + } + }, + "@babel/helpers": { + "version": "7.21.5", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.21.5.tgz", + "integrity": "sha512-BSY+JSlHxOmGsPTydUkPf1MdMQ3M81x5xGCOVgWM3G8XH77sJ292Y2oqcp0CbbgxhqBuI46iUz1tT7hqP7EfgA==", + "dev": true, + "requires": { + "@babel/template": "^7.20.7", + "@babel/traverse": "^7.21.5", + "@babel/types": "^7.21.5" + } + }, + "@babel/highlight": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.16.7.tgz", + "integrity": "sha512-aKpPMfLvGO3Q97V0qhw/V2SWNWlwfJknuwAunU7wZLSfrM4xTBvg7E5opUVi1kJTBKihE38CPg4nBiqX83PWYw==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.16.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 + }, + "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.21.9", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.21.9.tgz", + "integrity": "sha512-q5PNg/Bi1OpGgx5jYlvWZwAorZepEudDMCLtj967aeS7WMont7dUZI46M2XwcIQqvUlMxWfdLFu4S/qSxeUu5g==", + "dev": true + }, + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { + "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==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.18.6" + } + }, + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.20.7.tgz", + "integrity": "sha512-sbr9+wNE5aXMBBFBICk01tt7sBf2Oc9ikRFEcem/ZORup9IMUdNhW7/wVLEbbtlWOsEubJet46mHAL2C8+2jKQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.20.2", + "@babel/helper-skip-transparent-expression-wrappers": "^7.20.0", + "@babel/plugin-proposal-optional-chaining": "^7.20.7" + } + }, + "@babel/plugin-proposal-async-generator-functions": { + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.20.7.tgz", + "integrity": "sha512-xMbiLsn/8RK7Wq7VeVytytS2L6qE69bXPB10YCmMdDZbKF4okCqY74pI/jJQ/8U0b/F6NrT2+14b8/P9/3AMGA==", + "dev": true, + "requires": { + "@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" + } + }, + "@babel/plugin-proposal-class-properties": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.18.6.tgz", + "integrity": "sha512-cumfXOF0+nzZrrN8Rf0t7M+tF6sZc7vhQwYQck9q1/5w2OExlD+b4v4RpMJFaV1Z7WcDRgO6FqvxqxGlwo+RHQ==", + "dev": true, + "requires": { + "@babel/helper-create-class-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + } + }, + "@babel/plugin-proposal-class-static-block": { + "version": "7.21.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-static-block/-/plugin-proposal-class-static-block-7.21.0.tgz", + "integrity": "sha512-XP5G9MWNUskFuP30IfFSEFB0Z6HzLIUcjYM4bYOPHXl7eiJ9HFv8tWj6TXTN5QODiEhDZAeI4hLok2iHFFV4hw==", + "dev": true, + "requires": { + "@babel/helper-create-class-features-plugin": "^7.21.0", + "@babel/helper-plugin-utils": "^7.20.2", + "@babel/plugin-syntax-class-static-block": "^7.14.5" + } + }, + "@babel/plugin-proposal-dynamic-import": { + "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==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/plugin-syntax-dynamic-import": "^7.8.3" + } + }, + "@babel/plugin-proposal-export-namespace-from": { + "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==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.18.9", + "@babel/plugin-syntax-export-namespace-from": "^7.8.3" + } + }, + "@babel/plugin-proposal-json-strings": { + "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==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/plugin-syntax-json-strings": "^7.8.3" + } + }, + "@babel/plugin-proposal-logical-assignment-operators": { + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-logical-assignment-operators/-/plugin-proposal-logical-assignment-operators-7.20.7.tgz", + "integrity": "sha512-y7C7cZgpMIjWlKE5T7eJwp+tnRYM89HmRvWM5EQuB5BoHEONjmQ8lSNmBUwOyy/GFRsohJED51YBF79hE1djug==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.20.2", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4" + } + }, + "@babel/plugin-proposal-nullish-coalescing-operator": { + "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==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3" + } + }, + "@babel/plugin-proposal-numeric-separator": { + "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==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/plugin-syntax-numeric-separator": "^7.10.4" + } + }, + "@babel/plugin-proposal-object-rest-spread": { + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.20.7.tgz", + "integrity": "sha512-d2S98yCiLxDVmBmE8UjGcfPvNEUbA1U5q5WxaWFUGRzJSVAZqm5W6MbPct0jxnegUZ0niLeNX+IOzEs7wYg9Dg==", + "dev": true, + "requires": { + "@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" + } + }, + "@babel/plugin-proposal-optional-catch-binding": { + "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==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3" + } + }, + "@babel/plugin-proposal-optional-chaining": { + "version": "7.21.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.21.0.tgz", + "integrity": "sha512-p4zeefM72gpmEe2fkUr/OnOXpWEf8nAgk7ZYVqqfFiyIG7oFfVZcCrU64hWn5xp4tQ9LkV4bTIa5rD0KANpKNA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.20.2", + "@babel/helper-skip-transparent-expression-wrappers": "^7.20.0", + "@babel/plugin-syntax-optional-chaining": "^7.8.3" + } + }, + "@babel/plugin-proposal-private-methods": { + "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==", + "dev": true, + "requires": { + "@babel/helper-create-class-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + } + }, + "@babel/plugin-proposal-private-property-in-object": { + "version": "7.21.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0.tgz", + "integrity": "sha512-ha4zfehbJjc5MmXBlHec1igel5TJXXLDDRbuJ4+XT2TJcyD9/V1919BA8gMvsdHcNMBy4WBUBiRb3nw/EQUtBw==", + "dev": true, + "requires": { + "@babel/helper-annotate-as-pure": "^7.18.6", + "@babel/helper-create-class-features-plugin": "^7.21.0", + "@babel/helper-plugin-utils": "^7.20.2", + "@babel/plugin-syntax-private-property-in-object": "^7.14.5" + } + }, + "@babel/plugin-proposal-unicode-property-regex": { + "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==", + "dev": true, + "requires": { + "@babel/helper-create-regexp-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + } + }, + "@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-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-class-static-block": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz", + "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.14.5" + } + }, + "@babel/plugin-syntax-dynamic-import": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz", + "integrity": "sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-export-namespace-from": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-export-namespace-from/-/plugin-syntax-export-namespace-from-7.8.3.tgz", + "integrity": "sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.3" + } + }, + "@babel/plugin-syntax-import-assertions": { + "version": "7.20.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.20.0.tgz", + "integrity": "sha512-IUh1vakzNoWalR8ch/areW7qFopR2AEw03JlG7BbrDqmQ4X3q9uuipQwSGrUn7oGiemKjtSLDhNtQHzMHr1JdQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.19.0" + } + }, + "@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-private-property-in-object": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", + "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.14.5" + } + }, + "@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/plugin-transform-arrow-functions": { + "version": "7.21.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.21.5.tgz", + "integrity": "sha512-wb1mhwGOCaXHDTcsRYMKF9e5bbMgqwxtqa2Y1ifH96dXJPwbuLX9qHy3clhrxVqgMz7nyNXs8VkxdH8UBcjKqA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.21.5" + } + }, + "@babel/plugin-transform-async-to-generator": { + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.20.7.tgz", + "integrity": "sha512-Uo5gwHPT9vgnSXQxqGtpdufUiWp96gk7yiP4Mp5bm1QMkEmLXBO7PAGYbKoJ6DhAwiNkcHFBol/x5zZZkL/t0Q==", + "dev": true, + "requires": { + "@babel/helper-module-imports": "^7.18.6", + "@babel/helper-plugin-utils": "^7.20.2", + "@babel/helper-remap-async-to-generator": "^7.18.9" + } + }, + "@babel/plugin-transform-block-scoped-functions": { + "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==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.18.6" + } + }, + "@babel/plugin-transform-block-scoping": { + "version": "7.21.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.21.0.tgz", + "integrity": "sha512-Mdrbunoh9SxwFZapeHVrwFmri16+oYotcZysSzhNIVDwIAb1UV+kvnxULSYq9J3/q5MDG+4X6w8QVgD1zhBXNQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.20.2" + } + }, + "@babel/plugin-transform-classes": { + "version": "7.21.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.21.0.tgz", + "integrity": "sha512-RZhbYTCEUAe6ntPehC4hlslPWosNHDox+vAs4On/mCLRLfoDVHf6hVEd7kuxr1RnHwJmxFfUM3cZiZRmPxJPXQ==", + "dev": true, + "requires": { + "@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.21.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" + } + }, + "@babel/plugin-transform-computed-properties": { + "version": "7.21.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.21.5.tgz", + "integrity": "sha512-TR653Ki3pAwxBxUe8srfF3e4Pe3FTA46uaNHYyQwIoM4oWKSoOZiDNyHJ0oIoDIUPSRQbQG7jzgVBX3FPVne1Q==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.21.5", + "@babel/template": "^7.20.7" + } + }, + "@babel/plugin-transform-destructuring": { + "version": "7.21.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.21.3.tgz", + "integrity": "sha512-bp6hwMFzuiE4HqYEyoGJ/V2LeIWn+hLVKc4pnj++E5XQptwhtcGmSayM029d/j2X1bPKGTlsyPwAubuU22KhMA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.20.2" + } + }, + "@babel/plugin-transform-dotall-regex": { + "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==", + "dev": true, + "requires": { + "@babel/helper-create-regexp-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + } + }, + "@babel/plugin-transform-duplicate-keys": { + "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==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.18.9" + } + }, + "@babel/plugin-transform-exponentiation-operator": { + "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==", + "dev": true, + "requires": { + "@babel/helper-builder-binary-assignment-operator-visitor": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + } + }, + "@babel/plugin-transform-for-of": { + "version": "7.21.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.21.5.tgz", + "integrity": "sha512-nYWpjKW/7j/I/mZkGVgHJXh4bA1sfdFnJoOXwJuj4m3Q2EraO/8ZyrkCau9P5tbHQk01RMSt6KYLCsW7730SXQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.21.5" + } + }, + "@babel/plugin-transform-function-name": { + "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==", + "dev": true, + "requires": { + "@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": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.18.9.tgz", + "integrity": "sha512-IFQDSRoTPnrAIrI5zoZv73IFeZu2dhu6irxQjY9rNjTT53VmKg9fenjvoiOWOkJ6mm4jKVPtdMzBY98Fp4Z4cg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.18.9" + } + }, + "@babel/plugin-transform-member-expression-literals": { + "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==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.18.6" + } + }, + "@babel/plugin-transform-modules-amd": { + "version": "7.20.11", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.20.11.tgz", + "integrity": "sha512-NuzCt5IIYOW0O30UvqktzHYR2ud5bOWbY0yaxWZ6G+aFzOMJvrs5YHNikrbdaT15+KNO31nPOy5Fim3ku6Zb5g==", + "dev": true, + "requires": { + "@babel/helper-module-transforms": "^7.20.11", + "@babel/helper-plugin-utils": "^7.20.2" + } + }, + "@babel/plugin-transform-modules-commonjs": { + "version": "7.21.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.21.5.tgz", + "integrity": "sha512-OVryBEgKUbtqMoB7eG2rs6UFexJi6Zj6FDXx+esBLPTCxCNxAY9o+8Di7IsUGJ+AVhp5ncK0fxWUBd0/1gPhrQ==", + "dev": true, + "requires": { + "@babel/helper-module-transforms": "^7.21.5", + "@babel/helper-plugin-utils": "^7.21.5", + "@babel/helper-simple-access": "^7.21.5" + } + }, + "@babel/plugin-transform-modules-systemjs": { + "version": "7.20.11", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.20.11.tgz", + "integrity": "sha512-vVu5g9BPQKSFEmvt2TA4Da5N+QVS66EX21d8uoOihC+OCpUoGvzVsXeqFdtAEfVa5BILAeFt+U7yVmLbQnAJmw==", + "dev": true, + "requires": { + "@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" + }, + "dependencies": { + "@babel/helper-validator-identifier": { + "version": "7.19.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz", + "integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==", + "dev": true + } + } + }, + "@babel/plugin-transform-modules-umd": { + "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==", + "dev": true, + "requires": { + "@babel/helper-module-transforms": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + } + }, + "@babel/plugin-transform-named-capturing-groups-regex": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.20.5.tgz", + "integrity": "sha512-mOW4tTzi5iTLnw+78iEq3gr8Aoq4WNRGpmSlrogqaiCBoR1HFhpU4JkpQFOHfeYx3ReVIFWOQJS4aZBRvuZ6mA==", + "dev": true, + "requires": { + "@babel/helper-create-regexp-features-plugin": "^7.20.5", + "@babel/helper-plugin-utils": "^7.20.2" + } + }, + "@babel/plugin-transform-new-target": { + "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==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.18.6" + } + }, + "@babel/plugin-transform-object-super": { + "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==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/helper-replace-supers": "^7.18.6" + } + }, + "@babel/plugin-transform-parameters": { + "version": "7.21.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.21.3.tgz", + "integrity": "sha512-Wxc+TvppQG9xWFYatvCGPvZ6+SIUxQ2ZdiBP+PHYMIjnPXD+uThCshaz4NZOnODAtBjjcVQQ/3OKs9LW28purQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.20.2" + } + }, + "@babel/plugin-transform-property-literals": { + "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==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.18.6" + } + }, + "@babel/plugin-transform-regenerator": { + "version": "7.21.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.21.5.tgz", + "integrity": "sha512-ZoYBKDb6LyMi5yCsByQ5jmXsHAQDDYeexT1Szvlmui+lADvfSecr5Dxd/PkrTC3pAD182Fcju1VQkB4oCp9M+w==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.21.5", + "regenerator-transform": "^0.15.1" + } + }, + "@babel/plugin-transform-reserved-words": { + "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==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.18.6" + } + }, + "@babel/plugin-transform-runtime": { + "version": "7.19.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.19.6.tgz", + "integrity": "sha512-PRH37lz4JU156lYFW1p8OxE5i7d6Sl/zV58ooyr+q1J1lnQPyg5tIiXlIwNVhJaY4W3TmOtdc8jqdXQcB1v5Yw==", + "dev": true, + "requires": { + "@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" + }, + "dependencies": { + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + } + } + }, + "@babel/plugin-transform-shorthand-properties": { + "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==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.18.6" + } + }, + "@babel/plugin-transform-spread": { + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.20.7.tgz", + "integrity": "sha512-ewBbHQ+1U/VnH1fxltbJqDeWBU1oNLG8Dj11uIv3xVf7nrQu0bPGe5Rf716r7K5Qz+SqtAOVswoVunoiBtGhxw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.20.2", + "@babel/helper-skip-transparent-expression-wrappers": "^7.20.0" + } + }, + "@babel/plugin-transform-sticky-regex": { + "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==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.18.6" + } + }, + "@babel/plugin-transform-template-literals": { + "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==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.18.9" + } + }, + "@babel/plugin-transform-typeof-symbol": { + "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==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.18.9" + } + }, + "@babel/plugin-transform-unicode-escapes": { + "version": "7.21.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.21.5.tgz", + "integrity": "sha512-LYm/gTOwZqsYohlvFUe/8Tujz75LqqVC2w+2qPHLR+WyWHGCZPN1KBpJCJn+4Bk4gOkQy/IXKIge6az5MqwlOg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.21.5" + } + }, + "@babel/plugin-transform-unicode-regex": { + "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==", + "dev": true, + "requires": { + "@babel/helper-create-regexp-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + } + }, + "@babel/preset-env": { + "version": "7.20.2", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.20.2.tgz", + "integrity": "sha512-1G0efQEWR1EHkKvKHqbG+IN/QdgwfByUpM5V5QroDzGV2t3S/WXNQd693cHiHTlCFMpr9B6FkPFXDA2lQcKoDg==", + "dev": true, + "requires": { + "@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" + }, + "dependencies": { + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + } + } + }, + "@babel/preset-modules": { + "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==", + "dev": true, + "requires": { + "@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/regjsgen": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@babel/regjsgen/-/regjsgen-0.8.0.tgz", + "integrity": "sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA==", + "dev": true + }, + "@babel/runtime": { + "version": "7.20.13", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.20.13.tgz", + "integrity": "sha512-gt3PKXs0DBoL9xCvOIIZ2NEqAGZqHjAnmVbfQtB620V0uReIQutpel14KcneZuer7UioY8ALKZ7iocavvzTNFA==", + "dev": true, + "requires": { + "regenerator-runtime": "^0.13.11" + } + }, + "@babel/template": { + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.20.7.tgz", + "integrity": "sha512-8SegXApWe6VoNw0r9JHpSteLKTpTiLZ4rMlGIm9JQ18KiCtyQiAMEazujAHrUS5flrcqYZa75ukev3P6QmUwUw==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.18.6", + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7" + }, + "dependencies": { + "@babel/code-frame": { + "version": "7.21.4", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.21.4.tgz", + "integrity": "sha512-LYvhNKfwWSPpocw8GI7gpK2nq3HSDuEPC/uSYaALSJu9xjsalaaYFOq0Pwt5KmVqwEbZlDu81aLXwBOmD/Fv9g==", + "dev": true, + "requires": { + "@babel/highlight": "^7.18.6" + } + }, + "@babel/helper-validator-identifier": { + "version": "7.19.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz", + "integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==", + "dev": true + }, + "@babel/highlight": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz", + "integrity": "sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.18.6", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + } + }, + "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": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true + }, + "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/traverse": { + "version": "7.21.5", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.21.5.tgz", + "integrity": "sha512-AhQoI3YjWi6u/y/ntv7k48mcrCXmus0t79J9qPNlk/lAsFlCiJ047RmbfMOawySTHtywXhbXgpx/8nXMYd+oFw==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.21.4", + "@babel/generator": "^7.21.5", + "@babel/helper-environment-visitor": "^7.21.5", + "@babel/helper-function-name": "^7.21.0", + "@babel/helper-hoist-variables": "^7.18.6", + "@babel/helper-split-export-declaration": "^7.18.6", + "@babel/parser": "^7.21.5", + "@babel/types": "^7.21.5", + "debug": "^4.1.0", + "globals": "^11.1.0" + }, + "dependencies": { + "@babel/code-frame": { + "version": "7.21.4", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.21.4.tgz", + "integrity": "sha512-LYvhNKfwWSPpocw8GI7gpK2nq3HSDuEPC/uSYaALSJu9xjsalaaYFOq0Pwt5KmVqwEbZlDu81aLXwBOmD/Fv9g==", + "dev": true, + "requires": { + "@babel/highlight": "^7.18.6" + } + }, + "@babel/generator": { + "version": "7.21.9", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.21.9.tgz", + "integrity": "sha512-F3fZga2uv09wFdEjEQIJxXALXfz0+JaOb7SabvVMmjHxeVTuGW8wgE8Vp1Hd7O+zMTYtcfEISGRzPkeiaPPsvg==", + "dev": true, + "requires": { + "@babel/types": "^7.21.5", + "@jridgewell/gen-mapping": "^0.3.2", + "@jridgewell/trace-mapping": "^0.3.17", + "jsesc": "^2.5.1" + } + }, + "@babel/helper-validator-identifier": { + "version": "7.19.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz", + "integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==", + "dev": true + }, + "@babel/highlight": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz", + "integrity": "sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.18.6", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + } + }, + "@jridgewell/gen-mapping": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", + "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==", + "dev": true, + "requires": { + "@jridgewell/set-array": "^1.0.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.9" + } + }, + "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": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true + }, + "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/types": { + "version": "7.21.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.21.5.tgz", + "integrity": "sha512-m4AfNvVF2mVC/F7fDEdH2El3HzUg9It/XsCxZiOTTA3m3qYfcSVSbTfM6Q9xG+hYDniZssYhlXKKUMD5m8tF4Q==", + "dev": true, + "requires": { + "@babel/helper-string-parser": "^7.21.5", + "@babel/helper-validator-identifier": "^7.19.1", + "to-fast-properties": "^2.0.0" + }, + "dependencies": { + "@babel/helper-validator-identifier": { + "version": "7.19.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz", + "integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==", + "dev": true + } + } + }, + "@colors/colors": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", + "integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==", + "dev": true + }, + "@cspotcode/source-map-consumer": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-consumer/-/source-map-consumer-0.8.0.tgz", + "integrity": "sha512-41qniHzTU8yAGbCp04ohlmSrZf8bkf/iJsl3V0dRGsQN/5GFfx+LbCSsCpp2gqrqjTVg/K6O8ycoV35JIwAzAg==", + "dev": true + }, + "@cspotcode/source-map-support": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.7.0.tgz", + "integrity": "sha512-X4xqRHqN8ACt2aHVe51OxeA2HjbcL4MqFqXkrmQszJ1NOUuUu5u6Vqx/0lZSVNku7velL5FC/s5uEAj1lsBMhA==", + "dev": true, + "requires": { + "@cspotcode/source-map-consumer": "0.8.0" + } + }, + "@ctrl/tinycolor": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/@ctrl/tinycolor/-/tinycolor-3.6.0.tgz", + "integrity": "sha512-/Z3l6pXthq0JvMYdUFyX9j0MaCltlIn6mfh9jLyQwg5aPKxkyNa0PTHtU1AlFXLNk55ZuAeJRcpvq+tmLfKmaQ==" + }, + "@discoveryjs/json-ext": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz", + "integrity": "sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==", + "dev": true + }, + "@esbuild/android-arm": { + "version": "0.17.8", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.17.8.tgz", + "integrity": "sha512-0/rb91GYKhrtbeglJXOhAv9RuYimgI8h623TplY2X+vA4EXnk3Zj1fXZreJ0J3OJJu1bwmb0W7g+2cT/d8/l/w==", + "dev": true, + "optional": true + }, + "@esbuild/android-arm64": { + "version": "0.17.8", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.17.8.tgz", + "integrity": "sha512-oa/N5j6v1svZQs7EIRPqR8f+Bf8g6HBDjD/xHC02radE/NjKHK7oQmtmLxPs1iVwYyvE+Kolo6lbpfEQ9xnhxQ==", + "dev": true, + "optional": true + }, + "@esbuild/android-x64": { + "version": "0.17.8", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.17.8.tgz", + "integrity": "sha512-bTliMLqD7pTOoPg4zZkXqCDuzIUguEWLpeqkNfC41ODBHwoUgZ2w5JBeYimv4oP6TDVocoYmEhZrCLQTrH89bg==", + "dev": true, + "optional": true + }, + "@esbuild/darwin-arm64": { + "version": "0.17.8", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.17.8.tgz", + "integrity": "sha512-ghAbV3ia2zybEefXRRm7+lx8J/rnupZT0gp9CaGy/3iolEXkJ6LYRq4IpQVI9zR97ID80KJVoUlo3LSeA/sMAg==", + "dev": true, + "optional": true + }, + "@esbuild/darwin-x64": { + "version": "0.17.8", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.17.8.tgz", + "integrity": "sha512-n5WOpyvZ9TIdv2V1K3/iIkkJeKmUpKaCTdun9buhGRWfH//osmUjlv4Z5mmWdPWind/VGcVxTHtLfLCOohsOXw==", + "dev": true, + "optional": true + }, + "@esbuild/freebsd-arm64": { + "version": "0.17.8", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.17.8.tgz", + "integrity": "sha512-a/SATTaOhPIPFWvHZDoZYgxaZRVHn0/LX1fHLGfZ6C13JqFUZ3K6SMD6/HCtwOQ8HnsNaEeokdiDSFLuizqv5A==", + "dev": true, + "optional": true + }, + "@esbuild/freebsd-x64": { + "version": "0.17.8", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.17.8.tgz", + "integrity": "sha512-xpFJb08dfXr5+rZc4E+ooZmayBW6R3q59daCpKZ/cDU96/kvDM+vkYzNeTJCGd8rtO6fHWMq5Rcv/1cY6p6/0Q==", + "dev": true, + "optional": true + }, + "@esbuild/linux-arm": { + "version": "0.17.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.17.8.tgz", + "integrity": "sha512-6Ij8gfuGszcEwZpi5jQIJCVIACLS8Tz2chnEBfYjlmMzVsfqBP1iGmHQPp7JSnZg5xxK9tjCc+pJ2WtAmPRFVA==", + "dev": true, + "optional": true + }, + "@esbuild/linux-arm64": { + "version": "0.17.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.17.8.tgz", + "integrity": "sha512-v3iwDQuDljLTxpsqQDl3fl/yihjPAyOguxuloON9kFHYwopeJEf1BkDXODzYyXEI19gisEsQlG1bM65YqKSIww==", + "dev": true, + "optional": true + }, + "@esbuild/linux-ia32": { + "version": "0.17.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.17.8.tgz", + "integrity": "sha512-8svILYKhE5XetuFk/B6raFYIyIqydQi+GngEXJgdPdI7OMKUbSd7uzR02wSY4kb53xBrClLkhH4Xs8P61Q2BaA==", + "dev": true, + "optional": true + }, + "@esbuild/linux-loong64": { + "version": "0.17.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.17.8.tgz", + "integrity": "sha512-B6FyMeRJeV0NpyEOYlm5qtQfxbdlgmiGdD+QsipzKfFky0K5HW5Td6dyK3L3ypu1eY4kOmo7wW0o94SBqlqBSA==", + "dev": true, + "optional": true + }, + "@esbuild/linux-mips64el": { + "version": "0.17.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.17.8.tgz", + "integrity": "sha512-CCb67RKahNobjm/eeEqeD/oJfJlrWyw29fgiyB6vcgyq97YAf3gCOuP6qMShYSPXgnlZe/i4a8WFHBw6N8bYAA==", + "dev": true, + "optional": true + }, + "@esbuild/linux-ppc64": { + "version": "0.17.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.17.8.tgz", + "integrity": "sha512-bytLJOi55y55+mGSdgwZ5qBm0K9WOCh0rx+vavVPx+gqLLhxtSFU0XbeYy/dsAAD6xECGEv4IQeFILaSS2auXw==", + "dev": true, + "optional": true + }, + "@esbuild/linux-riscv64": { + "version": "0.17.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.17.8.tgz", + "integrity": "sha512-2YpRyQJmKVBEHSBLa8kBAtbhucaclb6ex4wchfY0Tj3Kg39kpjeJ9vhRU7x4mUpq8ISLXRXH1L0dBYjAeqzZAw==", + "dev": true, + "optional": true + }, + "@esbuild/linux-s390x": { + "version": "0.17.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.17.8.tgz", + "integrity": "sha512-QgbNY/V3IFXvNf11SS6exkpVcX0LJcob+0RWCgV9OiDAmVElnxciHIisoSix9uzYzScPmS6dJFbZULdSAEkQVw==", + "dev": true, + "optional": true + }, + "@esbuild/linux-x64": { + "version": "0.17.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.17.8.tgz", + "integrity": "sha512-mM/9S0SbAFDBc4OPoyP6SEOo5324LpUxdpeIUUSrSTOfhHU9hEfqRngmKgqILqwx/0DVJBzeNW7HmLEWp9vcOA==", + "dev": true, + "optional": true + }, + "@esbuild/netbsd-x64": { + "version": "0.17.8", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.17.8.tgz", + "integrity": "sha512-eKUYcWaWTaYr9zbj8GertdVtlt1DTS1gNBWov+iQfWuWyuu59YN6gSEJvFzC5ESJ4kMcKR0uqWThKUn5o8We6Q==", + "dev": true, + "optional": true + }, + "@esbuild/openbsd-x64": { + "version": "0.17.8", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.17.8.tgz", + "integrity": "sha512-Vc9J4dXOboDyMXKD0eCeW0SIeEzr8K9oTHJU+Ci1mZc5njPfhKAqkRt3B/fUNU7dP+mRyralPu8QUkiaQn7iIg==", + "dev": true, + "optional": true + }, + "@esbuild/sunos-x64": { + "version": "0.17.8", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.17.8.tgz", + "integrity": "sha512-0xvOTNuPXI7ft1LYUgiaXtpCEjp90RuBBYovdd2lqAFxje4sEucurg30M1WIm03+3jxByd3mfo+VUmPtRSVuOw==", + "dev": true, + "optional": true + }, + "@esbuild/win32-arm64": { + "version": "0.17.8", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.17.8.tgz", + "integrity": "sha512-G0JQwUI5WdEFEnYNKzklxtBheCPkuDdu1YrtRrjuQv30WsYbkkoixKxLLv8qhJmNI+ATEWquZe/N0d0rpr55Mg==", + "dev": true, + "optional": true + }, + "@esbuild/win32-ia32": { + "version": "0.17.8", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.17.8.tgz", + "integrity": "sha512-Fqy63515xl20OHGFykjJsMnoIWS+38fqfg88ClvPXyDbLtgXal2DTlhb1TfTX34qWi3u4I7Cq563QcHpqgLx8w==", + "dev": true, + "optional": true + }, + "@esbuild/win32-x64": { + "version": "0.17.8", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.17.8.tgz", + "integrity": "sha512-1iuezdyDNngPnz8rLRDO2C/ZZ/emJLb72OsZeqQ6gL6Avko/XCXZw+NuxBSNhBAP13Hie418V7VMt9et1FMvpg==", + "dev": true, + "optional": true + }, + "@eslint/eslintrc": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.3.0.tgz", + "integrity": "sha512-UWW0TMTmk2d7hLcWD1/e2g5HDM/HQ3csaLSqXCfqwh4uNDuNqlaKWXmEsL4Cs41Z0KnILNvwbHAah3C2yt06kw==", + "requires": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.3.2", + "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" + }, + "dependencies": { + "ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "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" + } + }, + "argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" + }, + "globals": { + "version": "13.16.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.16.0.tgz", + "integrity": "sha512-A1lrQfpNF+McdPOnnFqY3kSN0AFTy485bTi1bkLk4mVPODIUEcSfhHgRqA+QdXPksrSTTztYXx37NFV+GpGk3Q==", + "requires": { + "type-fest": "^0.20.2" + } + }, + "js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "requires": { + "argparse": "^2.0.1" + } + }, + "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==" + }, + "minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "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==" + } + } + }, + "@gar/promisify": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@gar/promisify/-/promisify-1.1.3.tgz", + "integrity": "sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw==", + "dev": true + }, + "@humanwhocodes/config-array": { + "version": "0.9.5", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.9.5.tgz", + "integrity": "sha512-ObyMyWxZiCu/yTisA7uzx81s40xR2fD5Cg/2Kq7G02ajkNubJf6BopgDTmDyc3U7sXpNKM8cYOw7s7Tyr+DnCw==", + "requires": { + "@humanwhocodes/object-schema": "^1.2.1", + "debug": "^4.1.1", + "minimatch": "^3.0.4" + } + }, + "@humanwhocodes/object-schema": { + "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==" + }, + "@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dev": true, + "requires": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "dev": true + }, + "ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "dev": true + }, + "emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true + }, + "string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "requires": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + } + }, + "string-width-cjs": { + "version": "npm:string-width@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" + }, + "dependencies": { + "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 + }, + "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 + }, + "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-ansi": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.0.1.tgz", + "integrity": "sha512-cXNxvT8dFNRVfhVME3JAe98mkXDYN2O1l7jmcwMnOslDeESg1rF/OZMtK0nRAhiari1unG5cD4jG3rapUAkLbw==", + "dev": true, + "requires": { + "ansi-regex": "^6.0.1" + } + }, + "strip-ansi-cjs": { + "version": "npm:strip-ansi@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" + }, + "dependencies": { + "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 + } + } + }, + "wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dev": true, + "requires": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + } + }, + "wrap-ansi-cjs": { + "version": "npm:wrap-ansi@7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "requires": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "dependencies": { + "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" + } + }, + "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 + }, + "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" + } + } + } + } + } + }, + "@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 + }, + "@jridgewell/gen-mapping": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.1.1.tgz", + "integrity": "sha512-sQXCasFk+U8lWYEe66WxRDOE9PjVz4vSM51fTu3Hw+ClTpUSQb718772vH3pyS5pShp6lvQM7SxgIDXXXmOX7w==", + "dev": true, + "requires": { + "@jridgewell/set-array": "^1.0.0", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, + "@jridgewell/resolve-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", + "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==", + "dev": true + }, + "@jridgewell/set-array": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", + "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", + "dev": true + }, + "@jridgewell/source-map": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.3.tgz", + "integrity": "sha512-b+fsZXeLYi9fEULmfBrhxn4IrPlINf8fiNarzTof004v3lFdntdwa9PF7vFJqm3mg7s+ScJMxXaE3Acp1irZcg==", + "dev": true, + "requires": { + "@jridgewell/gen-mapping": "^0.3.0", + "@jridgewell/trace-mapping": "^0.3.9" + }, + "dependencies": { + "@jridgewell/gen-mapping": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", + "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==", + "dev": true, + "requires": { + "@jridgewell/set-array": "^1.0.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.9" + } + } + } + }, + "@jridgewell/sourcemap-codec": { + "version": "1.4.15", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", + "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", + "dev": true + }, + "@jridgewell/trace-mapping": { + "version": "0.3.18", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.18.tgz", + "integrity": "sha512-w+niJYzMHdd7USdiH2U6869nqhD2nbfZXND5Yp93qIbEmnDNk7PD48o+YchRVpzMU7M6jVCbenTR7PA1FLQ9pA==", + "dev": true, + "requires": { + "@jridgewell/resolve-uri": "3.1.0", + "@jridgewell/sourcemap-codec": "1.4.14" + }, + "dependencies": { + "@jridgewell/sourcemap-codec": { + "version": "1.4.14", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", + "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==", + "dev": true + } + } + }, + "@leichtgewicht/ip-codec": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.4.tgz", + "integrity": "sha512-Hcv+nVC0kZnQ3tD9GVu5xSMR4VVYOteQIr/hwFPVEvPdlXqgGEuRjiheChHgdM+JyqdgNcmzZOX/tnl0JOiI7A==", + "dev": true + }, + "@material/animation": { + "version": "15.0.0-canary.684e33d25.0", + "resolved": "https://registry.npmjs.org/@material/animation/-/animation-15.0.0-canary.684e33d25.0.tgz", + "integrity": "sha512-5osi1z4JQIXcklPALbH/zTfOm2pDzHt9Fxm7ZyURy250xIZj6QjULRzPTnzOhC2ropfix9ra2Cfggbf0dcRbEQ==", + "requires": { + "tslib": "^2.1.0" + } + }, + "@material/auto-init": { + "version": "15.0.0-canary.684e33d25.0", + "resolved": "https://registry.npmjs.org/@material/auto-init/-/auto-init-15.0.0-canary.684e33d25.0.tgz", + "integrity": "sha512-OigQTmrVzkcGvxNjOaIe5oItTFPgrO9xLewvharDI6m6yvO1z7OBnkcW+sFN6ggLNYNxd0O1u9v64vMsmeDABQ==", + "requires": { + "@material/base": "15.0.0-canary.684e33d25.0", + "tslib": "^2.1.0" + } + }, + "@material/banner": { + "version": "15.0.0-canary.684e33d25.0", + "resolved": "https://registry.npmjs.org/@material/banner/-/banner-15.0.0-canary.684e33d25.0.tgz", + "integrity": "sha512-PqtGp3KWzdu58rWv/DIvSfe38m5YKOBbAAbBinSvgadBb/da+IE1t5F7YPNKE1T5lJsQBGVUYx6QBIeXm+aI/A==", + "requires": { + "@material/base": "15.0.0-canary.684e33d25.0", + "@material/button": "15.0.0-canary.684e33d25.0", + "@material/dom": "15.0.0-canary.684e33d25.0", + "@material/elevation": "15.0.0-canary.684e33d25.0", + "@material/feature-targeting": "15.0.0-canary.684e33d25.0", + "@material/ripple": "15.0.0-canary.684e33d25.0", + "@material/rtl": "15.0.0-canary.684e33d25.0", + "@material/shape": "15.0.0-canary.684e33d25.0", + "@material/theme": "15.0.0-canary.684e33d25.0", + "@material/tokens": "15.0.0-canary.684e33d25.0", + "@material/typography": "15.0.0-canary.684e33d25.0", + "tslib": "^2.1.0" + } + }, + "@material/base": { + "version": "15.0.0-canary.684e33d25.0", + "resolved": "https://registry.npmjs.org/@material/base/-/base-15.0.0-canary.684e33d25.0.tgz", + "integrity": "sha512-oOaqb/SfjWwTKsdJUZmeh/Qrs41nIJI0N+zELsxnvbGjSIN1ZMAKYZFPMahqvC68OJ6+5CvJM8PoTNs5l+B8IQ==", + "requires": { + "tslib": "^2.1.0" + } + }, + "@material/button": { + "version": "15.0.0-canary.684e33d25.0", + "resolved": "https://registry.npmjs.org/@material/button/-/button-15.0.0-canary.684e33d25.0.tgz", + "integrity": "sha512-Nkekk4edeX+ObVOa7UlwavaHdmckPV5wU4SAJf3iA3R61cmz+KsgAgpzfcwv5WfNhIlc2nLu8QYEecpHdo9d/w==", + "requires": { + "@material/density": "15.0.0-canary.684e33d25.0", + "@material/dom": "15.0.0-canary.684e33d25.0", + "@material/elevation": "15.0.0-canary.684e33d25.0", + "@material/feature-targeting": "15.0.0-canary.684e33d25.0", + "@material/focus-ring": "15.0.0-canary.684e33d25.0", + "@material/ripple": "15.0.0-canary.684e33d25.0", + "@material/rtl": "15.0.0-canary.684e33d25.0", + "@material/shape": "15.0.0-canary.684e33d25.0", + "@material/theme": "15.0.0-canary.684e33d25.0", + "@material/tokens": "15.0.0-canary.684e33d25.0", + "@material/touch-target": "15.0.0-canary.684e33d25.0", + "@material/typography": "15.0.0-canary.684e33d25.0", + "tslib": "^2.1.0" + } + }, + "@material/card": { + "version": "15.0.0-canary.684e33d25.0", + "resolved": "https://registry.npmjs.org/@material/card/-/card-15.0.0-canary.684e33d25.0.tgz", + "integrity": "sha512-xhyB7XX5KkEiCEqwSPkl58ZGYL6xFdnY62zimyBXJRG/Eaa0Swj3kW20hVCpt4f7c9Zmp8Se27rg8vnKmhvO3g==", + "requires": { + "@material/dom": "15.0.0-canary.684e33d25.0", + "@material/elevation": "15.0.0-canary.684e33d25.0", + "@material/feature-targeting": "15.0.0-canary.684e33d25.0", + "@material/ripple": "15.0.0-canary.684e33d25.0", + "@material/rtl": "15.0.0-canary.684e33d25.0", + "@material/shape": "15.0.0-canary.684e33d25.0", + "@material/theme": "15.0.0-canary.684e33d25.0", + "@material/tokens": "15.0.0-canary.684e33d25.0", + "tslib": "^2.1.0" + } + }, + "@material/checkbox": { + "version": "15.0.0-canary.684e33d25.0", + "resolved": "https://registry.npmjs.org/@material/checkbox/-/checkbox-15.0.0-canary.684e33d25.0.tgz", + "integrity": "sha512-NFpM3TS924PmVsk2KQLNU95OYCf8ZwYgzeqfnAexU0bEfjUJXINBun2Go0AaeOUMjuvWUe+byjrXgv8SFYbMUA==", + "requires": { + "@material/animation": "15.0.0-canary.684e33d25.0", + "@material/base": "15.0.0-canary.684e33d25.0", + "@material/density": "15.0.0-canary.684e33d25.0", + "@material/dom": "15.0.0-canary.684e33d25.0", + "@material/feature-targeting": "15.0.0-canary.684e33d25.0", + "@material/focus-ring": "15.0.0-canary.684e33d25.0", + "@material/ripple": "15.0.0-canary.684e33d25.0", + "@material/theme": "15.0.0-canary.684e33d25.0", + "@material/touch-target": "15.0.0-canary.684e33d25.0", + "tslib": "^2.1.0" + } + }, + "@material/chips": { + "version": "15.0.0-canary.684e33d25.0", + "resolved": "https://registry.npmjs.org/@material/chips/-/chips-15.0.0-canary.684e33d25.0.tgz", + "integrity": "sha512-z4ajQ4NnsAQ/Si9tZ4xmxzjj2Qb+vW++4QjCjjjwAGIZbCe0xglAnMh2t66XLJUxt7RoKZuZVEO7ZqcFZpvJFQ==", + "requires": { + "@material/animation": "15.0.0-canary.684e33d25.0", + "@material/base": "15.0.0-canary.684e33d25.0", + "@material/checkbox": "15.0.0-canary.684e33d25.0", + "@material/density": "15.0.0-canary.684e33d25.0", + "@material/dom": "15.0.0-canary.684e33d25.0", + "@material/elevation": "15.0.0-canary.684e33d25.0", + "@material/feature-targeting": "15.0.0-canary.684e33d25.0", + "@material/focus-ring": "15.0.0-canary.684e33d25.0", + "@material/ripple": "15.0.0-canary.684e33d25.0", + "@material/rtl": "15.0.0-canary.684e33d25.0", + "@material/shape": "15.0.0-canary.684e33d25.0", + "@material/theme": "15.0.0-canary.684e33d25.0", + "@material/tokens": "15.0.0-canary.684e33d25.0", + "@material/touch-target": "15.0.0-canary.684e33d25.0", + "@material/typography": "15.0.0-canary.684e33d25.0", + "safevalues": "^0.3.4", + "tslib": "^2.1.0" + } + }, + "@material/circular-progress": { + "version": "15.0.0-canary.684e33d25.0", + "resolved": "https://registry.npmjs.org/@material/circular-progress/-/circular-progress-15.0.0-canary.684e33d25.0.tgz", + "integrity": "sha512-G6qD0nGNtEUwWnAMJuA9INYFpZoKtx7KFjBaPF4Ol2YLHtmShALNAYyn54TMAK8AZ2IpW08PXjGS7Ye88vrdEQ==", + "requires": { + "@material/animation": "15.0.0-canary.684e33d25.0", + "@material/base": "15.0.0-canary.684e33d25.0", + "@material/dom": "15.0.0-canary.684e33d25.0", + "@material/feature-targeting": "15.0.0-canary.684e33d25.0", + "@material/progress-indicator": "15.0.0-canary.684e33d25.0", + "@material/rtl": "15.0.0-canary.684e33d25.0", + "@material/theme": "15.0.0-canary.684e33d25.0", + "tslib": "^2.1.0" + } + }, + "@material/data-table": { + "version": "15.0.0-canary.684e33d25.0", + "resolved": "https://registry.npmjs.org/@material/data-table/-/data-table-15.0.0-canary.684e33d25.0.tgz", + "integrity": "sha512-+wDw1DDDFfAsKAMzs84f/5GCjux39zjNfW8tL4wFbkWNwewmQrG9zaQMJhBpVOtLCrM8Gj6SOgOANqgqoCjvGg==", + "requires": { + "@material/animation": "15.0.0-canary.684e33d25.0", + "@material/base": "15.0.0-canary.684e33d25.0", + "@material/checkbox": "15.0.0-canary.684e33d25.0", + "@material/density": "15.0.0-canary.684e33d25.0", + "@material/dom": "15.0.0-canary.684e33d25.0", + "@material/elevation": "15.0.0-canary.684e33d25.0", + "@material/feature-targeting": "15.0.0-canary.684e33d25.0", + "@material/icon-button": "15.0.0-canary.684e33d25.0", + "@material/linear-progress": "15.0.0-canary.684e33d25.0", + "@material/list": "15.0.0-canary.684e33d25.0", + "@material/menu": "15.0.0-canary.684e33d25.0", + "@material/rtl": "15.0.0-canary.684e33d25.0", + "@material/select": "15.0.0-canary.684e33d25.0", + "@material/shape": "15.0.0-canary.684e33d25.0", + "@material/theme": "15.0.0-canary.684e33d25.0", + "@material/tokens": "15.0.0-canary.684e33d25.0", + "@material/touch-target": "15.0.0-canary.684e33d25.0", + "@material/typography": "15.0.0-canary.684e33d25.0", + "tslib": "^2.1.0" + } + }, + "@material/density": { + "version": "15.0.0-canary.684e33d25.0", + "resolved": "https://registry.npmjs.org/@material/density/-/density-15.0.0-canary.684e33d25.0.tgz", + "integrity": "sha512-661yEVRMGrlq6S6WuSbPRO+ZwpdUOg2glCc7y96doM6itSLOa3UEAldjOLfsYZVB74GnKCiuDp//QmfoRyYTfA==", + "requires": { + "tslib": "^2.1.0" + } + }, + "@material/dialog": { + "version": "15.0.0-canary.684e33d25.0", + "resolved": "https://registry.npmjs.org/@material/dialog/-/dialog-15.0.0-canary.684e33d25.0.tgz", + "integrity": "sha512-szn0dHnfeQTSOC6SSRSGAzX6Tnx+4NnSMUwNkXm+3bwjds8ZVK26+DXwLrP5f3ID5F1K5sFsRf2INo5/TNTHyQ==", + "requires": { + "@material/animation": "15.0.0-canary.684e33d25.0", + "@material/base": "15.0.0-canary.684e33d25.0", + "@material/button": "15.0.0-canary.684e33d25.0", + "@material/dom": "15.0.0-canary.684e33d25.0", + "@material/elevation": "15.0.0-canary.684e33d25.0", + "@material/feature-targeting": "15.0.0-canary.684e33d25.0", + "@material/icon-button": "15.0.0-canary.684e33d25.0", + "@material/ripple": "15.0.0-canary.684e33d25.0", + "@material/rtl": "15.0.0-canary.684e33d25.0", + "@material/shape": "15.0.0-canary.684e33d25.0", + "@material/theme": "15.0.0-canary.684e33d25.0", + "@material/tokens": "15.0.0-canary.684e33d25.0", + "@material/touch-target": "15.0.0-canary.684e33d25.0", + "@material/typography": "15.0.0-canary.684e33d25.0", + "tslib": "^2.1.0" + } + }, + "@material/dom": { + "version": "15.0.0-canary.684e33d25.0", + "resolved": "https://registry.npmjs.org/@material/dom/-/dom-15.0.0-canary.684e33d25.0.tgz", + "integrity": "sha512-7pEJLYov+tGgfuD8mZxoVU6rWtPI8ppjTAhz+F27Hz9FG0JETMWTKpDPBXLnKvX7vhIxL83GvZ9geNHCe8Hfog==", + "requires": { + "@material/feature-targeting": "15.0.0-canary.684e33d25.0", + "@material/rtl": "15.0.0-canary.684e33d25.0", + "tslib": "^2.1.0" + } + }, + "@material/drawer": { + "version": "15.0.0-canary.684e33d25.0", + "resolved": "https://registry.npmjs.org/@material/drawer/-/drawer-15.0.0-canary.684e33d25.0.tgz", + "integrity": "sha512-/KMckLf1PYU/H3PXnS4e0aFl03qG3JlSv4LGgX6juJufcONqGTl/m63EMO/L/eUy6H1CRrXmVDjik/jzHLyDhg==", + "requires": { + "@material/animation": "15.0.0-canary.684e33d25.0", + "@material/base": "15.0.0-canary.684e33d25.0", + "@material/dom": "15.0.0-canary.684e33d25.0", + "@material/elevation": "15.0.0-canary.684e33d25.0", + "@material/feature-targeting": "15.0.0-canary.684e33d25.0", + "@material/list": "15.0.0-canary.684e33d25.0", + "@material/ripple": "15.0.0-canary.684e33d25.0", + "@material/rtl": "15.0.0-canary.684e33d25.0", + "@material/shape": "15.0.0-canary.684e33d25.0", + "@material/theme": "15.0.0-canary.684e33d25.0", + "@material/typography": "15.0.0-canary.684e33d25.0", + "tslib": "^2.1.0" + } + }, + "@material/elevation": { + "version": "15.0.0-canary.684e33d25.0", + "resolved": "https://registry.npmjs.org/@material/elevation/-/elevation-15.0.0-canary.684e33d25.0.tgz", + "integrity": "sha512-WDF8SsRtq3rXUbVVbd9K4DUijIPH0bUFSOreVYxudpuxAfTlDS5+aeS1EK9UIBFYLuba4u5wVT2tDv6e1RTfrQ==", + "requires": { + "@material/animation": "15.0.0-canary.684e33d25.0", + "@material/base": "15.0.0-canary.684e33d25.0", + "@material/feature-targeting": "15.0.0-canary.684e33d25.0", + "@material/rtl": "15.0.0-canary.684e33d25.0", + "@material/theme": "15.0.0-canary.684e33d25.0", + "tslib": "^2.1.0" + } + }, + "@material/fab": { + "version": "15.0.0-canary.684e33d25.0", + "resolved": "https://registry.npmjs.org/@material/fab/-/fab-15.0.0-canary.684e33d25.0.tgz", + "integrity": "sha512-KCu87rWOKEAe9vZcAm6K8XazYSWPNjMG+OhrbPjHW6bCO7as1YCgtmkBkhff7csY/rFmcVpIy884xtUfLmSudQ==", + "requires": { + "@material/animation": "15.0.0-canary.684e33d25.0", + "@material/dom": "15.0.0-canary.684e33d25.0", + "@material/elevation": "15.0.0-canary.684e33d25.0", + "@material/feature-targeting": "15.0.0-canary.684e33d25.0", + "@material/focus-ring": "15.0.0-canary.684e33d25.0", + "@material/ripple": "15.0.0-canary.684e33d25.0", + "@material/rtl": "15.0.0-canary.684e33d25.0", + "@material/shape": "15.0.0-canary.684e33d25.0", + "@material/theme": "15.0.0-canary.684e33d25.0", + "@material/tokens": "15.0.0-canary.684e33d25.0", + "@material/touch-target": "15.0.0-canary.684e33d25.0", + "@material/typography": "15.0.0-canary.684e33d25.0", + "tslib": "^2.1.0" + } + }, + "@material/feature-targeting": { + "version": "15.0.0-canary.684e33d25.0", + "resolved": "https://registry.npmjs.org/@material/feature-targeting/-/feature-targeting-15.0.0-canary.684e33d25.0.tgz", + "integrity": "sha512-HyH1erNTSjS63sigNSUMaCd0nJhTNdDFeC+myrxwtDaQm+uYJ8troCNtQM3g6mx0XATNtX5aTOoPmrM6yVVi1A==", + "requires": { + "tslib": "^2.1.0" + } + }, + "@material/floating-label": { + "version": "15.0.0-canary.684e33d25.0", + "resolved": "https://registry.npmjs.org/@material/floating-label/-/floating-label-15.0.0-canary.684e33d25.0.tgz", + "integrity": "sha512-f7TPp6bKpGvV3sYYiZHSGlrixXKkXXITW3Esp7KB9jRq42c0H82novmdwvY0eTef4ootmA2JEysr78KQfHBUPg==", + "requires": { + "@material/animation": "15.0.0-canary.684e33d25.0", + "@material/base": "15.0.0-canary.684e33d25.0", + "@material/dom": "15.0.0-canary.684e33d25.0", + "@material/feature-targeting": "15.0.0-canary.684e33d25.0", + "@material/rtl": "15.0.0-canary.684e33d25.0", + "@material/theme": "15.0.0-canary.684e33d25.0", + "@material/typography": "15.0.0-canary.684e33d25.0", + "tslib": "^2.1.0" + } + }, + "@material/focus-ring": { + "version": "15.0.0-canary.684e33d25.0", + "resolved": "https://registry.npmjs.org/@material/focus-ring/-/focus-ring-15.0.0-canary.684e33d25.0.tgz", + "integrity": "sha512-ikw2RVUfgzXChpWIzPH1VzRvTjYb5ZKj4H+CZf7jqPUXMstFOZg90Bp7ARLZHqYiyNMuUq3zUTHozS6iHorSqg==", + "requires": { + "@material/dom": "15.0.0-canary.684e33d25.0", + "@material/feature-targeting": "15.0.0-canary.684e33d25.0", + "@material/rtl": "15.0.0-canary.684e33d25.0" + } + }, + "@material/form-field": { + "version": "15.0.0-canary.684e33d25.0", + "resolved": "https://registry.npmjs.org/@material/form-field/-/form-field-15.0.0-canary.684e33d25.0.tgz", + "integrity": "sha512-vpF9N/uq5no/7+8GAbEH0868FhOuBgxAWRr1Sfb+jthKfBr8OS/wPU/AHzZHdHdAm7PQynbeOXfDsX2dI//PDA==", + "requires": { + "@material/base": "15.0.0-canary.684e33d25.0", + "@material/feature-targeting": "15.0.0-canary.684e33d25.0", + "@material/ripple": "15.0.0-canary.684e33d25.0", + "@material/rtl": "15.0.0-canary.684e33d25.0", + "@material/theme": "15.0.0-canary.684e33d25.0", + "@material/typography": "15.0.0-canary.684e33d25.0", + "tslib": "^2.1.0" + } + }, + "@material/icon-button": { + "version": "15.0.0-canary.684e33d25.0", + "resolved": "https://registry.npmjs.org/@material/icon-button/-/icon-button-15.0.0-canary.684e33d25.0.tgz", + "integrity": "sha512-wMI+XGzmIN/o2ePBKg2hLyx7H4pXCRAyyIKMQS1FMp1UKa2tYmiHVX/V8skhKwCqxg3i6Ls/LxMjfPxTR18WvQ==", + "requires": { + "@material/base": "15.0.0-canary.684e33d25.0", + "@material/density": "15.0.0-canary.684e33d25.0", + "@material/dom": "15.0.0-canary.684e33d25.0", + "@material/elevation": "15.0.0-canary.684e33d25.0", + "@material/feature-targeting": "15.0.0-canary.684e33d25.0", + "@material/focus-ring": "15.0.0-canary.684e33d25.0", + "@material/ripple": "15.0.0-canary.684e33d25.0", + "@material/rtl": "15.0.0-canary.684e33d25.0", + "@material/theme": "15.0.0-canary.684e33d25.0", + "@material/touch-target": "15.0.0-canary.684e33d25.0", + "tslib": "^2.1.0" + } + }, + "@material/image-list": { + "version": "15.0.0-canary.684e33d25.0", + "resolved": "https://registry.npmjs.org/@material/image-list/-/image-list-15.0.0-canary.684e33d25.0.tgz", + "integrity": "sha512-Ol+uaHYBe5R/cgzlfh5ONnMVX0wO6fV74JMUcQCQlxP6lXau/edARo4tkRc7A7UJUkU3VRv0EpEjLoCRNUPGaA==", + "requires": { + "@material/feature-targeting": "15.0.0-canary.684e33d25.0", + "@material/shape": "15.0.0-canary.684e33d25.0", + "@material/theme": "15.0.0-canary.684e33d25.0", + "@material/typography": "15.0.0-canary.684e33d25.0", + "tslib": "^2.1.0" + } + }, + "@material/layout-grid": { + "version": "15.0.0-canary.684e33d25.0", + "resolved": "https://registry.npmjs.org/@material/layout-grid/-/layout-grid-15.0.0-canary.684e33d25.0.tgz", + "integrity": "sha512-ALXE1mqFNb/RB2lVRQ3/r1Aufw2mFZnOjRE+boYDVepmAG/xWyPCyaGoavELJF5l4GAb0tXi8wA/8HeGbLOpuA==", + "requires": { + "tslib": "^2.1.0" + } + }, + "@material/line-ripple": { + "version": "15.0.0-canary.684e33d25.0", + "resolved": "https://registry.npmjs.org/@material/line-ripple/-/line-ripple-15.0.0-canary.684e33d25.0.tgz", + "integrity": "sha512-7hRx8C/e9i0P6pgQpNOMfTwSS2r1fwEvBL72QDVGLtLuoKKwsjjgP6Z0Jat/GeHJe87u9LQvGBoD4upt+of/HA==", + "requires": { + "@material/animation": "15.0.0-canary.684e33d25.0", + "@material/base": "15.0.0-canary.684e33d25.0", + "@material/feature-targeting": "15.0.0-canary.684e33d25.0", + "@material/theme": "15.0.0-canary.684e33d25.0", + "tslib": "^2.1.0" + } + }, + "@material/linear-progress": { + "version": "15.0.0-canary.684e33d25.0", + "resolved": "https://registry.npmjs.org/@material/linear-progress/-/linear-progress-15.0.0-canary.684e33d25.0.tgz", + "integrity": "sha512-iJclt7mKmcMk6pqD7ocXKfCWZhqBoODp7N593jYlxVpTJuEz2wiVAjZUDn/YGj/Uz3CRH+2YFfOiLr9pwWjhDg==", + "requires": { + "@material/animation": "15.0.0-canary.684e33d25.0", + "@material/base": "15.0.0-canary.684e33d25.0", + "@material/dom": "15.0.0-canary.684e33d25.0", + "@material/feature-targeting": "15.0.0-canary.684e33d25.0", + "@material/progress-indicator": "15.0.0-canary.684e33d25.0", + "@material/rtl": "15.0.0-canary.684e33d25.0", + "@material/theme": "15.0.0-canary.684e33d25.0", + "tslib": "^2.1.0" + } + }, + "@material/list": { + "version": "15.0.0-canary.684e33d25.0", + "resolved": "https://registry.npmjs.org/@material/list/-/list-15.0.0-canary.684e33d25.0.tgz", + "integrity": "sha512-rQ+FCSdzmwTcT00IYE0uRV3CS4oGSccKFl9hkcF+aHFW61L7ORh/SCGUDPrEfQFrFkMn5f8qroVJjpUAMXBz4g==", + "requires": { + "@material/base": "15.0.0-canary.684e33d25.0", + "@material/density": "15.0.0-canary.684e33d25.0", + "@material/dom": "15.0.0-canary.684e33d25.0", + "@material/feature-targeting": "15.0.0-canary.684e33d25.0", + "@material/ripple": "15.0.0-canary.684e33d25.0", + "@material/rtl": "15.0.0-canary.684e33d25.0", + "@material/shape": "15.0.0-canary.684e33d25.0", + "@material/theme": "15.0.0-canary.684e33d25.0", + "@material/tokens": "15.0.0-canary.684e33d25.0", + "@material/typography": "15.0.0-canary.684e33d25.0", + "tslib": "^2.1.0" + } + }, + "@material/menu": { + "version": "15.0.0-canary.684e33d25.0", + "resolved": "https://registry.npmjs.org/@material/menu/-/menu-15.0.0-canary.684e33d25.0.tgz", + "integrity": "sha512-r7wzDLSGSI9629/mfpvsMzkVxpmV75kcD3IrW0Pcu6/Bv/1xi0EvjcUXzNJJoQlwN4Zj35Ymz/PCjZkIDIz68Q==", + "requires": { + "@material/base": "15.0.0-canary.684e33d25.0", + "@material/dom": "15.0.0-canary.684e33d25.0", + "@material/elevation": "15.0.0-canary.684e33d25.0", + "@material/feature-targeting": "15.0.0-canary.684e33d25.0", + "@material/list": "15.0.0-canary.684e33d25.0", + "@material/menu-surface": "15.0.0-canary.684e33d25.0", + "@material/ripple": "15.0.0-canary.684e33d25.0", + "@material/rtl": "15.0.0-canary.684e33d25.0", + "@material/shape": "15.0.0-canary.684e33d25.0", + "@material/theme": "15.0.0-canary.684e33d25.0", + "@material/tokens": "15.0.0-canary.684e33d25.0", + "tslib": "^2.1.0" + } + }, + "@material/menu-surface": { + "version": "15.0.0-canary.684e33d25.0", + "resolved": "https://registry.npmjs.org/@material/menu-surface/-/menu-surface-15.0.0-canary.684e33d25.0.tgz", + "integrity": "sha512-RVO5GAYcfWPaKwxsF/NhUAmrYXQCQBKvRQW0TIlbmAJz6lcFeTs6YZqF3u1C7qrL3ZQGz+sur/7ywj6QU0oMow==", + "requires": { + "@material/animation": "15.0.0-canary.684e33d25.0", + "@material/base": "15.0.0-canary.684e33d25.0", + "@material/elevation": "15.0.0-canary.684e33d25.0", + "@material/feature-targeting": "15.0.0-canary.684e33d25.0", + "@material/rtl": "15.0.0-canary.684e33d25.0", + "@material/shape": "15.0.0-canary.684e33d25.0", + "@material/theme": "15.0.0-canary.684e33d25.0", + "tslib": "^2.1.0" + } + }, + "@material/notched-outline": { + "version": "15.0.0-canary.684e33d25.0", + "resolved": "https://registry.npmjs.org/@material/notched-outline/-/notched-outline-15.0.0-canary.684e33d25.0.tgz", + "integrity": "sha512-9YHcBkvJLPVYzkHcWoTpBZAFrEd+j1hjhGxLhh0LuNrZe8VroUkZD1TTnUAPHRG3os6EqEWWaKb0RN+aPIF2yQ==", + "requires": { + "@material/base": "15.0.0-canary.684e33d25.0", + "@material/feature-targeting": "15.0.0-canary.684e33d25.0", + "@material/floating-label": "15.0.0-canary.684e33d25.0", + "@material/rtl": "15.0.0-canary.684e33d25.0", + "@material/shape": "15.0.0-canary.684e33d25.0", + "@material/theme": "15.0.0-canary.684e33d25.0", + "tslib": "^2.1.0" + } + }, + "@material/progress-indicator": { + "version": "15.0.0-canary.684e33d25.0", + "resolved": "https://registry.npmjs.org/@material/progress-indicator/-/progress-indicator-15.0.0-canary.684e33d25.0.tgz", + "integrity": "sha512-c0icji4faeNWUoqGENGC7Hav0Puxh0RwXIDVizffaUxKIGbajpIp5+4Zop73fK/xFLGMB/npg7TbP+aCGjQ3fw==", + "requires": { + "tslib": "^2.1.0" + } + }, + "@material/radio": { + "version": "15.0.0-canary.684e33d25.0", + "resolved": "https://registry.npmjs.org/@material/radio/-/radio-15.0.0-canary.684e33d25.0.tgz", + "integrity": "sha512-U3Eh8sNUA8trDla1Bq8Bo02foxYvtoewaKeF8A8tAju81XZ4jRiftfOsOWZDZEHCVbbCB2QwvutvFlnay5n+Aw==", + "requires": { + "@material/animation": "15.0.0-canary.684e33d25.0", + "@material/base": "15.0.0-canary.684e33d25.0", + "@material/density": "15.0.0-canary.684e33d25.0", + "@material/dom": "15.0.0-canary.684e33d25.0", + "@material/feature-targeting": "15.0.0-canary.684e33d25.0", + "@material/focus-ring": "15.0.0-canary.684e33d25.0", + "@material/ripple": "15.0.0-canary.684e33d25.0", + "@material/theme": "15.0.0-canary.684e33d25.0", + "@material/touch-target": "15.0.0-canary.684e33d25.0", + "tslib": "^2.1.0" + } + }, + "@material/ripple": { + "version": "15.0.0-canary.684e33d25.0", + "resolved": "https://registry.npmjs.org/@material/ripple/-/ripple-15.0.0-canary.684e33d25.0.tgz", + "integrity": "sha512-RyePu7SjIm/OuyyEieZ/gxiPYkNZOZHeid72WRcN9ofdlljj2pifcdPvcfZA+v/DMS33xo5GjG2L/Qj6ClWrKw==", + "requires": { + "@material/animation": "15.0.0-canary.684e33d25.0", + "@material/base": "15.0.0-canary.684e33d25.0", + "@material/dom": "15.0.0-canary.684e33d25.0", + "@material/feature-targeting": "15.0.0-canary.684e33d25.0", + "@material/rtl": "15.0.0-canary.684e33d25.0", + "@material/theme": "15.0.0-canary.684e33d25.0", + "tslib": "^2.1.0" + } + }, + "@material/rtl": { + "version": "15.0.0-canary.684e33d25.0", + "resolved": "https://registry.npmjs.org/@material/rtl/-/rtl-15.0.0-canary.684e33d25.0.tgz", + "integrity": "sha512-NqdJl8Ayupp1Th+vCNCpVQHbUFOuF7TCte9LD1norTIBUF/QizIxWby2W5uUEiPbnh5j9PmE1CJtfLwKun3pcw==", + "requires": { + "@material/theme": "15.0.0-canary.684e33d25.0", + "tslib": "^2.1.0" + } + }, + "@material/segmented-button": { + "version": "15.0.0-canary.684e33d25.0", + "resolved": "https://registry.npmjs.org/@material/segmented-button/-/segmented-button-15.0.0-canary.684e33d25.0.tgz", + "integrity": "sha512-bEGgg8vgXNLyukyV8HRjFMuQ6t6nm5LQ4Pgm22um61Yc8qyi0BOqV41OR4SVdUrUqZxh1aVD+p+4NN03+LfQXw==", + "requires": { + "@material/base": "15.0.0-canary.684e33d25.0", + "@material/elevation": "15.0.0-canary.684e33d25.0", + "@material/feature-targeting": "15.0.0-canary.684e33d25.0", + "@material/ripple": "15.0.0-canary.684e33d25.0", + "@material/theme": "15.0.0-canary.684e33d25.0", + "@material/touch-target": "15.0.0-canary.684e33d25.0", + "@material/typography": "15.0.0-canary.684e33d25.0", + "tslib": "^2.1.0" + } + }, + "@material/select": { + "version": "15.0.0-canary.684e33d25.0", + "resolved": "https://registry.npmjs.org/@material/select/-/select-15.0.0-canary.684e33d25.0.tgz", + "integrity": "sha512-kf178/2TeEinTv0mgmSBcmmExQ2h7a7dtR1E3WuqQgisJ/R6+zVLMkC2CnfIyzxYX2vkuUTG0ue3Reh/6XiqSg==", + "requires": { + "@material/animation": "15.0.0-canary.684e33d25.0", + "@material/base": "15.0.0-canary.684e33d25.0", + "@material/density": "15.0.0-canary.684e33d25.0", + "@material/dom": "15.0.0-canary.684e33d25.0", + "@material/elevation": "15.0.0-canary.684e33d25.0", + "@material/feature-targeting": "15.0.0-canary.684e33d25.0", + "@material/floating-label": "15.0.0-canary.684e33d25.0", + "@material/line-ripple": "15.0.0-canary.684e33d25.0", + "@material/list": "15.0.0-canary.684e33d25.0", + "@material/menu": "15.0.0-canary.684e33d25.0", + "@material/menu-surface": "15.0.0-canary.684e33d25.0", + "@material/notched-outline": "15.0.0-canary.684e33d25.0", + "@material/ripple": "15.0.0-canary.684e33d25.0", + "@material/rtl": "15.0.0-canary.684e33d25.0", + "@material/shape": "15.0.0-canary.684e33d25.0", + "@material/theme": "15.0.0-canary.684e33d25.0", + "@material/tokens": "15.0.0-canary.684e33d25.0", + "@material/typography": "15.0.0-canary.684e33d25.0", + "tslib": "^2.1.0" + } + }, + "@material/shape": { + "version": "15.0.0-canary.684e33d25.0", + "resolved": "https://registry.npmjs.org/@material/shape/-/shape-15.0.0-canary.684e33d25.0.tgz", + "integrity": "sha512-aEelpaTFmpnCji3TUGP9bVCS/bRVjUmLTHBPZtuu1gOrUVVtJ6kYOg73dZNJF+XOoNL2yOX/LRcKwsop29tptA==", + "requires": { + "@material/feature-targeting": "15.0.0-canary.684e33d25.0", + "@material/rtl": "15.0.0-canary.684e33d25.0", + "@material/theme": "15.0.0-canary.684e33d25.0", + "tslib": "^2.1.0" + } + }, + "@material/slider": { + "version": "15.0.0-canary.684e33d25.0", + "resolved": "https://registry.npmjs.org/@material/slider/-/slider-15.0.0-canary.684e33d25.0.tgz", + "integrity": "sha512-WVyK+2pSNSZmj07M2K/a3TADoQ9FBCndfNC/vE7/wGIg4dddJJK5KvQ+yruf9R2cSzTL/S1sZ5WpyyeM8E9HTw==", + "requires": { + "@material/animation": "15.0.0-canary.684e33d25.0", + "@material/base": "15.0.0-canary.684e33d25.0", + "@material/dom": "15.0.0-canary.684e33d25.0", + "@material/elevation": "15.0.0-canary.684e33d25.0", + "@material/feature-targeting": "15.0.0-canary.684e33d25.0", + "@material/ripple": "15.0.0-canary.684e33d25.0", + "@material/rtl": "15.0.0-canary.684e33d25.0", + "@material/theme": "15.0.0-canary.684e33d25.0", + "@material/tokens": "15.0.0-canary.684e33d25.0", + "@material/typography": "15.0.0-canary.684e33d25.0", + "tslib": "^2.1.0" + } + }, + "@material/snackbar": { + "version": "15.0.0-canary.684e33d25.0", + "resolved": "https://registry.npmjs.org/@material/snackbar/-/snackbar-15.0.0-canary.684e33d25.0.tgz", + "integrity": "sha512-itO+DCkOannZzR1/cCHcqAm7ifhuFvXmDItNoA8qLEcAyJDJJRkhpwj3XQ01yuo9gBFcSctp7Txt7e+Hncm/Jg==", + "requires": { + "@material/animation": "15.0.0-canary.684e33d25.0", + "@material/base": "15.0.0-canary.684e33d25.0", + "@material/button": "15.0.0-canary.684e33d25.0", + "@material/dom": "15.0.0-canary.684e33d25.0", + "@material/elevation": "15.0.0-canary.684e33d25.0", + "@material/feature-targeting": "15.0.0-canary.684e33d25.0", + "@material/icon-button": "15.0.0-canary.684e33d25.0", + "@material/ripple": "15.0.0-canary.684e33d25.0", + "@material/rtl": "15.0.0-canary.684e33d25.0", + "@material/shape": "15.0.0-canary.684e33d25.0", + "@material/theme": "15.0.0-canary.684e33d25.0", + "@material/tokens": "15.0.0-canary.684e33d25.0", + "@material/typography": "15.0.0-canary.684e33d25.0", + "tslib": "^2.1.0" + } + }, + "@material/switch": { + "version": "15.0.0-canary.684e33d25.0", + "resolved": "https://registry.npmjs.org/@material/switch/-/switch-15.0.0-canary.684e33d25.0.tgz", + "integrity": "sha512-Jxi0gl92yvvZZsAPxvVHzXx2ga+T/djMow98jvEczmpUorWnAhgiCr9CsSSRoosahWyRB8NLZOxUQrACxvffjw==", + "requires": { + "@material/animation": "15.0.0-canary.684e33d25.0", + "@material/base": "15.0.0-canary.684e33d25.0", + "@material/density": "15.0.0-canary.684e33d25.0", + "@material/dom": "15.0.0-canary.684e33d25.0", + "@material/elevation": "15.0.0-canary.684e33d25.0", + "@material/feature-targeting": "15.0.0-canary.684e33d25.0", + "@material/focus-ring": "15.0.0-canary.684e33d25.0", + "@material/ripple": "15.0.0-canary.684e33d25.0", + "@material/rtl": "15.0.0-canary.684e33d25.0", + "@material/shape": "15.0.0-canary.684e33d25.0", + "@material/theme": "15.0.0-canary.684e33d25.0", + "@material/tokens": "15.0.0-canary.684e33d25.0", + "safevalues": "^0.3.4", + "tslib": "^2.1.0" + } + }, + "@material/tab": { + "version": "15.0.0-canary.684e33d25.0", + "resolved": "https://registry.npmjs.org/@material/tab/-/tab-15.0.0-canary.684e33d25.0.tgz", + "integrity": "sha512-WQL3wj9syHNcfe8KbgGGUcA34M8C/xZ+n0Fkkh8Kk6puVwaU+xqUNihsxPY6YzKpmh4PZ4oJaBdiN8zvFT1zqQ==", + "requires": { + "@material/base": "15.0.0-canary.684e33d25.0", + "@material/elevation": "15.0.0-canary.684e33d25.0", + "@material/feature-targeting": "15.0.0-canary.684e33d25.0", + "@material/focus-ring": "15.0.0-canary.684e33d25.0", + "@material/ripple": "15.0.0-canary.684e33d25.0", + "@material/rtl": "15.0.0-canary.684e33d25.0", + "@material/tab-indicator": "15.0.0-canary.684e33d25.0", + "@material/theme": "15.0.0-canary.684e33d25.0", + "@material/tokens": "15.0.0-canary.684e33d25.0", + "@material/typography": "15.0.0-canary.684e33d25.0", + "tslib": "^2.1.0" + } + }, + "@material/tab-bar": { + "version": "15.0.0-canary.684e33d25.0", + "resolved": "https://registry.npmjs.org/@material/tab-bar/-/tab-bar-15.0.0-canary.684e33d25.0.tgz", + "integrity": "sha512-SW/cMaDsIGGkM1ag3A7GJRlmr8eXmObWsvitQJzh6Azr5zzZtSI+GQygkMesAEE1gbpqOVN8d40rh3H7VVIAcA==", + "requires": { + "@material/animation": "15.0.0-canary.684e33d25.0", + "@material/base": "15.0.0-canary.684e33d25.0", + "@material/density": "15.0.0-canary.684e33d25.0", + "@material/elevation": "15.0.0-canary.684e33d25.0", + "@material/feature-targeting": "15.0.0-canary.684e33d25.0", + "@material/tab": "15.0.0-canary.684e33d25.0", + "@material/tab-indicator": "15.0.0-canary.684e33d25.0", + "@material/tab-scroller": "15.0.0-canary.684e33d25.0", + "@material/theme": "15.0.0-canary.684e33d25.0", + "@material/tokens": "15.0.0-canary.684e33d25.0", + "@material/typography": "15.0.0-canary.684e33d25.0", + "tslib": "^2.1.0" + } + }, + "@material/tab-indicator": { + "version": "15.0.0-canary.684e33d25.0", + "resolved": "https://registry.npmjs.org/@material/tab-indicator/-/tab-indicator-15.0.0-canary.684e33d25.0.tgz", + "integrity": "sha512-kKICqSPqOlaf0lzaFFCmuOqPXJC+cK48Qmsc+m5o6fJhkmuZRCYpIwB2JeP+uZSOq/bTH+SrPtCtnVlgWg6ksA==", + "requires": { + "@material/animation": "15.0.0-canary.684e33d25.0", + "@material/base": "15.0.0-canary.684e33d25.0", + "@material/feature-targeting": "15.0.0-canary.684e33d25.0", + "@material/theme": "15.0.0-canary.684e33d25.0", + "tslib": "^2.1.0" + } + }, + "@material/tab-scroller": { + "version": "15.0.0-canary.684e33d25.0", + "resolved": "https://registry.npmjs.org/@material/tab-scroller/-/tab-scroller-15.0.0-canary.684e33d25.0.tgz", + "integrity": "sha512-H6EU/TSiK/M2DyyORX5GEtXD9rKYxTMHC2VxsNWARPMFJGzgeW2ugYkFv+rKI1/c0bs0CJ4e+qFnOlBsQXZvyQ==", + "requires": { + "@material/animation": "15.0.0-canary.684e33d25.0", + "@material/base": "15.0.0-canary.684e33d25.0", + "@material/dom": "15.0.0-canary.684e33d25.0", + "@material/feature-targeting": "15.0.0-canary.684e33d25.0", + "@material/tab": "15.0.0-canary.684e33d25.0", + "tslib": "^2.1.0" + } + }, + "@material/textfield": { + "version": "15.0.0-canary.684e33d25.0", + "resolved": "https://registry.npmjs.org/@material/textfield/-/textfield-15.0.0-canary.684e33d25.0.tgz", + "integrity": "sha512-OvgpDXjvpyJTtAWskO69IDybFvDNzr9w2PN/Fk7yFm+uNVupaWz1Ew8lZ4gGslaTNSVmh2XcsvmzxcLINSiiNg==", + "requires": { + "@material/animation": "15.0.0-canary.684e33d25.0", + "@material/base": "15.0.0-canary.684e33d25.0", + "@material/density": "15.0.0-canary.684e33d25.0", + "@material/dom": "15.0.0-canary.684e33d25.0", + "@material/feature-targeting": "15.0.0-canary.684e33d25.0", + "@material/floating-label": "15.0.0-canary.684e33d25.0", + "@material/line-ripple": "15.0.0-canary.684e33d25.0", + "@material/notched-outline": "15.0.0-canary.684e33d25.0", + "@material/ripple": "15.0.0-canary.684e33d25.0", + "@material/rtl": "15.0.0-canary.684e33d25.0", + "@material/shape": "15.0.0-canary.684e33d25.0", + "@material/theme": "15.0.0-canary.684e33d25.0", + "@material/tokens": "15.0.0-canary.684e33d25.0", + "@material/typography": "15.0.0-canary.684e33d25.0", + "tslib": "^2.1.0" + } + }, + "@material/theme": { + "version": "15.0.0-canary.684e33d25.0", + "resolved": "https://registry.npmjs.org/@material/theme/-/theme-15.0.0-canary.684e33d25.0.tgz", + "integrity": "sha512-AZxaXXAvRKzAi20RlMxzt2U5UmkCWyv7DMWEBXsxtG5Tk54mi1HsbVUp3fxDPTlmL7Pq8p1/DESg/o7TgRCVlw==", + "requires": { + "@material/feature-targeting": "15.0.0-canary.684e33d25.0", + "tslib": "^2.1.0" + } + }, + "@material/tokens": { + "version": "15.0.0-canary.684e33d25.0", + "resolved": "https://registry.npmjs.org/@material/tokens/-/tokens-15.0.0-canary.684e33d25.0.tgz", + "integrity": "sha512-wVwbQOTCXDPKYPdHQHLr026y36MMFelID1CmbfRk6mSol4O8yE9U0fXcShfRDW8Qo5E3X31w9c2A6T3neJY7wQ==", + "requires": { + "@material/elevation": "15.0.0-canary.684e33d25.0" + } + }, + "@material/tooltip": { + "version": "15.0.0-canary.684e33d25.0", + "resolved": "https://registry.npmjs.org/@material/tooltip/-/tooltip-15.0.0-canary.684e33d25.0.tgz", + "integrity": "sha512-dtm26QjxyQdinc8btgz6yys07b7bUW4FZgNF2EBPeGrICrPg7jf+JEvDziz5g8VMaTBQLOQRSCGy0MKuRlOjLw==", + "requires": { + "@material/animation": "15.0.0-canary.684e33d25.0", + "@material/base": "15.0.0-canary.684e33d25.0", + "@material/button": "15.0.0-canary.684e33d25.0", + "@material/dom": "15.0.0-canary.684e33d25.0", + "@material/elevation": "15.0.0-canary.684e33d25.0", + "@material/feature-targeting": "15.0.0-canary.684e33d25.0", + "@material/rtl": "15.0.0-canary.684e33d25.0", + "@material/shape": "15.0.0-canary.684e33d25.0", + "@material/theme": "15.0.0-canary.684e33d25.0", + "@material/tokens": "15.0.0-canary.684e33d25.0", + "@material/typography": "15.0.0-canary.684e33d25.0", + "safevalues": "^0.3.4", + "tslib": "^2.1.0" + } + }, + "@material/top-app-bar": { + "version": "15.0.0-canary.684e33d25.0", + "resolved": "https://registry.npmjs.org/@material/top-app-bar/-/top-app-bar-15.0.0-canary.684e33d25.0.tgz", + "integrity": "sha512-1M+oupUxflfW7u81P1XlxoLZB8bLzwtpKofIfDNRbEsiKhlLTERJR3Yak3BGE9xakNMysAaBHlkb5MrN5bNPFw==", + "requires": { + "@material/animation": "15.0.0-canary.684e33d25.0", + "@material/base": "15.0.0-canary.684e33d25.0", + "@material/elevation": "15.0.0-canary.684e33d25.0", + "@material/ripple": "15.0.0-canary.684e33d25.0", + "@material/rtl": "15.0.0-canary.684e33d25.0", + "@material/shape": "15.0.0-canary.684e33d25.0", + "@material/theme": "15.0.0-canary.684e33d25.0", + "@material/typography": "15.0.0-canary.684e33d25.0", + "tslib": "^2.1.0" + } + }, + "@material/touch-target": { + "version": "15.0.0-canary.684e33d25.0", + "resolved": "https://registry.npmjs.org/@material/touch-target/-/touch-target-15.0.0-canary.684e33d25.0.tgz", + "integrity": "sha512-zdE69Slg8+T7sTn1OwqZ6H7WBYac9mxJ/JlJqfTqthzIjZRcCxBSYymQJcDHjsrPnUojOtr9U4Tpm5YZ96TEkQ==", + "requires": { + "@material/base": "15.0.0-canary.684e33d25.0", + "@material/feature-targeting": "15.0.0-canary.684e33d25.0", + "@material/rtl": "15.0.0-canary.684e33d25.0", + "@material/theme": "15.0.0-canary.684e33d25.0", + "tslib": "^2.1.0" + } + }, + "@material/typography": { + "version": "15.0.0-canary.684e33d25.0", + "resolved": "https://registry.npmjs.org/@material/typography/-/typography-15.0.0-canary.684e33d25.0.tgz", + "integrity": "sha512-aVnvgMwcfNa/K4wujzpKDIxjGl2hbkEL+m+OKDSQqWYjKcP9QrbzCXJruJBqxrBoPRHLbqo47k5f9uT8raSgjw==", + "requires": { + "@material/feature-targeting": "15.0.0-canary.684e33d25.0", + "@material/theme": "15.0.0-canary.684e33d25.0", + "tslib": "^2.1.0" + } + }, + "@ng-matero/extensions": { + "version": "15.5.0", + "resolved": "https://registry.npmjs.org/@ng-matero/extensions/-/extensions-15.5.0.tgz", + "integrity": "sha512-16v3T4fo8gY5sZ4qvFJSeI6FBQfbu9pBVX1qMbmahGp7du1b5+Ui+Pdv0ZdO9ZcvCLwFnnXEnczFsINn7dFIOQ==", + "requires": { + "@ng-select/ng-select": "^10.0.0", + "ngx-color": "^8.0.0", + "photoviewer": "^3.7.0", + "tslib": "^2.4.0" + }, + "dependencies": { + "tslib": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.2.tgz", + "integrity": "sha512-5svOrSA2w3iGFDs1HibEVBGbDrAY82bFQ3HZ3ixB+88nsbsWQoKqDRb5UBYAUPEzbBn6dAp5gRNXglySbx1MlA==" + } + } + }, + "@ng-select/ng-select": { + "version": "10.0.3", + "resolved": "https://registry.npmjs.org/@ng-select/ng-select/-/ng-select-10.0.3.tgz", + "integrity": "sha512-Ma8pzKMI5TWnsKgOiONQLGeUeTko9gI6AtqpMMOVhrCktjtUSo9h5N17WomHRGtba9D7QviTZcR7UBhKOPwZ7g==", + "requires": { + "tslib": "^2.3.1" + } + }, + "@ngneat/until-destroy": { + "version": "8.1.4", + "resolved": "https://registry.npmjs.org/@ngneat/until-destroy/-/until-destroy-8.1.4.tgz", + "integrity": "sha512-Bk+jS0ZyCKvW2wLJPMJ8jr6lhYlDVVbE2/WbQ5L3eBcwmem82649o6s7G68tDNA+5UeHtfW7fbpK4Bhj8r+B9g==", + "requires": { + "tslib": "^2.2.0" + } + }, + "@ngtools/webpack": { + "version": "15.2.8", + "resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-15.2.8.tgz", + "integrity": "sha512-BJexeT4FxMtToVBGa3wdl6rrkYXgilP0kkSH4Qzu4MPlLPbeBSr4XQalQriewlpC2uzG0r2SJfrAe2eDhtSykA==" + }, + "@ngx-translate/core": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/@ngx-translate/core/-/core-14.0.0.tgz", + "integrity": "sha512-UevdwNCXMRCdJv//0kC8h2eSfmi02r29xeE8E9gJ1Al4D4jEJ7eiLPdjslTMc21oJNGguqqWeEVjf64SFtvw2w==", + "requires": { + "tslib": "^2.3.0" + } + }, + "@ngx-translate/http-loader": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/@ngx-translate/http-loader/-/http-loader-6.0.0.tgz", + "integrity": "sha512-LCekn6qCbeXWlhESCxU1rAbZz33WzDG0lI7Ig0pYC1o5YxJWrkU9y3Y4tNi+jakQ7R6YhTR2D3ox6APxDtA0wA==", + "requires": { + "tslib": "^2.0.0" + } + }, + "@ngxs-labs/immer-adapter": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@ngxs-labs/immer-adapter/-/immer-adapter-3.0.5.tgz", + "integrity": "sha512-GZHqPR1XPoLc/RG+Oy4+vTiZOUIGR1eTFz2BXc+jzYvBG67U64Snwsaud+hASHhEcezrPOhF34jCQihpVYfaCg==", + "requires": { + "tslib": "^1.9.0" + }, + "dependencies": { + "tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" + } + } + }, + "@ngxs-labs/select-snapshot": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@ngxs-labs/select-snapshot/-/select-snapshot-4.0.0.tgz", + "integrity": "sha512-Gk5ER5bTICBD7TpUV5kAp+IJtl+1flYsAqrZgo5LMpPF81aLpE9MG1AS8hZO+G35jECcYwHzTKYwwWtObLw02Q==", + "requires": { + "tslib": "^2.3.0" + } + }, + "@ngxs/devtools-plugin": { + "version": "3.7.6", + "resolved": "https://registry.npmjs.org/@ngxs/devtools-plugin/-/devtools-plugin-3.7.6.tgz", + "integrity": "sha512-KbFak7ZF8U23z1TDwpkKqReNMco4c958SAJ02JOc89U+O8scpwOCTUh/aJxoy8ZsqV4/F1eb4DeZ5EtXXXznwg==", + "requires": { + "tslib": "^1.9.0" + }, + "dependencies": { + "tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" + } + } + }, + "@ngxs/form-plugin": { + "version": "3.7.6", + "resolved": "https://registry.npmjs.org/@ngxs/form-plugin/-/form-plugin-3.7.6.tgz", + "integrity": "sha512-eOS7YbIrQjn05kArbuIpK+ftiTAJLg59SoUWPW0CYcIc6B+usPaUJ5xY9XtRJ5BhJP3ruSCivFmX2xItkbdpqA==", + "requires": { + "tslib": "^1.9.0" + }, + "dependencies": { + "tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" + } + } + }, + "@ngxs/storage-plugin": { + "version": "3.7.6", + "resolved": "https://registry.npmjs.org/@ngxs/storage-plugin/-/storage-plugin-3.7.6.tgz", + "integrity": "sha512-jeeoUgsMdb4M8kIHdnO0X6I73okmRJhMW9znaqhYF/aZ+PiQrogdJ5xvSXmoQ7Gg57YfQEhVErM+zf/HD7fnMA==", + "requires": { + "tslib": "^1.9.0" + }, + "dependencies": { + "tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" + } + } + }, + "@ngxs/store": { + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/@ngxs/store/-/store-3.8.0.tgz", + "integrity": "sha512-ZIVM2ftudeB3VSjULJjb31RS3WKYVgs/67IHdDjukn1S0X8ZRk07QNH6cUCfUB5h345sMLCwliwVuqh+IXbYSw==", + "requires": { + "tslib": "^2.2.0" + } + }, + "@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "requires": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + } + }, + "@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true + }, + "@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "requires": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + } + }, + "@npmcli/fs": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-3.1.0.tgz", + "integrity": "sha512-7kZUAaLscfgbwBQRbvdMYaZOWyMEcPTH/tJjnyAWJ/dvvs9Ef+CERx/qJb9GExJpl1qipaDGn7KqHnFGGixd0w==", + "dev": true, + "requires": { + "semver": "^7.3.5" + } + }, + "@npmcli/git": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@npmcli/git/-/git-4.0.4.tgz", + "integrity": "sha512-5yZghx+u5M47LghaybLCkdSyFzV/w4OuH12d96HO389Ik9CDsLaDZJVynSGGVJOLn6gy/k7Dz5XYcplM3uxXRg==", + "dev": true, + "requires": { + "@npmcli/promise-spawn": "^6.0.0", + "lru-cache": "^7.4.4", + "npm-pick-manifest": "^8.0.0", + "proc-log": "^3.0.0", + "promise-inflight": "^1.0.1", + "promise-retry": "^2.0.1", + "semver": "^7.3.5", + "which": "^3.0.0" + }, + "dependencies": { + "lru-cache": { + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", + "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "dev": true + }, + "which": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/which/-/which-3.0.1.tgz", + "integrity": "sha512-XA1b62dzQzLfaEOSQFTCOd5KFf/1VSzZo7/7TUjnya6u0vGGKzU96UQBZTAThCb2j4/xjBAyii1OhRLJEivHvg==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + } + } + }, + "@npmcli/installed-package-contents": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@npmcli/installed-package-contents/-/installed-package-contents-2.0.2.tgz", + "integrity": "sha512-xACzLPhnfD51GKvTOOuNX2/V4G4mz9/1I2MfDoye9kBM3RYe5g2YbscsaGoTlaWqkxeiapBWyseULVKpSVHtKQ==", + "dev": true, + "requires": { + "npm-bundled": "^3.0.0", + "npm-normalize-package-bin": "^3.0.0" + } + }, + "@npmcli/move-file": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@npmcli/move-file/-/move-file-2.0.1.tgz", + "integrity": "sha512-mJd2Z5TjYWq/ttPLLGqArdtnC74J6bOzg4rMDnN+p1xTacZ2yPRCk2y0oSWQtygLR9YVQXgOcONrwtnk3JupxQ==", + "dev": true, + "requires": { + "mkdirp": "^1.0.4", + "rimraf": "^3.0.2" + } + }, + "@npmcli/node-gyp": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@npmcli/node-gyp/-/node-gyp-3.0.0.tgz", + "integrity": "sha512-gp8pRXC2oOxu0DUE1/M3bYtb1b3/DbJ5aM113+XJBgfXdussRAsX0YOrOhdd8WvnAR6auDBvJomGAkLKA5ydxA==", + "dev": true + }, + "@npmcli/promise-spawn": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@npmcli/promise-spawn/-/promise-spawn-6.0.2.tgz", + "integrity": "sha512-gGq0NJkIGSwdbUt4yhdF8ZrmkGKVz9vAdVzpOfnom+V8PLSmSOVhZwbNvZZS1EYcJN5hzzKBxmmVVAInM6HQLg==", + "dev": true, + "requires": { + "which": "^3.0.0" + }, + "dependencies": { + "which": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/which/-/which-3.0.1.tgz", + "integrity": "sha512-XA1b62dzQzLfaEOSQFTCOd5KFf/1VSzZo7/7TUjnya6u0vGGKzU96UQBZTAThCb2j4/xjBAyii1OhRLJEivHvg==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + } + } + }, + "@npmcli/run-script": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@npmcli/run-script/-/run-script-6.0.2.tgz", + "integrity": "sha512-NCcr1uQo1k5U+SYlnIrbAh3cxy+OQT1VtqiAbxdymSlptbzBb62AjH2xXgjNCoP073hoa1CfCAcwoZ8k96C4nA==", + "dev": true, + "requires": { + "@npmcli/node-gyp": "^3.0.0", + "@npmcli/promise-spawn": "^6.0.0", + "node-gyp": "^9.0.0", + "read-package-json-fast": "^3.0.0", + "which": "^3.0.0" + }, + "dependencies": { + "which": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/which/-/which-3.0.1.tgz", + "integrity": "sha512-XA1b62dzQzLfaEOSQFTCOd5KFf/1VSzZo7/7TUjnya6u0vGGKzU96UQBZTAThCb2j4/xjBAyii1OhRLJEivHvg==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + } + } + }, + "@perfectmemory/ngx-contextmenu": { + "version": "15.1.1", + "resolved": "https://registry.npmjs.org/@perfectmemory/ngx-contextmenu/-/ngx-contextmenu-15.1.1.tgz", + "integrity": "sha512-3jWvL+OC8KvJGNuqrJZauKGQzENhtZi2G7vPoIsF7IO6XfhwzSlCxFITlDhZflKnZHXB7H7L8kIDunpAp1zODQ==", + "requires": { + "tslib": "^2.3.1" + } + }, + "@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "dev": true, + "optional": true + }, + "@schematics/angular": { + "version": "15.2.8", + "resolved": "https://registry.npmjs.org/@schematics/angular/-/angular-15.2.8.tgz", + "integrity": "sha512-F49IEzCFxQlpaMIgTO/wF1l/CLQKif7VaiDdyiTKOeT22IMmyd61FUmWDyZYfCBqMlvBmvDGx64HaHWes1HYCg==", + "dev": true, + "requires": { + "@angular-devkit/core": "15.2.8", + "@angular-devkit/schematics": "15.2.8", + "jsonc-parser": "3.2.0" + } + }, + "@sentry/browser": { + "version": "5.30.0", + "resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-5.30.0.tgz", + "integrity": "sha512-rOb58ZNVJWh1VuMuBG1mL9r54nZqKeaIlwSlvzJfc89vyfd7n6tQ1UXMN383QBz/MS5H5z44Hy5eE+7pCrYAfw==", + "requires": { + "@sentry/core": "5.30.0", + "@sentry/types": "5.30.0", + "@sentry/utils": "5.30.0", + "tslib": "^1.9.3" + }, + "dependencies": { + "tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" + } + } + }, + "@sentry/core": { + "version": "5.30.0", + "resolved": "https://registry.npmjs.org/@sentry/core/-/core-5.30.0.tgz", + "integrity": "sha512-TmfrII8w1PQZSZgPpUESqjB+jC6MvZJZdLtE/0hZ+SrnKhW3x5WlYLvTXZpcWePYBku7rl2wn1RZu6uT0qCTeg==", + "requires": { + "@sentry/hub": "5.30.0", + "@sentry/minimal": "5.30.0", + "@sentry/types": "5.30.0", + "@sentry/utils": "5.30.0", + "tslib": "^1.9.3" + }, + "dependencies": { + "tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" + } + } + }, + "@sentry/hub": { + "version": "5.30.0", + "resolved": "https://registry.npmjs.org/@sentry/hub/-/hub-5.30.0.tgz", + "integrity": "sha512-2tYrGnzb1gKz2EkMDQcfLrDTvmGcQPuWxLnJKXJvYTQDGLlEvi2tWz1VIHjunmOvJrB5aIQLhm+dcMRwFZDCqQ==", + "requires": { + "@sentry/types": "5.30.0", + "@sentry/utils": "5.30.0", + "tslib": "^1.9.3" + }, + "dependencies": { + "tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" + } + } + }, + "@sentry/minimal": { + "version": "5.30.0", + "resolved": "https://registry.npmjs.org/@sentry/minimal/-/minimal-5.30.0.tgz", + "integrity": "sha512-BwWb/owZKtkDX+Sc4zCSTNcvZUq7YcH3uAVlmh/gtR9rmUvbzAA3ewLuB3myi4wWRAMEtny6+J/FN/x+2wn9Xw==", + "requires": { + "@sentry/hub": "5.30.0", + "@sentry/types": "5.30.0", + "tslib": "^1.9.3" + }, + "dependencies": { + "tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" + } + } + }, + "@sentry/types": { + "version": "5.30.0", + "resolved": "https://registry.npmjs.org/@sentry/types/-/types-5.30.0.tgz", + "integrity": "sha512-R8xOqlSTZ+htqrfteCWU5Nk0CDN5ApUTvrlvBuiH1DyP6czDZ4ktbZB0hAgBlVcK0U+qpD3ag3Tqqpa5Q67rPw==" + }, + "@sentry/utils": { + "version": "5.30.0", + "resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-5.30.0.tgz", + "integrity": "sha512-zaYmoH0NWWtvnJjC9/CBseXMtKHm/tm40sz3YfJRxeQjyzRqNQPgivpd9R/oDJCYj999mzdW382p/qi2ypjLww==", + "requires": { + "@sentry/types": "5.30.0", + "tslib": "^1.9.3" + }, + "dependencies": { + "tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" + } + } + }, + "@sigstore/protobuf-specs": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/@sigstore/protobuf-specs/-/protobuf-specs-0.1.0.tgz", + "integrity": "sha512-a31EnjuIDSX8IXBUib3cYLDRlPMU36AWX4xS8ysLaNu4ZzUesDiPt83pgrW2X1YLMe5L2HbDyaKK5BrL4cNKaQ==", + "dev": true + }, + "@socket.io/component-emitter": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.0.tgz", + "integrity": "sha512-+9jVqKhRSpsc591z5vX+X5Yyw+he/HCB4iQ/RYxw35CEPaY1gnsNE43nf9n9AaYjAQrTiI/mOwKUKdUs9vf7Xg==" + }, + "@tootallnate/once": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", + "integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==", + "dev": true + }, + "@tsconfig/node10": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.8.tgz", + "integrity": "sha512-6XFfSQmMgq0CFLY1MslA/CPUfhIL919M1rMsa5lP2P097N2Wd1sSX0tx1u4olM16fLNhtHZpRhedZJphNJqmZg==", + "dev": true + }, + "@tsconfig/node12": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.9.tgz", + "integrity": "sha512-/yBMcem+fbvhSREH+s14YJi18sp7J9jpuhYByADT2rypfajMZZN4WQ6zBGgBKp53NKmqI36wFYDb3yaMPurITw==", + "dev": true + }, + "@tsconfig/node14": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.1.tgz", + "integrity": "sha512-509r2+yARFfHHE7T6Puu2jjkoycftovhXRqW328PDXTVGKihlb1P8Z9mMZH04ebyajfRY7dedfGynlrFHJUQCg==", + "dev": true + }, + "@tsconfig/node16": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.2.tgz", + "integrity": "sha512-eZxlbI8GZscaGS7kkc/trHTT5xgrjH3/1n2JDwusC9iahPKWMRvRjJSAN5mCXviuTGQ/lHnhvv8Q1YTpnfz9gA==", + "dev": true + }, + "@tufjs/canonical-json": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@tufjs/canonical-json/-/canonical-json-1.0.0.tgz", + "integrity": "sha512-QTnf++uxunWvG2z3UFNzAoQPHxnSXOwtaI3iJ+AohhV+5vONuArPjJE7aPXPVXfXJsqrVbZBu9b81AJoSd09IQ==", + "dev": true + }, + "@tufjs/models": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@tufjs/models/-/models-1.0.4.tgz", + "integrity": "sha512-qaGV9ltJP0EO25YfFUPhxRVK0evXFIAGicsVXuRim4Ed9cjPxYhNnNJ49SFmbeLgtxpslIkX317IgpfcHPVj/A==", + "dev": true, + "requires": { + "@tufjs/canonical-json": "1.0.0", + "minimatch": "^9.0.0" + }, + "dependencies": { + "brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0" + } + }, + "minimatch": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.1.tgz", + "integrity": "sha512-0jWhJpD/MdhPXwPuiRkCbfYfSKp2qnn2eOc279qI7f+osl/l+prKSrvhg157zSYvx/1nmgn2NqdT6k2Z7zSH9w==", + "dev": true, + "requires": { + "brace-expansion": "^2.0.1" + } + } + } + }, + "@types/body-parser": { + "version": "1.19.2", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.2.tgz", + "integrity": "sha512-ALYone6pm6QmwZoAgeyNksccT9Q4AWZQ6PvfwR37GT6r6FWUPguq6sUmNGSMV2Wr761oQoBxwGGa6DR5o1DC9g==", + "dev": true, + "requires": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "@types/bonjour": { + "version": "3.5.10", + "resolved": "https://registry.npmjs.org/@types/bonjour/-/bonjour-3.5.10.tgz", + "integrity": "sha512-p7ienRMiS41Nu2/igbJxxLDWrSZ0WxM8UQgCeO9KhoVF7cOVFkrKsiDr1EsJIla8vV3oEEjGcz11jc5yimhzZw==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "@types/c3": { + "version": "0.7.8", + "resolved": "https://registry.npmjs.org/@types/c3/-/c3-0.7.8.tgz", + "integrity": "sha512-qUhbhHIa7SzpDZVHTUx51XUKPzkG3xLHKZGhwvfIs5Fy3NSc8qtH8I1u6N3Dp44Ih54qyUMw6xTIiDuOUBanxA==", + "requires": { + "@types/d3": "^4" + }, + "dependencies": { + "@types/d3": { + "version": "4.13.12", + "resolved": "https://registry.npmjs.org/@types/d3/-/d3-4.13.12.tgz", + "integrity": "sha512-/bbFtkOBc04gGGN8N9rMG5ps3T0eIj5I8bnYe9iIyeM5qoOrydPCbFYlEPUnj2h9ibc2i+QZfDam9jY5XTrTxQ==", + "requires": { + "@types/d3-array": "^1", + "@types/d3-axis": "^1", + "@types/d3-brush": "^1", + "@types/d3-chord": "^1", + "@types/d3-collection": "*", + "@types/d3-color": "^1", + "@types/d3-dispatch": "^1", + "@types/d3-drag": "^1", + "@types/d3-dsv": "^1", + "@types/d3-ease": "^1", + "@types/d3-force": "^1", + "@types/d3-format": "^1", + "@types/d3-geo": "^1", + "@types/d3-hierarchy": "^1", + "@types/d3-interpolate": "^1", + "@types/d3-path": "^1", + "@types/d3-polygon": "^1", + "@types/d3-quadtree": "^1", + "@types/d3-queue": "*", + "@types/d3-random": "^1", + "@types/d3-request": "*", + "@types/d3-scale": "^1", + "@types/d3-selection": "^1", + "@types/d3-shape": "^1", + "@types/d3-time": "^1", + "@types/d3-time-format": "^2", + "@types/d3-timer": "^1", + "@types/d3-transition": "^1", + "@types/d3-voronoi": "*", + "@types/d3-zoom": "^1" + } + }, + "@types/d3-array": { + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-1.2.9.tgz", + "integrity": "sha512-E/7RgPr2ylT5dWG0CswMi9NpFcjIEDqLcUSBgNHe/EMahfqYaTx4zhcggG3khqoEB/leY4Vl6nTSbwLUPjXceA==" + }, + "@types/d3-axis": { + "version": "1.0.16", + "resolved": "https://registry.npmjs.org/@types/d3-axis/-/d3-axis-1.0.16.tgz", + "integrity": "sha512-p7085weOmo4W+DzlRRVC/7OI/jugaKbVa6WMQGCQscaMylcbuaVEGk7abJLNyGVFLeCBNrHTdDiqRGnzvL0nXQ==", + "requires": { + "@types/d3-selection": "^1" + } + }, + "@types/d3-brush": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@types/d3-brush/-/d3-brush-1.1.5.tgz", + "integrity": "sha512-4zGkBafJf5zCsBtLtvDj/pNMo5X9+Ii/1hUz0GvQ+wEwelUBm2AbIDAzJnp2hLDFF307o0fhxmmocHclhXC+tw==", + "requires": { + "@types/d3-selection": "^1" + } + }, + "@types/d3-chord": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@types/d3-chord/-/d3-chord-1.0.11.tgz", + "integrity": "sha512-0DdfJ//bxyW3G9Nefwq/LDgazSKNN8NU0lBT3Cza6uVuInC2awMNsAcv1oKyRFLn9z7kXClH5XjwpveZjuz2eg==" + }, + "@types/d3-color": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-1.4.2.tgz", + "integrity": "sha512-fYtiVLBYy7VQX+Kx7wU/uOIkGQn8aAEY8oWMoyja3N4dLd8Yf6XgSIR/4yWvMuveNOH5VShnqCgRqqh/UNanBA==" + }, + "@types/d3-dispatch": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/d3-dispatch/-/d3-dispatch-1.0.9.tgz", + "integrity": "sha512-zJ44YgjqALmyps+II7b1mZLhrtfV/FOxw9owT87mrweGWcg+WK5oiJX2M3SYJ0XUAExBduarysfgbR11YxzojQ==" + }, + "@types/d3-drag": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/@types/d3-drag/-/d3-drag-1.2.5.tgz", + "integrity": "sha512-7NeTnfolst1Js3Vs7myctBkmJWu6DMI3k597AaHUX98saHjHWJ6vouT83UrpE+xfbSceHV+8A0JgxuwgqgmqWw==", + "requires": { + "@types/d3-selection": "^1" + } + }, + "@types/d3-dsv": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@types/d3-dsv/-/d3-dsv-1.2.2.tgz", + "integrity": "sha512-GRnz9z8ypqb7OsQ/xw/BmFAp0/k3pgM1s19FTZZSlCMY0EvyVTkU8xzZKKDXzytGXPpTNC4R5pGl9oxEvVSnHQ==" + }, + "@types/d3-ease": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@types/d3-ease/-/d3-ease-1.0.11.tgz", + "integrity": "sha512-wUigPL0kleGZ9u3RhzBP07lxxkMcUjL5IODP42mN/05UNL+JJCDnpEPpFbJiPvLcTeRKGIRpBBJyP/1BNwYsVA==" + }, + "@types/d3-force": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@types/d3-force/-/d3-force-1.2.4.tgz", + "integrity": "sha512-fkorLTKvt6AQbFBQwn4aq7h9rJ4c7ZVcPMGB8X6eFFveAyMZcv7t7m6wgF4Eg93rkPgPORU7sAho1QSHNcZu6w==" + }, + "@types/d3-format": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/@types/d3-format/-/d3-format-1.4.2.tgz", + "integrity": "sha512-WeGCHAs7PHdZYq6lwl/+jsl+Nfc1J2W1kNcMeIMYzQsT6mtBDBgtJ/rcdjZ0k0rVIvqEZqhhuD5TK/v3P2gFHQ==" + }, + "@types/d3-geo": { + "version": "1.12.4", + "resolved": "https://registry.npmjs.org/@types/d3-geo/-/d3-geo-1.12.4.tgz", + "integrity": "sha512-lNDaAuOaML6w2d1XE0Txr5YOXLBQSF1q2IU6eXh/u1TTPQSm2Ah+TMIub1+CIMq8J/7DOzi5Cr8/yHqjNvqLKA==", + "requires": { + "@types/geojson": "*" + } + }, + "@types/d3-hierarchy": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/@types/d3-hierarchy/-/d3-hierarchy-1.1.8.tgz", + "integrity": "sha512-AbStKxNyWiMDQPGDguG2Kuhlq1Sv539pZSxYbx4UZeYkutpPwXCcgyiRrlV4YH64nIOsKx7XVnOMy9O7rJsXkg==" + }, + "@types/d3-interpolate": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-1.4.2.tgz", + "integrity": "sha512-ylycts6llFf8yAEs1tXzx2loxxzDZHseuhPokrqKprTQSTcD3JbJI1omZP1rphsELZO3Q+of3ff0ZS7+O6yVzg==", + "requires": { + "@types/d3-color": "^1" + } + }, + "@types/d3-path": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-1.0.9.tgz", + "integrity": "sha512-NaIeSIBiFgSC6IGUBjZWcscUJEq7vpVu7KthHN8eieTV9d9MqkSOZLH4chq1PmcKy06PNe3axLeKmRIyxJ+PZQ==" + }, + "@types/d3-polygon": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/d3-polygon/-/d3-polygon-1.0.8.tgz", + "integrity": "sha512-1TOJPXCBJC9V3+K3tGbTqD/CsqLyv/YkTXAcwdsZzxqw5cvpdnCuDl42M4Dvi8XzMxZNCT9pL4ibrK2n4VmAcw==" + }, + "@types/d3-quadtree": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/d3-quadtree/-/d3-quadtree-1.0.9.tgz", + "integrity": "sha512-5E0OJJn2QVavITFEc1AQlI8gLcIoDZcTKOD3feKFckQVmFV4CXhqRFt83tYNVNIN4ZzRkjlAMavJa1ldMhf5rA==" + }, + "@types/d3-random": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@types/d3-random/-/d3-random-1.1.3.tgz", + "integrity": "sha512-XXR+ZbFCoOd4peXSMYJzwk0/elP37WWAzS/DG+90eilzVbUSsgKhBcWqylGWe+lA2ubgr7afWAOBaBxRgMUrBQ==" + }, + "@types/d3-scale": { + "version": "1.0.17", + "resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-1.0.17.tgz", + "integrity": "sha512-baIP5/gw+PS8Axs1lfZCeIjcOXen/jxQmgFEjbYThwaj2drvivOIrJMh2Ig4MeenrogCH6zkhiOxCPRkvN1scA==", + "requires": { + "@types/d3-time": "^1" + } + }, + "@types/d3-selection": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/@types/d3-selection/-/d3-selection-1.4.3.tgz", + "integrity": "sha512-GjKQWVZO6Sa96HiKO6R93VBE8DUW+DDkFpIMf9vpY5S78qZTlRRSNUsHr/afDpF7TvLDV7VxrUFOWW7vdIlYkA==" + }, + "@types/d3-shape": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-1.3.8.tgz", + "integrity": "sha512-gqfnMz6Fd5H6GOLYixOZP/xlrMtJms9BaS+6oWxTKHNqPGZ93BkWWupQSCYm6YHqx6h9wjRupuJb90bun6ZaYg==", + "requires": { + "@types/d3-path": "^1" + } + }, + "@types/d3-time": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-1.1.1.tgz", + "integrity": "sha512-ULX7LoqXTCYtM+tLYOaeAJK7IwCT+4Gxlm2MaH0ErKLi07R5lh8NHCAyWcDkCCmx1AfRcBEV6H9QE9R25uP7jw==" + }, + "@types/d3-time-format": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/@types/d3-time-format/-/d3-time-format-2.3.1.tgz", + "integrity": "sha512-fck0Z9RGfIQn3GJIEKVrp15h9m6Vlg0d5XXeiE/6+CQiBmMDZxfR21XtjEPuDeg7gC3bBM0SdieA5XF3GW1wKA==" + }, + "@types/d3-timer": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/@types/d3-timer/-/d3-timer-1.0.10.tgz", + "integrity": "sha512-ZnAbquVqy+4ZjdW0cY6URp+qF/AzTVNda2jYyOzpR2cPT35FTXl78s15Bomph9+ckOiI1TtkljnWkwbIGAb6rg==" + }, + "@types/d3-transition": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@types/d3-transition/-/d3-transition-1.3.2.tgz", + "integrity": "sha512-J+a3SuF/E7wXbOSN19p8ZieQSFIm5hU2Egqtndbc54LXaAEOpLfDx4sBu/PKAKzHOdgKK1wkMhINKqNh4aoZAg==", + "requires": { + "@types/d3-selection": "^1" + } + }, + "@types/d3-zoom": { + "version": "1.8.3", + "resolved": "https://registry.npmjs.org/@types/d3-zoom/-/d3-zoom-1.8.3.tgz", + "integrity": "sha512-3kHkL6sPiDdbfGhzlp5gIHyu3kULhtnHTTAl3UBZVtWB1PzcLL8vdmz5mTx7plLiUqOA2Y+yT2GKjt/TdA2p7Q==", + "requires": { + "@types/d3-interpolate": "^1", + "@types/d3-selection": "^1" + } + } + } + }, + "@types/connect": { + "version": "3.4.35", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.35.tgz", + "integrity": "sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "@types/connect-history-api-fallback": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@types/connect-history-api-fallback/-/connect-history-api-fallback-1.5.0.tgz", + "integrity": "sha512-4x5FkPpLipqwthjPsF7ZRbOv3uoLUFkTA9G9v583qi4pACvq0uTELrB8OLUzPWUI4IJIyvM85vzkV1nyiI2Lig==", + "dev": true, + "requires": { + "@types/express-serve-static-core": "*", + "@types/node": "*" + } + }, + "@types/cookie": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.4.1.tgz", + "integrity": "sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q==" + }, + "@types/cors": { + "version": "2.8.13", + "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.13.tgz", + "integrity": "sha512-RG8AStHlUiV5ysZQKq97copd2UmVYw3/pRMLefISZ3S1hK104Cwm7iLQ3fTKx+lsUH2CE8FlLaYeEA2LSeqYUA==", + "requires": { + "@types/node": "*" + } + }, + "@types/d3-collection": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/@types/d3-collection/-/d3-collection-1.0.10.tgz", + "integrity": "sha512-54Fdv8u5JbuXymtmXm2SYzi1x/Svt+jfWBU5junkhrCewL92VjqtCBDn97coBRVwVFmYNnVTNDyV8gQyPYfm+A==" + }, + "@types/d3-queue": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@types/d3-queue/-/d3-queue-3.0.8.tgz", + "integrity": "sha512-1FWOiI/MYwS5Z1Sa9EvS1Xet3isiVIIX5ozD6iGnwHonGcqL+RcC1eThXN5VfDmAiYt9Me9EWNEv/9J9k9RIKQ==" + }, + "@types/d3-request": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-request/-/d3-request-1.0.6.tgz", + "integrity": "sha512-4nRKDUBg3EBx8VowpMvM3NAVMiMMI1qFUOYv3OJsclGjHX6xjtu09nsWhRQ0fvSUla3MEjb5Ch4IeaYarMEi1w==", + "requires": { + "@types/d3-dsv": "^1" + }, + "dependencies": { + "@types/d3-dsv": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@types/d3-dsv/-/d3-dsv-1.2.2.tgz", + "integrity": "sha512-GRnz9z8ypqb7OsQ/xw/BmFAp0/k3pgM1s19FTZZSlCMY0EvyVTkU8xzZKKDXzytGXPpTNC4R5pGl9oxEvVSnHQ==" + } + } + }, + "@types/d3-voronoi": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/@types/d3-voronoi/-/d3-voronoi-1.1.9.tgz", + "integrity": "sha512-DExNQkaHd1F3dFPvGA/Aw2NGyjMln6E9QzsiqOcBgnE+VInYnFBHBBySbZQts6z6xD+5jTfKCP7M4OqMyVjdwQ==" + }, + "@types/eslint": { + "version": "8.40.0", + "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.40.0.tgz", + "integrity": "sha512-nbq2mvc/tBrK9zQQuItvjJl++GTN5j06DaPtp3hZCpngmG6Q3xoyEmd0TwZI0gAy/G1X0zhGBbr2imsGFdFV0g==", + "dev": true, + "requires": { + "@types/estree": "*", + "@types/json-schema": "*" + } + }, + "@types/eslint-scope": { + "version": "3.7.4", + "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.4.tgz", + "integrity": "sha512-9K4zoImiZc3HlIp6AVUDE4CWYx22a+lhSZMYNpbjW04+YF0KWj4pJXnEMjdnFTiQibFFmElcsasJXDbdI/EPhA==", + "dev": true, + "requires": { + "@types/eslint": "*", + "@types/estree": "*" + } + }, + "@types/estree": { + "version": "0.0.51", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.51.tgz", + "integrity": "sha512-CuPgU6f3eT/XgKKPqKd/gLZV1Xmvf1a2R5POBOGQa6uv82xpls89HU5zKeVoyR8XzHd1RGNOlQlvUe3CFkjWNQ==", + "dev": true + }, + "@types/express": { + "version": "4.17.17", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.17.tgz", + "integrity": "sha512-Q4FmmuLGBG58btUnfS1c1r/NQdlp3DMfGDGig8WhfpA2YRUtEkxAjkZb0yvplJGYdF1fsQ81iMDcH24sSCNC/Q==", + "dev": true, + "requires": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^4.17.33", + "@types/qs": "*", + "@types/serve-static": "*" + } + }, + "@types/express-serve-static-core": { + "version": "4.17.35", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.35.tgz", + "integrity": "sha512-wALWQwrgiB2AWTT91CB62b6Yt0sNHpznUXeZEcnPU3DRdlDIz74x8Qg1UUYKSVFi+va5vKOLYRBI1bRKiLLKIg==", + "dev": true, + "requires": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" + } + }, + "@types/geojson": { + "version": "7946.0.10", + "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.10.tgz", + "integrity": "sha512-Nmh0K3iWQJzniTuPRcJn5hxXkfB1T1pgB89SBig5PlJQU5yocazeu4jATJlaA0GYFKWMqDdvYemoSnF2pXgLVA==" + }, + "@types/http-proxy": { + "version": "1.17.11", + "resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.11.tgz", + "integrity": "sha512-HC8G7c1WmaF2ekqpnFq626xd3Zz0uvaqFmBJNRZCGEZCXkvSdJoNFn/8Ygbd9fKNQj8UzLdCETaI0UWPAjK7IA==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "@types/jasmine": { + "version": "3.10.5", + "resolved": "https://registry.npmjs.org/@types/jasmine/-/jasmine-3.10.5.tgz", + "integrity": "sha512-RYxEJkk/RK1S6W0nMOx9Mxm5v2bKwAVjzTHiBcHkQskwKDqHKHjSU46jx/q9IffyuctNl8ebaAjb5/461faKqQ==", + "dev": true + }, + "@types/jasminewd2": { + "version": "2.0.10", + "resolved": "https://registry.npmjs.org/@types/jasminewd2/-/jasminewd2-2.0.10.tgz", + "integrity": "sha512-J7mDz7ovjwjc+Y9rR9rY53hFWKATcIkrr9DwQWmOas4/pnIPJTXawnzjwpHm3RSxz/e3ZVUvQ7cRbd5UQLo10g==", + "dev": true, + "requires": { + "@types/jasmine": "*" + } + }, + "@types/jquery": { + "version": "3.5.14", + "resolved": "https://registry.npmjs.org/@types/jquery/-/jquery-3.5.14.tgz", + "integrity": "sha512-X1gtMRMbziVQkErhTQmSe2jFwwENA/Zr+PprCkF63vFq+Yt5PZ4AlKqgmeNlwgn7dhsXEK888eIW2520EpC+xg==", + "dev": true, + "requires": { + "@types/sizzle": "*" + } + }, + "@types/json-schema": { + "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==", + "dev": true + }, + "@types/mime": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.2.tgz", + "integrity": "sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw==", + "dev": true + }, + "@types/node": { + "version": "15.14.9", + "resolved": "https://registry.npmjs.org/@types/node/-/node-15.14.9.tgz", + "integrity": "sha512-qjd88DrCxupx/kJD5yQgZdcYKZKSIGBVDIBE1/LTGcNm3d2Np/jxojkdePDdfnBHJc5W7vSMpbJ1aB7p/Py69A==" + }, + "@types/parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==", + "dev": true + }, + "@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==", + "dev": true + }, + "@types/range-parser": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.4.tgz", + "integrity": "sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==", + "dev": true + }, + "@types/retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.0.tgz", + "integrity": "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==", + "dev": true + }, + "@types/send": { + "version": "0.17.1", + "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.1.tgz", + "integrity": "sha512-Cwo8LE/0rnvX7kIIa3QHCkcuF21c05Ayb0ZfxPiv0W8VRiZiNW/WuRupHKpqqGVGf7SUA44QSOUKaEd9lIrd/Q==", + "dev": true, + "requires": { + "@types/mime": "^1", + "@types/node": "*" + } + }, + "@types/serve-index": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/@types/serve-index/-/serve-index-1.9.1.tgz", + "integrity": "sha512-d/Hs3nWDxNL2xAczmOVZNj92YZCS6RGxfBPjKzuu/XirCgXdpKEb88dYNbrYGint6IVWLNP+yonwVAuRC0T2Dg==", + "dev": true, + "requires": { + "@types/express": "*" + } + }, + "@types/serve-static": { + "version": "1.15.1", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.1.tgz", + "integrity": "sha512-NUo5XNiAdULrJENtJXZZ3fHtfMolzZwczzBbnAeBbqBwG+LaG6YaJtuwzwGSQZ2wsCrxjEhNNjAkKigy3n8teQ==", + "dev": true, + "requires": { + "@types/mime": "*", + "@types/node": "*" + } + }, + "@types/sizzle": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/@types/sizzle/-/sizzle-2.3.3.tgz", + "integrity": "sha512-JYM8x9EGF163bEyhdJBpR2QX1R5naCJHC8ucJylJ3w9/CVBaskdQ8WqBf8MmQrd1kRvp/a4TS8HJ+bxzR7ZJYQ==", + "dev": true + }, + "@types/sockjs": { + "version": "0.3.33", + "resolved": "https://registry.npmjs.org/@types/sockjs/-/sockjs-0.3.33.tgz", + "integrity": "sha512-f0KEEe05NvUnat+boPTZ0dgaLZ4SfSouXUgv5noUiefG2ajgKjmETo9ZJyuqsl7dfl2aHlLJUiki6B4ZYldiiw==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "@types/ws": { + "version": "8.5.4", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.4.tgz", + "integrity": "sha512-zdQDHKUgcX/zBc4GrwsE/7dVdAD8JR4EuiAXiiUhhfyIJXXb2+PrGshFyeXWQPMmmZ2XxgaqclgpIC7eTXc1mg==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "@webassemblyjs/ast": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.1.tgz", + "integrity": "sha512-ukBh14qFLjxTQNTXocdyksN5QdM28S1CxHt2rdskFyL+xFV7VremuBLVbmCePj+URalXBENx/9Lm7lnhihtCSw==", + "dev": true, + "requires": { + "@webassemblyjs/helper-numbers": "1.11.1", + "@webassemblyjs/helper-wasm-bytecode": "1.11.1" + } + }, + "@webassemblyjs/floating-point-hex-parser": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.1.tgz", + "integrity": "sha512-iGRfyc5Bq+NnNuX8b5hwBrRjzf0ocrJPI6GWFodBFzmFnyvrQ83SHKhmilCU/8Jv67i4GJZBMhEzltxzcNagtQ==", + "dev": true + }, + "@webassemblyjs/helper-api-error": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.1.tgz", + "integrity": "sha512-RlhS8CBCXfRUR/cwo2ho9bkheSXG0+NwooXcc3PAILALf2QLdFyj7KGsKRbVc95hZnhnERon4kW/D3SZpp6Tcg==", + "dev": true + }, + "@webassemblyjs/helper-buffer": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.1.tgz", + "integrity": "sha512-gwikF65aDNeeXa8JxXa2BAk+REjSyhrNC9ZwdT0f8jc4dQQeDQ7G4m0f2QCLPJiMTTO6wfDmRmj/pW0PsUvIcA==", + "dev": true + }, + "@webassemblyjs/helper-numbers": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.1.tgz", + "integrity": "sha512-vDkbxiB8zfnPdNK9Rajcey5C0w+QJugEglN0of+kmO8l7lDb77AnlKYQF7aarZuCrv+l0UvqL+68gSDr3k9LPQ==", + "dev": true, + "requires": { + "@webassemblyjs/floating-point-hex-parser": "1.11.1", + "@webassemblyjs/helper-api-error": "1.11.1", + "@xtuc/long": "4.2.2" + } + }, + "@webassemblyjs/helper-wasm-bytecode": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.1.tgz", + "integrity": "sha512-PvpoOGiJwXeTrSf/qfudJhwlvDQxFgelbMqtq52WWiXC6Xgg1IREdngmPN3bs4RoO83PnL/nFrxucXj1+BX62Q==", + "dev": true + }, + "@webassemblyjs/helper-wasm-section": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.1.tgz", + "integrity": "sha512-10P9No29rYX1j7F3EVPX3JvGPQPae+AomuSTPiF9eBQeChHI6iqjMIwR9JmOJXwpnn/oVGDk7I5IlskuMwU/pg==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.11.1", + "@webassemblyjs/helper-buffer": "1.11.1", + "@webassemblyjs/helper-wasm-bytecode": "1.11.1", + "@webassemblyjs/wasm-gen": "1.11.1" + } + }, + "@webassemblyjs/ieee754": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.1.tgz", + "integrity": "sha512-hJ87QIPtAMKbFq6CGTkZYJivEwZDbQUgYd3qKSadTNOhVY7p+gfP6Sr0lLRVTaG1JjFj+r3YchoqRYxNH3M0GQ==", + "dev": true, + "requires": { + "@xtuc/ieee754": "^1.2.0" + } + }, + "@webassemblyjs/leb128": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.1.tgz", + "integrity": "sha512-BJ2P0hNZ0u+Th1YZXJpzW6miwqQUGcIHT1G/sf72gLVD9DZ5AdYTqPNbHZh6K1M5VmKvFXwGSWZADz+qBWxeRw==", + "dev": true, + "requires": { + "@xtuc/long": "4.2.2" + } + }, + "@webassemblyjs/utf8": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.11.1.tgz", + "integrity": "sha512-9kqcxAEdMhiwQkHpkNiorZzqpGrodQQ2IGrHHxCy+Ozng0ofyMA0lTqiLkVs1uzTRejX+/O0EOT7KxqVPuXosQ==", + "dev": true + }, + "@webassemblyjs/wasm-edit": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.1.tgz", + "integrity": "sha512-g+RsupUC1aTHfR8CDgnsVRVZFJqdkFHpsHMfJuWQzWU3tvnLC07UqHICfP+4XyL2tnr1amvl1Sdp06TnYCmVkA==", + "dev": true, + "requires": { + "@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" + } + }, + "@webassemblyjs/wasm-gen": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.1.tgz", + "integrity": "sha512-F7QqKXwwNlMmsulj6+O7r4mmtAlCWfO/0HdgOxSklZfQcDu0TpLiD1mRt/zF25Bk59FIjEuGAIyn5ei4yMfLhA==", + "dev": true, + "requires": { + "@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" + } + }, + "@webassemblyjs/wasm-opt": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.1.tgz", + "integrity": "sha512-VqnkNqnZlU5EB64pp1l7hdm3hmQw7Vgqa0KF/KCNO9sIpI6Fk6brDEiX+iCOYrvMuBWDws0NkTOxYEb85XQHHw==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.11.1", + "@webassemblyjs/helper-buffer": "1.11.1", + "@webassemblyjs/wasm-gen": "1.11.1", + "@webassemblyjs/wasm-parser": "1.11.1" + } + }, + "@webassemblyjs/wasm-parser": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.1.tgz", + "integrity": "sha512-rrBujw+dJu32gYB7/Lup6UhdkPx9S9SnobZzRVL7VcBH9Bt9bCBLEuX/YXOOtBsOZ4NQrRykKhffRWHvigQvOA==", + "dev": true, + "requires": { + "@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" + } + }, + "@webassemblyjs/wast-printer": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.11.1.tgz", + "integrity": "sha512-IQboUWM4eKzWW+N/jij2sRatKMh99QEelo3Eb2q0qXkvPRISAj8Qxtmw5itwqK+TTkBuUIE45AxYPToqPtL5gg==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.11.1", + "@xtuc/long": "4.2.2" + } + }, + "@xtuc/ieee754": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", + "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", + "dev": true + }, + "@xtuc/long": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", + "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", + "dev": true + }, + "@yarnpkg/lockfile": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@yarnpkg/lockfile/-/lockfile-1.1.0.tgz", + "integrity": "sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ==", + "dev": true + }, + "abab": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.6.tgz", + "integrity": "sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==", + "dev": true + }, + "abbrev": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", + "dev": true + }, + "accepts": { + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", + "integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==", + "requires": { + "mime-types": "~2.1.24", + "negotiator": "0.6.2" + } + }, + "acorn": { + "version": "8.7.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.0.tgz", + "integrity": "sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ==", + "dev": true + }, + "acorn-import-assertions": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.9.0.tgz", + "integrity": "sha512-cmMwop9x+8KFhxvKrKfPYmN6/pKTYYHBqLa0DfvVZcKMJWNyWLnaqND7dx/qn66R7ewM1UX5XMaDVP5wlVTaVA==", + "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==" + }, + "acorn-walk": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", + "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", + "dev": true + }, + "adjust-sourcemap-loader": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/adjust-sourcemap-loader/-/adjust-sourcemap-loader-4.0.0.tgz", + "integrity": "sha512-OXwN5b9pCUXNQHJpwwD2qP40byEmSgzj8B4ydSN0uMNYWiFmJ6x6KwUllMmfk8Rwu/HJDFR7U8ubsWBoN0Xp0A==", + "dev": true, + "requires": { + "loader-utils": "^2.0.0", + "regex-parser": "^2.2.11" + }, + "dependencies": { + "loader-utils": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.4.tgz", + "integrity": "sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==", + "dev": true, + "requires": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^2.1.2" + } + } + } + }, + "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" + } + }, + "agentkeepalive": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.3.0.tgz", + "integrity": "sha512-7Epl1Blf4Sy37j4v9f9FjICCh4+KAQOyXgHEwlyBiAQLbhKdq/i2QQU3amQalS/wPhdPzDXPL5DMR5bkn+YeWg==", + "dev": true, + "requires": { + "debug": "^4.1.0", + "depd": "^2.0.0", + "humanize-ms": "^1.2.1" + } + }, + "aggregate-error": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", + "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", + "dev": true, + "requires": { + "clean-stack": "^2.0.0", + "indent-string": "^4.0.0" + } + }, + "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" + } + }, + "ajv-formats": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", + "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", + "dev": true, + "requires": { + "ajv": "^8.0.0" + }, + "dependencies": { + "ajv": { + "version": "8.12.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", + "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", + "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 + } + } + }, + "ajv-keywords": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", + "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==" + }, + "angular-svg-round-progressbar": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/angular-svg-round-progressbar/-/angular-svg-round-progressbar-10.0.0.tgz", + "integrity": "sha512-X7t7+P6dEMySJHPefjDnTUQ72iM/qGGmrtbsTBg8WYJQVj7woN5AiqiZazshrLhge2ksCbryAVULm8XMGR0uvA==", + "requires": { + "tslib": "^2.3.0" + } + }, + "ansi-colors": { + "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==", + "dev": true + }, + "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-html-community": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/ansi-html-community/-/ansi-html-community-0.0.8.tgz", + "integrity": "sha512-1APHAyr3+PCamwNw3bXCPp4HFLONZt/yIH0sZp0/469KWNTEy+qN5jQ3GVX6DMZ1UXAi34yVwtTeaG/HpBuuzw==", + "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": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "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" + } + }, + "aproba": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz", + "integrity": "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==", + "dev": true + }, + "are-we-there-yet": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-3.0.1.tgz", + "integrity": "sha512-QZW4EDmGwlYur0Yyf/b2uGucHQMa8aFUP7eu9ddR73vvhFyt4V0Vl3QHPcTNJ8l6qYOBdxgXdnBXQrHilfRQBg==", + "dev": true, + "requires": { + "delegates": "^1.0.0", + "readable-stream": "^3.6.0" + } + }, + "arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true + }, + "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" + } + }, + "array-flatten": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-2.1.2.tgz", + "integrity": "sha512-hNfzcOV8W4NdualtqBFPyVO+54DSJuZGY9qT4pRroB6S9e3iiido2ISIC5h9R2sPJ8H3FHCIiEnsv1lPXO3KtQ==", + "dev": true + }, + "autoprefixer": { + "version": "10.4.13", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.13.tgz", + "integrity": "sha512-49vKpMqcZYsJjwotvt4+h/BCjJVnhGwcLpDt5xkcaOG3eLrG/HUYLagrihYsQ+qrIBgIzX1Rw7a6L8I/ZA1Atg==", + "dev": true, + "requires": { + "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" + } + }, + "babel-loader": { + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-9.1.2.tgz", + "integrity": "sha512-mN14niXW43tddohGl8HPu5yfQq70iUThvFL/4QzESA7GcZoC0eVOhvWdQ8+3UlSjaDE9MVtsW9mxDY07W7VpVA==", + "dev": true, + "requires": { + "find-cache-dir": "^3.3.2", + "schema-utils": "^4.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" + } + }, + "babel-plugin-polyfill-corejs2": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.3.3.tgz", + "integrity": "sha512-8hOdmFYFSZhqg2C/JgLUQ+t52o5nirNwaWM2B9LWteozwIvM14VSwdsCAUET10qT+kmySAlseadmfeeSWFCy+Q==", + "dev": true, + "requires": { + "@babel/compat-data": "^7.17.7", + "@babel/helper-define-polyfill-provider": "^0.3.3", + "semver": "^6.1.1" + }, + "dependencies": { + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + } + } + }, + "babel-plugin-polyfill-corejs3": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.6.0.tgz", + "integrity": "sha512-+eHqR6OPcBhJOGgsIar7xoAB1GcSwVUA3XjAd7HJNzOXT4wv6/H7KIdA/Nc60cvUlDbKApmqNvD1B1bzOt4nyA==", + "dev": true, + "requires": { + "@babel/helper-define-polyfill-provider": "^0.3.3", + "core-js-compat": "^3.25.1" + } + }, + "babel-plugin-polyfill-regenerator": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.4.1.tgz", + "integrity": "sha512-NtQGmyQDXjQqQ+IzRkBVwEOz9lQ4zxAQZgoAYEtU9dJjnl1Oc98qnN7jcp+bE7O7aYzVpavXE3/VKXNzUbh7aw==", + "dev": true, + "requires": { + "@babel/helper-define-polyfill-provider": "^0.3.3" + } + }, + "balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" + }, + "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 + }, + "base64id": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz", + "integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==" + }, + "batch": { + "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==", + "dev": true + }, + "big.js": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", + "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==", + "dev": true + }, + "binary-extensions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", + "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", + "dev": true + }, + "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" + } + }, + "body-parser": { + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz", + "integrity": "sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==", + "dev": true, + "requires": { + "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" + }, + "dependencies": { + "bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "dev": true + }, + "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" + } + }, + "depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "dev": true + }, + "http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "dev": true, + "requires": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + }, + "on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "dev": true, + "requires": { + "ee-first": "1.1.1" + } + }, + "qs": { + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", + "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", + "dev": true, + "requires": { + "side-channel": "^1.0.4" + } + }, + "raw-body": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz", + "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==", + "dev": true, + "requires": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + } + }, + "statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "dev": true + } + } + }, + "bonjour-service": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/bonjour-service/-/bonjour-service-1.1.1.tgz", + "integrity": "sha512-Z/5lQRMOG9k7W+FkeGTNjh7htqn/2LMnfOvBZ8pynNZCM9MwkQkI3zeI4oz09uWdcgmgHugVvBqxGg4VQJ5PCg==", + "dev": true, + "requires": { + "array-flatten": "^2.1.2", + "dns-equal": "^1.0.0", + "fast-deep-equal": "^3.1.3", + "multicast-dns": "^7.2.5" + } + }, + "boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", + "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==", + "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" + } + }, + "browserslist": { + "version": "4.21.5", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.5.tgz", + "integrity": "sha512-tUkiguQGW7S3IhB7N+c2MV/HZPSCPAAiYBZXLsBhFB/PCy6ZKKsZrmBayHV9fdGV/ARIfJ14NkxKzRDjvp7L6w==", + "dev": true, + "requires": { + "caniuse-lite": "^1.0.30001449", + "electron-to-chromium": "^1.4.284", + "node-releases": "^2.0.8", + "update-browserslist-db": "^1.0.10" + } + }, + "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-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 + }, + "builtin-modules": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz", + "integrity": "sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8=", + "dev": true + }, + "builtins": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/builtins/-/builtins-5.0.1.tgz", + "integrity": "sha512-qwVpFEHNfhYJIzNRBvd2C1kyo6jz3ZSMPyyuR47OPdiKWlbYnZNyDWuyR175qDnAJLiCo5fBBqPb3RiXgWlkOQ==", + "dev": true, + "requires": { + "semver": "^7.0.0" + } + }, + "bytes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", + "integrity": "sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw==", + "dev": true + }, + "cacache": { + "version": "17.0.4", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-17.0.4.tgz", + "integrity": "sha512-Z/nL3gU+zTUjz5pCA5vVjYM8pmaw2kxM7JEiE0fv3w77Wj+sFbi70CrBruUWH0uNcEdvLDixFpgA2JM4F4DBjA==", + "dev": true, + "requires": { + "@npmcli/fs": "^3.1.0", + "fs-minipass": "^3.0.0", + "glob": "^8.0.1", + "lru-cache": "^7.7.1", + "minipass": "^4.0.0", + "minipass-collect": "^1.0.2", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "p-map": "^4.0.0", + "promise-inflight": "^1.0.1", + "ssri": "^10.0.0", + "tar": "^6.1.11", + "unique-filename": "^3.0.0" + }, + "dependencies": { + "brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0" + } + }, + "glob": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", + "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^5.0.1", + "once": "^1.3.0" + } + }, + "lru-cache": { + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", + "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "dev": true + }, + "minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "dev": true, + "requires": { + "brace-expansion": "^2.0.1" + } + } + } + }, + "call-bind": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", + "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "dev": true, + "requires": { + "function-bind": "^1.1.1", + "get-intrinsic": "^1.0.2" + } + }, + "callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==" + }, + "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.30001489", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001489.tgz", + "integrity": "sha512-x1mgZEXK8jHIfAxm+xgdpHpk50IN3z3q3zP261/WS+uvePxW8izXuCu6AHz0lkuYTlATDehiZ/tNyYBdSQsOUQ==", + "dev": true + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "chardet": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", + "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", + "dev": true + }, + "chokidar": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", + "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", + "dev": true, + "requires": { + "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" + } + }, + "chownr": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", + "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", + "dev": true + }, + "chrome-trace-event": { + "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==", + "dev": true + }, + "clean-stack": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", + "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", + "dev": true + }, + "cli-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", + "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", + "dev": true, + "requires": { + "restore-cursor": "^3.1.0" + } + }, + "cli-spinners": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.0.tgz", + "integrity": "sha512-4/aL9X3Wh0yiMQlE+eeRhWP6vclO3QRtw1JHKIT0FFUs5FjpFmESqtMvYZ0+lbzBw900b95mS0hohy+qn2VK/g==", + "dev": true + }, + "cli-width": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-3.0.0.tgz", + "integrity": "sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==", + "dev": true + }, + "cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "requires": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + } + }, + "clone": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", + "integrity": "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==", + "dev": true + }, + "clone-deep": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz", + "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==", + "dev": true, + "requires": { + "is-plain-object": "^2.0.4", + "kind-of": "^6.0.2", + "shallow-clone": "^3.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==", + "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==" + }, + "color-support": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", + "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==", + "dev": true + }, + "colorette": { + "version": "2.0.20", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", + "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", + "dev": true + }, + "commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true + }, + "commondir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", + "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==", + "dev": true + }, + "compare-versions": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/compare-versions/-/compare-versions-3.6.0.tgz", + "integrity": "sha512-W6Af2Iw1z4CB7q4uU4hv646dW9GQuBM+YpC0UvUCWSD8w90SJjp+ujJuXaEMtAXBtSqGfMPuFOVn4/+FlaqfBA==" + }, + "compressible": { + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", + "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", + "dev": true, + "requires": { + "mime-db": ">= 1.43.0 < 2" + } + }, + "compression": { + "version": "1.7.4", + "resolved": "https://registry.npmjs.org/compression/-/compression-1.7.4.tgz", + "integrity": "sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ==", + "dev": true, + "requires": { + "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" + }, + "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" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "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 + } + } + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" + }, + "connect": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/connect/-/connect-3.7.0.tgz", + "integrity": "sha512-ZqRXc+tZukToSNmh5C2iWMSoV3X1YUcPbqEM4DkEG5tNQXrQUZCNVGGv3IuicnkMtPfGf3Xtp8WCXs295iQ1pQ==", + "dev": true, + "requires": { + "debug": "2.6.9", + "finalhandler": "1.1.2", + "parseurl": "~1.3.3", + "utils-merge": "1.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" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + } + } + }, + "connect-history-api-fallback": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/connect-history-api-fallback/-/connect-history-api-fallback-2.0.0.tgz", + "integrity": "sha512-U73+6lQFmfiNPrYbXqr6kZ1i1wiRqXnp2nhMsINseWXO8lDau0LGEffJ8kQi4EjLZympVgRdvqjAgiZ1tgzDDA==", + "dev": true + }, + "console-control-strings": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", + "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==", + "dev": true + }, + "content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "dev": true, + "requires": { + "safe-buffer": "5.2.1" + } + }, + "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==", + "dev": true + }, + "convert-source-map": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", + "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", + "dev": true + }, + "cookie": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz", + "integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==" + }, + "cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", + "dev": true + }, + "copy-anything": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/copy-anything/-/copy-anything-2.0.6.tgz", + "integrity": "sha512-1j20GZTsvKNkc4BY3NpMOM8tt///wY3FpIzozTOFO2ffuZcV61nojHXVKIy3WM+7ADCy5FVhdZYHYDdgTU0yJw==", + "dev": true, + "requires": { + "is-what": "^3.14.1" + } + }, + "copy-webpack-plugin": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-11.0.0.tgz", + "integrity": "sha512-fX2MWpamkW0hZxMEg0+mYnA40LTosOSa5TqZ9GYIBzyJa9C3QUaMPSE2xAi/buNr8u89SfD9wHSQVBzrRa/SOQ==", + "dev": true, + "requires": { + "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" + }, + "dependencies": { + "glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "requires": { + "is-glob": "^4.0.3" + } + } + } + }, + "core-js": { + "version": "3.21.1", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.21.1.tgz", + "integrity": "sha512-FRq5b/VMrWlrmCzwRrpDYNxyHP9BcAZC+xHJaqTgIE5091ZV1NTmyh0sGOg5XqpnHvR0svdy0sv1gWA1zmhxig==" + }, + "core-js-compat": { + "version": "3.30.2", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.30.2.tgz", + "integrity": "sha512-nriW1nuJjUgvkEjIot1Spwakz52V9YkYHZAQG6A1eCgC8AA1p0zngrQEP9R0+V6hji5XilWKG1Bd0YRppmGimA==", + "dev": true, + "requires": { + "browserslist": "^4.21.5" + } + }, + "core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "dev": true + }, + "cors": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "requires": { + "object-assign": "^4", + "vary": "^1" + } + }, + "cosmiconfig": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz", + "integrity": "sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==", + "dev": true, + "requires": { + "@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-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "dev": true + }, + "critters": { + "version": "0.0.16", + "resolved": "https://registry.npmjs.org/critters/-/critters-0.0.16.tgz", + "integrity": "sha512-JwjgmO6i3y6RWtLYmXwO5jMd+maZt8Tnfu7VVISmEWyQqfLpB8soBswf8/2bu6SBXxtKA68Al3c+qIG1ApT68A==", + "dev": true, + "requires": { + "chalk": "^4.1.0", + "css-select": "^4.2.0", + "parse5": "^6.0.1", + "parse5-htmlparser2-tree-adapter": "^6.0.1", + "postcss": "^8.3.7", + "pretty-bytes": "^5.3.0" + }, + "dependencies": { + "parse5": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", + "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==", + "dev": true + } + } + }, + "cron-parser": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/cron-parser/-/cron-parser-4.3.0.tgz", + "integrity": "sha512-mK6qJ6k9Kn0/U7Cv6LKQnReUW3GqAW4exgwmHJGb3tPgcy0LrS+PeqxPPiwL8uW/4IJsMsCZrCc4vf1nnXMjzA==", + "requires": { + "luxon": "^1.28.0" + } + }, + "cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "requires": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + } + }, + "css-loader": { + "version": "6.7.3", + "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-6.7.3.tgz", + "integrity": "sha512-qhOH1KlBMnZP8FzRO6YCH9UHXQhVMcEGLyNdb7Hv2cpcmJbW0YrddO+tG1ab5nT41KpHIYGsbeHqxB9xPu1pKQ==", + "dev": true, + "requires": { + "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" + } + }, + "css-select": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-4.3.0.tgz", + "integrity": "sha512-wPpOYtnsVontu2mODhA19JrqWxNsfdatRKd64kmpRbQgh1KtItko5sTnEpPdpSaJszTOhEMlF/RPz28qj4HqhQ==", + "dev": true, + "requires": { + "boolbase": "^1.0.0", + "css-what": "^6.0.1", + "domhandler": "^4.3.1", + "domutils": "^2.8.0", + "nth-check": "^2.0.1" + } + }, + "css-what": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz", + "integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==", + "dev": true + }, + "cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "dev": true + }, + "custom-event": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/custom-event/-/custom-event-1.0.1.tgz", + "integrity": "sha1-XQKkaFCt8bSjF5RqOSj8y1v9BCU=", + "dev": true + }, + "d3": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/d3/-/d3-7.8.3.tgz", + "integrity": "sha512-cAa866AkPXtt4IzRx6nzGf50uerq6VYks7p/tTH94be4QfhUkyTfJfaxXGPOB5ZRVUZmUV1wcM1dism/Ua0lCw==", + "requires": { + "d3-array": "3", + "d3-axis": "3", + "d3-brush": "3", + "d3-chord": "3", + "d3-color": "3", + "d3-contour": "4", + "d3-delaunay": "6", + "d3-dispatch": "3", + "d3-drag": "3", + "d3-dsv": "3", + "d3-ease": "3", + "d3-fetch": "3", + "d3-force": "3", + "d3-format": "3", + "d3-geo": "3", + "d3-hierarchy": "3", + "d3-interpolate": "3", + "d3-path": "3", + "d3-polygon": "3", + "d3-quadtree": "3", + "d3-random": "3", + "d3-scale": "4", + "d3-scale-chromatic": "3", + "d3-selection": "3", + "d3-shape": "3", + "d3-time": "3", + "d3-time-format": "4", + "d3-timer": "3", + "d3-transition": "3", + "d3-zoom": "3" + } + }, + "d3-array": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.3.tgz", + "integrity": "sha512-JRHwbQQ84XuAESWhvIPaUV4/1UYTBOLiOPGWqgFDHZS1D5QN9c57FbH3QpEnQMYiOXNzKUQyGTZf+EVO7RT5TQ==", + "requires": { + "internmap": "1 - 2" + } + }, + "d3-axis": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-axis/-/d3-axis-3.0.0.tgz", + "integrity": "sha512-IH5tgjV4jE/GhHkRV0HiVYPDtvfjHQlQfJHs0usq7M30XcSBvOotpmH1IgkcXsO/5gEQZD43B//fc7SRT5S+xw==" + }, + "d3-brush": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-brush/-/d3-brush-3.0.0.tgz", + "integrity": "sha512-ALnjWlVYkXsVIGlOsuWH1+3udkYFI48Ljihfnh8FZPF2QS9o+PzGLBslO0PjzVoHLZ2KCVgAM8NVkXPJB2aNnQ==", + "requires": { + "d3-dispatch": "1 - 3", + "d3-drag": "2 - 3", + "d3-interpolate": "1 - 3", + "d3-selection": "3", + "d3-transition": "3" + } + }, + "d3-chord": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-chord/-/d3-chord-3.0.1.tgz", + "integrity": "sha512-VE5S6TNa+j8msksl7HwjxMHDM2yNK3XCkusIlpX5kwauBfXuyLAtNg9jCp/iHH61tgI4sb6R/EIMWCqEIdjT/g==", + "requires": { + "d3-path": "1 - 3" + } + }, + "d3-color": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz", + "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==" + }, + "d3-contour": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/d3-contour/-/d3-contour-4.0.2.tgz", + "integrity": "sha512-4EzFTRIikzs47RGmdxbeUvLWtGedDUNkTcmzoeyg4sP/dvCexO47AaQL7VKy/gul85TOxw+IBgA8US2xwbToNA==", + "requires": { + "d3-array": "^3.2.0" + } + }, + "d3-delaunay": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/d3-delaunay/-/d3-delaunay-6.0.3.tgz", + "integrity": "sha512-1gPbiMuikAgU/rFcT6WMu17zx0aelw9Hh80go7/TwZQ+/uq8DqqmiNYy+EqPEvTSp/BkJFIpQxjac4Gk/w0zOg==", + "requires": { + "delaunator": "5" + } + }, + "d3-dispatch": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-3.0.1.tgz", + "integrity": "sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==" + }, + "d3-drag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-drag/-/d3-drag-3.0.0.tgz", + "integrity": "sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==", + "requires": { + "d3-dispatch": "1 - 3", + "d3-selection": "3" + } + }, + "d3-dsv": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-dsv/-/d3-dsv-3.0.1.tgz", + "integrity": "sha512-UG6OvdI5afDIFP9w4G0mNq50dSOsXHJaRE8arAS5o9ApWnIElp8GZw1Dun8vP8OyHOZ/QJUKUJwxiiCCnUwm+Q==", + "requires": { + "commander": "7", + "iconv-lite": "0.6", + "rw": "1" + }, + "dependencies": { + "commander": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", + "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==" + }, + "iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "requires": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + } + } + } + }, + "d3-ease": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz", + "integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==" + }, + "d3-fetch": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-fetch/-/d3-fetch-3.0.1.tgz", + "integrity": "sha512-kpkQIM20n3oLVBKGg6oHrUchHM3xODkTzjMoj7aWQFq5QEM+R6E4WkzT5+tojDY7yjez8KgCBRoj4aEr99Fdqw==", + "requires": { + "d3-dsv": "1 - 3" + } + }, + "d3-force": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-force/-/d3-force-3.0.0.tgz", + "integrity": "sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg==", + "requires": { + "d3-dispatch": "1 - 3", + "d3-quadtree": "1 - 3", + "d3-timer": "1 - 3" + } + }, + "d3-format": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.0.tgz", + "integrity": "sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==" + }, + "d3-geo": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-geo/-/d3-geo-3.1.0.tgz", + "integrity": "sha512-JEo5HxXDdDYXCaWdwLRt79y7giK8SbhZJbFWXqbRTolCHFI5jRqteLzCsq51NKbUoX0PjBVSohxrx+NoOUujYA==", + "requires": { + "d3-array": "2.5.0 - 3" + } + }, + "d3-hierarchy": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/d3-hierarchy/-/d3-hierarchy-3.1.2.tgz", + "integrity": "sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA==" + }, + "d3-interpolate": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz", + "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==", + "requires": { + "d3-color": "1 - 3" + } + }, + "d3-path": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz", + "integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==" + }, + "d3-polygon": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-polygon/-/d3-polygon-3.0.1.tgz", + "integrity": "sha512-3vbA7vXYwfe1SYhED++fPUQlWSYTTGmFmQiany/gdbiWgU/iEyQzyymwL9SkJjFFuCS4902BSzewVGsHHmHtXg==" + }, + "d3-quadtree": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-quadtree/-/d3-quadtree-3.0.1.tgz", + "integrity": "sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw==" + }, + "d3-random": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-random/-/d3-random-3.0.1.tgz", + "integrity": "sha512-FXMe9GfxTxqd5D6jFsQ+DJ8BJS4E/fT5mqqdjovykEB2oFbTMDVdg1MGFxfQW+FBOGoB++k8swBrgwSHT1cUXQ==" + }, + "d3-scale": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz", + "integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==", + "requires": { + "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" + } + }, + "d3-scale-chromatic": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-scale-chromatic/-/d3-scale-chromatic-3.0.0.tgz", + "integrity": "sha512-Lx9thtxAKrO2Pq6OO2Ua474opeziKr279P/TKZsMAhYyNDD3EnCffdbgeSYN5O7m2ByQsxtuP2CSDczNUIZ22g==", + "requires": { + "d3-color": "1 - 3", + "d3-interpolate": "1 - 3" + } + }, + "d3-selection": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz", + "integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==" + }, + "d3-shape": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz", + "integrity": "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==", + "requires": { + "d3-path": "^3.1.0" + } + }, + "d3-time": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz", + "integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==", + "requires": { + "d3-array": "2 - 3" + } + }, + "d3-time-format": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz", + "integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==", + "requires": { + "d3-time": "1 - 3" + } + }, + "d3-timer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz", + "integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==" + }, + "d3-transition": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-transition/-/d3-transition-3.0.1.tgz", + "integrity": "sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==", + "requires": { + "d3-color": "1 - 3", + "d3-dispatch": "1 - 3", + "d3-ease": "1 - 3", + "d3-interpolate": "1 - 3", + "d3-timer": "1 - 3" + } + }, + "d3-zoom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-zoom/-/d3-zoom-3.0.0.tgz", + "integrity": "sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==", + "requires": { + "d3-dispatch": "1 - 3", + "d3-drag": "2 - 3", + "d3-interpolate": "1 - 3", + "d3-selection": "2 - 3", + "d3-transition": "2 - 3" + } + }, + "date-fns": { + "version": "2.28.0", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.28.0.tgz", + "integrity": "sha512-8d35hViGYx/QH0icHYCeLmsLmMUheMmTyV9Fcm6gvNwdw31yXXH+O85sOBJ+OLnLQMKZowvpKb6FgMIQjcpvQw==" + }, + "date-format": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/date-format/-/date-format-4.0.4.tgz", + "integrity": "sha512-/jyf4rhB17ge328HJuJjAcmRtCsGd+NDeAtahRBTaK6vSPR6MO5HlrAit3Nn7dVjaa6sowW0WXt8yQtLyZQFRg==", + "dev": true + }, + "debug": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", + "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", + "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==" + }, + "default-gateway": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/default-gateway/-/default-gateway-6.0.3.tgz", + "integrity": "sha512-fwSOJsbbNzZ/CUFpqFBqYfYNLj1NbMPm8MMCIzHjC83iSJRBEGmDUxU+WP661BaBQImeC2yHwXtz+P/O9o+XEg==", + "dev": true, + "requires": { + "execa": "^5.0.0" + } + }, + "defaults": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz", + "integrity": "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==", + "dev": true, + "requires": { + "clone": "^1.0.2" + } + }, + "define-lazy-prop": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz", + "integrity": "sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==", + "dev": true + }, + "delaunator": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/delaunator/-/delaunator-5.0.0.tgz", + "integrity": "sha512-AyLvtyJdbv/U1GkiS6gUUzclRoAY4Gs75qkMygJJhU75LW4DNuSF2RMzpxs9jw9Oz1BobHjTdkG3zdP55VxAqw==", + "requires": { + "robust-predicates": "^3.0.0" + } + }, + "delegates": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", + "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==", + "dev": true + }, + "depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "dev": true + }, + "dependency-graph": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/dependency-graph/-/dependency-graph-0.11.0.tgz", + "integrity": "sha512-JeMq7fEshyepOWDfcfHK06N3MhyPhz++vtqWhMT5O9A3K42rdsEDpfdVqjaqaAhsw6a+ZqeDvQVtD0hFHQWrzg==", + "dev": true + }, + "destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "dev": true + }, + "detect-node": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz", + "integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==", + "dev": true + }, + "di": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/di/-/di-0.0.1.tgz", + "integrity": "sha1-gGZJMmzqp8qjMG112YXqJ0i6kTw=", + "dev": true + }, + "diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true + }, + "dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "requires": { + "path-type": "^4.0.0" + } + }, + "dns-equal": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/dns-equal/-/dns-equal-1.0.0.tgz", + "integrity": "sha512-z+paD6YUQsk+AbGCEM4PrOXSss5gd66QfcVBFTKR/HpFL9jCqikS94HYwKww6fQyO7IxrIIyUu+g0Ka9tUS2Cg==", + "dev": true + }, + "dns-packet": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-5.6.0.tgz", + "integrity": "sha512-rza3UH1LwdHh9qyPXp8lkwpjSNk/AMD3dPytUoRoqnypDUhY0xvbdmVhWOfxO68frEfV9BU8V12Ez7ZsHGZpCQ==", + "dev": true, + "requires": { + "@leichtgewicht/ip-codec": "^2.0.1" + } + }, + "doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "requires": { + "esutils": "^2.0.2" + } + }, + "dom-serialize": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/dom-serialize/-/dom-serialize-2.2.1.tgz", + "integrity": "sha1-ViromZ9Evl6jB29UGdzVnrQ6yVs=", + "dev": true, + "requires": { + "custom-event": "~1.0.0", + "ent": "~2.2.0", + "extend": "^3.0.0", + "void-elements": "^2.0.0" + } + }, + "dom-serializer": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.4.1.tgz", + "integrity": "sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag==", + "dev": true, + "requires": { + "domelementtype": "^2.0.1", + "domhandler": "^4.2.0", + "entities": "^2.0.0" + } + }, + "domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", + "dev": true + }, + "domhandler": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-4.3.1.tgz", + "integrity": "sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==", + "dev": true, + "requires": { + "domelementtype": "^2.2.0" + } + }, + "domq.js": { + "version": "0.6.7", + "resolved": "https://registry.npmjs.org/domq.js/-/domq.js-0.6.7.tgz", + "integrity": "sha512-WwRGORo/eYGf7v7YXZ3M6x/PEoxCsP3D0my7pnAVwtbfsKRvW6qioSdlLsy1MFzfwN1TM9oO9QJcvkE8ERYmlg==" + }, + "domutils": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-2.8.0.tgz", + "integrity": "sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==", + "dev": true, + "requires": { + "dom-serializer": "^1.0.1", + "domelementtype": "^2.2.0", + "domhandler": "^4.2.0" + } + }, + "eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true + }, + "ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=", + "dev": true + }, + "electron-to-chromium": { + "version": "1.4.407", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.407.tgz", + "integrity": "sha512-5smEvFSFYMv90tICOzRVP7Opp98DAC4KW7RRipg3BuNpGbbV3N+x24Zh3sbLb1T5haGtOSy/hrBfXsWnIM9aCg==", + "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 + }, + "emojis-list": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz", + "integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==", + "dev": true + }, + "encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=", + "dev": true + }, + "encoding": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz", + "integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==", + "dev": true, + "optional": true, + "requires": { + "iconv-lite": "^0.6.2" + }, + "dependencies": { + "iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dev": true, + "optional": true, + "requires": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + } + } + } + }, + "engine.io": { + "version": "6.4.2", + "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.4.2.tgz", + "integrity": "sha512-FKn/3oMiJjrOEOeUub2WCox6JhxBXq/Zn3fZOMCBxKnNYtsdKjxhl7yR3fZhM9PV+rdE75SU5SYMc+2PGzo+Tg==", + "requires": { + "@types/cookie": "^0.4.1", + "@types/cors": "^2.8.12", + "@types/node": ">=10.0.0", + "accepts": "~1.3.4", + "base64id": "2.0.0", + "cookie": "~0.4.1", + "cors": "~2.8.5", + "debug": "~4.3.1", + "engine.io-parser": "~5.0.3", + "ws": "~8.11.0" + }, + "dependencies": { + "ws": { + "version": "8.11.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.11.0.tgz", + "integrity": "sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg==" + } + } + }, + "engine.io-client": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.2.3.tgz", + "integrity": "sha512-aXPtgF1JS3RuuKcpSrBtimSjYvrbhKW9froICH4s0F3XQWLxsKNxqzG39nnvQZQnva4CMvUK63T7shevxRyYHw==", + "requires": { + "@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": { + "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==" + }, + "enhanced-resolve": { + "version": "5.14.1", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.14.1.tgz", + "integrity": "sha512-Vklwq2vDKtl0y/vtwjSesgJ5MYS7Etuk5txS8VdKL4AOS1aUlD96zqIfsOSLQsdv3xgMRbtkWM8eG9XDfKUPow==", + "dev": true, + "requires": { + "graceful-fs": "^4.2.4", + "tapable": "^2.2.0" + } + }, + "ent": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/ent/-/ent-2.2.0.tgz", + "integrity": "sha1-6WQhkyWiHQX0RGai9obtbOX13R0=", + "dev": true + }, + "entities": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz", + "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==", + "dev": true + }, + "env-paths": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", + "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", + "dev": true + }, + "err-code": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/err-code/-/err-code-2.0.3.tgz", + "integrity": "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==", + "dev": true + }, + "errno": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.8.tgz", + "integrity": "sha512-dJ6oBr5SQ1VSd9qkk7ByRgb/1SH4JZjCHSW/mr63/QcXO9zLVxvJ6Oy13nio03rxpSnVDDjFor75SjVeZWPW/A==", + "dev": true, + "optional": true, + "requires": { + "prr": "~1.0.1" + } + }, + "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" + } + }, + "es-module-lexer": { + "version": "0.9.3", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-0.9.3.tgz", + "integrity": "sha512-1HQ2M2sPtxwnvOvT1ZClHyQDiggdNjURWpY2we6aMKCQiUVxTmVs2UYPLIrD84sS+kMdUwfBSylbJPwNnBrnHQ==", + "dev": true + }, + "esbuild-wasm": { + "version": "0.17.8", + "resolved": "https://registry.npmjs.org/esbuild-wasm/-/esbuild-wasm-0.17.8.tgz", + "integrity": "sha512-zCmpxv95E0FuCmvdw1K836UHnj4EdiQnFfjTby35y3LAjRPtXMj3sbHDRHjbD8Mqg5lTwq3knacr/1qIFU51CQ==", + "dev": true + }, + "escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "dev": true + }, + "escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=", + "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 + }, + "eslint": { + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.20.0.tgz", + "integrity": "sha512-d4ixhz5SKCa1D6SCPrivP7yYVi7nyD6A4vs6HIAul9ujBzcEmZVM3/0NN/yu5nKhmO1wjp5xQ46iRfmDGlOviA==", + "requires": { + "@eslint/eslintrc": "^1.3.0", + "@humanwhocodes/config-array": "^0.9.2", + "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.3.2", + "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": "^6.0.1", + "globals": "^13.15.0", + "ignore": "^5.2.0", + "import-fresh": "^3.0.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "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", + "v8-compile-cache": "^2.0.3" + }, + "dependencies": { + "ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "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-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==" + }, + "argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" + }, + "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==" + }, + "eslint-scope": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.1.1.tgz", + "integrity": "sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw==", + "requires": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + } + }, + "estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==" + }, + "glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "requires": { + "is-glob": "^4.0.3" + } + }, + "globals": { + "version": "13.16.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.16.0.tgz", + "integrity": "sha512-A1lrQfpNF+McdPOnnFqY3kSN0AFTy485bTi1bkLk4mVPODIUEcSfhHgRqA+QdXPksrSTTztYXx37NFV+GpGk3Q==", + "requires": { + "type-fest": "^0.20.2" + } + }, + "js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "requires": { + "argparse": "^2.0.1" + } + }, + "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==" + }, + "minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "requires": { + "ansi-regex": "^5.0.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==" + } + } + }, + "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": "3.0.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz", + "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==", + "requires": { + "eslint-visitor-keys": "^2.0.0" + }, + "dependencies": { + "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==" + } + } + }, + "eslint-visitor-keys": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz", + "integrity": "sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==" + }, + "espree": { + "version": "9.3.2", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.3.2.tgz", + "integrity": "sha512-D211tC7ZwouTIuY5x9XnS0E9sWNChB7IYKX/Xp5eQj3nFXhqmiUDB9q27y76oFl8jTg3pXcQx/bpxMfs3CIZbA==", + "requires": { + "acorn": "^8.7.1", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.3.0" + }, + "dependencies": { + "acorn": { + "version": "8.7.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.1.tgz", + "integrity": "sha512-Xx54uLJQZ19lKygFXOWsscKUbsBZW0CPykPhVQdhIeIwrbPmJzqeASDInc8nKBnp/JT6igTs82qPXz069H8I/A==" + } + } + }, + "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==", + "requires": { + "estraverse": "^5.1.0" + }, + "dependencies": { + "estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==" + } + } + }, + "esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "requires": { + "estraverse": "^5.2.0" + }, + "dependencies": { + "estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==" + } + } + }, + "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==" + }, + "etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "dev": true + }, + "eventemitter-asyncresource": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/eventemitter-asyncresource/-/eventemitter-asyncresource-1.0.0.tgz", + "integrity": "sha512-39F7TBIV0G7gTelxwbEqnwhp90eqCPON1k0NwNfwhgKn4Co4ybUbj2pECcXT0B3ztRKZ7Pw1JujUUgmQJHcVAQ==", + "dev": true + }, + "eventemitter3": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", + "dev": true + }, + "events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "dev": true + }, + "execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "dev": true, + "requires": { + "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" + } + }, + "express": { + "version": "4.18.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz", + "integrity": "sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==", + "dev": true, + "requires": { + "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" + }, + "dependencies": { + "accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "dev": true, + "requires": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + } + }, + "array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", + "dev": true + }, + "cookie": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", + "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==", + "dev": true + }, + "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" + } + }, + "finalhandler": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", + "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", + "dev": true, + "requires": { + "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" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + }, + "negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "dev": true + }, + "on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "dev": true, + "requires": { + "ee-first": "1.1.1" + } + }, + "statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "dev": true + } + } + }, + "extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "dev": true + }, + "external-editor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz", + "integrity": "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==", + "dev": true, + "requires": { + "chardet": "^0.7.0", + "iconv-lite": "^0.4.24", + "tmp": "^0.0.33" + } + }, + "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==" + }, + "fast-glob": { + "version": "3.2.12", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.12.tgz", + "integrity": "sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==", + "dev": true, + "requires": { + "@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": { + "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": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==" + }, + "fastq": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", + "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==", + "dev": true, + "requires": { + "reusify": "^1.0.4" + } + }, + "faye-websocket": { + "version": "0.11.4", + "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.4.tgz", + "integrity": "sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g==", + "dev": true, + "requires": { + "websocket-driver": ">=0.5.1" + } + }, + "figures": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", + "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==", + "dev": true, + "requires": { + "escape-string-regexp": "^1.0.5" + } + }, + "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==", + "requires": { + "flat-cache": "^3.0.4" + } + }, + "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" + } + }, + "finalhandler": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", + "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", + "dev": true, + "requires": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "~2.3.0", + "parseurl": "~1.3.3", + "statuses": "~1.5.0", + "unpipe": "~1.0.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" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + } + } + }, + "find-cache-dir": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.2.tgz", + "integrity": "sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==", + "dev": true, + "requires": { + "commondir": "^1.0.1", + "make-dir": "^3.0.2", + "pkg-dir": "^4.1.0" + } + }, + "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" + } + }, + "flat-cache": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", + "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", + "requires": { + "flatted": "^3.1.0", + "rimraf": "^3.0.2" + } + }, + "flatted": { + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.5.tgz", + "integrity": "sha512-WIWGi2L3DyTUvUrwRKgGi9TwxQMUEqPOPQBVi71R96jZXJdFskXEmf54BoZaS1kknGODoIGASGEzBUYdyMCBJg==" + }, + "follow-redirects": { + "version": "1.14.9", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.9.tgz", + "integrity": "sha512-MQDfihBQYMcyy5dhRDJUHcw7lb2Pv/TuE6xP1vyraLukNDHKbDxDNaOE3NbCAdKQApno+GPRyo1YAp89yCjK4w==", + "dev": true + }, + "foreground-child": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.1.1.tgz", + "integrity": "sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg==", + "dev": true, + "requires": { + "cross-spawn": "^7.0.0", + "signal-exit": "^4.0.1" + }, + "dependencies": { + "signal-exit": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.0.2.tgz", + "integrity": "sha512-MY2/qGx4enyjprQnFaZsHib3Yadh3IXyV2C321GY0pjGfVBu4un0uDJkwgdxqO+Rdx8JMT8IfJIRwbYVz3Ob3Q==", + "dev": true + } + } + }, + "forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "dev": true + }, + "fraction.js": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.2.0.tgz", + "integrity": "sha512-MhLuK+2gUcnZe8ZHlaaINnQLl0xRIGRfcGk2yl8xoQAfHrSsL3rYu6FCmBdkdbhc9EPlwyGHewaRsvwRMJtAlA==", + "dev": true + }, + "fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "dev": true + }, + "fs-extra": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.0.1.tgz", + "integrity": "sha512-NbdoVMZso2Lsrn/QwLXOy6rm0ufY2zEOKCDzJR/0kBsb0E6qed0P3iYK+Ath3BfvXEeu4JhEtXLgILx5psUfag==", + "dev": true, + "requires": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + } + }, + "fs-minipass": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-3.0.2.tgz", + "integrity": "sha512-2GAfyfoaCDRrM6jaOS3UsBts8yJ55VioXdWcOL7dK9zdAuKT71+WBA4ifnNYqVjYv+4SsPxjK0JT4yIIn4cA/g==", + "dev": true, + "requires": { + "minipass": "^5.0.0" + }, + "dependencies": { + "minipass": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", + "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", + "dev": true + } + } + }, + "fs-monkey": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/fs-monkey/-/fs-monkey-1.0.3.tgz", + "integrity": "sha512-cybjIfiiE+pTWicSCLFHSrXZ6EilF30oh91FDP9S2B051prEa7QWfrVTQm10/dDpswBDXZugPa1Ogu8Yh+HV0Q==", + "dev": true + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" + }, + "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 + }, + "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": "sha512-dsKNQNdj6xA3T+QlADDA7mOSlX0qiMINjn0cgr+eGHGsbSHzTabcIogz2+p/iqP1Xs6EP/sS2SbqH+brGTbq0g==" + }, + "gauge": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-4.0.4.tgz", + "integrity": "sha512-f9m+BEN5jkg6a0fZjleidjN51VE1X+mPFQ2DJ0uv1V39oCLCbsGe6yjbBnp7eK7z/+GAon99a3nHuqbuuthyPg==", + "dev": true, + "requires": { + "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" + } + }, + "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-intrinsic": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.2.tgz", + "integrity": "sha512-Jfm3OyCxHh9DJyc28qGk+JmfkpO41A4XkneDSujN9MDXrm4oDKdHvndhZ2dN94+ERNfkYJWDclW6k2L/ZGHjXA==", + "dev": true, + "requires": { + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.3" + } + }, + "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": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "dev": true + }, + "glob": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", + "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", + "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-to-regexp": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", + "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", + "dev": true + }, + "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 + }, + "globby": { + "version": "13.1.4", + "resolved": "https://registry.npmjs.org/globby/-/globby-13.1.4.tgz", + "integrity": "sha512-iui/IiiW+QrJ1X1hKH5qwlMQyv34wJAYwH1vrf8b9kBA4sNiif3gKsMHa+BrdnOpEudWjpotfa7LrTzB1ERS/g==", + "dev": true, + "requires": { + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.11", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^4.0.0" + } + }, + "graceful-fs": { + "version": "4.2.9", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.9.tgz", + "integrity": "sha512-NtNxqUcXgpW2iMrfqSfR73Glt39K+BLwWsPs94yR63v45T0Wbej7eRmL5cWfwEgqXnmjQp3zaJTshdRW/qC2ZQ==", + "dev": true + }, + "handle-thing": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/handle-thing/-/handle-thing-2.0.1.tgz", + "integrity": "sha512-9Qn4yBxelxoh2Ow62nP+Ka/kMnOXRi8BXnRaUwezLNhqelnN49xKz4F/dPP8OYLxLxq6JDtZb2i9XznUQbNPTg==", + "dev": 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==" + }, + "has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "dev": true + }, + "has-unicode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", + "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==", + "dev": true + }, + "hdr-histogram-js": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/hdr-histogram-js/-/hdr-histogram-js-2.0.3.tgz", + "integrity": "sha512-Hkn78wwzWHNCp2uarhzQ2SGFLU3JY8SBDDd3TAABK4fc30wm+MuPOrg5QVFVfkKOQd6Bfz3ukJEI+q9sXEkK1g==", + "dev": true, + "requires": { + "@assemblyscript/loader": "^0.10.1", + "base64-js": "^1.2.0", + "pako": "^1.0.3" + } + }, + "hdr-histogram-percentiles-obj": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hdr-histogram-percentiles-obj/-/hdr-histogram-percentiles-obj-3.0.0.tgz", + "integrity": "sha512-7kIufnBqdsBGcSZLPJwqHT3yhk1QTsSlFsVD3kx5ixH/AlgBs9yM1q6DPhXZ8f8gtdqgh7N7/5btRLpQsS2gHw==", + "dev": true + }, + "highcharts": { + "version": "11.0.1", + "resolved": "https://registry.npmjs.org/highcharts/-/highcharts-11.0.1.tgz", + "integrity": "sha512-KRwNwm6kJsp3JxNSpORuWLzvLnPuqCR3n0qbGjw+0m6MIxrlkUWnFaZJ2uXjMur6j3RUC66te36tldlSheYydQ==" + }, + "highcharts-angular": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/highcharts-angular/-/highcharts-angular-2.10.0.tgz", + "integrity": "sha512-ICeLKeN6bgFqgBKmHE6p/c15IvWIeUqhDzB0PG8oAoZLpKKRjxGddFu6g2iVy9v952eDvywFcEXqRPO9PPRvag==", + "requires": { + "tslib": "^2.0.0" + } + }, + "hosted-git-info": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-6.1.1.tgz", + "integrity": "sha512-r0EI+HBMcXadMrugk0GCQ+6BQV39PiWAZVfq7oIckeGiN7sjRGyQxPdft3nQekFTCQbYxLBH+/axZMeH8UX6+w==", + "dev": true, + "requires": { + "lru-cache": "^7.5.1" + }, + "dependencies": { + "lru-cache": { + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", + "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "dev": true + } + } + }, + "hpack.js": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/hpack.js/-/hpack.js-2.1.6.tgz", + "integrity": "sha512-zJxVehUdMGIKsRaNt7apO2Gqp0BdqW5yaiGHXXmbpvxgBYVZnAql+BJb4RO5ad2MgpbZKn5G6nMnegrH1FcNYQ==", + "dev": true, + "requires": { + "inherits": "^2.0.1", + "obuf": "^1.0.0", + "readable-stream": "^2.0.1", + "wbuf": "^1.1.0" + }, + "dependencies": { + "readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dev": true, + "requires": { + "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" + } + }, + "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 + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, + "html-entities": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.3.3.tgz", + "integrity": "sha512-DV5Ln36z34NNTDgnz0EWGBLZENelNAtkiFA4kyNOG2tDI6Mz1uSWiq1wAKdyjnJwyDiDO7Fa2SO1CTxPXL8VxA==", + "dev": true + }, + "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-cache-semantics": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz", + "integrity": "sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==", + "dev": true + }, + "http-deceiver": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/http-deceiver/-/http-deceiver-1.2.7.tgz", + "integrity": "sha512-LmpOGxTfbpgtGVxJrj5k7asXHCgNZp5nLfp+hWc8QQRqtb7fUy6kRY3BO1h9ddF6yIPYUARgxGOwB42DnxIaNw==", + "dev": true + }, + "http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "dev": true, + "requires": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "dependencies": { + "statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "dev": true + } + } + }, + "http-parser-js": { + "version": "0.5.8", + "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.8.tgz", + "integrity": "sha512-SGeBX54F94Wgu5RH3X5jsDtf4eHyRogWX1XGT3b4HuW3tQPM4AaBzoUji/4AAJNXCEOWZ5O0DgZmJw1947gD5Q==", + "dev": true + }, + "http-proxy": { + "version": "1.18.1", + "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz", + "integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==", + "dev": true, + "requires": { + "eventemitter3": "^4.0.0", + "follow-redirects": "^1.0.0", + "requires-port": "^1.0.0" + } + }, + "http-proxy-agent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", + "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", + "dev": true, + "requires": { + "@tootallnate/once": "2", + "agent-base": "6", + "debug": "4" + } + }, + "http-proxy-middleware": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.6.tgz", + "integrity": "sha512-ya/UeJ6HVBYxrgYotAZo1KvPWlgB48kUJLDePFeneHsVujFaW5WNj2NgWCAE//B1Dl02BIfYlpNgBy8Kf8Rjmw==", + "dev": true, + "requires": { + "@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" + } + }, + "https-proxy-agent": { + "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==", + "dev": true, + "requires": { + "agent-base": "6", + "debug": "4" + } + }, + "human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "dev": true + }, + "humanize-ms": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz", + "integrity": "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==", + "dev": true, + "requires": { + "ms": "^2.0.0" + } + }, + "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" + } + }, + "icss-utils": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-5.1.0.tgz", + "integrity": "sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==", + "dev": true + }, + "ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "dev": true + }, + "ignore": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.0.tgz", + "integrity": "sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==" + }, + "ignore-walk": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-6.0.3.tgz", + "integrity": "sha512-C7FfFoTA+bI10qfeydT8aZbvr91vAEU+2W5BZUlzPec47oNb07SsOfwYrtxuvOYdUApPP/Qlh4DtAO51Ekk2QA==", + "dev": true, + "requires": { + "minimatch": "^9.0.0" + }, + "dependencies": { + "brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0" + } + }, + "minimatch": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.1.tgz", + "integrity": "sha512-0jWhJpD/MdhPXwPuiRkCbfYfSKp2qnn2eOc279qI7f+osl/l+prKSrvhg157zSYvx/1nmgn2NqdT6k2Z7zSH9w==", + "dev": true, + "requires": { + "brace-expansion": "^2.0.1" + } + } + } + }, + "image-size": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/image-size/-/image-size-0.5.5.tgz", + "integrity": "sha512-6TDAlDPZxUFCv+fuOkIoXT/V/f3Qbq8e37p+YOiYrUv3v9cc3/6x78VdfPgFVaB9dZYeLUfKgHRebpkm/oP2VQ==", + "dev": true, + "optional": true + }, + "immer": { + "version": "9.0.12", + "resolved": "https://registry.npmjs.org/immer/-/immer-9.0.12.tgz", + "integrity": "sha512-lk7UNmSbAukB5B6dh9fnh5D0bJTOFKxVg2cyJWTYrWRfhLrLMBquONcUs3aFq507hNoIZEDDh8lb8UtOizSMhA==" + }, + "immutable": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.0.tgz", + "integrity": "sha512-0AOCmOip+xgJwEVTQj1EfiDDOkPmuyllDuTuEX+DDXUgapLAsBIfkg3sxCYyCEA8mQqZrrxPUGjcOQ2JS3WLkg==", + "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==", + "requires": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "dependencies": { + "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==" + } + } + }, + "imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=" + }, + "indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "dev": true + }, + "infer-owner": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/infer-owner/-/infer-owner-1.0.4.tgz", + "integrity": "sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A==", + "dev": true + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "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==" + }, + "ini": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ini/-/ini-3.0.1.tgz", + "integrity": "sha512-it4HyVAUTKBc6m8e1iXWvXSTdndF7HbdN713+kvLrymxTaU4AUBWrJ4vEooP+V7fexnVD3LKcBshjGGPefSMUQ==", + "dev": true + }, + "inquirer": { + "version": "8.2.4", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-8.2.4.tgz", + "integrity": "sha512-nn4F01dxU8VeKfq192IjLsxu0/OmMZ4Lg3xKAns148rCaXP6ntAoEkVYZThWjwON8AlzdZZi6oqnhNbxUG9hVg==", + "dev": true, + "requires": { + "ansi-escapes": "^4.2.1", + "chalk": "^4.1.1", + "cli-cursor": "^3.1.0", + "cli-width": "^3.0.0", + "external-editor": "^3.0.3", + "figures": "^3.0.0", + "lodash": "^4.17.21", + "mute-stream": "0.0.8", + "ora": "^5.4.1", + "run-async": "^2.4.0", + "rxjs": "^7.5.5", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0", + "through": "^2.3.6", + "wrap-ansi": "^7.0.0" + } + }, + "install": { + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/install/-/install-0.13.0.tgz", + "integrity": "sha512-zDml/jzr2PKU9I8J/xyZBQn8rPCAY//UOYNmR01XwNwyfhEWObo2SWfSl1+0tm1u6PhxLwDnfsT/6jB7OUxqFA==" + }, + "internmap": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz", + "integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==" + }, + "ip": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ip/-/ip-2.0.0.tgz", + "integrity": "sha512-WKa+XuLG1A1R0UWhl2+1XQSi+fZWMsYKffMZTTYsiZaUD8k2yDAj5atimTUD2TZkyCkNEeYE5NhFZmupOGtjYQ==", + "dev": true + }, + "ipaddr.js": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.0.1.tgz", + "integrity": "sha512-1qTgH9NG+IIJ4yfKs2e6Pp1bZg8wbDbKHT21HrLIeYBTRLgMYKnMTPAuI3Lcs61nfx5h1xlXnbJtH1kX5/d/ng==", + "dev": true + }, + "is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "dev": true + }, + "is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "requires": { + "binary-extensions": "^2.0.0" + } + }, + "is-core-module": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.8.1.tgz", + "integrity": "sha512-SdNCUs284hr40hFTFP6l0IfZ/RSrMXF3qgoRHd3/79unUTvrFO/JoXwkGm+5J/Oe3E/b5GsnG330uUNgRpu1PA==", + "dev": true, + "requires": { + "has": "^1.0.3" + } + }, + "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 + }, + "is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=" + }, + "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.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "requires": { + "is-extglob": "^2.1.1" + } + }, + "is-interactive": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz", + "integrity": "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==", + "dev": true + }, + "is-lambda": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-lambda/-/is-lambda-1.0.1.tgz", + "integrity": "sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ==", + "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-obj": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-3.0.0.tgz", + "integrity": "sha512-gwsOE28k+23GP1B6vFl1oVh/WOzmawBrKwo5Ev6wMKzPkaXaCDIQKzLnvsA42DRlbVTWorkgTKIviAKCWkfUwA==", + "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-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-unicode-supported": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", + "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", + "dev": true + }, + "is-what": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/is-what/-/is-what-3.14.1.tgz", + "integrity": "sha512-sNxgpk9793nzSs7bA6JQJGeIuRBQhAaNGG77kzYQgMkrID+lS6SlK07K5LaptscDlSaIgH+GPFzf+d75FVxozA==", + "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, + "requires": { + "is-docker": "^2.0.0" + } + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "dev": true + }, + "isbinaryfile": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-4.0.8.tgz", + "integrity": "sha512-53h6XFniq77YdW+spoRrebh0mnmTxRPTlcuIArO57lmMdq4uBKFKaeTjnb92oYWrSn/LVL+LT+Hap2tFQj8V+w==", + "dev": true + }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=" + }, + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", + "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": "5.2.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz", + "integrity": "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==", + "dev": true, + "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" + }, + "dependencies": { + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + } + } + }, + "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": "3.0.6", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-3.0.6.tgz", + "integrity": "sha512-R47KzMtDJH6X4/YW9XTx+jrLnZnscW4VpNN+1PViSYTejLVPWv7oov+Duf8YQSPyVRUvueQqz1TcsC6mooZTXw==", + "dev": true, + "requires": { + "debug": "^4.1.1", + "istanbul-lib-coverage": "^2.0.5", + "make-dir": "^2.1.0", + "rimraf": "^2.6.3", + "source-map": "^0.6.1" + }, + "dependencies": { + "istanbul-lib-coverage": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.5.tgz", + "integrity": "sha512-8aXznuEPCJvGnMSRft4udDRDtb1V3pkQkMMI5LI+6HuQz5oQ4J2UFn1H82raA3qJtyOLkkwVqICBQkjnGtn5mA==", + "dev": true + }, + "make-dir": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", + "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", + "dev": true, + "requires": { + "pify": "^4.0.1", + "semver": "^5.6.0" + } + }, + "rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + }, + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + }, + "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 + } + } + }, + "istanbul-reports": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.3.tgz", + "integrity": "sha512-x9LtDVtfm/t1GFiLl3NffC7hz+I1ragvgX1P/Lg1NlIagifZDKUkuuaAxH/qpwj2IuEfD8G2Bs/UKp+sZ/pKkg==", + "dev": true, + "requires": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + } + }, + "jackspeak": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-2.2.1.tgz", + "integrity": "sha512-MXbxovZ/Pm42f6cDIDkl3xpwv1AGwObKwfmjs2nQePiy85tP3fatofl3FC1aBsOtP/6fq5SbtgHwWcMsLP+bDw==", + "dev": true, + "requires": { + "@isaacs/cliui": "^8.0.2", + "@pkgjs/parseargs": "^0.11.0" + } + }, + "jasmine-core": { + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-3.8.0.tgz", + "integrity": "sha512-zl0nZWDrmbCiKns0NcjkFGYkVTGCPUgoHypTaj+G2AzaWus7QGoXARSlYsSle2VRpSdfJmM+hzmFKzQNhF2kHg==", + "dev": true + }, + "jasmine-spec-reporter": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/jasmine-spec-reporter/-/jasmine-spec-reporter-7.0.0.tgz", + "integrity": "sha512-OtC7JRasiTcjsaCBPtMO0Tl8glCejM4J4/dNuOJdA8lBjz4PmWjYQ6pzb0uzpBNAWJMDudYuj9OdXJWqM2QTJg==", + "dev": true, + "requires": { + "colors": "1.4.0" + }, + "dependencies": { + "colors": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz", + "integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==", + "dev": true + } + } + }, + "jest-worker": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", + "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", + "dev": true, + "requires": { + "@types/node": "*", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "dependencies": { + "supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "requires": { + "has-flag": "^4.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" + } + }, + "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 + }, + "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": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==" + }, + "json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true + }, + "jsonc-parser": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.0.tgz", + "integrity": "sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==", + "dev": true + }, + "jsonfile": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.6", + "universalify": "^2.0.0" + } + }, + "jsonparse": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz", + "integrity": "sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg==", + "dev": true + }, + "karma": { + "version": "6.3.17", + "resolved": "https://registry.npmjs.org/karma/-/karma-6.3.17.tgz", + "integrity": "sha512-2TfjHwrRExC8yHoWlPBULyaLwAFmXmxQrcuFImt/JsAsSZu1uOWTZ1ZsWjqQtWpHLiatJOHL5jFjXSJIgCd01g==", + "dev": true, + "requires": { + "@colors/colors": "1.5.0", + "body-parser": "^1.19.0", + "braces": "^3.0.2", + "chokidar": "^3.5.1", + "connect": "^3.7.0", + "di": "^0.0.1", + "dom-serialize": "^2.2.1", + "glob": "^7.1.7", + "graceful-fs": "^4.2.6", + "http-proxy": "^1.18.1", + "isbinaryfile": "^4.0.8", + "lodash": "^4.17.21", + "log4js": "^6.4.1", + "mime": "^2.5.2", + "minimatch": "^3.0.4", + "mkdirp": "^0.5.5", + "qjobs": "^1.2.0", + "range-parser": "^1.2.1", + "rimraf": "^3.0.2", + "socket.io": "^4.2.0", + "source-map": "^0.6.1", + "tmp": "^0.2.1", + "ua-parser-js": "^0.7.30", + "yargs": "^16.1.1" + }, + "dependencies": { + "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 + }, + "cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "dev": true, + "requires": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "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 + }, + "mime": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz", + "integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==", + "dev": true + }, + "mkdirp": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", + "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", + "dev": true, + "requires": { + "minimist": "^1.2.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 + }, + "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" + } + }, + "tmp": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz", + "integrity": "sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==", + "dev": true, + "requires": { + "rimraf": "^3.0.0" + } + }, + "y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true + }, + "yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "dev": true, + "requires": { + "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" + } + }, + "yargs-parser": { + "version": "20.2.9", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", + "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", + "dev": true + } + } + }, + "karma-chrome-launcher": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/karma-chrome-launcher/-/karma-chrome-launcher-3.1.1.tgz", + "integrity": "sha512-hsIglcq1vtboGPAN+DGCISCFOxW+ZVnIqhDQcCMqqCp+4dmJ0Qpq5QAjkbA0X2L9Mi6OBkHi2Srrbmm7pUKkzQ==", + "dev": true, + "requires": { + "which": "^1.2.1" + }, + "dependencies": { + "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" + } + } + } + }, + "karma-coverage-istanbul-reporter": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/karma-coverage-istanbul-reporter/-/karma-coverage-istanbul-reporter-3.0.3.tgz", + "integrity": "sha512-wE4VFhG/QZv2Y4CdAYWDbMmcAHeS926ZIji4z+FkB2aF/EposRb6DP6G5ncT/wXhqUfAb/d7kZrNKPonbvsATw==", + "dev": true, + "requires": { + "istanbul-lib-coverage": "^3.0.0", + "istanbul-lib-report": "^3.0.0", + "istanbul-lib-source-maps": "^3.0.6", + "istanbul-reports": "^3.0.2", + "minimatch": "^3.0.4" + } + }, + "karma-jasmine": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/karma-jasmine/-/karma-jasmine-4.0.2.tgz", + "integrity": "sha512-ggi84RMNQffSDmWSyyt4zxzh2CQGwsxvYYsprgyR1j8ikzIduEdOlcLvXjZGwXG/0j41KUXOWsUCBfbEHPWP9g==", + "dev": true, + "requires": { + "jasmine-core": "^3.6.0" + } + }, + "karma-jasmine-html-reporter": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/karma-jasmine-html-reporter/-/karma-jasmine-html-reporter-1.7.0.tgz", + "integrity": "sha512-pzum1TL7j90DTE86eFt48/s12hqwQuiD+e5aXx2Dc9wDEn2LfGq6RoAxEZZjFiN0RDSCOnosEKRZWxbQ+iMpQQ==", + "dev": true + }, + "karma-source-map-support": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/karma-source-map-support/-/karma-source-map-support-1.4.0.tgz", + "integrity": "sha512-RsBECncGO17KAoJCYXjv+ckIz+Ii9NCi+9enk+rq6XC81ezYkb4/RHE6CTXdA7IOJqoF3wcaLfVG0CPmE5ca6A==", + "dev": true, + "requires": { + "source-map-support": "^0.5.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 + }, + "klona": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/klona/-/klona-2.0.6.tgz", + "integrity": "sha512-dhG34DXATL5hSxJbIexCft8FChFXtmskoZYnoPWjXQuebWYCNkVeV3KkGegCK9CP1oswI/vQibS2GY7Em/sJJA==", + "dev": true + }, + "less": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/less/-/less-4.1.3.tgz", + "integrity": "sha512-w16Xk/Ta9Hhyei0Gpz9m7VS8F28nieJaL/VyShID7cYvP6IL5oHeL6p4TXSDJqZE/lNv0oJ2pGVjJsRkfwm5FA==", + "dev": true, + "requires": { + "copy-anything": "^2.0.1", + "errno": "^0.1.1", + "graceful-fs": "^4.1.2", + "image-size": "~0.5.0", + "make-dir": "^2.1.0", + "mime": "^1.4.1", + "needle": "^3.1.0", + "parse-node-version": "^1.0.1", + "source-map": "~0.6.0", + "tslib": "^2.3.0" + }, + "dependencies": { + "make-dir": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", + "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", + "dev": true, + "optional": true, + "requires": { + "pify": "^4.0.1", + "semver": "^5.6.0" + } + }, + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true, + "optional": true + }, + "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, + "optional": true + } + } + }, + "less-loader": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/less-loader/-/less-loader-11.1.0.tgz", + "integrity": "sha512-C+uDBV7kS7W5fJlUjq5mPBeBVhYpTIm5gB09APT9o3n/ILeaXVsiSFTbZpTJCJwQ/Crczfn3DmfQFwxYusWFug==", + "dev": true, + "requires": { + "klona": "^2.0.4" + } + }, + "levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "requires": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + } + }, + "license-webpack-plugin": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/license-webpack-plugin/-/license-webpack-plugin-4.0.2.tgz", + "integrity": "sha512-771TFWFD70G1wLTC4oU2Cw4qvtmNrIw+wRvBtn+okgHl7slJVi7zfNcdmqDL72BojM30VNJ2UHylr1o77U37Jw==", + "dev": true, + "requires": { + "webpack-sources": "^3.0.0" + } + }, + "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 + }, + "loader-runner": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz", + "integrity": "sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==", + "dev": true + }, + "loader-utils": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-3.2.1.tgz", + "integrity": "sha512-ZvFw1KWS3GVyYBYb7qkmRM/WwL2TQQBxgCK62rlvm4WpVQ23Nb4tYjApUlfjrEGvOs7KHEsmyUn75OHZrJMWPw==", + "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 + }, + "lodash.debounce": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", + "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==", + "dev": true + }, + "lodash.keyby": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/lodash.keyby/-/lodash.keyby-4.6.0.tgz", + "integrity": "sha1-f2oavak/0k4icopNNh7YvLpaQ1Q=" + }, + "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==" + }, + "log-symbols": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", + "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", + "dev": true, + "requires": { + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" + } + }, + "log4js": { + "version": "6.4.2", + "resolved": "https://registry.npmjs.org/log4js/-/log4js-6.4.2.tgz", + "integrity": "sha512-k80cggS2sZQLBwllpT1p06GtfvzMmSdUCkW96f0Hj83rKGJDAu2vZjt9B9ag2vx8Zz1IXzxoLgqvRJCdMKybGg==", + "dev": true, + "requires": { + "date-format": "^4.0.4", + "debug": "^4.3.3", + "flatted": "^3.2.5", + "rfdc": "^1.3.0", + "streamroller": "^3.0.4" + } + }, + "lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "requires": { + "yallist": "^3.0.2" + } + }, + "luxon": { + "version": "1.28.1", + "resolved": "https://registry.npmjs.org/luxon/-/luxon-1.28.1.tgz", + "integrity": "sha512-gYHAa180mKrNIUJCbwpmD0aTu9kV0dREDrwNnuyFAsO1Wt0EVYSZelPnJlbj9HplzXX/YWXHFTL45kvZ53M0pw==" + }, + "magic-string": { + "version": "0.29.0", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.29.0.tgz", + "integrity": "sha512-WcfidHrDjMY+eLjlU+8OvwREqHwpgCeKVBUpQ3OhYYuvfaYCUgcbuBzappNzZvg/v8onU3oQj+BYpkOJe9Iw4Q==", + "dev": true, + "requires": { + "@jridgewell/sourcemap-codec": "^1.4.13" + } + }, + "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" + }, + "dependencies": { + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + } + } + }, + "make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true + }, + "make-fetch-happen": { + "version": "10.2.1", + "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-10.2.1.tgz", + "integrity": "sha512-NgOPbRiaQM10DYXvN3/hhGVI2M5MtITFryzBGxHM5p4wnFxsVCbxkrBrDsk+EZ5OB4jEOT7AjDxtdF+KVEFT7w==", + "dev": true, + "requires": { + "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" + }, + "dependencies": { + "@npmcli/fs": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-2.1.2.tgz", + "integrity": "sha512-yOJKRvohFOaLqipNtwYB9WugyZKhC/DZC4VYPmpaCzDBrA8YpK3qHZ8/HGscMnE4GqbkLNuVcCnxkeQEdGt6LQ==", + "dev": true, + "requires": { + "@gar/promisify": "^1.1.3", + "semver": "^7.3.5" + } + }, + "brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0" + } + }, + "cacache": { + "version": "16.1.3", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-16.1.3.tgz", + "integrity": "sha512-/+Emcj9DAXxX4cwlLmRI9c166RuL3w30zp4R7Joiv2cQTtTtA+jeuCAjH3ZlGnYS3tKENSrKhAzVVP9GVyzeYQ==", + "dev": true, + "requires": { + "@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" + } + }, + "fs-minipass": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", + "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", + "dev": true, + "requires": { + "minipass": "^3.0.0" + } + }, + "glob": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", + "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^5.0.1", + "once": "^1.3.0" + } + }, + "lru-cache": { + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", + "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "dev": true + }, + "minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "dev": true, + "requires": { + "brace-expansion": "^2.0.1" + } + }, + "minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "requires": { + "yallist": "^4.0.0" + } + }, + "negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "dev": true + }, + "ssri": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-9.0.1.tgz", + "integrity": "sha512-o57Wcn66jMQvfHG1FlYbWeZWW/dHZhJXjpIcTfXldXEk5nz5lStPo3mK0OJQfGR3RbZUlbISexbljkJzuEj/8Q==", + "dev": true, + "requires": { + "minipass": "^3.1.1" + } + }, + "unique-filename": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-2.0.1.tgz", + "integrity": "sha512-ODWHtkkdx3IAR+veKxFV+VBkUMcN+FaqzUUd7IZzt+0zhDZFPFxhlqwPF3YQvMHx1TD0tdgYl+kuPnJ8E6ql7A==", + "dev": true, + "requires": { + "unique-slug": "^3.0.0" + } + }, + "unique-slug": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-3.0.0.tgz", + "integrity": "sha512-8EyMynh679x/0gqE9fT9oilG+qEt+ibFyqjuVTsZn1+CMxH+XLlpvr2UZx4nVcCwTpx81nICr2JQFkM+HPLq4w==", + "dev": true, + "requires": { + "imurmurhash": "^0.1.4" + } + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + } + } + }, + "material-colors": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/material-colors/-/material-colors-1.2.6.tgz", + "integrity": "sha512-6qE4B9deFBIa9YSpOc9O0Sgc43zTeVYbgDT5veRKSlB2+ZuHNoVVxA1L/ckMUayV9Ay9y7Z/SZCLcGteW9i7bg==" + }, + "media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=", + "dev": true + }, + "memfs": { + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/memfs/-/memfs-3.5.1.tgz", + "integrity": "sha512-UWbFJKvj5k+nETdteFndTpYxdeTMox/ULeqX5k/dpaQJCCFmj5EeKv3dBcyO2xmkRAx2vppRu5dVG7SOtsGOzA==", + "dev": true, + "requires": { + "fs-monkey": "^1.0.3" + } + }, + "memo-decorator": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/memo-decorator/-/memo-decorator-2.0.1.tgz", + "integrity": "sha512-Cydoauo7y1Uad1UuznJqhuEQCt6adIl1w5ik3WmNl4FJeBmWAaMs64qyGRahaXWK/Dlmt/+QNesRTeFUcpJPkQ==" + }, + "merge-descriptors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", + "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==", + "dev": true + }, + "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 + }, + "merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true + }, + "methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "dev": true + }, + "micromatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "dev": true, + "requires": { + "braces": "^3.0.2", + "picomatch": "^2.3.1" + } + }, + "mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "dev": true + }, + "mime-db": { + "version": "1.51.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.51.0.tgz", + "integrity": "sha512-5y8A56jg7XVQx2mbv1lu49NR4dokRnhZYTtL+KGfaa27uq4pSTXkwQkFJl4pkRMyNFz/EtYDSkiiEHx3F7UN6g==" + }, + "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==", + "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 + }, + "mini-css-extract-plugin": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-2.7.2.tgz", + "integrity": "sha512-EdlUizq13o0Pd+uCp+WO/JpkLvHRVGt97RqfeGhXqAcorYo1ypJSpkV+WDT0vY/kmh/p7wRdJNJtuyK540PXDw==", + "dev": true, + "requires": { + "schema-utils": "^4.0.0" + } + }, + "minimalistic-assert": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", + "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==", + "dev": true + }, + "minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", + "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==", + "dev": true + }, + "minipass": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-4.2.8.tgz", + "integrity": "sha512-fNzuVyifolSLFL4NzpF+wEF4qrgqaaKX0haXPQEdQ7NKAN+WecoKMHV09YcuL/DHxrUsYQOK3MiuDf7Ip2OXfQ==", + "dev": true + }, + "minipass-collect": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/minipass-collect/-/minipass-collect-1.0.2.tgz", + "integrity": "sha512-6T6lH0H8OG9kITm/Jm6tdooIbogG9e0tLgpY6mphXSm/A9u8Nq1ryBG+Qspiub9LjWlBPsPS3tWQ/Botq4FdxA==", + "dev": true, + "requires": { + "minipass": "^3.0.0" + }, + "dependencies": { + "minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "requires": { + "yallist": "^4.0.0" + } + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + } + } + }, + "minipass-fetch": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-2.1.2.tgz", + "integrity": "sha512-LT49Zi2/WMROHYoqGgdlQIZh8mLPZmOrN2NdJjMXxYe4nkN6FUyuPuOAOedNJDrx0IRGg9+4guZewtp8hE6TxA==", + "dev": true, + "requires": { + "encoding": "^0.1.13", + "minipass": "^3.1.6", + "minipass-sized": "^1.0.3", + "minizlib": "^2.1.2" + }, + "dependencies": { + "minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "requires": { + "yallist": "^4.0.0" + } + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + } + } + }, + "minipass-flush": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/minipass-flush/-/minipass-flush-1.0.5.tgz", + "integrity": "sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw==", + "dev": true, + "requires": { + "minipass": "^3.0.0" + }, + "dependencies": { + "minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "requires": { + "yallist": "^4.0.0" + } + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + } + } + }, + "minipass-json-stream": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minipass-json-stream/-/minipass-json-stream-1.0.1.tgz", + "integrity": "sha512-ODqY18UZt/I8k+b7rl2AENgbWE8IDYam+undIJONvigAz8KR5GWblsFTEfQs0WODsjbSXWlm+JHEv8Gr6Tfdbg==", + "dev": true, + "requires": { + "jsonparse": "^1.3.1", + "minipass": "^3.0.0" + }, + "dependencies": { + "minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "requires": { + "yallist": "^4.0.0" + } + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + } + } + }, + "minipass-pipeline": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz", + "integrity": "sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==", + "dev": true, + "requires": { + "minipass": "^3.0.0" + }, + "dependencies": { + "minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "requires": { + "yallist": "^4.0.0" + } + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + } + } + }, + "minipass-sized": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/minipass-sized/-/minipass-sized-1.0.3.tgz", + "integrity": "sha512-MbkQQ2CTiBMlA2Dm/5cY+9SWFEN8pzzOXi6rlM5Xxq0Yqbda5ZQy9sU75a673FE9ZK0Zsbr6Y5iP6u9nktfg2g==", + "dev": true, + "requires": { + "minipass": "^3.0.0" + }, + "dependencies": { + "minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "requires": { + "yallist": "^4.0.0" + } + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + } + } + }, + "minizlib": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", + "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", + "dev": true, + "requires": { + "minipass": "^3.0.0", + "yallist": "^4.0.0" + }, + "dependencies": { + "minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "requires": { + "yallist": "^4.0.0" + } + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + } + } + }, + "mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "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==" + }, + "multicast-dns": { + "version": "7.2.5", + "resolved": "https://registry.npmjs.org/multicast-dns/-/multicast-dns-7.2.5.tgz", + "integrity": "sha512-2eznPJP8z2BFLX50tf0LuODrpINqP1RVIm/CObbTcBRITQgmC/TjcREF1NeTBzIcR5XO/ukWo+YHOjBbFwIupg==", + "dev": true, + "requires": { + "dns-packet": "^5.2.2", + "thunky": "^1.0.2" + } + }, + "mute-stream": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", + "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==", + "dev": true + }, + "nanoid": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz", + "integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==", + "dev": true + }, + "natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==" + }, + "needle": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/needle/-/needle-3.2.0.tgz", + "integrity": "sha512-oUvzXnyLiVyVGoianLijF9O/RecZUf7TkBfimjGrLM4eQhXyeJwM6GeAWccwfQ9aa4gMCZKqhAOuLaMIcQxajQ==", + "dev": true, + "optional": true, + "requires": { + "debug": "^3.2.6", + "iconv-lite": "^0.6.3", + "sax": "^1.2.4" + }, + "dependencies": { + "debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "optional": true, + "requires": { + "ms": "^2.1.1" + } + }, + "iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dev": true, + "optional": true, + "requires": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + } + } + } + }, + "negotiator": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", + "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==" + }, + "neo-async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", + "dev": true + }, + "nested-property": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/nested-property/-/nested-property-4.0.0.tgz", + "integrity": "sha512-yFehXNWRs4cM0+dz7QxCd06hTbWbSkV0ISsqBfkntU6TOY4Qm3Q88fRRLOddkGh2Qq6dZvnKVAahfhjcUvLnyA==" + }, + "ngx-amvara-toolbox": { + "version": "0.0.17", + "resolved": "https://registry.npmjs.org/ngx-amvara-toolbox/-/ngx-amvara-toolbox-0.0.17.tgz", + "integrity": "sha512-1ZLuc0mx090mIm9Q24NqB8kQVNMIcELaJ92fHmlCYy/VsVTlhWF9xVVVauK1Vel7SU91nlxzGxqsSgMCsWNwbQ==", + "requires": { + "tslib": "^2.0.0" + } + }, + "ngx-clipboard": { + "version": "15.1.0", + "resolved": "https://registry.npmjs.org/ngx-clipboard/-/ngx-clipboard-15.1.0.tgz", + "integrity": "sha512-dUJl1cNtdkCqL953oAhP7wmUPFrqW2aDg5OPhwPU9R3cLEdQgU2NbsHEUz4zaPyEopTXu8SR37onVm1Ep8qOHg==", + "requires": { + "ngx-window-token": ">=6.0.0", + "tslib": "^2.0.0" + } + }, + "ngx-color": { + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/ngx-color/-/ngx-color-8.0.3.tgz", + "integrity": "sha512-tuLP+uIoDEu2m0bh711kb2P1M1bh/oIrOn8mJd9mb8xGL2v+OcokcxPmVvWRn0avMG1lXL53CjSlWXGkdV4CDA==", + "requires": { + "@ctrl/tinycolor": "^3.4.1", + "material-colors": "^1.2.6", + "tslib": "^2.3.0" + } + }, + "ngx-network-error": { + "version": "0.6.35-c", + "resolved": "https://registry.npmjs.org/ngx-network-error/-/ngx-network-error-0.6.35-c.tgz", + "integrity": "sha512-mPlHqVMKH6zQZJbTWaiiCmZuKW17FXAUIM+5XUmVXm4hZeMILuulg1YgZxin/SzMnjwLZHRtYbRFoi/uL0jlzg==", + "requires": { + "@angular/cdk": "^15.2.9", + "@angular/material": "^15.2.9", + "@sentry/browser": "^5.30.0", + "tslib": "^2.5.2" + }, + "dependencies": { + "tslib": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.2.tgz", + "integrity": "sha512-5svOrSA2w3iGFDs1HibEVBGbDrAY82bFQ3HZ3ixB+88nsbsWQoKqDRb5UBYAUPEzbBn6dAp5gRNXglySbx1MlA==" + } + } + }, + "ngx-window-token": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/ngx-window-token/-/ngx-window-token-6.0.0.tgz", + "integrity": "sha512-IeLKO1jzfzSvZ6vlAt4QSY/B5XcHEhdOwTjqvWEPt6/esWV9T3mA2ln10kj6SCc9pUSx4NybxE10gcyyYroImg==", + "requires": { + "tslib": "^2.0.0" + } + }, + "nice-napi": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/nice-napi/-/nice-napi-1.0.2.tgz", + "integrity": "sha512-px/KnJAJZf5RuBGcfD+Sp2pAKq0ytz8j+1NehvgIGFkvtvFrDM3T8E4x/JJODXK9WZow8RRGrbA9QQ3hs+pDhA==", + "dev": true, + "optional": true, + "requires": { + "node-addon-api": "^3.0.0", + "node-gyp-build": "^4.2.2" + } + }, + "node-addon-api": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-3.2.1.tgz", + "integrity": "sha512-mmcei9JghVNDYydghQmeDX8KoAm0FAiYyIcUt/N4nhyAipB17pllZQDOJD2fotxABnt4Mdz+dKTO7eftLg4d0A==", + "dev": true, + "optional": true + }, + "node-forge": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz", + "integrity": "sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==", + "dev": true + }, + "node-gyp": { + "version": "9.3.1", + "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-9.3.1.tgz", + "integrity": "sha512-4Q16ZCqq3g8awk6UplT7AuxQ35XN4R/yf/+wSAwcBUAjg7l58RTactWaP8fIDTi0FzI7YcVLujwExakZlfWkXg==", + "dev": true, + "requires": { + "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" + } + }, + "node-gyp-build": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.6.0.tgz", + "integrity": "sha512-NTZVKn9IylLwUzaKjkas1e4u2DLNcV4rdYagA4PWdPwW87Bi7z+BznyKSRwS/761tV/lzCGXplWsiaMjLqP2zQ==", + "dev": true, + "optional": true + }, + "node-releases": { + "version": "2.0.12", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.12.tgz", + "integrity": "sha512-QzsYKWhXTWx8h1kIvqfnC++o0pEmpRQA/aenALsL2F4pqNVr7YzcdMlDij5WBnwftRbJCNJL/O7zdKaxKPHqgQ==", + "dev": true + }, + "nopt": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-6.0.0.tgz", + "integrity": "sha512-ZwLpbTgdhuZUnZzjd7nb1ZV+4DoiC6/sfiVKok72ym/4Tlf+DFdlHYmT2JPmcNNWV6Pi3SDf1kT+A4r9RTuT9g==", + "dev": true, + "requires": { + "abbrev": "^1.0.0" + } + }, + "normalize-package-data": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-5.0.0.tgz", + "integrity": "sha512-h9iPVIfrVZ9wVYQnxFgtw1ugSvGEMOlyPWWtm8BMJhnwyEL/FLbYbTY3V3PpjI/BUK67n9PEWDu6eHzu1fB15Q==", + "dev": true, + "requires": { + "hosted-git-info": "^6.0.0", + "is-core-module": "^2.8.1", + "semver": "^7.3.5", + "validate-npm-package-license": "^3.0.4" + } + }, + "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 + }, + "normalize-range": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz", + "integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==", + "dev": true + }, + "npm": { + "version": "9.6.7", + "resolved": "https://registry.npmjs.org/npm/-/npm-9.6.7.tgz", + "integrity": "sha512-xwkU1hSZl6Qrkfw3fhxVmMfNWu0A67+aZZs5gz/LoehCeAPkVhQDB90Z2NFoPSI1KpfBWCJ6Bp28wXzv5U5/2g==", + "requires": { + "@isaacs/string-locale-compare": "^1.1.0", + "@npmcli/arborist": "^6.2.9", + "@npmcli/config": "^6.1.7", + "@npmcli/map-workspaces": "^3.0.4", + "@npmcli/package-json": "^3.1.0", + "@npmcli/run-script": "^6.0.2", + "abbrev": "^2.0.0", + "archy": "~1.0.0", + "cacache": "^17.1.2", + "chalk": "^4.1.2", + "ci-info": "^3.8.0", + "cli-columns": "^4.0.0", + "cli-table3": "^0.6.3", + "columnify": "^1.6.0", + "fastest-levenshtein": "^1.0.16", + "fs-minipass": "^3.0.2", + "glob": "^10.2.4", + "graceful-fs": "^4.2.11", + "hosted-git-info": "^6.1.1", + "ini": "^4.1.0", + "init-package-json": "^5.0.0", + "is-cidr": "^4.0.2", + "json-parse-even-better-errors": "^3.0.0", + "libnpmaccess": "^7.0.2", + "libnpmdiff": "^5.0.17", + "libnpmexec": "^5.0.17", + "libnpmfund": "^4.0.17", + "libnpmhook": "^9.0.3", + "libnpmorg": "^5.0.4", + "libnpmpack": "^5.0.17", + "libnpmpublish": "^7.2.0", + "libnpmsearch": "^6.0.2", + "libnpmteam": "^5.0.3", + "libnpmversion": "^4.0.2", + "make-fetch-happen": "^11.1.1", + "minimatch": "^9.0.0", + "minipass": "^5.0.0", + "minipass-pipeline": "^1.2.4", + "ms": "^2.1.2", + "node-gyp": "^9.3.1", + "nopt": "^7.1.0", + "npm-audit-report": "^4.0.0", + "npm-install-checks": "^6.1.1", + "npm-package-arg": "^10.1.0", + "npm-pick-manifest": "^8.0.1", + "npm-profile": "^7.0.1", + "npm-registry-fetch": "^14.0.5", + "npm-user-validate": "^2.0.0", + "npmlog": "^7.0.1", + "p-map": "^4.0.0", + "pacote": "^15.1.3", + "parse-conflict-json": "^3.0.1", + "proc-log": "^3.0.0", + "qrcode-terminal": "^0.12.0", + "read": "^2.1.0", + "read-package-json": "^6.0.3", + "read-package-json-fast": "^3.0.2", + "semver": "^7.5.1", + "ssri": "^10.0.4", + "tar": "^6.1.14", + "text-table": "~0.2.0", + "tiny-relative-date": "^1.3.0", + "treeverse": "^3.0.0", + "validate-npm-package-name": "^5.0.0", + "which": "^3.0.1", + "write-file-atomic": "^5.0.1" + }, + "dependencies": { + "@colors/colors": { + "version": "1.5.0", + "bundled": true, + "optional": true + }, + "@gar/promisify": { + "version": "1.1.3", + "bundled": true + }, + "@isaacs/cliui": { + "version": "8.0.2", + "bundled": true, + "requires": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "6.0.1", + "bundled": true + }, + "emoji-regex": { + "version": "9.2.2", + "bundled": true + }, + "string-width": { + "version": "5.1.2", + "bundled": true, + "requires": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + } + }, + "string-width-cjs": { + "version": "npm:string-width@4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "dependencies": { + "ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==" + }, + "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==" + }, + "strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "requires": { + "ansi-regex": "^5.0.1" + } + } + } + }, + "strip-ansi": { + "version": "7.0.1", + "bundled": true, + "requires": { + "ansi-regex": "^6.0.1" + } + }, + "strip-ansi-cjs": { + "version": "npm:strip-ansi@6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "requires": { + "ansi-regex": "^5.0.1" + }, + "dependencies": { + "ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==" + } + } + }, + "wrap-ansi-cjs": { + "version": "npm:wrap-ansi@7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "requires": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==" + }, + "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==" + }, + "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==", + "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==", + "requires": { + "ansi-regex": "^5.0.1" + } + } + } + } + } + }, + "@isaacs/string-locale-compare": { + "version": "1.1.0", + "bundled": true + }, + "@npmcli/arborist": { + "version": "6.2.9", + "bundled": true, + "requires": { + "@isaacs/string-locale-compare": "^1.1.0", + "@npmcli/fs": "^3.1.0", + "@npmcli/installed-package-contents": "^2.0.2", + "@npmcli/map-workspaces": "^3.0.2", + "@npmcli/metavuln-calculator": "^5.0.0", + "@npmcli/name-from-folder": "^2.0.0", + "@npmcli/node-gyp": "^3.0.0", + "@npmcli/package-json": "^3.0.0", + "@npmcli/query": "^3.0.0", + "@npmcli/run-script": "^6.0.0", + "bin-links": "^4.0.1", + "cacache": "^17.0.4", + "common-ancestor-path": "^1.0.1", + "hosted-git-info": "^6.1.1", + "json-parse-even-better-errors": "^3.0.0", + "json-stringify-nice": "^1.1.4", + "minimatch": "^9.0.0", + "nopt": "^7.0.0", + "npm-install-checks": "^6.0.0", + "npm-package-arg": "^10.1.0", + "npm-pick-manifest": "^8.0.1", + "npm-registry-fetch": "^14.0.3", + "npmlog": "^7.0.1", + "pacote": "^15.0.8", + "parse-conflict-json": "^3.0.0", + "proc-log": "^3.0.0", + "promise-all-reject-late": "^1.0.0", + "promise-call-limit": "^1.0.2", + "read-package-json-fast": "^3.0.2", + "semver": "^7.3.7", + "ssri": "^10.0.1", + "treeverse": "^3.0.0", + "walk-up-path": "^3.0.1" + } + }, + "@npmcli/config": { + "version": "6.1.7", + "bundled": true, + "requires": { + "@npmcli/map-workspaces": "^3.0.2", + "ini": "^4.1.0", + "nopt": "^7.0.0", + "proc-log": "^3.0.0", + "read-package-json-fast": "^3.0.2", + "semver": "^7.3.5", + "walk-up-path": "^3.0.1" + } + }, + "@npmcli/disparity-colors": { + "version": "3.0.0", + "bundled": true, + "requires": { + "ansi-styles": "^4.3.0" + } + }, + "@npmcli/fs": { + "version": "3.1.0", + "bundled": true, + "requires": { + "semver": "^7.3.5" + } + }, + "@npmcli/git": { + "version": "4.0.4", + "bundled": true, + "requires": { + "@npmcli/promise-spawn": "^6.0.0", + "lru-cache": "^7.4.4", + "npm-pick-manifest": "^8.0.0", + "proc-log": "^3.0.0", + "promise-inflight": "^1.0.1", + "promise-retry": "^2.0.1", + "semver": "^7.3.5", + "which": "^3.0.0" + } + }, + "@npmcli/installed-package-contents": { + "version": "2.0.2", + "bundled": true, + "requires": { + "npm-bundled": "^3.0.0", + "npm-normalize-package-bin": "^3.0.0" + } + }, + "@npmcli/map-workspaces": { + "version": "3.0.4", + "bundled": true, + "requires": { + "@npmcli/name-from-folder": "^2.0.0", + "glob": "^10.2.2", + "minimatch": "^9.0.0", + "read-package-json-fast": "^3.0.0" + } + }, + "@npmcli/metavuln-calculator": { + "version": "5.0.1", + "bundled": true, + "requires": { + "cacache": "^17.0.0", + "json-parse-even-better-errors": "^3.0.0", + "pacote": "^15.0.0", + "semver": "^7.3.5" + } + }, + "@npmcli/move-file": { + "version": "2.0.1", + "bundled": true, + "requires": { + "mkdirp": "^1.0.4", + "rimraf": "^3.0.2" + } + }, + "@npmcli/name-from-folder": { + "version": "2.0.0", + "bundled": true + }, + "@npmcli/node-gyp": { + "version": "3.0.0", + "bundled": true + }, + "@npmcli/package-json": { + "version": "3.1.0", + "bundled": true, + "requires": { + "glob": "^10.2.2", + "json-parse-even-better-errors": "^3.0.0", + "normalize-package-data": "^5.0.0", + "npm-normalize-package-bin": "^3.0.1" + } + }, + "@npmcli/promise-spawn": { + "version": "6.0.2", + "bundled": true, + "requires": { + "which": "^3.0.0" + } + }, + "@npmcli/query": { + "version": "3.0.0", + "bundled": true, + "requires": { + "postcss-selector-parser": "^6.0.10" + } + }, + "@npmcli/run-script": { + "version": "6.0.2", + "bundled": true, + "requires": { + "@npmcli/node-gyp": "^3.0.0", + "@npmcli/promise-spawn": "^6.0.0", + "node-gyp": "^9.0.0", + "read-package-json-fast": "^3.0.0", + "which": "^3.0.0" + } + }, + "@pkgjs/parseargs": { + "version": "0.11.0", + "bundled": true, + "optional": true + }, + "@sigstore/protobuf-specs": { + "version": "0.1.0", + "bundled": true + }, + "@tootallnate/once": { + "version": "2.0.0", + "bundled": true + }, + "@tufjs/canonical-json": { + "version": "1.0.0", + "bundled": true + }, + "@tufjs/models": { + "version": "1.0.4", + "bundled": true, + "requires": { + "@tufjs/canonical-json": "1.0.0", + "minimatch": "^9.0.0" + } + }, + "abbrev": { + "version": "2.0.0", + "bundled": true + }, + "abort-controller": { + "version": "3.0.0", + "bundled": true, + "requires": { + "event-target-shim": "^5.0.0" + } + }, + "agent-base": { + "version": "6.0.2", + "bundled": true, + "requires": { + "debug": "4" + } + }, + "agentkeepalive": { + "version": "4.3.0", + "bundled": true, + "requires": { + "debug": "^4.1.0", + "depd": "^2.0.0", + "humanize-ms": "^1.2.1" + } + }, + "aggregate-error": { + "version": "3.1.0", + "bundled": true, + "requires": { + "clean-stack": "^2.0.0", + "indent-string": "^4.0.0" + } + }, + "ansi-regex": { + "version": "5.0.1", + "bundled": true + }, + "ansi-styles": { + "version": "4.3.0", + "bundled": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "aproba": { + "version": "2.0.0", + "bundled": true + }, + "archy": { + "version": "1.0.0", + "bundled": true + }, + "are-we-there-yet": { + "version": "4.0.0", + "bundled": true, + "requires": { + "delegates": "^1.0.0", + "readable-stream": "^4.1.0" + } + }, + "balanced-match": { + "version": "1.0.2", + "bundled": true + }, + "base64-js": { + "version": "1.5.1", + "bundled": true + }, + "bin-links": { + "version": "4.0.1", + "bundled": true, + "requires": { + "cmd-shim": "^6.0.0", + "npm-normalize-package-bin": "^3.0.0", + "read-cmd-shim": "^4.0.0", + "write-file-atomic": "^5.0.0" + } + }, + "binary-extensions": { + "version": "2.2.0", + "bundled": true + }, + "brace-expansion": { + "version": "2.0.1", + "bundled": true, + "requires": { + "balanced-match": "^1.0.0" + } + }, + "buffer": { + "version": "6.0.3", + "bundled": true, + "requires": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "builtins": { + "version": "5.0.1", + "bundled": true, + "requires": { + "semver": "^7.0.0" + } + }, + "cacache": { + "version": "17.1.2", + "bundled": true, + "requires": { + "@npmcli/fs": "^3.1.0", + "fs-minipass": "^3.0.0", + "glob": "^10.2.2", + "lru-cache": "^7.7.1", + "minipass": "^5.0.0", + "minipass-collect": "^1.0.2", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "p-map": "^4.0.0", + "ssri": "^10.0.0", + "tar": "^6.1.11", + "unique-filename": "^3.0.0" + } + }, + "chalk": { + "version": "4.1.2", + "bundled": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "chownr": { + "version": "2.0.0", + "bundled": true + }, + "ci-info": { + "version": "3.8.0", + "bundled": true + }, + "cidr-regex": { + "version": "3.1.1", + "bundled": true, + "requires": { + "ip-regex": "^4.1.0" + } + }, + "clean-stack": { + "version": "2.2.0", + "bundled": true + }, + "cli-columns": { + "version": "4.0.0", + "bundled": true, + "requires": { + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1" + } + }, + "cli-table3": { + "version": "0.6.3", + "bundled": true, + "requires": { + "@colors/colors": "1.5.0", + "string-width": "^4.2.0" + } + }, + "clone": { + "version": "1.0.4", + "bundled": true + }, + "cmd-shim": { + "version": "6.0.1", + "bundled": true + }, + "color-convert": { + "version": "2.0.1", + "bundled": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "bundled": true + }, + "color-support": { + "version": "1.1.3", + "bundled": true + }, + "columnify": { + "version": "1.6.0", + "bundled": true, + "requires": { + "strip-ansi": "^6.0.1", + "wcwidth": "^1.0.0" + } + }, + "common-ancestor-path": { + "version": "1.0.1", + "bundled": true + }, + "concat-map": { + "version": "0.0.1", + "bundled": true + }, + "console-control-strings": { + "version": "1.1.0", + "bundled": true + }, + "cross-spawn": { + "version": "7.0.3", + "bundled": true, + "requires": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "dependencies": { + "which": { + "version": "2.0.2", + "bundled": true, + "requires": { + "isexe": "^2.0.0" + } + } + } + }, + "cssesc": { + "version": "3.0.0", + "bundled": true + }, + "debug": { + "version": "4.3.4", + "bundled": true, + "requires": { + "ms": "2.1.2" + }, + "dependencies": { + "ms": { + "version": "2.1.2", + "bundled": true + } + } + }, + "defaults": { + "version": "1.0.4", + "bundled": true, + "requires": { + "clone": "^1.0.2" + } + }, + "delegates": { + "version": "1.0.0", + "bundled": true + }, + "depd": { + "version": "2.0.0", + "bundled": true + }, + "diff": { + "version": "5.1.0", + "bundled": true + }, + "eastasianwidth": { + "version": "0.2.0", + "bundled": true + }, + "emoji-regex": { + "version": "8.0.0", + "bundled": true + }, + "encoding": { + "version": "0.1.13", + "bundled": true, + "optional": true, + "requires": { + "iconv-lite": "^0.6.2" + } + }, + "env-paths": { + "version": "2.2.1", + "bundled": true + }, + "err-code": { + "version": "2.0.3", + "bundled": true + }, + "event-target-shim": { + "version": "5.0.1", + "bundled": true + }, + "events": { + "version": "3.3.0", + "bundled": true + }, + "fastest-levenshtein": { + "version": "1.0.16", + "bundled": true + }, + "foreground-child": { + "version": "3.1.1", + "bundled": true, + "requires": { + "cross-spawn": "^7.0.0", + "signal-exit": "^4.0.1" + } + }, + "fs-minipass": { + "version": "3.0.2", + "bundled": true, + "requires": { + "minipass": "^5.0.0" + } + }, + "fs.realpath": { + "version": "1.0.0", + "bundled": true + }, + "function-bind": { + "version": "1.1.1", + "bundled": true + }, + "gauge": { + "version": "5.0.1", + "bundled": true, + "requires": { + "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": "^4.0.1", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1", + "wide-align": "^1.1.5" + } + }, + "glob": { + "version": "10.2.4", + "bundled": true, + "requires": { + "foreground-child": "^3.1.0", + "jackspeak": "^2.0.3", + "minimatch": "^9.0.0", + "minipass": "^5.0.0 || ^6.0.0", + "path-scurry": "^1.7.0" + } + }, + "graceful-fs": { + "version": "4.2.11", + "bundled": true + }, + "has": { + "version": "1.0.3", + "bundled": true, + "requires": { + "function-bind": "^1.1.1" + } + }, + "has-flag": { + "version": "4.0.0", + "bundled": true + }, + "has-unicode": { + "version": "2.0.1", + "bundled": true + }, + "hosted-git-info": { + "version": "6.1.1", + "bundled": true, + "requires": { + "lru-cache": "^7.5.1" + } + }, + "http-cache-semantics": { + "version": "4.1.1", + "bundled": true + }, + "http-proxy-agent": { + "version": "5.0.0", + "bundled": true, + "requires": { + "@tootallnate/once": "2", + "agent-base": "6", + "debug": "4" + } + }, + "https-proxy-agent": { + "version": "5.0.1", + "bundled": true, + "requires": { + "agent-base": "6", + "debug": "4" + } + }, + "humanize-ms": { + "version": "1.2.1", + "bundled": true, + "requires": { + "ms": "^2.0.0" + } + }, + "iconv-lite": { + "version": "0.6.3", + "bundled": true, + "optional": true, + "requires": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + } + }, + "ieee754": { + "version": "1.2.1", + "bundled": true + }, + "ignore-walk": { + "version": "6.0.3", + "bundled": true, + "requires": { + "minimatch": "^9.0.0" + } + }, + "imurmurhash": { + "version": "0.1.4", + "bundled": true + }, + "indent-string": { + "version": "4.0.0", + "bundled": true + }, + "infer-owner": { + "version": "1.0.4", + "bundled": true + }, + "inflight": { + "version": "1.0.6", + "bundled": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.4", + "bundled": true + }, + "ini": { + "version": "4.1.0", + "bundled": true + }, + "init-package-json": { + "version": "5.0.0", + "bundled": true, + "requires": { + "npm-package-arg": "^10.0.0", + "promzard": "^1.0.0", + "read": "^2.0.0", + "read-package-json": "^6.0.0", + "semver": "^7.3.5", + "validate-npm-package-license": "^3.0.4", + "validate-npm-package-name": "^5.0.0" + } + }, + "ip": { + "version": "2.0.0", + "bundled": true + }, + "ip-regex": { + "version": "4.3.0", + "bundled": true + }, + "is-cidr": { + "version": "4.0.2", + "bundled": true, + "requires": { + "cidr-regex": "^3.1.1" + } + }, + "is-core-module": { + "version": "2.12.0", + "bundled": true, + "requires": { + "has": "^1.0.3" + } + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "bundled": true + }, + "is-lambda": { + "version": "1.0.1", + "bundled": true + }, + "isexe": { + "version": "2.0.0", + "bundled": true + }, + "jackspeak": { + "version": "2.2.0", + "bundled": true, + "requires": { + "@isaacs/cliui": "^8.0.2", + "@pkgjs/parseargs": "^0.11.0" + } + }, + "json-parse-even-better-errors": { + "version": "3.0.0", + "bundled": true + }, + "json-stringify-nice": { + "version": "1.1.4", + "bundled": true + }, + "jsonparse": { + "version": "1.3.1", + "bundled": true + }, + "just-diff": { + "version": "6.0.2", + "bundled": true + }, + "just-diff-apply": { + "version": "5.5.0", + "bundled": true + }, + "libnpmaccess": { + "version": "7.0.2", + "bundled": true, + "requires": { + "npm-package-arg": "^10.1.0", + "npm-registry-fetch": "^14.0.3" + } + }, + "libnpmdiff": { + "version": "5.0.17", + "bundled": true, + "requires": { + "@npmcli/arborist": "^6.2.9", + "@npmcli/disparity-colors": "^3.0.0", + "@npmcli/installed-package-contents": "^2.0.2", + "binary-extensions": "^2.2.0", + "diff": "^5.1.0", + "minimatch": "^9.0.0", + "npm-package-arg": "^10.1.0", + "pacote": "^15.0.8", + "tar": "^6.1.13" + } + }, + "libnpmexec": { + "version": "5.0.17", + "bundled": true, + "requires": { + "@npmcli/arborist": "^6.2.9", + "@npmcli/run-script": "^6.0.0", + "chalk": "^4.1.0", + "ci-info": "^3.7.1", + "npm-package-arg": "^10.1.0", + "npmlog": "^7.0.1", + "pacote": "^15.0.8", + "proc-log": "^3.0.0", + "read": "^2.0.0", + "read-package-json-fast": "^3.0.2", + "semver": "^7.3.7", + "walk-up-path": "^3.0.1" + } + }, + "libnpmfund": { + "version": "4.0.17", + "bundled": true, + "requires": { + "@npmcli/arborist": "^6.2.9" + } + }, + "libnpmhook": { + "version": "9.0.3", + "bundled": true, + "requires": { + "aproba": "^2.0.0", + "npm-registry-fetch": "^14.0.3" + } + }, + "libnpmorg": { + "version": "5.0.4", + "bundled": true, + "requires": { + "aproba": "^2.0.0", + "npm-registry-fetch": "^14.0.3" + } + }, + "libnpmpack": { + "version": "5.0.17", + "bundled": true, + "requires": { + "@npmcli/arborist": "^6.2.9", + "@npmcli/run-script": "^6.0.0", + "npm-package-arg": "^10.1.0", + "pacote": "^15.0.8" + } + }, + "libnpmpublish": { + "version": "7.2.0", + "bundled": true, + "requires": { + "ci-info": "^3.6.1", + "normalize-package-data": "^5.0.0", + "npm-package-arg": "^10.1.0", + "npm-registry-fetch": "^14.0.3", + "proc-log": "^3.0.0", + "semver": "^7.3.7", + "sigstore": "^1.4.0", + "ssri": "^10.0.1" + } + }, + "libnpmsearch": { + "version": "6.0.2", + "bundled": true, + "requires": { + "npm-registry-fetch": "^14.0.3" + } + }, + "libnpmteam": { + "version": "5.0.3", + "bundled": true, + "requires": { + "aproba": "^2.0.0", + "npm-registry-fetch": "^14.0.3" + } + }, + "libnpmversion": { + "version": "4.0.2", + "bundled": true, + "requires": { + "@npmcli/git": "^4.0.1", + "@npmcli/run-script": "^6.0.0", + "json-parse-even-better-errors": "^3.0.0", + "proc-log": "^3.0.0", + "semver": "^7.3.7" + } + }, + "lru-cache": { + "version": "7.18.3", + "bundled": true + }, + "make-fetch-happen": { + "version": "11.1.1", + "bundled": true, + "requires": { + "agentkeepalive": "^4.2.1", + "cacache": "^17.0.0", + "http-cache-semantics": "^4.1.1", + "http-proxy-agent": "^5.0.0", + "https-proxy-agent": "^5.0.0", + "is-lambda": "^1.0.1", + "lru-cache": "^7.7.1", + "minipass": "^5.0.0", + "minipass-fetch": "^3.0.0", + "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": "^10.0.0" + } + }, + "minimatch": { + "version": "9.0.0", + "bundled": true, + "requires": { + "brace-expansion": "^2.0.1" + } + }, + "minipass": { + "version": "5.0.0", + "bundled": true + }, + "minipass-collect": { + "version": "1.0.2", + "bundled": true, + "requires": { + "minipass": "^3.0.0" + }, + "dependencies": { + "minipass": { + "version": "3.3.6", + "bundled": true, + "requires": { + "yallist": "^4.0.0" + } + } + } + }, + "minipass-fetch": { + "version": "3.0.3", + "bundled": true, + "requires": { + "encoding": "^0.1.13", + "minipass": "^5.0.0", + "minipass-sized": "^1.0.3", + "minizlib": "^2.1.2" + } + }, + "minipass-flush": { + "version": "1.0.5", + "bundled": true, + "requires": { + "minipass": "^3.0.0" + }, + "dependencies": { + "minipass": { + "version": "3.3.6", + "bundled": true, + "requires": { + "yallist": "^4.0.0" + } + } + } + }, + "minipass-json-stream": { + "version": "1.0.1", + "bundled": true, + "requires": { + "jsonparse": "^1.3.1", + "minipass": "^3.0.0" + }, + "dependencies": { + "minipass": { + "version": "3.3.6", + "bundled": true, + "requires": { + "yallist": "^4.0.0" + } + } + } + }, + "minipass-pipeline": { + "version": "1.2.4", + "bundled": true, + "requires": { + "minipass": "^3.0.0" + }, + "dependencies": { + "minipass": { + "version": "3.3.6", + "bundled": true, + "requires": { + "yallist": "^4.0.0" + } + } + } + }, + "minipass-sized": { + "version": "1.0.3", + "bundled": true, + "requires": { + "minipass": "^3.0.0" + }, + "dependencies": { + "minipass": { + "version": "3.3.6", + "bundled": true, + "requires": { + "yallist": "^4.0.0" + } + } + } + }, + "minizlib": { + "version": "2.1.2", + "bundled": true, + "requires": { + "minipass": "^3.0.0", + "yallist": "^4.0.0" + }, + "dependencies": { + "minipass": { + "version": "3.3.6", + "bundled": true, + "requires": { + "yallist": "^4.0.0" + } + } + } + }, + "mkdirp": { + "version": "1.0.4", + "bundled": true + }, + "ms": { + "version": "2.1.3", + "bundled": true + }, + "mute-stream": { + "version": "1.0.0", + "bundled": true + }, + "negotiator": { + "version": "0.6.3", + "bundled": true + }, + "node-gyp": { + "version": "9.3.1", + "bundled": true, + "requires": { + "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" + }, + "dependencies": { + "@npmcli/fs": { + "version": "2.1.2", + "bundled": true, + "requires": { + "@gar/promisify": "^1.1.3", + "semver": "^7.3.5" + } + }, + "abbrev": { + "version": "1.1.1", + "bundled": true + }, + "are-we-there-yet": { + "version": "3.0.1", + "bundled": true, + "requires": { + "delegates": "^1.0.0", + "readable-stream": "^3.6.0" + } + }, + "brace-expansion": { + "version": "1.1.11", + "bundled": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "cacache": { + "version": "16.1.3", + "bundled": true, + "requires": { + "@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" + }, + "dependencies": { + "brace-expansion": { + "version": "2.0.1", + "bundled": true, + "requires": { + "balanced-match": "^1.0.0" + } + }, + "glob": { + "version": "8.1.0", + "bundled": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^5.0.1", + "once": "^1.3.0" + } + }, + "minimatch": { + "version": "5.1.6", + "bundled": true, + "requires": { + "brace-expansion": "^2.0.1" + } + } + } + }, + "fs-minipass": { + "version": "2.1.0", + "bundled": true, + "requires": { + "minipass": "^3.0.0" + } + }, + "gauge": { + "version": "4.0.4", + "bundled": true, + "requires": { + "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" + } + }, + "glob": { + "version": "7.2.3", + "bundled": true, + "requires": { + "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" + } + }, + "make-fetch-happen": { + "version": "10.2.1", + "bundled": true, + "requires": { + "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" + } + }, + "minimatch": { + "version": "3.1.2", + "bundled": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minipass": { + "version": "3.3.6", + "bundled": true, + "requires": { + "yallist": "^4.0.0" + } + }, + "minipass-fetch": { + "version": "2.1.2", + "bundled": true, + "requires": { + "encoding": "^0.1.13", + "minipass": "^3.1.6", + "minipass-sized": "^1.0.3", + "minizlib": "^2.1.2" + } + }, + "nopt": { + "version": "6.0.0", + "bundled": true, + "requires": { + "abbrev": "^1.0.0" + } + }, + "npmlog": { + "version": "6.0.2", + "bundled": true, + "requires": { + "are-we-there-yet": "^3.0.0", + "console-control-strings": "^1.1.0", + "gauge": "^4.0.3", + "set-blocking": "^2.0.0" + } + }, + "readable-stream": { + "version": "3.6.2", + "bundled": true, + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + }, + "signal-exit": { + "version": "3.0.7", + "bundled": true + }, + "ssri": { + "version": "9.0.1", + "bundled": true, + "requires": { + "minipass": "^3.1.1" + } + }, + "unique-filename": { + "version": "2.0.1", + "bundled": true, + "requires": { + "unique-slug": "^3.0.0" + } + }, + "unique-slug": { + "version": "3.0.0", + "bundled": true, + "requires": { + "imurmurhash": "^0.1.4" + } + }, + "which": { + "version": "2.0.2", + "bundled": true, + "requires": { + "isexe": "^2.0.0" + } + } + } + }, + "nopt": { + "version": "7.1.0", + "bundled": true, + "requires": { + "abbrev": "^2.0.0" + } + }, + "normalize-package-data": { + "version": "5.0.0", + "bundled": true, + "requires": { + "hosted-git-info": "^6.0.0", + "is-core-module": "^2.8.1", + "semver": "^7.3.5", + "validate-npm-package-license": "^3.0.4" + } + }, + "npm-audit-report": { + "version": "4.0.0", + "bundled": true, + "requires": { + "chalk": "^4.0.0" + } + }, + "npm-bundled": { + "version": "3.0.0", + "bundled": true, + "requires": { + "npm-normalize-package-bin": "^3.0.0" + } + }, + "npm-install-checks": { + "version": "6.1.1", + "bundled": true, + "requires": { + "semver": "^7.1.1" + } + }, + "npm-normalize-package-bin": { + "version": "3.0.1", + "bundled": true + }, + "npm-package-arg": { + "version": "10.1.0", + "bundled": true, + "requires": { + "hosted-git-info": "^6.0.0", + "proc-log": "^3.0.0", + "semver": "^7.3.5", + "validate-npm-package-name": "^5.0.0" + } + }, + "npm-packlist": { + "version": "7.0.4", + "bundled": true, + "requires": { + "ignore-walk": "^6.0.0" + } + }, + "npm-pick-manifest": { + "version": "8.0.1", + "bundled": true, + "requires": { + "npm-install-checks": "^6.0.0", + "npm-normalize-package-bin": "^3.0.0", + "npm-package-arg": "^10.0.0", + "semver": "^7.3.5" + } + }, + "npm-profile": { + "version": "7.0.1", + "bundled": true, + "requires": { + "npm-registry-fetch": "^14.0.0", + "proc-log": "^3.0.0" + } + }, + "npm-registry-fetch": { + "version": "14.0.5", + "bundled": true, + "requires": { + "make-fetch-happen": "^11.0.0", + "minipass": "^5.0.0", + "minipass-fetch": "^3.0.0", + "minipass-json-stream": "^1.0.1", + "minizlib": "^2.1.2", + "npm-package-arg": "^10.0.0", + "proc-log": "^3.0.0" + } + }, + "npm-user-validate": { + "version": "2.0.0", + "bundled": true + }, + "npmlog": { + "version": "7.0.1", + "bundled": true, + "requires": { + "are-we-there-yet": "^4.0.0", + "console-control-strings": "^1.1.0", + "gauge": "^5.0.0", + "set-blocking": "^2.0.0" + } + }, + "once": { + "version": "1.4.0", + "bundled": true, + "requires": { + "wrappy": "1" + } + }, + "p-map": { + "version": "4.0.0", + "bundled": true, + "requires": { + "aggregate-error": "^3.0.0" + } + }, + "pacote": { + "version": "15.1.3", + "bundled": true, + "requires": { + "@npmcli/git": "^4.0.0", + "@npmcli/installed-package-contents": "^2.0.1", + "@npmcli/promise-spawn": "^6.0.1", + "@npmcli/run-script": "^6.0.0", + "cacache": "^17.0.0", + "fs-minipass": "^3.0.0", + "minipass": "^5.0.0", + "npm-package-arg": "^10.0.0", + "npm-packlist": "^7.0.0", + "npm-pick-manifest": "^8.0.0", + "npm-registry-fetch": "^14.0.0", + "proc-log": "^3.0.0", + "promise-retry": "^2.0.1", + "read-package-json": "^6.0.0", + "read-package-json-fast": "^3.0.0", + "sigstore": "^1.3.0", + "ssri": "^10.0.0", + "tar": "^6.1.11" + } + }, + "parse-conflict-json": { + "version": "3.0.1", + "bundled": true, + "requires": { + "json-parse-even-better-errors": "^3.0.0", + "just-diff": "^6.0.0", + "just-diff-apply": "^5.2.0" + } + }, + "path-is-absolute": { + "version": "1.0.1", + "bundled": true + }, + "path-key": { + "version": "3.1.1", + "bundled": true + }, + "path-scurry": { + "version": "1.9.1", + "bundled": true, + "requires": { + "lru-cache": "^9.1.1", + "minipass": "^5.0.0 || ^6.0.0" + }, + "dependencies": { + "lru-cache": { + "version": "9.1.1", + "bundled": true + } + } + }, + "postcss-selector-parser": { + "version": "6.0.13", + "bundled": true, + "requires": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + } + }, + "proc-log": { + "version": "3.0.0", + "bundled": true + }, + "process": { + "version": "0.11.10", + "bundled": true + }, + "promise-all-reject-late": { + "version": "1.0.1", + "bundled": true + }, + "promise-call-limit": { + "version": "1.0.2", + "bundled": true + }, + "promise-inflight": { + "version": "1.0.1", + "bundled": true + }, + "promise-retry": { + "version": "2.0.1", + "bundled": true, + "requires": { + "err-code": "^2.0.2", + "retry": "^0.12.0" + } + }, + "promzard": { + "version": "1.0.0", + "bundled": true, + "requires": { + "read": "^2.0.0" + } + }, + "qrcode-terminal": { + "version": "0.12.0", + "bundled": true + }, + "read": { + "version": "2.1.0", + "bundled": true, + "requires": { + "mute-stream": "~1.0.0" + } + }, + "read-cmd-shim": { + "version": "4.0.0", + "bundled": true + }, + "read-package-json": { + "version": "6.0.3", + "bundled": true, + "requires": { + "glob": "^10.2.2", + "json-parse-even-better-errors": "^3.0.0", + "normalize-package-data": "^5.0.0", + "npm-normalize-package-bin": "^3.0.0" + } + }, + "read-package-json-fast": { + "version": "3.0.2", + "bundled": true, + "requires": { + "json-parse-even-better-errors": "^3.0.0", + "npm-normalize-package-bin": "^3.0.0" + } + }, + "readable-stream": { + "version": "4.4.0", + "bundled": true, + "requires": { + "abort-controller": "^3.0.0", + "buffer": "^6.0.3", + "events": "^3.3.0", + "process": "^0.11.10" + } + }, + "retry": { + "version": "0.12.0", + "bundled": true + }, + "rimraf": { + "version": "3.0.2", + "bundled": true, + "requires": { + "glob": "^7.1.3" + }, + "dependencies": { + "brace-expansion": { + "version": "1.1.11", + "bundled": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "glob": { + "version": "7.2.3", + "bundled": true, + "requires": { + "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" + } + }, + "minimatch": { + "version": "3.1.2", + "bundled": true, + "requires": { + "brace-expansion": "^1.1.7" + } + } + } + }, + "safe-buffer": { + "version": "5.1.2", + "bundled": true + }, + "safer-buffer": { + "version": "2.1.2", + "bundled": true, + "optional": true + }, + "semver": { + "version": "7.5.1", + "bundled": true, + "requires": { + "lru-cache": "^6.0.0" + }, + "dependencies": { + "lru-cache": { + "version": "6.0.0", + "bundled": true, + "requires": { + "yallist": "^4.0.0" + } + } + } + }, + "set-blocking": { + "version": "2.0.0", + "bundled": true + }, + "shebang-command": { + "version": "2.0.0", + "bundled": true, + "requires": { + "shebang-regex": "^3.0.0" + } + }, + "shebang-regex": { + "version": "3.0.0", + "bundled": true + }, + "signal-exit": { + "version": "4.0.2", + "bundled": true + }, + "sigstore": { + "version": "1.5.2", + "bundled": true, + "requires": { + "@sigstore/protobuf-specs": "^0.1.0", + "make-fetch-happen": "^11.0.1", + "tuf-js": "^1.1.3" + } + }, + "smart-buffer": { + "version": "4.2.0", + "bundled": true + }, + "socks": { + "version": "2.7.1", + "bundled": true, + "requires": { + "ip": "^2.0.0", + "smart-buffer": "^4.2.0" + } + }, + "socks-proxy-agent": { + "version": "7.0.0", + "bundled": true, + "requires": { + "agent-base": "^6.0.2", + "debug": "^4.3.3", + "socks": "^2.6.2" + } + }, + "spdx-correct": { + "version": "3.2.0", + "bundled": true, + "requires": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "spdx-exceptions": { + "version": "2.3.0", + "bundled": true + }, + "spdx-expression-parse": { + "version": "3.0.1", + "bundled": true, + "requires": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "spdx-license-ids": { + "version": "3.0.13", + "bundled": true + }, + "ssri": { + "version": "10.0.4", + "bundled": true, + "requires": { + "minipass": "^5.0.0" + } + }, + "string-width": { + "version": "4.2.3", + "bundled": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + } + }, + "string-width-cjs": { + "version": "npm:string-width-cjs@4.2.3", + "bundled": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + } + }, + "string_decoder": { + "version": "1.1.1", + "bundled": true, + "requires": { + "safe-buffer": "~5.1.0" + } + }, + "strip-ansi": { + "version": "6.0.1", + "bundled": true, + "requires": { + "ansi-regex": "^5.0.1" + } + }, + "strip-ansi-cjs": { + "version": "npm:strip-ansi-cjs@6.0.1", + "bundled": true, + "requires": { + "ansi-regex": "^5.0.1" + } + }, + "supports-color": { + "version": "7.2.0", + "bundled": true, + "requires": { + "has-flag": "^4.0.0" + } + }, + "tar": { + "version": "6.1.14", + "bundled": true, + "requires": { + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "minipass": "^5.0.0", + "minizlib": "^2.1.1", + "mkdirp": "^1.0.3", + "yallist": "^4.0.0" + }, + "dependencies": { + "fs-minipass": { + "version": "2.1.0", + "bundled": true, + "requires": { + "minipass": "^3.0.0" + }, + "dependencies": { + "minipass": { + "version": "3.3.6", + "bundled": true, + "requires": { + "yallist": "^4.0.0" + } + } + } + } + } + }, + "text-table": { + "version": "0.2.0", + "bundled": true + }, + "tiny-relative-date": { + "version": "1.3.0", + "bundled": true + }, + "treeverse": { + "version": "3.0.0", + "bundled": true + }, + "tuf-js": { + "version": "1.1.6", + "bundled": true, + "requires": { + "@tufjs/models": "1.0.4", + "debug": "^4.3.4", + "make-fetch-happen": "^11.1.0" + } + }, + "unique-filename": { + "version": "3.0.0", + "bundled": true, + "requires": { + "unique-slug": "^4.0.0" + } + }, + "unique-slug": { + "version": "4.0.0", + "bundled": true, + "requires": { + "imurmurhash": "^0.1.4" + } + }, + "util-deprecate": { + "version": "1.0.2", + "bundled": true + }, + "validate-npm-package-license": { + "version": "3.0.4", + "bundled": true, + "requires": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, + "validate-npm-package-name": { + "version": "5.0.0", + "bundled": true, + "requires": { + "builtins": "^5.0.0" + } + }, + "walk-up-path": { + "version": "3.0.1", + "bundled": true + }, + "wcwidth": { + "version": "1.0.1", + "bundled": true, + "requires": { + "defaults": "^1.0.3" + } + }, + "which": { + "version": "3.0.1", + "bundled": true, + "requires": { + "isexe": "^2.0.0" + } + }, + "wide-align": { + "version": "1.1.5", + "bundled": true, + "requires": { + "string-width": "^1.0.2 || 2 || 3 || 4" + } + }, + "wrap-ansi": { + "version": "8.1.0", + "bundled": true, + "requires": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "dependencies": { + "ansi-regex": { + "version": "6.0.1", + "bundled": true + }, + "ansi-styles": { + "version": "6.2.1", + "bundled": true + }, + "emoji-regex": { + "version": "9.2.2", + "bundled": true + }, + "string-width": { + "version": "5.1.2", + "bundled": true, + "requires": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + } + }, + "strip-ansi": { + "version": "7.0.1", + "bundled": true, + "requires": { + "ansi-regex": "^6.0.1" + } + } + } + }, + "wrap-ansi-cjs": { + "version": "npm:wrap-ansi-cjs@7.0.0", + "bundled": true, + "requires": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + } + }, + "wrappy": { + "version": "1.0.2", + "bundled": true + }, + "write-file-atomic": { + "version": "5.0.1", + "bundled": true, + "requires": { + "imurmurhash": "^0.1.4", + "signal-exit": "^4.0.1" + } + }, + "yallist": { + "version": "4.0.0", + "bundled": true + } + } + }, + "npm-bundled": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-3.0.0.tgz", + "integrity": "sha512-Vq0eyEQy+elFpzsKjMss9kxqb9tG3YHg4dsyWuUENuzvSUWe1TCnW/vV9FkhvBk/brEDoDiVd+M1Btosa6ImdQ==", + "dev": true, + "requires": { + "npm-normalize-package-bin": "^3.0.0" + } + }, + "npm-install-checks": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/npm-install-checks/-/npm-install-checks-6.1.1.tgz", + "integrity": "sha512-dH3GmQL4vsPtld59cOn8uY0iOqRmqKvV+DLGwNXV/Q7MDgD2QfOADWd/mFXcIE5LVhYYGjA3baz6W9JneqnuCw==", + "dev": true, + "requires": { + "semver": "^7.1.1" + } + }, + "npm-normalize-package-bin": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-3.0.1.tgz", + "integrity": "sha512-dMxCf+zZ+3zeQZXKxmyuCKlIDPGuv8EF940xbkC4kQVDTtqoh6rJFO+JTKSA6/Rwi0getWmtuy4Itup0AMcaDQ==", + "dev": true + }, + "npm-package-arg": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-10.1.0.tgz", + "integrity": "sha512-uFyyCEmgBfZTtrKk/5xDfHp6+MdrqGotX/VoOyEEl3mBwiEE5FlBaePanazJSVMPT7vKepcjYBY2ztg9A3yPIA==", + "dev": true, + "requires": { + "hosted-git-info": "^6.0.0", + "proc-log": "^3.0.0", + "semver": "^7.3.5", + "validate-npm-package-name": "^5.0.0" + } + }, + "npm-packlist": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-7.0.4.tgz", + "integrity": "sha512-d6RGEuRrNS5/N84iglPivjaJPxhDbZmlbTwTDX2IbcRHG5bZCdtysYMhwiPvcF4GisXHGn7xsxv+GQ7T/02M5Q==", + "dev": true, + "requires": { + "ignore-walk": "^6.0.0" + } + }, + "npm-pick-manifest": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/npm-pick-manifest/-/npm-pick-manifest-8.0.1.tgz", + "integrity": "sha512-mRtvlBjTsJvfCCdmPtiu2bdlx8d/KXtF7yNXNWe7G0Z36qWA9Ny5zXsI2PfBZEv7SXgoxTmNaTzGSbbzDZChoA==", + "dev": true, + "requires": { + "npm-install-checks": "^6.0.0", + "npm-normalize-package-bin": "^3.0.0", + "npm-package-arg": "^10.0.0", + "semver": "^7.3.5" + } + }, + "npm-registry-fetch": { + "version": "14.0.5", + "resolved": "https://registry.npmjs.org/npm-registry-fetch/-/npm-registry-fetch-14.0.5.tgz", + "integrity": "sha512-kIDMIo4aBm6xg7jOttupWZamsZRkAqMqwqqbVXnUqstY5+tapvv6bkH/qMR76jdgV+YljEUCyWx3hRYMrJiAgA==", + "dev": true, + "requires": { + "make-fetch-happen": "^11.0.0", + "minipass": "^5.0.0", + "minipass-fetch": "^3.0.0", + "minipass-json-stream": "^1.0.1", + "minizlib": "^2.1.2", + "npm-package-arg": "^10.0.0", + "proc-log": "^3.0.0" + }, + "dependencies": { + "lru-cache": { + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", + "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "dev": true + }, + "make-fetch-happen": { + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-11.1.1.tgz", + "integrity": "sha512-rLWS7GCSTcEujjVBs2YqG7Y4643u8ucvCJeSRqiLYhesrDuzeuFIk37xREzAsfQaqzl8b9rNCE4m6J8tvX4Q8w==", + "dev": true, + "requires": { + "agentkeepalive": "^4.2.1", + "cacache": "^17.0.0", + "http-cache-semantics": "^4.1.1", + "http-proxy-agent": "^5.0.0", + "https-proxy-agent": "^5.0.0", + "is-lambda": "^1.0.1", + "lru-cache": "^7.7.1", + "minipass": "^5.0.0", + "minipass-fetch": "^3.0.0", + "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": "^10.0.0" + } + }, + "minipass": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", + "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", + "dev": true + }, + "minipass-fetch": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-3.0.3.tgz", + "integrity": "sha512-n5ITsTkDqYkYJZjcRWzZt9qnZKCT7nKCosJhHoj7S7zD+BP4jVbWs+odsniw5TA3E0sLomhTKOKjF86wf11PuQ==", + "dev": true, + "requires": { + "encoding": "^0.1.13", + "minipass": "^5.0.0", + "minipass-sized": "^1.0.3", + "minizlib": "^2.1.2" + } + }, + "negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "dev": true + } + } + }, + "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" + } + }, + "npmlog": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-6.0.2.tgz", + "integrity": "sha512-/vBvz5Jfr9dT/aFWd0FIRf+T/Q2WBsLENygUaFUqstqsycmZAP/t5BvFJTK0viFmSUxiUKTUplWy5vt+rvKIxg==", + "dev": true, + "requires": { + "are-we-there-yet": "^3.0.0", + "console-control-strings": "^1.1.0", + "gauge": "^4.0.3", + "set-blocking": "^2.0.0" + } + }, + "nth-check": { + "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==", + "dev": true, + "requires": { + "boolbase": "^1.0.0" + } + }, + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" + }, + "object-inspect": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.2.tgz", + "integrity": "sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ==", + "dev": true + }, + "obuf": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/obuf/-/obuf-1.1.2.tgz", + "integrity": "sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==", + "dev": true + }, + "on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", + "dev": true, + "requires": { + "ee-first": "1.1.1" + } + }, + "on-headers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", + "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==", + "dev": true + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "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" + } + }, + "open": { + "version": "8.4.1", + "resolved": "https://registry.npmjs.org/open/-/open-8.4.1.tgz", + "integrity": "sha512-/4b7qZNhv6Uhd7jjnREh1NjnPxlTq+XNWPG88Ydkj5AILcA5m3ajvcg57pB24EQjKv0dK62XnDqk9c/hkIG5Kg==", + "dev": true, + "requires": { + "define-lazy-prop": "^2.0.0", + "is-docker": "^2.1.1", + "is-wsl": "^2.2.0" + } + }, + "optionator": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", + "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", + "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" + } + }, + "ora": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz", + "integrity": "sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==", + "dev": true, + "requires": { + "bl": "^4.1.0", + "chalk": "^4.1.0", + "cli-cursor": "^3.1.0", + "cli-spinners": "^2.5.0", + "is-interactive": "^1.0.0", + "is-unicode-supported": "^0.1.0", + "log-symbols": "^4.1.0", + "strip-ansi": "^6.0.0", + "wcwidth": "^1.0.1" + } + }, + "os-tmpdir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "integrity": "sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==", + "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-map": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", + "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", + "dev": true, + "requires": { + "aggregate-error": "^3.0.0" + } + }, + "p-retry": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-4.6.2.tgz", + "integrity": "sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ==", + "dev": true, + "requires": { + "@types/retry": "0.12.0", + "retry": "^0.13.1" + } + }, + "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 + }, + "pacote": { + "version": "15.1.0", + "resolved": "https://registry.npmjs.org/pacote/-/pacote-15.1.0.tgz", + "integrity": "sha512-FFcjtIl+BQNfeliSm7MZz5cpdohvUV1yjGnqgVM4UnVF7JslRY0ImXAygdaCDV0jjUADEWu4y5xsDV8brtrTLg==", + "dev": true, + "requires": { + "@npmcli/git": "^4.0.0", + "@npmcli/installed-package-contents": "^2.0.1", + "@npmcli/promise-spawn": "^6.0.1", + "@npmcli/run-script": "^6.0.0", + "cacache": "^17.0.0", + "fs-minipass": "^3.0.0", + "minipass": "^4.0.0", + "npm-package-arg": "^10.0.0", + "npm-packlist": "^7.0.0", + "npm-pick-manifest": "^8.0.0", + "npm-registry-fetch": "^14.0.0", + "proc-log": "^3.0.0", + "promise-retry": "^2.0.1", + "read-package-json": "^6.0.0", + "read-package-json-fast": "^3.0.0", + "sigstore": "^1.0.0", + "ssri": "^10.0.0", + "tar": "^6.1.11" + } + }, + "pako": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", + "dev": true + }, + "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==", + "requires": { + "callsites": "^3.0.0" + } + }, + "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" + } + }, + "parse-node-version": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parse-node-version/-/parse-node-version-1.0.1.tgz", + "integrity": "sha512-3YHlOa/JgH6Mnpr05jP9eDG254US9ek25LyIxZlDItp2iJtwyaXQb57lBYLdT3MowkUFYEV2XXNAYIPlESvJlA==", + "dev": true + }, + "parse5": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.1.2.tgz", + "integrity": "sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==", + "optional": true, + "requires": { + "entities": "^4.4.0" + }, + "dependencies": { + "entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "optional": true + } + } + }, + "parse5-html-rewriting-stream": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/parse5-html-rewriting-stream/-/parse5-html-rewriting-stream-7.0.0.tgz", + "integrity": "sha512-mazCyGWkmCRWDI15Zp+UiCqMp/0dgEmkZRvhlsqqKYr4SsVm/TvnSpD9fCvqCA2zoWJcfRym846ejWBBHRiYEg==", + "dev": true, + "requires": { + "entities": "^4.3.0", + "parse5": "^7.0.0", + "parse5-sax-parser": "^7.0.0" + }, + "dependencies": { + "entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "dev": true + }, + "parse5": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.1.2.tgz", + "integrity": "sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==", + "dev": true, + "requires": { + "entities": "^4.4.0" + } + } + } + }, + "parse5-htmlparser2-tree-adapter": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-6.0.1.tgz", + "integrity": "sha512-qPuWvbLgvDGilKc5BoicRovlT4MtYT6JfJyBOMDsKoiT+GiuP5qyrPCnR9HcPECIJJmZh5jRndyNThnhhb/vlA==", + "dev": true, + "requires": { + "parse5": "^6.0.1" + }, + "dependencies": { + "parse5": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", + "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==", + "dev": true + } + } + }, + "parse5-sax-parser": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/parse5-sax-parser/-/parse5-sax-parser-7.0.0.tgz", + "integrity": "sha512-5A+v2SNsq8T6/mG3ahcz8ZtQ0OUFTatxPbeidoMB7tkJSGDY3tdfl4MHovtLQHkEn5CGxijNWRQHhRQ6IRpXKg==", + "dev": true, + "requires": { + "parse5": "^7.0.0" + }, + "dependencies": { + "entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "dev": true + }, + "parse5": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.1.2.tgz", + "integrity": "sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==", + "dev": true, + "requires": { + "entities": "^4.4.0" + } + } + } + }, + "parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "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=" + }, + "path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==" + }, + "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 + }, + "path-scurry": { + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.9.2.tgz", + "integrity": "sha512-qSDLy2aGFPm8i4rsbHd4MNyTcrzHFsLQykrtbuGRknZZCBBVXSv2tSCDN2Cg6Rt/GFRw8GoW9y9Ecw5rIPG1sg==", + "dev": true, + "requires": { + "lru-cache": "^9.1.1", + "minipass": "^5.0.0 || ^6.0.2" + }, + "dependencies": { + "lru-cache": { + "version": "9.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-9.1.1.tgz", + "integrity": "sha512-65/Jky17UwSb0BuB9V+MyDpsOtXKmYwzhyl+cOa9XUiI4uV2Ouy/2voFP3+al0BjZbJgMBD8FojMpAf+Z+qn4A==", + "dev": true + }, + "minipass": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-6.0.2.tgz", + "integrity": "sha512-MzWSV5nYVT7mVyWCwn2o7JH13w2TBRmmSqSRCKzTw+lmft9X4z+3wjvs06Tzijo5z4W/kahUCDpRXTF+ZrmF/w==", + "dev": true + } + } + }, + "path-to-regexp": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", + "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==", + "dev": true + }, + "path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true + }, + "photoviewer": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/photoviewer/-/photoviewer-3.7.1.tgz", + "integrity": "sha512-lvOfIBCP+KXMHA6/CN2e4tLYorH5mqhwzvxiJWIkYb/OSjkEVgLHHyWGSmNkAHS7MNh5KBfoDTT4vsfiDAWnaw==", + "requires": { + "domq.js": "^0.6.7" + } + }, + "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.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true + }, + "pify": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", + "dev": true + }, + "piscina": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/piscina/-/piscina-3.2.0.tgz", + "integrity": "sha512-yn/jMdHRw+q2ZJhFhyqsmANcbF6V2QwmD84c6xRau+QpQOmtrBCoRGdvTfeuFDYXB5W2m6MfLkjkvQa9lUSmIA==", + "dev": true, + "requires": { + "eventemitter-asyncresource": "^1.0.0", + "hdr-histogram-js": "^2.0.1", + "hdr-histogram-percentiles-obj": "^3.0.0", + "nice-napi": "^1.0.2" + } + }, + "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" + } + }, + "postcss": { + "version": "8.4.21", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.21.tgz", + "integrity": "sha512-tP7u/Sn/dVxK2NnruI4H9BG+x+Wxz6oeZ1cJ8P6G/PZY0IKk4k/63TDsQf2kQq3+qoJeLm2kIBUNlZe3zgb4Zg==", + "dev": true, + "requires": { + "nanoid": "^3.3.4", + "picocolors": "^1.0.0", + "source-map-js": "^1.0.2" + } + }, + "postcss-loader": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/postcss-loader/-/postcss-loader-7.0.2.tgz", + "integrity": "sha512-fUJzV/QH7NXUAqV8dWJ9Lg4aTkDCezpTS5HgJ2DvqznexTbSTxgi/dTECvTZ15BwKTtk8G/bqI/QTu2HPd3ZCg==", + "dev": true, + "requires": { + "cosmiconfig": "^7.0.0", + "klona": "^2.0.5", + "semver": "^7.3.8" + } + }, + "postcss-modules-extract-imports": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.0.0.tgz", + "integrity": "sha512-bdHleFnP3kZ4NYDhuGlVK+CMrQ/pqUm8bx/oGL93K6gVwiclvX5x0n76fYMKuIGKzlABOy13zsvqjb0f92TEXw==", + "dev": true + }, + "postcss-modules-local-by-default": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.3.tgz", + "integrity": "sha512-2/u2zraspoACtrbFRnTijMiQtb4GW4BvatjaG/bCjYQo8kLTdevCUlwuBHx2sCnSyrI3x3qj4ZK1j5LQBgzmwA==", + "dev": true, + "requires": { + "icss-utils": "^5.0.0", + "postcss-selector-parser": "^6.0.2", + "postcss-value-parser": "^4.1.0" + } + }, + "postcss-modules-scope": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-3.0.0.tgz", + "integrity": "sha512-hncihwFA2yPath8oZ15PZqvWGkWf+XUfQgUGamS4LqoP1anQLOsOJw0vr7J7IwLpoY9fatA2qiGUGmuZL0Iqlg==", + "dev": true, + "requires": { + "postcss-selector-parser": "^6.0.4" + } + }, + "postcss-modules-values": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/postcss-modules-values/-/postcss-modules-values-4.0.0.tgz", + "integrity": "sha512-RDxHkAiEGI78gS2ofyvCsu7iycRv7oqw5xMWn9iMoR0N/7mf9D50ecQqUo5BZ9Zh2vH4bCUR/ktCqbB9m8vJjQ==", + "dev": true, + "requires": { + "icss-utils": "^5.0.0" + } + }, + "postcss-selector-parser": { + "version": "6.0.13", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.13.tgz", + "integrity": "sha512-EaV1Gl4mUEV4ddhDnv/xtj7sxwrwxdetHdWUGnT4VJQf+4d05v6lHYZr8N573k5Z0BViss7BDhfWtKS3+sfAqQ==", + "dev": true, + "requires": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + } + }, + "postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", + "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==" + }, + "pretty-bytes": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-5.6.0.tgz", + "integrity": "sha512-FFw039TmrBqFK8ma/7OL3sDz/VytdtJr044/QUJtH0wK9lb9jLq9tJyIxUwtQJHwar2BqtiA4iCWSwo9JLkzFg==", + "dev": true + }, + "proc-log": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-3.0.0.tgz", + "integrity": "sha512-++Vn7NS4Xf9NacaU9Xq3URUuqZETPsf8L4j5/ckhaRYsfPeRyzGw+iDjFhV/Jr3uNmTvvddEJFWh5R1gRgUH8A==", + "dev": true + }, + "process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "dev": true + }, + "promise-inflight": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz", + "integrity": "sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g==", + "dev": true + }, + "promise-retry": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/promise-retry/-/promise-retry-2.0.1.tgz", + "integrity": "sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==", + "dev": true, + "requires": { + "err-code": "^2.0.2", + "retry": "^0.12.0" + }, + "dependencies": { + "retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", + "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==", + "dev": true + } + } + }, + "proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "dev": true, + "requires": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "dependencies": { + "ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "dev": true + } + } + }, + "prr": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz", + "integrity": "sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw==", + "dev": true, + "optional": 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==" + }, + "qjobs": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/qjobs/-/qjobs-1.2.0.tgz", + "integrity": "sha512-8YOJEHtxpySA3fFDyCRxA+UUV+fA+rTWnuWvylOK/NCjhY+b4ocCtmu8TtsWb+mYeU+GCHf/S66KZF/AsteKHg==", + "dev": true + }, + "qs": { + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", + "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", + "dev": true, + "requires": { + "side-channel": "^1.0.4" + } + }, + "queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true + }, + "randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dev": true, + "requires": { + "safe-buffer": "^5.1.0" + } + }, + "range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "dev": true + }, + "read-package-json": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/read-package-json/-/read-package-json-6.0.3.tgz", + "integrity": "sha512-4QbpReW4kxFgeBQ0vPAqh2y8sXEB3D4t3jsXbJKIhBiF80KT6XRo45reqwtftju5J6ru1ax06A2Gb/wM1qCOEQ==", + "dev": true, + "requires": { + "glob": "^10.2.2", + "json-parse-even-better-errors": "^3.0.0", + "normalize-package-data": "^5.0.0", + "npm-normalize-package-bin": "^3.0.0" + }, + "dependencies": { + "brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0" + } + }, + "glob": { + "version": "10.2.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.2.6.tgz", + "integrity": "sha512-U/rnDpXJGF414QQQZv5uVsabTVxMSwzS5CH0p3DRCIV6ownl4f7PzGnkGmvlum2wB+9RlJWJZ6ACU1INnBqiPA==", + "dev": true, + "requires": { + "foreground-child": "^3.1.0", + "jackspeak": "^2.0.3", + "minimatch": "^9.0.1", + "minipass": "^5.0.0 || ^6.0.2", + "path-scurry": "^1.7.0" + } + }, + "json-parse-even-better-errors": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-3.0.0.tgz", + "integrity": "sha512-iZbGHafX/59r39gPwVPRBGw0QQKnA7tte5pSMrhWOW7swGsVvVTjmfyAV9pNqk8YGT7tRCdxRu8uzcgZwoDooA==", + "dev": true + }, + "minimatch": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.1.tgz", + "integrity": "sha512-0jWhJpD/MdhPXwPuiRkCbfYfSKp2qnn2eOc279qI7f+osl/l+prKSrvhg157zSYvx/1nmgn2NqdT6k2Z7zSH9w==", + "dev": true, + "requires": { + "brace-expansion": "^2.0.1" + } + }, + "minipass": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-6.0.2.tgz", + "integrity": "sha512-MzWSV5nYVT7mVyWCwn2o7JH13w2TBRmmSqSRCKzTw+lmft9X4z+3wjvs06Tzijo5z4W/kahUCDpRXTF+ZrmF/w==", + "dev": true + } + } + }, + "read-package-json-fast": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/read-package-json-fast/-/read-package-json-fast-3.0.2.tgz", + "integrity": "sha512-0J+Msgym3vrLOUB3hzQCuZHII0xkNGCtz/HJH9xZshwv9DbDwkw1KaE3gx/e2J5rpEY5rtOy6cyhKOPrkP7FZw==", + "dev": true, + "requires": { + "json-parse-even-better-errors": "^3.0.0", + "npm-normalize-package-bin": "^3.0.0" + }, + "dependencies": { + "json-parse-even-better-errors": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-3.0.0.tgz", + "integrity": "sha512-iZbGHafX/59r39gPwVPRBGw0QQKnA7tte5pSMrhWOW7swGsVvVTjmfyAV9pNqk8YGT7tRCdxRu8uzcgZwoDooA==", + "dev": true + } + } + }, + "readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dev": true, + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + }, + "readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "requires": { + "picomatch": "^2.2.1" + } + }, + "reflect-metadata": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.1.13.tgz", + "integrity": "sha512-Ts1Y/anZELhSsjMcU605fU9RE4Oi3p5ORujwbIKXfWa+0Zxs510Qrmrce5/Jowq3cHSZSJqBjypxmHarc+vEWg==", + "dev": true + }, + "regenerate": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", + "integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==", + "dev": true + }, + "regenerate-unicode-properties": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.1.0.tgz", + "integrity": "sha512-d1VudCLoIGitcU/hEg2QqvyGZQmdC0Lf8BqdOMXGFSvJP4bNV1+XqbPQeHHLD51Jh4QJJ225dlIFvY4Ly6MXmQ==", + "dev": true, + "requires": { + "regenerate": "^1.4.2" + } + }, + "regenerator-runtime": { + "version": "0.13.11", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", + "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==", + "dev": true + }, + "regenerator-transform": { + "version": "0.15.1", + "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.15.1.tgz", + "integrity": "sha512-knzmNAcuyxV+gQCufkYcvOqX/qIIfHLv0u5x79kRxuGojfYVky1f15TzZEu2Avte8QGepvUNTnLskf8E6X6Vyg==", + "dev": true, + "requires": { + "@babel/runtime": "^7.8.4" + } + }, + "regex-parser": { + "version": "2.2.11", + "resolved": "https://registry.npmjs.org/regex-parser/-/regex-parser-2.2.11.tgz", + "integrity": "sha512-jbD/FT0+9MBU2XAZluI7w2OBs1RBi6p9M83nkoZayQXXU9e8Robt69FcZc7wU4eJD/YFTjn1JdCk3rbMJajz8Q==", + "dev": true + }, + "regexpp": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", + "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==" + }, + "regexpu-core": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-5.3.2.tgz", + "integrity": "sha512-RAM5FlZz+Lhmo7db9L298p2vHP5ZywrVXmVXpmAD9GuL5MPH6t9ROw1iA/wfHkQ76Qe7AaPF0nGuim96/IrQMQ==", + "dev": true, + "requires": { + "@babel/regjsgen": "^0.8.0", + "regenerate": "^1.4.2", + "regenerate-unicode-properties": "^10.1.0", + "regjsparser": "^0.9.1", + "unicode-match-property-ecmascript": "^2.0.0", + "unicode-match-property-value-ecmascript": "^2.1.0" + } + }, + "regjsparser": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.9.1.tgz", + "integrity": "sha512-dQUtn90WanSNl+7mQKcXAgZxvUe7Z0SqXlgzv0za4LwiUhyzBC58yQO3liFoUgu8GiJVInAhJjkj1N0EtQ5nkQ==", + "dev": true, + "requires": { + "jsesc": "~0.5.0" + }, + "dependencies": { + "jsesc": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", + "integrity": "sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA==", + "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-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 + }, + "requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=", + "dev": true + }, + "resolve": { + "version": "1.21.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.21.0.tgz", + "integrity": "sha512-3wCbTpk5WJlyE4mSOtDLhqQmGFi0/TD9VPwmiolnk8U0wRgMEktqCXd3vy5buTO3tljvalNvKrjHEfrd2WpEKA==", + "dev": true, + "requires": { + "is-core-module": "^2.8.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.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-loader": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-url-loader/-/resolve-url-loader-5.0.0.tgz", + "integrity": "sha512-uZtduh8/8srhBoMx//5bwqjQ+rfYOUq8zC9NrMUGtjBiGTtFJM42s58/36+hTqeqINcnYe08Nj3LkK9lW4N8Xg==", + "dev": true, + "requires": { + "adjust-sourcemap-loader": "^4.0.0", + "convert-source-map": "^1.7.0", + "loader-utils": "^2.0.0", + "postcss": "^8.2.14", + "source-map": "0.6.1" + }, + "dependencies": { + "loader-utils": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.4.tgz", + "integrity": "sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==", + "dev": true, + "requires": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^2.1.2" + } + }, + "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 + } + } + }, + "restore-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", + "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", + "dev": true, + "requires": { + "onetime": "^5.1.0", + "signal-exit": "^3.0.2" + } + }, + "retry": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", + "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==", + "dev": true + }, + "reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true + }, + "rfdc": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.3.0.tgz", + "integrity": "sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA==", + "dev": true + }, + "rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "requires": { + "glob": "^7.1.3" + } + }, + "robust-predicates": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/robust-predicates/-/robust-predicates-3.0.1.tgz", + "integrity": "sha512-ndEIpszUHiG4HtDsQLeIuMvRsDnn8c8rYStabochtUeCvfuvNptb5TUbVD68LRAILPX7p9nqQGh4xJgn3EHS/g==" + }, + "run-async": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz", + "integrity": "sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==", + "dev": true + }, + "run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "requires": { + "queue-microtask": "^1.2.2" + } + }, + "rw": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/rw/-/rw-1.3.3.tgz", + "integrity": "sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==" + }, + "rxjs": { + "version": "7.5.5", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.5.5.tgz", + "integrity": "sha512-sy+H0pQofO95VDmFLzyaw9xNJU4KTRSwQIGM6+iG3SypAtCiLDzpeG8sJrNCWn2Up9km+KhkvTdbkrdy+yzZdw==", + "dev": true, + "requires": { + "tslib": "^2.1.0" + } + }, + "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 + }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "safevalues": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/safevalues/-/safevalues-0.3.4.tgz", + "integrity": "sha512-LRneZZRXNgjzwG4bDQdOTSbze3fHm1EAKN/8bePxnlEZiBmkYEDggaHbuvHI9/hoqHbGfsEA7tWS9GhYHZBBsw==" + }, + "sass": { + "version": "1.58.1", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.58.1.tgz", + "integrity": "sha512-bnINi6nPXbP1XNRaranMFEBZWUfdW/AF16Ql5+ypRxfTvCRTTKrLsMIakyDcayUt2t/RZotmL4kgJwNH5xO+bg==", + "dev": true, + "requires": { + "chokidar": ">=3.0.0 <4.0.0", + "immutable": "^4.0.0", + "source-map-js": ">=0.6.2 <2.0.0" + } + }, + "sass-loader": { + "version": "13.2.0", + "resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-13.2.0.tgz", + "integrity": "sha512-JWEp48djQA4nbZxmgC02/Wh0eroSUutulROUusYJO9P9zltRbNN80JCBHqRGzjd4cmZCa/r88xgfkjGD0TXsHg==", + "dev": true, + "requires": { + "klona": "^2.0.4", + "neo-async": "^2.6.2" + } + }, + "sax": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", + "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==", + "dev": true, + "optional": true + }, + "schema-utils": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.0.1.tgz", + "integrity": "sha512-lELhBAAly9NowEsX0yZBlw9ahZG+sK/1RJ21EpzdYHKEs13Vku3LJ+MIPhh4sMs0oCCeufZQEQbMekiA4vuVIQ==", + "dev": true, + "requires": { + "@types/json-schema": "^7.0.9", + "ajv": "^8.9.0", + "ajv-formats": "^2.1.1", + "ajv-keywords": "^5.1.0" + }, + "dependencies": { + "ajv": { + "version": "8.12.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", + "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", + "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" + } + }, + "ajv-keywords": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", + "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.3" + } + }, + "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 + } + } + }, + "select-hose": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz", + "integrity": "sha512-mEugaLK+YfkijB4fx0e6kImuJdCIt2LxCRcbEYPqRGCs4F2ogyfZU5IAZRdjCP8JPq2AtdNoC/Dux63d9Kiryg==", + "dev": true + }, + "selfsigned": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-2.1.1.tgz", + "integrity": "sha512-GSL3aowiF7wa/WtSFwnUrludWFoNhftq8bUkH9pkzjpN2XSPOAYEgg6e0sS9s0rZwgJzJiQRPU18A6clnoW5wQ==", + "dev": true, + "requires": { + "node-forge": "^1" + } + }, + "semver": { + "version": "7.3.8", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", + "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + }, + "dependencies": { + "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" + } + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + } + } + }, + "send": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", + "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", + "dev": true, + "requires": { + "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" + }, + "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" + }, + "dependencies": { + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + } + } + }, + "ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + }, + "on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "dev": true, + "requires": { + "ee-first": "1.1.1" + } + }, + "statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "dev": true + } + } + }, + "serialize-javascript": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.1.tgz", + "integrity": "sha512-owoXEFjWRllis8/M1Q+Cw5k8ZH40e3zhp/ovX+Xr/vi1qj6QesbyXXViFbpNvWvPNAD62SutwEXavefrLJWj7w==", + "dev": true, + "requires": { + "randombytes": "^2.1.0" + } + }, + "serve-index": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/serve-index/-/serve-index-1.9.1.tgz", + "integrity": "sha512-pXHfKNP4qujrtteMrSBb0rc8HJ9Ms/GrXwcUtUtD5s4ewDJI8bT3Cz2zTVRMKtri49pLx2e0Ya8ziP5Ya2pZZw==", + "dev": true, + "requires": { + "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" + }, + "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" + } + }, + "depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==", + "dev": true + }, + "http-errors": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", + "integrity": "sha512-lks+lVC8dgGyh97jxvxeYTWQFvh4uw4yC12gVl63Cg30sjPX4wuGcdkICVXDAESr6OJGjqGA8Iz5mkeN6zlD7A==", + "dev": true, + "requires": { + "depd": "~1.1.2", + "inherits": "2.0.3", + "setprototypeof": "1.1.0", + "statuses": ">= 1.4.0 < 2" + } + }, + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==", + "dev": true + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + }, + "setprototypeof": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", + "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==", + "dev": true + } + } + }, + "serve-static": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", + "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", + "dev": true, + "requires": { + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.18.0" + } + }, + "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==", + "dev": true + }, + "setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "dev": true + }, + "shallow-clone": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", + "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==", + "dev": true, + "requires": { + "kind-of": "^6.0.2" + } + }, + "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==", + "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==" + }, + "side-channel": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", + "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "dev": true, + "requires": { + "call-bind": "^1.0.0", + "get-intrinsic": "^1.0.2", + "object-inspect": "^1.9.0" + } + }, + "signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true + }, + "sigstore": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/sigstore/-/sigstore-1.5.2.tgz", + "integrity": "sha512-X95v6xAAooVpn7PaB94TDmFeSO5SBfCtB1R23fvzr36WTfjtkiiyOeei979nbTjc8nzh6FSLeltQZuODsm1EjQ==", + "dev": true, + "requires": { + "@sigstore/protobuf-specs": "^0.1.0", + "make-fetch-happen": "^11.0.1", + "tuf-js": "^1.1.3" + }, + "dependencies": { + "lru-cache": { + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", + "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "dev": true + }, + "make-fetch-happen": { + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-11.1.1.tgz", + "integrity": "sha512-rLWS7GCSTcEujjVBs2YqG7Y4643u8ucvCJeSRqiLYhesrDuzeuFIk37xREzAsfQaqzl8b9rNCE4m6J8tvX4Q8w==", + "dev": true, + "requires": { + "agentkeepalive": "^4.2.1", + "cacache": "^17.0.0", + "http-cache-semantics": "^4.1.1", + "http-proxy-agent": "^5.0.0", + "https-proxy-agent": "^5.0.0", + "is-lambda": "^1.0.1", + "lru-cache": "^7.7.1", + "minipass": "^5.0.0", + "minipass-fetch": "^3.0.0", + "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": "^10.0.0" + } + }, + "minipass": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", + "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", + "dev": true + }, + "minipass-fetch": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-3.0.3.tgz", + "integrity": "sha512-n5ITsTkDqYkYJZjcRWzZt9qnZKCT7nKCosJhHoj7S7zD+BP4jVbWs+odsniw5TA3E0sLomhTKOKjF86wf11PuQ==", + "dev": true, + "requires": { + "encoding": "^0.1.13", + "minipass": "^5.0.0", + "minipass-sized": "^1.0.3", + "minizlib": "^2.1.2" + } + }, + "negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "dev": true + } + } + }, + "slash": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-4.0.0.tgz", + "integrity": "sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew==", + "dev": true + }, + "smart-buffer": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", + "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", + "dev": true + }, + "snyk": { + "version": "1.1087.0", + "resolved": "https://registry.npmjs.org/snyk/-/snyk-1.1087.0.tgz", + "integrity": "sha512-4Ap2O59+qHCKmzeJu0Kq6S74M409UwRer3ss3wLCNO7vwj8hpfi4wvA+bZc8ywwKv028X9nLhw8hzcJ7bmsavQ==", + "dev": true + }, + "socket.io": { + "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==", + "requires": { + "accepts": "~1.3.4", + "base64id": "~2.0.0", + "debug": "~4.3.2", + "engine.io": "~6.4.1", + "socket.io-adapter": "~2.5.2", + "socket.io-parser": "~4.2.1" + } + }, + "socket.io-adapter": { + "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==", + "requires": { + "ws": "~8.11.0" + }, + "dependencies": { + "ws": { + "version": "8.11.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.11.0.tgz", + "integrity": "sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg==" + } + } + }, + "socket.io-client": { + "version": "4.5.3", + "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.5.3.tgz", + "integrity": "sha512-I/hqDYpQ6JKwtJOf5ikM+Qz+YujZPMEl6qBLhxiP0nX+TfXKhW4KZZG8lamrD6Y5ngjmYHreESVasVCgi5Kl3A==", + "requires": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.2", + "engine.io-client": "~6.2.3", + "socket.io-parser": "~4.2.0" + } + }, + "socket.io-parser": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.3.tgz", + "integrity": "sha512-JMafRntWVO2DCJimKsRTh/wnqVvO4hrfwOqtO7f+uzwsQMuxO6VwImtYxaQ+ieoyshWOTJyV0fA21lccEXRPpQ==", + "requires": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.1" + } + }, + "sockjs": { + "version": "0.3.24", + "resolved": "https://registry.npmjs.org/sockjs/-/sockjs-0.3.24.tgz", + "integrity": "sha512-GJgLTZ7vYb/JtPSSZ10hsOYIvEYsjbNU+zPdIHcUaWVNUEPivzxku31865sSSud0Da0W4lEeOPlmw93zLQchuQ==", + "dev": true, + "requires": { + "faye-websocket": "^0.11.3", + "uuid": "^8.3.2", + "websocket-driver": "^0.7.4" + } + }, + "socks": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.7.1.tgz", + "integrity": "sha512-7maUZy1N7uo6+WVEX6psASxtNlKaNVMlGQKkG/63nEDdLOWNbiUMoLK7X4uYoLhQstau72mLgfEWcXcwsaHbYQ==", + "dev": true, + "requires": { + "ip": "^2.0.0", + "smart-buffer": "^4.2.0" + } + }, + "socks-proxy-agent": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-7.0.0.tgz", + "integrity": "sha512-Fgl0YPZ902wEsAyiQ+idGd1A7rSFx/ayC1CQVMw5P+EQx2V0SgpGtf6OKFhVjPflPUl9YMmEOnmfjCdMUsygww==", + "dev": true, + "requires": { + "agent-base": "^6.0.2", + "debug": "^4.3.3", + "socks": "^2.6.2" + } + }, + "source-map": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", + "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==", + "dev": true + }, + "source-map-js": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", + "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", + "dev": true + }, + "source-map-loader": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/source-map-loader/-/source-map-loader-4.0.1.tgz", + "integrity": "sha512-oqXpzDIByKONVY8g1NUPOTQhe0UTU5bWUl32GSkqK2LjJj0HmwTMVKxcUip0RgAYhY1mqgOxjbQM48a0mmeNfA==", + "dev": true, + "requires": { + "abab": "^2.0.6", + "iconv-lite": "^0.6.3", + "source-map-js": "^1.0.2" + }, + "dependencies": { + "iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dev": true, + "requires": { + "safer-buffer": ">= 2.1.2 < 3.0.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" + }, + "dependencies": { + "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 + } + } + }, + "spdx-correct": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.2.0.tgz", + "integrity": "sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==", + "dev": true, + "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.13", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.13.tgz", + "integrity": "sha512-XkD+zwiqXHikFZm4AX/7JSCXA98U5Db4AFd5XUg/+9UNtnH75+Z9KxtpYiJZx36mUDVOwH83pl7yvCer6ewM3w==", + "dev": true + }, + "spdy": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/spdy/-/spdy-4.0.2.tgz", + "integrity": "sha512-r46gZQZQV+Kl9oItvl1JZZqJKGr+oEkB08A6BzkiR7593/7IbtuncXHd2YoYeTsG4157ZssMu9KYvUHLcjcDoA==", + "dev": true, + "requires": { + "debug": "^4.1.0", + "handle-thing": "^2.0.0", + "http-deceiver": "^1.2.7", + "select-hose": "^2.0.0", + "spdy-transport": "^3.0.0" + } + }, + "spdy-transport": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/spdy-transport/-/spdy-transport-3.0.0.tgz", + "integrity": "sha512-hsLVFE5SjA6TCisWeJXFKniGGOpBgMLmerfO2aCyCU5s7nJ/rpAepqmFifv/GCbSbueEeAJJnmSQ2rKC/g8Fcw==", + "dev": true, + "requires": { + "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" + } + }, + "sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "dev": true + }, + "ssri": { + "version": "10.0.4", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-10.0.4.tgz", + "integrity": "sha512-12+IR2CB2C28MMAw0Ncqwj5QbTcs0nGIhgJzYWzDkb21vWmfNI83KS4f3Ci6GI98WreIfG7o9UXp3C0qbpA8nQ==", + "dev": true, + "requires": { + "minipass": "^5.0.0" + }, + "dependencies": { + "minipass": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", + "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", + "dev": true + } + } + }, + "statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=", + "dev": true + }, + "streamroller": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/streamroller/-/streamroller-3.0.4.tgz", + "integrity": "sha512-GI9NzeD+D88UFuIlJkKNDH/IsuR+qIN7Qh8EsmhoRZr9bQoehTraRgwtLUkZbpcAw+hLPfHOypmppz8YyGK68w==", + "dev": true, + "requires": { + "date-format": "^4.0.4", + "debug": "^4.3.3", + "fs-extra": "^10.0.1" + } + }, + "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" + } + }, + "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" + } + }, + "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-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 + }, + "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==" + }, + "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==", + "requires": { + "has-flag": "^4.0.0" + } + }, + "supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true + }, + "symbol-observable": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-4.0.0.tgz", + "integrity": "sha512-b19dMThMV4HVFynSAM1++gBHAbk2Tc/osgLIBZMKsyqh34jb2e8Os7T6ZW/Bt3pJFdBTd2JwAnAAEQV7rSNvcQ==", + "dev": true + }, + "tapable": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", + "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", + "dev": true + }, + "tar": { + "version": "6.1.15", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.1.15.tgz", + "integrity": "sha512-/zKt9UyngnxIT/EAGYuxaMYgOIJiP81ab9ZfkILq4oNLPFX50qyYmu7jRj9qeXoxmJHjGlbH0+cm2uy1WCs10A==", + "dev": true, + "requires": { + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "minipass": "^5.0.0", + "minizlib": "^2.1.1", + "mkdirp": "^1.0.3", + "yallist": "^4.0.0" + }, + "dependencies": { + "fs-minipass": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", + "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", + "dev": true, + "requires": { + "minipass": "^3.0.0" + }, + "dependencies": { + "minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "requires": { + "yallist": "^4.0.0" + } + } + } + }, + "minipass": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", + "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", + "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 + } + } + }, + "terser": { + "version": "5.16.3", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.16.3.tgz", + "integrity": "sha512-v8wWLaS/xt3nE9dgKEWhNUFP6q4kngO5B8eYFUuebsu7Dw/UNAnpUod6UHo04jSSkv8TzKHjZDSd7EXdDQAl8Q==", + "dev": true, + "requires": { + "@jridgewell/source-map": "^0.3.2", + "acorn": "^8.5.0", + "commander": "^2.20.0", + "source-map-support": "~0.5.20" + } + }, + "terser-webpack-plugin": { + "version": "5.3.9", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.9.tgz", + "integrity": "sha512-ZuXsqE07EcggTWQjXUj+Aot/OMcD0bMKGgF63f7UxYcu5/AJF53aIpK1YoP5xR9l6s/Hy2b+t1AM0bLNPRuhwA==", + "dev": true, + "requires": { + "@jridgewell/trace-mapping": "^0.3.17", + "jest-worker": "^27.4.5", + "schema-utils": "^3.1.1", + "serialize-javascript": "^6.0.1", + "terser": "^5.16.8" + }, + "dependencies": { + "ajv-keywords": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", + "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", + "dev": true + }, + "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 + }, + "schema-utils": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.2.tgz", + "integrity": "sha512-pvjEHOgWc9OWA/f/DE3ohBWTD6EleVLf7iFUkoSwAxttdBhB9QUebQgxER2kWueOvRJXPHNnyrvvh9eZINB8Eg==", + "dev": true, + "requires": { + "@types/json-schema": "^7.0.8", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + }, + "dependencies": { + "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" + } + } + } + }, + "terser": { + "version": "5.17.6", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.17.6.tgz", + "integrity": "sha512-V8QHcs8YuyLkLHsJO5ucyff1ykrLVsR4dNnS//L5Y3NiSXpbK1J+WMVUs67eI0KTxs9JtHhgEQpXQVHlHI92DQ==", + "dev": true, + "requires": { + "@jridgewell/source-map": "^0.3.2", + "acorn": "^8.5.0", + "commander": "^2.20.0", + "source-map-support": "~0.5.20" + } + } + } + }, + "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" + } + }, + "text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=" + }, + "through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==", + "dev": true + }, + "thunky": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/thunky/-/thunky-1.1.0.tgz", + "integrity": "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==", + "dev": true + }, + "tmp": { + "version": "0.0.33", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", + "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", + "dev": true, + "requires": { + "os-tmpdir": "~1.0.2" + } + }, + "to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", + "dev": true + }, + "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" + } + }, + "toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "dev": true + }, + "tree-kill": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", + "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", + "dev": true + }, + "ts-node": { + "version": "10.7.0", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.7.0.tgz", + "integrity": "sha512-TbIGS4xgJoX2i3do417KSaep1uRAW/Lu+WAL2doDHC0D6ummjirVOXU5/7aiZotbQ5p1Zp9tP7U6cYhA0O7M8A==", + "dev": true, + "requires": { + "@cspotcode/source-map-support": "0.7.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.0", + "yn": "3.1.1" + } + }, + "tslib": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", + "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==" + }, + "tslint": { + "version": "6.1.3", + "resolved": "https://registry.npmjs.org/tslint/-/tslint-6.1.3.tgz", + "integrity": "sha512-IbR4nkT96EQOvKE2PW/djGz8iGNeJ4rF2mBfiYaR/nvUWYKJhLwimoJKgjIFEIDibBtOevj7BqCRL4oHeWWUCg==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "builtin-modules": "^1.1.1", + "chalk": "^2.3.0", + "commander": "^2.12.1", + "diff": "^4.0.1", + "glob": "^7.1.1", + "js-yaml": "^3.13.1", + "minimatch": "^3.0.4", + "mkdirp": "^0.5.3", + "resolve": "^1.3.2", + "semver": "^5.3.0", + "tslib": "^1.13.0", + "tsutils": "^2.29.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" + } + }, + "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" + } + }, + "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 + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "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" + } + }, + "mkdirp": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", + "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", + "dev": true, + "requires": { + "minimist": "^1.2.5" + } + }, + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + }, + "sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", + "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" + } + }, + "tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + } + } + }, + "tsutils": { + "version": "2.29.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-2.29.0.tgz", + "integrity": "sha512-g5JVHCIJwzfISaXpXE1qvNalca5Jwob6FjI4AoPlqMusJ6ftFE7IkkFoMhVLRgK+4Kx3gkzb8UZK5t5yTTvEmA==", + "dev": true, + "requires": { + "tslib": "^1.8.1" + }, + "dependencies": { + "tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + } + } + }, + "tuf-js": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/tuf-js/-/tuf-js-1.1.6.tgz", + "integrity": "sha512-CXwFVIsXGbVY4vFiWF7TJKWmlKJAT8TWkH4RmiohJRcDJInix++F0dznDmoVbtJNzZ8yLprKUG4YrDIhv3nBMg==", + "dev": true, + "requires": { + "@tufjs/models": "1.0.4", + "debug": "^4.3.4", + "make-fetch-happen": "^11.1.0" + }, + "dependencies": { + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, + "lru-cache": { + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", + "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "dev": true + }, + "make-fetch-happen": { + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-11.1.1.tgz", + "integrity": "sha512-rLWS7GCSTcEujjVBs2YqG7Y4643u8ucvCJeSRqiLYhesrDuzeuFIk37xREzAsfQaqzl8b9rNCE4m6J8tvX4Q8w==", + "dev": true, + "requires": { + "agentkeepalive": "^4.2.1", + "cacache": "^17.0.0", + "http-cache-semantics": "^4.1.1", + "http-proxy-agent": "^5.0.0", + "https-proxy-agent": "^5.0.0", + "is-lambda": "^1.0.1", + "lru-cache": "^7.7.1", + "minipass": "^5.0.0", + "minipass-fetch": "^3.0.0", + "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": "^10.0.0" + } + }, + "minipass": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", + "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", + "dev": true + }, + "minipass-fetch": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-3.0.3.tgz", + "integrity": "sha512-n5ITsTkDqYkYJZjcRWzZt9qnZKCT7nKCosJhHoj7S7zD+BP4jVbWs+odsniw5TA3E0sLomhTKOKjF86wf11PuQ==", + "dev": true, + "requires": { + "encoding": "^0.1.13", + "minipass": "^5.0.0", + "minipass-sized": "^1.0.3", + "minizlib": "^2.1.2" + } + }, + "negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "dev": true + } + } + }, + "type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "requires": { + "prelude-ls": "^1.2.1" + } + }, + "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 + }, + "type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "dev": true, + "requires": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + } + }, + "typed-assert": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/typed-assert/-/typed-assert-1.0.9.tgz", + "integrity": "sha512-KNNZtayBCtmnNmbo5mG47p1XsCyrx6iVqomjcZnec/1Y5GGARaxPs6r49RnSPeUP3YjNYiU9sQHAtY4BBvnZwg==", + "dev": true + }, + "typescript": { + "version": "4.8.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.8.2.tgz", + "integrity": "sha512-C0I1UsrrDHo2fYI5oaCGbSejwX4ch+9Y5jTQELvovfmFkK3HHSZJB8MSJcWLmCUBzQBchCrZ9rMRV6GuNrvGtw==", + "dev": true + }, + "ua-parser-js": { + "version": "0.7.33", + "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.33.tgz", + "integrity": "sha512-s8ax/CeZdK9R/56Sui0WM6y9OFREJarMRHqLB2EwkovemBxNQ+Bqu8GAsUnVcXKgphb++ghr/B2BZx4mahujPw==", + "dev": true + }, + "unicode-canonical-property-names-ecmascript": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz", + "integrity": "sha512-yY5PpDlfVIU5+y/BSCxAJRBIS1Zc2dDG3Ujq+sR0U+JjUevW2JhocOF+soROYDSaAezOzOKuyyixhD6mBknSmQ==", + "dev": true + }, + "unicode-match-property-ecmascript": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz", + "integrity": "sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==", + "dev": true, + "requires": { + "unicode-canonical-property-names-ecmascript": "^2.0.0", + "unicode-property-aliases-ecmascript": "^2.0.0" + } + }, + "unicode-match-property-value-ecmascript": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.1.0.tgz", + "integrity": "sha512-qxkjQt6qjg/mYscYMC0XKRn3Rh0wFPlfxB0xkt9CfyTvpX1Ra0+rAmdX2QyAobptSEvuy4RtpPRui6XkV+8wjA==", + "dev": true + }, + "unicode-property-aliases-ecmascript": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.1.0.tgz", + "integrity": "sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w==", + "dev": true + }, + "unique-filename": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-3.0.0.tgz", + "integrity": "sha512-afXhuC55wkAmZ0P18QsVE6kp8JaxrEokN2HGIoIVv2ijHQd419H0+6EigAFcIzXeMIkcIkNBpB3L/DXB3cTS/g==", + "dev": true, + "requires": { + "unique-slug": "^4.0.0" + } + }, + "unique-slug": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-4.0.0.tgz", + "integrity": "sha512-WrcA6AyEfqDX5bWige/4NQfPZMtASNVxdmWR76WESYQVAACSgWcR6e9i0mofqqBxYFtL4oAxPIptY73/0YE1DQ==", + "dev": true, + "requires": { + "imurmurhash": "^0.1.4" + } + }, + "universalify": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", + "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", + "dev": true + }, + "unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=", + "dev": true + }, + "update-browserslist-db": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.11.tgz", + "integrity": "sha512-dCwEFf0/oT85M1fHBg4F0jtLwJrutGoHSQXCh7u4o2t1drG+c0a9Flnqww6XUKSfQMPpJBRjU8d4RXB09qtvaA==", + "dev": true, + "requires": { + "escalade": "^3.1.1", + "picocolors": "^1.0.0" + } + }, + "uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "requires": { + "punycode": "^2.1.0" + } + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true + }, + "utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=", + "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 + }, + "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==" + }, + "v8-compile-cache-lib": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.0.tgz", + "integrity": "sha512-mpSYqfsFvASnSn5qMiwrr4VKfumbPyONLCOPmsR3A6pTY/r0+tSaVbgPWSAIuzbk3lCTa+FForeTiO+wBQGkjA==", + "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" + } + }, + "validate-npm-package-name": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-5.0.0.tgz", + "integrity": "sha512-YuKoXDAhBYxY7SfOKxHBDoSyENFeW5VvIIQp2TGQuit8gpK6MnWaQelBKxso72DoxTZfZdcP3W90LqpSkgPzLQ==", + "dev": true, + "requires": { + "builtins": "^5.0.0" + } + }, + "vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=" + }, + "void-elements": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-2.0.1.tgz", + "integrity": "sha1-wGavtYK7HLQSjWDqkjkulNXp2+w=", + "dev": true + }, + "watchpack": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz", + "integrity": "sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg==", + "dev": true, + "requires": { + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.1.2" + } + }, + "wbuf": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/wbuf/-/wbuf-1.7.3.tgz", + "integrity": "sha512-O84QOnr0icsbFGLS0O3bI5FswxzRr8/gHwWkDlQFskhSPryQXvrTMxjxGP4+iWYoauLoBvfDpkrOauZ+0iZpDA==", + "dev": true, + "requires": { + "minimalistic-assert": "^1.0.0" + } + }, + "wcwidth": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", + "integrity": "sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==", + "dev": true, + "requires": { + "defaults": "^1.0.3" + } + }, + "webpack": { + "version": "5.76.1", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.76.1.tgz", + "integrity": "sha512-4+YIK4Abzv8172/SGqObnUjaIHjLEuUasz9EwQj/9xmPPkYJy2Mh03Q/lJfSD3YLzbxy5FeTq5Uw0323Oh6SJQ==", + "dev": true, + "requires": { + "@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" + }, + "dependencies": { + "acorn": { + "version": "8.8.2", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz", + "integrity": "sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==", + "dev": true + }, + "ajv-keywords": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", + "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", + "dev": true + }, + "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 + }, + "schema-utils": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.2.tgz", + "integrity": "sha512-pvjEHOgWc9OWA/f/DE3ohBWTD6EleVLf7iFUkoSwAxttdBhB9QUebQgxER2kWueOvRJXPHNnyrvvh9eZINB8Eg==", + "dev": true, + "requires": { + "@types/json-schema": "^7.0.8", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + }, + "dependencies": { + "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" + } + } + } + } + } + }, + "webpack-dev-middleware": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-6.0.1.tgz", + "integrity": "sha512-PZPZ6jFinmqVPJZbisfggDiC+2EeGZ1ZByyMP5sOFJcPPWSexalISz+cvm+j+oYPT7FIJyxT76esjnw9DhE5sw==", + "dev": true, + "requires": { + "colorette": "^2.0.10", + "memfs": "^3.4.12", + "mime-types": "^2.1.31", + "range-parser": "^1.2.1", + "schema-utils": "^4.0.0" + } + }, + "webpack-dev-server": { + "version": "4.11.1", + "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-4.11.1.tgz", + "integrity": "sha512-lILVz9tAUy1zGFwieuaQtYiadImb5M3d+H+L1zDYalYoDl0cksAB1UNyuE5MMWJrG6zR1tXkCP2fitl7yoUJiw==", + "dev": true, + "requires": { + "@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" + }, + "dependencies": { + "webpack-dev-middleware": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-5.3.3.tgz", + "integrity": "sha512-hj5CYrY0bZLB+eTO+x/j67Pkrquiy7kWepMHmUMoPsmcUaeEnQJqFzHJOyxgWlq746/wUuA64p9ta34Kyb01pA==", + "dev": true, + "requires": { + "colorette": "^2.0.10", + "memfs": "^3.4.3", + "mime-types": "^2.1.31", + "range-parser": "^1.2.1", + "schema-utils": "^4.0.0" + } + }, + "ws": { + "version": "8.13.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.13.0.tgz", + "integrity": "sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==", + "dev": true + } + } + }, + "webpack-merge": { + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-5.8.0.tgz", + "integrity": "sha512-/SaI7xY0831XwP6kzuwhKWVKDP9t1QY1h65lAFLbZqMPIuYcD9QAW4u9STIbU9kaJbPBB/geU/gLr1wDjOhQ+Q==", + "dev": true, + "requires": { + "clone-deep": "^4.0.1", + "wildcard": "^2.0.0" + } + }, + "webpack-sources": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz", + "integrity": "sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==", + "dev": true + }, + "webpack-subresource-integrity": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/webpack-subresource-integrity/-/webpack-subresource-integrity-5.1.0.tgz", + "integrity": "sha512-sacXoX+xd8r4WKsy9MvH/q/vBtEHr86cpImXwyg74pFIpERKt6FmB8cXpeuh0ZLgclOlHI4Wcll7+R5L02xk9Q==", + "dev": true, + "requires": { + "typed-assert": "^1.0.8" + } + }, + "websocket-driver": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz", + "integrity": "sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg==", + "dev": true, + "requires": { + "http-parser-js": ">=0.5.1", + "safe-buffer": ">=5.1.0", + "websocket-extensions": ">=0.1.1" + } + }, + "websocket-extensions": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.4.tgz", + "integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==", + "dev": true + }, + "which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "requires": { + "isexe": "^2.0.0" + } + }, + "wide-align": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", + "integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==", + "dev": true, + "requires": { + "string-width": "^1.0.2 || 2 || 3 || 4" + } + }, + "wildcard": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/wildcard/-/wildcard-2.0.1.tgz", + "integrity": "sha512-CC1bOL87PIWSBhDcTrdeLo6eGT7mCFtrg0uIJtqJUFyK+eJnzl8A1niH56uu7KMa5XFrtiV+AQuHO3n7DsHnLQ==", + "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==" + }, + "wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "requires": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "dependencies": { + "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 + }, + "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 + }, + "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" + } + } + } + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" + }, + "ws": { + "version": "8.2.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.2.3.tgz", + "integrity": "sha512-wBuoj1BDpC6ZQ1B7DWQBYVLphPWkm8i9Y0/3YdHjHKHiohOJ1ws+3OccDWtH+PoC9DZD5WOTrJvNbWvjS6JWaA==" + }, + "xmlhttprequest-ssl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.0.0.tgz", + "integrity": "sha512-QKxVRxiRACQcVuQEYFsI1hhkrMlrXHPegbbd1yn9UHOmRxY+si12nQYzri3vbzt8VdTTRviqcKxcyllFas5z2A==" + }, + "y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true + }, + "yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true + }, + "yaml": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", + "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", + "dev": true + }, + "yargs": { + "version": "17.6.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.6.2.tgz", + "integrity": "sha512-1/9UrdHjDZc0eOU0HxOHoS78C69UD3JRMvzlJ7S79S2nTaWRA/whGCTV8o9e/N/1Va9YIV7Q4sOxD8VV4pCWOw==", + "dev": true, + "requires": { + "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" + } + }, + "yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true + }, + "yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "dev": true + }, + "zone.js": { + "version": "0.11.5", + "resolved": "https://registry.npmjs.org/zone.js/-/zone.js-0.11.5.tgz", + "integrity": "sha512-D1/7VxEuQ7xk6z/kAROe4SUbd9CzxY4zOwVGnGHerd/SgLIVU5f4esDzQUsOCeArn933BZfWMKydH7l7dPEp0g==", + "requires": { + "tslib": "^2.3.0" + } + } + } +} diff --git a/front/package.json b/front/package.json index f6042866..cfe72a26 100755 --- a/front/package.json +++ b/front/package.json @@ -12,76 +12,76 @@ }, "private": true, "dependencies": { - "@angular/animations": "13.1.1", - "@angular/cdk": "13.1.1", - "@angular/common": "13.1.1", - "@angular/compiler": "13.1.1", - "@angular/core": "13.1.1", - "@angular/forms": "13.1.1", - "@angular/http": "7.2.16", - "@angular/material": "13.1.1", - "@angular/platform-browser": "13.1.1", - "@angular/platform-browser-dynamic": "13.1.1", - "@angular/router": "13.1.1", - "@angular/service-worker": "^13.1.1", - "@ng-matero/extensions": "^12.2.5", - "@ngneat/until-destroy": "^9.0.0", + "@angular/animations": "^15.2.9", + "@angular/cdk": "15.2.9", + "@angular/compiler": "^15.2.9", + "@angular/forms": "^15.2.9", + "@angular/material": "15.2.9", + "@angular/platform-browser": "^15.2.9", + "@angular/platform-browser-dynamic": "^15.2.9", + "@angular/router": "^15.2.9", + "@angular/service-worker": "^15.2.9", + "@ng-matero/extensions": "^15.5.0", + "@ng-select/ng-select": "^10.0.3", + "@ngneat/until-destroy": "^8.0.4", + "@ngtools/webpack": "^15.2.8", "@ngx-translate/core": "^14.0.0", - "@ngx-translate/http-loader": "^7.0.0", - "@ngxs-labs/dispatch-decorator": "^3.1.0", + "@ngx-translate/http-loader": "^6.0.0", "@ngxs-labs/immer-adapter": "^3.0.5", - "@ngxs-labs/select-snapshot": "^3.0.0", - "@ngxs/form-plugin": "^3.7.3", - "@ngxs/storage-plugin": "3.7.3", - "@ngxs/store": "3.7.3", - "@ngxs/websocket-plugin": "3.7.3", + "@ngxs-labs/select-snapshot": "^4.0.0", + "@ngxs/devtools-plugin": "^3.7.6", + "@ngxs/form-plugin": "^3.7.6", + "@ngxs/storage-plugin": "^3.7.6", + "@ngxs/store": "^3.8.0", + "@perfectmemory/ngx-contextmenu": "^15.1.1", "@types/c3": "^0.7.8", - "angular-svg-round-progressbar": "^8.0.0", - "c3": "^0.7.20", - "classlist.js": "^1.1.20150312", - "compare-versions": "^4.1.2", - "core-js": "^3.20.2", - "date-fns": "^2.28.0", - "highcharts": "^9.3.2", - "highcharts-angular": "^3.0.0", - "immer": "^9.0.7", + "ajv-keywords": "^3.5.2", + "angular-svg-round-progressbar": "^10.0.0", + "compare-versions": "^3.6.0", + "core-js": "^3.21.1", + "cron-parser": "^4.3.0", + "d3": "^7.8.3", + "date-fns": "^2.22.1", + "eslint": "^8.20.0", + "highcharts": "^11.0.1", + "highcharts-angular": "^2.10.0", + "immer": "^9.0.12", + "install": "^0.13.0", "lodash.keyby": "^4.6.0", "memo-decorator": "^2.0.1", "nested-property": "^4.0.0", "ngx-amvara-toolbox": "0.0.17", - "ngx-clipboard": "^15.0.1", - "ngx-contextmenu": "^6.0.0", - "ngx-network-error": "^0.6.35", - "npm-check-updates": "^12.1.0", - "rxjs": "^7.5.1", - "socket.io": "^4.4.1", - "socket.io-client": "^4.4.1", - "tslib": "^2.3.1", - "web-animations-js": "^2.3.2", - "zone.js": "~0.11.4" + "ngx-clipboard": "^15.1.0", + "ngx-network-error": "^0.6.35-c", + "npm": "^9.6.7", + "socket.io": "^4.6.1", + "socket.io-client": "^4.5.3", + "tslib": "^2.3.0", + "zone.js": "^0.11.5" }, "devDependencies": { - "@angular-devkit/build-angular": "^13.1.2", - "@angular/cli": "^13.1.2", - "@angular/compiler-cli": "^13.1.1", - "@angular/language-service": "^13.1.1", - "@ngxs/devtools-plugin": "3.7.3", - "@types/jasmine": "^3.10.3", - "@types/jasminewd2": "^2.0.10", - "@types/jquery": "^3.5.11", - "@types/node": "^17.0.8", - "codelyzer": "^6.0.2", - "jasmine-core": "~4.0.0", + "@angular-devkit/build-angular": "^15.2.8", + "@angular/cli": "^15.2.8", + "@angular/common": "^15.2.9", + "@angular/compiler-cli": "^15.2.9", + "@angular/core": "^15.2.9", + "@angular/language-service": "^15.2.9", + "@types/jasmine": "^3.10.5", + "@types/jasminewd2": "^2.0.9", + "@types/jquery": "^3.5.14", + "@types/node": "^15.14.0", + "ajv": "^6.12.6", + "jasmine-core": "~3.8.0", "jasmine-spec-reporter": "~7.0.0", - "karma": "~6.3.9", - "karma-chrome-launcher": "~3.1.0", + "karma": "^6.3.17", + "karma-chrome-launcher": "^3.1.1", "karma-coverage-istanbul-reporter": "~3.0.3", - "karma-jasmine": "~4.0.1", - "karma-jasmine-html-reporter": "^1.7.0", - "protractor": "~7.0.0", - "snyk": "^1.818.0", - "ts-node": "^10.4.0", + "karma-jasmine": "^4.0.2", + "karma-jasmine-html-reporter": "^1.6.0", + "rxjs": "^7.5.5", + "snyk": "^1.1087.0", + "ts-node": "^10.7.0", "tslint": "~6.1.2", - "typescript": "^4.5.4" + "typescript": "^4.8.2" } } diff --git a/front/protractor.conf.js b/front/protractor.conf.js deleted file mode 100755 index 7ee3b5ee..00000000 --- a/front/protractor.conf.js +++ /dev/null @@ -1,28 +0,0 @@ -// Protractor configuration file, see link for more information -// https://github.com/angular/protractor/blob/master/lib/config.ts - -const { SpecReporter } = require('jasmine-spec-reporter'); - -exports.config = { - allScriptsTimeout: 11000, - specs: [ - './e2e/**/*.e2e-spec.ts' - ], - capabilities: { - 'browserName': 'chrome' - }, - directConnect: true, - baseUrl: 'http://localhost:4200/', - framework: 'jasmine', - jasmineNodeOpts: { - showColors: true, - defaultTimeoutInterval: 30000, - print: function() {} - }, - onPrepare() { - require('ts-node').register({ - project: 'e2e/tsconfig.e2e.json' - }); - jasmine.getEnv().addReporter(new SpecReporter({ spec: { displayStacktrace: true } })); - } -}; diff --git a/front/src/app/app.component.ts b/front/src/app/app.component.ts index c0bed277..22f7e908 100755 --- a/front/src/app/app.component.ts +++ b/front/src/app/app.component.ts @@ -7,13 +7,11 @@ import { SocketService } from '@services/socket.service'; import { interval, Observable, Subscription } from 'rxjs'; import { HttpClient } from '@angular/common/http'; import { catchError, retry, switchMap } from 'rxjs/operators'; -import { MatDialog } from '@angular/material/dialog'; +import { MatLegacyDialog as MatDialog } from '@angular/material/legacy-dialog'; import { CookiesExpiredDialog } from '@dialogs/cookies-expired/cookies-expired.component'; import { SelectSnapshot } from '@ngxs-labs/select-snapshot'; import { TourService } from '@services/tour.service'; import { WhatsNewService } from '@services/whats-new.service'; -import { CustomSelectors } from '@others/custom-selectors'; -import { Router } from '@angular/router'; @Component({ selector: 'cometa', @@ -33,6 +31,7 @@ export class CometaComponent implements OnInit { heartbeat: Subscription; + // Initialize translator service constructor( private translate: TranslateService, @@ -43,7 +42,6 @@ export class CometaComponent implements OnInit { private _dialog: MatDialog, private _tourService: TourService, private _whatsNew: WhatsNewService, - private _router: Router ) { this._socket.Init(); this.translate.setDefaultLang('en'); diff --git a/front/src/app/app.module.ts b/front/src/app/app.module.ts index f95efa12..1c461c99 100755 --- a/front/src/app/app.module.ts +++ b/front/src/app/app.module.ts @@ -14,6 +14,7 @@ import { HeaderComponent } from '@components/header/header.component'; import { ToursComponent } from '@components/tours/tours.component'; import { FooterComponent } from '@components/footer/footer.component'; import { EditFeature } from '@dialogs/edit-feature/edit-feature.component'; +import { DataDrivenExecution } from '@dialogs/data-driven-execution/data-driven-execution.component'; import { CookiesExpiredDialog } from '@dialogs/cookies-expired/cookies-expired.component'; import { WhatsNewDialog } from '@dialogs/whats-new/whats-new.component'; import { SureRemoveFeatureComponent } from '@dialogs/sure-remove-feature/sure-remove-feature.component'; @@ -27,20 +28,21 @@ import { LiveStepComponent } from '@dialogs/live-steps/live-step/live-step.compo import { ScreenshotComponent } from '@dialogs/screenshot/screenshot.component'; import { ScheduleHelp } from '@dialogs/edit-feature/schedule-help/schedule-help.component'; import { FeatureCreated } from '@dialogs/edit-feature/feature-created/feature-created.component'; +import { DataDrivenTestExecuted } from '@dialogs/data-driven-execution/data-driven-executed/data-driven-executed.component'; /* Plugins */ import { TranslateModule, TranslateLoader } from '@ngx-translate/core'; import { TranslateHttpLoader } from '@ngx-translate/http-loader'; import { ClipboardModule } from 'ngx-clipboard'; -import { ContextMenuModule } from 'ngx-contextmenu'; +import { ContextMenuModule } from '@perfectmemory/ngx-contextmenu'; import { NgxsModule } from '@ngxs/store'; -import { NgxsDispatchPluginModule } from '@ngxs-labs/dispatch-decorator'; import { NgxsSelectSnapshotModule } from '@ngxs-labs/select-snapshot'; import { JoyrideModule } from './plugins/ngx-joyride/joyride.module'; import { NgxNetworkErrorModule } from 'ngx-network-error'; /* Services */ import { ApiService } from '@services/api.service'; +import { DownloadService } from '@services/download.service'; import { PaymentsService } from '@services/payments.service'; import { SocketService } from '@services/socket.service'; import { ConfigService } from '@services/config.service'; @@ -48,6 +50,7 @@ import { TourService } from '@services/tour.service'; import { SharedActionsService } from '@services/shared-actions.service'; import { WhatsNewService } from '@services/whats-new.service'; import { Tours } from '@services/tours'; +import { CometaTitleStrategyService } from '@services/titles/cometa-title.service' /* Module */ import { SharedModule } from '@modules/shared.module'; @@ -83,7 +86,10 @@ import { environment } from '@environments/environment'; import { i18nMatPaginatorIntl } from '@services/paginator-intl'; -import { MatPaginatorIntl } from '@angular/material/paginator'; +import { MatLegacyPaginatorIntl as MatPaginatorIntl } from '@angular/material/legacy-paginator'; +import { MatLegacyTooltipDefaultOptions as MatTooltipDefaultOptions, MAT_LEGACY_TOOLTIP_DEFAULT_OPTIONS as MAT_TOOLTIP_DEFAULT_OPTIONS } from '@angular/material/legacy-tooltip'; +import { MatLegacyDialogRef as MatDialogRef, MAT_LEGACY_DIALOG_DATA as MAT_DIALOG_DATA } from '@angular/material/legacy-dialog'; +import { TitleStrategy } from '@angular/router'; /* Translate Loader */ export function createTranslateLoader(http: HttpClient) { @@ -164,6 +170,13 @@ export function getWebpSupport() { } } +export const customTooltipDefaults: MatTooltipDefaultOptions = { + showDelay: 0, + hideDelay: 0, + touchendHideDelay: 1500, + disableTooltipInteractivity: true +}; + export function getStripeApiKey() { const testDomains = [ /cometa-stage\.amvara\./, @@ -172,6 +185,7 @@ export function getStripeApiKey() { /co\.meta\.de/, /cometa-dev\.ddns\./ ]; + let STRIPE_TEST_KEY = STRIPE_PUBLIC_TEST_KEY; // Try to get also key from localStorage, useful for local testing purposes const STRIPE_PUBLIC_TEST_KEY_STORAGE = localStorage?.getItem('STRIPE_PUBLIC_TEST_KEY'); @@ -194,8 +208,10 @@ export function getStripeApiKey() { FooterComponent, ImportJSONComponent, EditFeature, + DataDrivenExecution, ScheduleHelp, FeatureCreated, + DataDrivenTestExecuted, SureRemoveFeatureComponent, AddStepComponent, LiveStepsComponent, @@ -247,9 +263,8 @@ export function getStripeApiKey() { suppressErrors: false } }), - NgxsDispatchPluginModule.forRoot(), NgxsSelectSnapshotModule.forRoot(), - ContextMenuModule.forRoot(), + ContextMenuModule, TranslateModule.forRoot({ loader: { provide: TranslateLoader, @@ -272,6 +287,7 @@ export function getStripeApiKey() { providers: [ ConfigService, ApiService, + DownloadService, PaymentsService, SocketService, ConfigService, @@ -319,7 +335,17 @@ export function getStripeApiKey() { { provide: MatPaginatorIntl, useClass: i18nMatPaginatorIntl - } + }, + + // provides default options for mat-tooltip, this will force tooltip to dissapear as soon as mouse pointer leaves hover zone + // careful, this can not be used with tooltips that are supposted to be copied, as user will not be able to copy it + { + provide: MAT_TOOLTIP_DEFAULT_OPTIONS, + useValue: customTooltipDefaults + }, + { provide: MAT_DIALOG_DATA, useValue: {} }, + { provide: MatDialogRef, useValue: {} }, + { provide: TitleStrategy, useClass:CometaTitleStrategyService } ], bootstrap: [CometaComponent] }) diff --git a/front/src/app/common/_breakpoints.scss b/front/src/app/common/_breakpoints.scss index 5d45eb64..fbbb982d 100755 --- a/front/src/app/common/_breakpoints.scss +++ b/front/src/app/common/_breakpoints.scss @@ -31,6 +31,18 @@ } } +@mixin maxHeight($point) { + @media (max-height: $point) { + @content; + } +} + +@mixin minHeight($point) { + @media (min-height: $point) { + @content; + } +} + @mixin pagination-mobile { @media (max-width: 440px) { @content; } } @@ -51,6 +63,15 @@ @mixin for-tablet-portrait-up { @media (min-width: 600px) { @content; } } + + @mixin for-tablet-portrait-up-plus30 { + @media (min-width: 630px) { @content; } + } + + @mixin for-super-large-desktop { + @media (min-width: 3300px) { @content; } + } + @mixin for-tablet-portrait-only { @media (min-width: 600px) and (max-width: 899px) { @content; } } diff --git a/front/src/app/common/_material-theme.scss b/front/src/app/common/_material-theme.scss index fe477d71..17cbcfa3 100755 --- a/front/src/app/common/_material-theme.scss +++ b/front/src/app/common/_material-theme.scss @@ -1,16 +1,21 @@ -@import '~@angular/material/theming'; -@include mat-core(); +@use '@angular/material' as mat; -$candy-app-primary: mat-palette($mat-blue, 800); -$candy-app-accent: mat-palette($mat-deep-purple, 900); +@include mat.core(); +@include mat.legacy-core(); -// The warn palette is optional (defaults to red). -$candy-app-warn: mat-palette($mat-red, 400); +$cometa-primary: mat.define-palette(mat.$blue-palette, 800); +$cometa-accent: mat.define-palette(mat.$purple-palette, 900); +$cometa-warn: mat.define-palette(mat.$red-palette, 400); -// Create the theme object (a Sass map containing all of the palettes). -$candy-app-theme: mat-light-theme($candy-app-primary, $candy-app-accent, $candy-app-warn); +$cometa-theme: mat.define-light-theme(( + color: ( + primary: $cometa-primary, + accent: $cometa-accent, + warn: $cometa-warn, + ), + typography: mat.define-typography-config(), + density: 0, +)); -// Include theme styles for core and each component used in your app. -// Alternatively, you can import and @include the theme mixins for each component -// that you are using. -@include angular-material-theme($candy-app-theme); \ No newline at end of file +@include mat.all-component-themes($cometa-theme); +@include mat.all-legacy-component-themes($cometa-theme); \ No newline at end of file diff --git a/front/src/app/components/about/about.component.ts b/front/src/app/components/about/about.component.ts index 0e0591c9..af0b9283 100755 --- a/front/src/app/components/about/about.component.ts +++ b/front/src/app/components/about/about.component.ts @@ -1,17 +1,10 @@ import { Component, ChangeDetectionStrategy } from '@angular/core'; -import { TranslateService } from '@ngx-translate/core'; -import { MatSnackBar } from '@angular/material/snack-bar'; import { ConfigState } from '@store/config.state'; import { ViewSelectSnapshot } from '@ngxs-labs/select-snapshot'; -import { MatCheckboxChange } from '@angular/material/checkbox'; -import { Dispatch } from '@ngxs-labs/dispatch-decorator'; -import { Configuration } from '@actions/config.actions'; import { SocketService } from '@services/socket.service'; import { Select } from '@ngxs/store'; import { CustomSelectors } from '@others/custom-selectors'; import { Observable } from 'rxjs'; -import { Router } from '@angular/router'; -import { Features } from '@store/actions/features.actions'; @Component({ selector: 'cometa-about', @@ -25,9 +18,7 @@ export class AboutComponent { @ViewSelectSnapshot(ConfigState) config$ !: Config; constructor( - private _snack: MatSnackBar, public _socketService: SocketService, - private _router: Router ) { } licenses = [ diff --git a/front/src/app/components/admin-wrapper/admin-wrapper.component.scss b/front/src/app/components/admin-wrapper/admin-wrapper.component.scss index 01c3de3f..87482b11 100755 --- a/front/src/app/components/admin-wrapper/admin-wrapper.component.scss +++ b/front/src/app/components/admin-wrapper/admin-wrapper.component.scss @@ -14,6 +14,7 @@ } overflow-x: hidden; max-height: calc(100vh - var(--header-height)); + height: calc(100vh - var(--header-height)); } :host::ng-deep .mat-tab-link { diff --git a/front/src/app/components/admin/accounts/account/account.component.html b/front/src/app/components/admin/accounts/account/account.component.html index 242d70b7..24c79687 100755 --- a/front/src/app/components/admin/accounts/account/account.component.html +++ b/front/src/app/components/admin/accounts/account/account.component.html @@ -10,7 +10,7 @@
-
+
diff --git a/front/src/app/components/admin/accounts/account/account.component.ts b/front/src/app/components/admin/accounts/account/account.component.ts index a5ceccf1..e73d537b 100755 --- a/front/src/app/components/admin/accounts/account/account.component.ts +++ b/front/src/app/components/admin/accounts/account/account.component.ts @@ -1,7 +1,7 @@ import { Component, Input, ChangeDetectionStrategy } from '@angular/core'; import { ApiService } from '@services/api.service'; -import { MatDialog } from '@angular/material/dialog'; -import { MatSnackBar } from '@angular/material/snack-bar'; +import { MatLegacyDialog as MatDialog } from '@angular/material/legacy-dialog'; +import { MatLegacySnackBar as MatSnackBar } from '@angular/material/legacy-snack-bar'; import { ModifyUserComponent } from '@dialogs/modify-user/modify-user.component'; import { ModifyPasswordComponent } from '@dialogs/modify-password/modify-password.component'; import { Store, Select } from '@ngxs/store'; @@ -31,6 +31,7 @@ export class AccountComponent { ) { } @Input() account: IAccount; + @Input() origin: string; changePassword() { this._dialog.open(ModifyPasswordComponent, { diff --git a/front/src/app/components/admin/accounts/accounts.component.html b/front/src/app/components/admin/accounts/accounts.component.html index d769e3af..2540feab 100755 --- a/front/src/app/components/admin/accounts/accounts.component.html +++ b/front/src/app/components/admin/accounts/accounts.component.html @@ -3,14 +3,16 @@

Manage accounts

Search user - - - - - No accounts found with this term. - \ No newline at end of file +
+ + + + + No accounts found with this term. + +
\ No newline at end of file diff --git a/front/src/app/components/admin/accounts/accounts.component.scss b/front/src/app/components/admin/accounts/accounts.component.scss index 7e90e2ed..aeaea311 100755 --- a/front/src/app/components/admin/accounts/accounts.component.scss +++ b/front/src/app/components/admin/accounts/accounts.component.scss @@ -7,12 +7,18 @@ h2 { :host { display: block; margin-bottom: 50px; + height: calc(100% - 158px); } :host::ng-deep .mat-form-field { width: 100%; } +.data{ + height: 100%; + overflow: hidden; +} + account:nth-child(2n + 2) { background-color: rgba(0,0,0,.05); border-radius: 5px; diff --git a/front/src/app/components/admin/accounts/accounts.component.ts b/front/src/app/components/admin/accounts/accounts.component.ts index 69498077..e7151687 100755 --- a/front/src/app/components/admin/accounts/accounts.component.ts +++ b/front/src/app/components/admin/accounts/accounts.component.ts @@ -1,6 +1,6 @@ import { Component, OnInit, ChangeDetectionStrategy } from '@angular/core'; import { Observable, timer } from 'rxjs'; -import { FormControl } from '@angular/forms'; +import { UntypedFormControl } from '@angular/forms'; import { debounce, map, startWith } from 'rxjs/operators'; @Component({ @@ -13,7 +13,7 @@ export class AccountsComponent implements OnInit { accountsUrl$: Observable; - search = new FormControl(''); + search = new UntypedFormControl(''); ngOnInit() { this.accountsUrl$ = this.search.valueChanges.pipe( diff --git a/front/src/app/components/admin/applications/application/application.component.ts b/front/src/app/components/admin/applications/application/application.component.ts index 0c54ace8..e2af2f53 100755 --- a/front/src/app/components/admin/applications/application/application.component.ts +++ b/front/src/app/components/admin/applications/application/application.component.ts @@ -1,7 +1,7 @@ import { Component, Input, ChangeDetectionStrategy } from '@angular/core'; import { ApiService } from '@services/api.service'; -import { MatDialog } from '@angular/material/dialog'; -import { MatSnackBar } from '@angular/material/snack-bar'; +import { MatLegacyDialog as MatDialog } from '@angular/material/legacy-dialog'; +import { MatLegacySnackBar as MatSnackBar } from '@angular/material/legacy-snack-bar'; import { Store, Select } from '@ngxs/store'; import { UserState } from '@store/user.state'; import { BehaviorSubject, Observable } from 'rxjs'; diff --git a/front/src/app/components/admin/applications/applications.component.ts b/front/src/app/components/admin/applications/applications.component.ts index 89d5901b..61995620 100755 --- a/front/src/app/components/admin/applications/applications.component.ts +++ b/front/src/app/components/admin/applications/applications.component.ts @@ -1,14 +1,13 @@ import { Component, OnInit, ChangeDetectionStrategy } from '@angular/core'; import { EnterValueComponent } from '@dialogs/enter-value/enter-value.component'; import { ApiService } from '@services/api.service'; -import { MatDialog } from '@angular/material/dialog'; +import { MatLegacyDialog as MatDialog } from '@angular/material/legacy-dialog'; import { Select, Store } from '@ngxs/store'; import { ApplicationsState } from '@store/applications.state'; import { UserState } from '@store/user.state'; import { Subscribe } from 'app/custom-decorators'; import { filter, map, switchMap } from 'rxjs/operators'; import { Observable } from 'rxjs'; -import { Dispatch } from '@ngxs-labs/dispatch-decorator'; import { Applications } from '@store/actions/applications.actions'; @Component({ @@ -29,9 +28,8 @@ export class ApplicationsComponent implements OnInit { @Select(ApplicationsState) applications$: Observable; - @Dispatch() ngOnInit() { - return new Applications.GetApplications(); + return this._store.dispatch(new Applications.GetApplications()); } trackByFn(index, app: Application) { diff --git a/front/src/app/components/admin/departments/department/department.component.html b/front/src/app/components/admin/departments/department/department.component.html index c043b477..35780663 100755 --- a/front/src/app/components/admin/departments/department/department.component.html +++ b/front/src/app/components/admin/departments/department/department.component.html @@ -2,5 +2,15 @@
+
+ people + {{department.users.length}} +
+ + +
+ access_time +
+
\ No newline at end of file diff --git a/front/src/app/components/admin/departments/department/department.component.scss b/front/src/app/components/admin/departments/department/department.component.scss index 981621b1..475b9bed 100755 --- a/front/src/app/components/admin/departments/department/department.component.scss +++ b/front/src/app/components/admin/departments/department/department.component.scss @@ -112,4 +112,54 @@ background-size: contain; background-repeat: no-repeat; } +} + +.timeout { + opacity: .6; + display: flex; + width: 40px; + align-items: center; + justify-content: center; + transition: transform .2s ease-in-out; + + // clock icon + mat-icon { + padding: 0 10px; + height: 20px; + cursor: pointer; + } + + &:not(.disabled):hover { + transform: scale(1.2); + } +} + +.users { + position: relative; + flex: 0 0 40px; + padding: 0 10px; + box-sizing: border-box; + position: relative; + cursor: pointer; + height: 15px; + width: 15px; + opacity: .6; + margin-top: 5px; + transition: all .2s ease-in-out; + &:not(.disabled):hover { + margin-top: 3px; + transform: scale(1.2); + } + .amount { + position: absolute; + bottom: -15px; + left: 50%; + transform: translateX(-50%); + font-size: 11px; + color: #000; + } + &.disabled { + cursor: default; + opacity: 0.2; + } } \ No newline at end of file diff --git a/front/src/app/components/admin/departments/department/department.component.ts b/front/src/app/components/admin/departments/department/department.component.ts index aba19214..87850055 100755 --- a/front/src/app/components/admin/departments/department/department.component.ts +++ b/front/src/app/components/admin/departments/department/department.component.ts @@ -1,13 +1,15 @@ import { Component, Input, ChangeDetectionStrategy } from '@angular/core'; import { ApiService } from '@services/api.service'; -import { MatDialog } from '@angular/material/dialog'; -import { MatSnackBar } from '@angular/material/snack-bar'; +import { MatLegacyDialog as MatDialog } from '@angular/material/legacy-dialog'; +import { MatLegacySnackBar as MatSnackBar } from '@angular/material/legacy-snack-bar'; import { Store, Select } from '@ngxs/store'; import { UserState } from '@store/user.state'; import { BehaviorSubject, Observable } from 'rxjs'; import { ModifyDepartmentComponent } from '@dialogs/modify-department/modify-department.component'; +import { ModifyDepartmentTimeoutComponent } from '@dialogs/modify-department-timeout/modify-department-timeout.component'; import { Departments } from '@store/actions/departments.actions'; import { AreYouSureData, AreYouSureDialog } from '@dialogs/are-you-sure/are-you-sure.component'; +import { AccountsDialog, AccountsDialogData } from '@dialogs/accounts-dialog/accounts-dialog.component'; @Component({ selector: 'department', @@ -19,6 +21,7 @@ export class DepartmentComponent { @Select(UserState.GetPermission('edit_department')) canEditDepartment$: Observable; @Select(UserState.GetPermission('delete_department')) canDeleteDepartment$: Observable; + @Select(UserState.GetPermission('show_department_users')) canSeeUsers$: Observable; constructor( private _api: ApiService, @@ -33,10 +36,22 @@ export class DepartmentComponent { saveOrEdit() { this._dialog.open(ModifyDepartmentComponent, { - data: this.department.department_id + data: this.department.department_id, + panelClass: 'modify-department-panel' }); } + showUsers() { + if (this.department.users.length < 1) return; + this._dialog.open(AccountsDialog, { + data: { + users: this.department.users, + department_name: this.department.department_name + } as AccountsDialogData, + width: "90%" + }) + } + removeIt() { this._dialog.open(AreYouSureDialog, { data: { @@ -53,4 +68,11 @@ export class DepartmentComponent { }); } + // open modify department timeout dialog + onModifyTimeoutClick() { + this._dialog.open(ModifyDepartmentTimeoutComponent, { + data: this.department.department_id, + panelClass: 'modify-department-timeout-panel' + }); + } } diff --git a/front/src/app/components/admin/departments/departments.component.ts b/front/src/app/components/admin/departments/departments.component.ts index 4f777db4..36eba9c2 100755 --- a/front/src/app/components/admin/departments/departments.component.ts +++ b/front/src/app/components/admin/departments/departments.component.ts @@ -1,6 +1,6 @@ import { Component, OnInit, ChangeDetectionStrategy } from '@angular/core'; import { ApiService } from '@services/api.service'; -import { MatDialog } from '@angular/material/dialog'; +import { MatLegacyDialog as MatDialog } from '@angular/material/legacy-dialog'; import { EnterValueComponent } from '@dialogs/enter-value/enter-value.component'; import { Select, Store } from '@ngxs/store'; import { DepartmentsState } from '@store/departments.state'; @@ -8,7 +8,6 @@ import { UserState } from '@store/user.state'; import { Subscribe } from 'app/custom-decorators'; import { filter, map, switchMap } from 'rxjs/operators'; import { Observable } from 'rxjs'; -import { Dispatch } from '@ngxs-labs/dispatch-decorator'; import { Departments } from '@store/actions/departments.actions'; @Component({ @@ -28,9 +27,8 @@ export class DepartmentsComponent implements OnInit { @Select(UserState.GetPermission('create_department')) canCreateDepartment$: Observable; @Select(DepartmentsState) departments$: Observable; - @Dispatch() ngOnInit() { - return new Departments.GetAdminDepartments(); + return this._store.dispatch(new Departments.GetAdminDepartments()); } trackByFn(index, item: Department) { diff --git a/front/src/app/components/admin/environments/environment/environment.component.ts b/front/src/app/components/admin/environments/environment/environment.component.ts index cba9aeb7..83398fc8 100755 --- a/front/src/app/components/admin/environments/environment/environment.component.ts +++ b/front/src/app/components/admin/environments/environment/environment.component.ts @@ -1,7 +1,7 @@ import { Component, Input, ChangeDetectionStrategy } from '@angular/core'; import { ApiService } from '@services/api.service'; -import { MatDialog } from '@angular/material/dialog'; -import { MatSnackBar } from '@angular/material/snack-bar'; +import { MatLegacyDialog as MatDialog } from '@angular/material/legacy-dialog'; +import { MatLegacySnackBar as MatSnackBar } from '@angular/material/legacy-snack-bar'; import { Select, Store } from '@ngxs/store'; import { UserState } from '@store/user.state'; import { BehaviorSubject, Observable } from 'rxjs'; diff --git a/front/src/app/components/admin/environments/environments.component.ts b/front/src/app/components/admin/environments/environments.component.ts index 9741dead..93b23060 100755 --- a/front/src/app/components/admin/environments/environments.component.ts +++ b/front/src/app/components/admin/environments/environments.component.ts @@ -1,6 +1,6 @@ import { Component, OnInit, ChangeDetectionStrategy } from '@angular/core'; import { ApiService } from '@services/api.service'; -import { MatDialog } from '@angular/material/dialog'; +import { MatLegacyDialog as MatDialog } from '@angular/material/legacy-dialog'; import { EnterValueComponent } from '@dialogs/enter-value/enter-value.component'; import { Select, Store } from '@ngxs/store'; import { EnvironmentsState } from '@store/environments.state'; @@ -8,7 +8,6 @@ import { UserState } from '@store/user.state'; import { Subscribe } from 'app/custom-decorators'; import { filter, map, switchMap } from 'rxjs/operators'; import { Observable } from 'rxjs'; -import { Dispatch } from '@ngxs-labs/dispatch-decorator'; import { Environments } from '@store/actions/environments.actions'; @Component({ @@ -32,9 +31,8 @@ export class EnvironmentsComponent implements OnInit { @Select(UserState.GetPermission('create_environment')) canCreateEnvironment$: Observable; @Select(EnvironmentsState) environments$: Observable; - @Dispatch() ngOnInit() { - return new Environments.GetEnvironments(); + return this._store.dispatch(new Environments.GetEnvironments()); } @Subscribe() diff --git a/front/src/app/components/admin/features/feature/feature.component.ts b/front/src/app/components/admin/features/feature/feature.component.ts index 2c2566c6..96240e5a 100755 --- a/front/src/app/components/admin/features/feature/feature.component.ts +++ b/front/src/app/components/admin/features/feature/feature.component.ts @@ -1,7 +1,7 @@ import { Component, Input, ChangeDetectionStrategy, OnInit, Output, EventEmitter, Host } from '@angular/core'; import { ApiService } from '@services/api.service'; -import { MatDialog } from '@angular/material/dialog'; -import { MatSnackBar } from '@angular/material/snack-bar'; +import { MatLegacyDialog as MatDialog } from '@angular/material/legacy-dialog'; +import { MatLegacySnackBar as MatSnackBar } from '@angular/material/legacy-snack-bar'; import { Store } from '@ngxs/store'; import { EditFeature } from '@dialogs/edit-feature/edit-feature.component'; import { map } from 'rxjs/operators'; diff --git a/front/src/app/components/admin/features/features.component.ts b/front/src/app/components/admin/features/features.component.ts index 68c749e1..32cbd163 100755 --- a/front/src/app/components/admin/features/features.component.ts +++ b/front/src/app/components/admin/features/features.component.ts @@ -3,13 +3,13 @@ import { FeaturesState } from '@store/features.state'; import { UserState } from '@store/user.state'; import { ApiService } from '@services/api.service'; import { map, concatMap, finalize } from 'rxjs/operators'; -import { MatSnackBar } from '@angular/material/snack-bar'; +import { MatLegacySnackBar as MatSnackBar } from '@angular/material/legacy-snack-bar'; import { BehaviorSubject, from } from 'rxjs'; -import { Dispatch } from '@ngxs-labs/dispatch-decorator'; import { ViewSelectSnapshot } from '@ngxs-labs/select-snapshot'; import { exportToJSONFile } from 'ngx-amvara-toolbox'; import { Features } from '@store/actions/features.actions'; import { SharedActionsService } from '@services/shared-actions.service'; +import { Store } from '@ngxs/store'; @Component({ selector: 'admin-features', @@ -25,16 +25,16 @@ export class FeaturesComponent implements OnInit { constructor( private _api: ApiService, private _snack: MatSnackBar, - private _actionsService: SharedActionsService + private _actionsService: SharedActionsService, + private _store: Store ) { } trackByFn(index, item: Feature) { return item.feature_id; } - @Dispatch() ngOnInit() { - return new Features.GetFeatures(); + return this._store.dispatch(new Features.GetFeatures()); } new() { diff --git a/front/src/app/components/behave-charts/behave-chart.component.scss b/front/src/app/components/behave-charts/behave-chart.component.scss index 648b35d9..3d2c59c6 100755 --- a/front/src/app/components/behave-charts/behave-chart.component.scss +++ b/front/src/app/components/behave-charts/behave-chart.component.scss @@ -9,6 +9,8 @@ padding: 0 10px; } -:host::ng-deep .highcharts-range-selector-buttons { - transform: translate(20px, 0); -} \ No newline at end of file +// overwrites highchart selection buttons vertical align, prevent it to be aligned at the right side +// we need to position on the right side, because by default it is positioned on the left, thus overlapping nav back to main button +// :host::ng-deep .highcharts-range-selector-buttons { +// transform: translate(20px, 0); +// } \ No newline at end of file diff --git a/front/src/app/components/behave-charts/behave-chart.component.ts b/front/src/app/components/behave-charts/behave-chart.component.ts index 9792450c..fcc2459d 100755 --- a/front/src/app/components/behave-charts/behave-chart.component.ts +++ b/front/src/app/components/behave-charts/behave-chart.component.ts @@ -19,7 +19,7 @@ export class BehaveChartTestComponent implements OnChanges, OnInit, AfterViewIni Highcharts: typeof Highcharts = Highcharts; - @Input() data: FeatureRun[] = []; + @Input() data: FeatureResult[] = []; chart$: Observable; updateFlag = false; @@ -73,9 +73,9 @@ export class BehaveChartTestComponent implements OnChanges, OnInit, AfterViewIni this.data$.next((changes?.data.currentValue || [])); } - getSeriesData(data: FeatureRun[]) { + getSeriesData(data: FeatureResult[]) { // Sort data by date time - data = [...data].sort((a, b) => this._amParse.transform(a.date_time).getTime() - this._amParse.transform(b.date_time).getTime()); + data = [...data].sort((a, b) => this._amParse.transform(a.result_date).getTime() - this._amParse.transform(b.result_date).getTime()); // Initialize array values of series const pixelArray = []; const timeArray = []; @@ -84,19 +84,19 @@ export class BehaveChartTestComponent implements OnChanges, OnInit, AfterViewIni // Iterate each run and push corresponding fields to each array for (const run of data) { pixelArray.push([ - this._amParse.transform(run.date_time).getTime(), + this._amParse.transform(run.result_date).getTime(), run.pixel_diff ]); timeArray.push([ - this._amParse.transform(run.date_time).getTime(), - run.execution_time / 1000 + this._amParse.transform(run.result_date).getTime(), + Number(run.execution_time) / 1000 ]); okArray.push([ - this._amParse.transform(run.date_time).getTime(), + this._amParse.transform(run.result_date).getTime(), run.ok ]); nokArray.push([ - this._amParse.transform(run.date_time).getTime(), + this._amParse.transform(run.result_date).getTime(), run.fails ]); }; diff --git a/front/src/app/components/behave-charts/chart-schema.ts b/front/src/app/components/behave-charts/chart-schema.ts index 52d999c3..b30583f6 100644 --- a/front/src/app/components/behave-charts/chart-schema.ts +++ b/front/src/app/components/behave-charts/chart-schema.ts @@ -5,6 +5,13 @@ export const MAIN_VIEW_CHART_SCHEMA: Options = { xAxis: {} }, rangeSelector: { + floating: false, + buttonPosition: { + align: 'right', + y: 0, + x: 0 + }, + selected: 5, inputEnabled: false, buttons: [{ diff --git a/front/src/app/components/browser-selection/browser-selection.component.ts b/front/src/app/components/browser-selection/browser-selection.component.ts index 772812f0..ce1b2ffc 100644 --- a/front/src/app/components/browser-selection/browser-selection.component.ts +++ b/front/src/app/components/browser-selection/browser-selection.component.ts @@ -1,5 +1,5 @@ import { Component, ChangeDetectionStrategy, Output, EventEmitter, OnInit, Input } from '@angular/core'; -import { FormControl } from '@angular/forms'; +import { UntypedFormControl } from '@angular/forms'; import { BrowserFavouritedPipe } from '@pipes/browser-favourited.pipe'; import { BrowserstackState } from '@store/browserstack.state'; import { UserState } from '@store/user.state'; @@ -7,11 +7,11 @@ import { PlatformSortPipe } from '@pipes/platform-sort.pipe'; import { map } from 'rxjs/operators'; import { BrowsersState } from '@store/browsers.state'; import { BehaviorSubject } from 'rxjs'; -import { Dispatch } from '@ngxs-labs/dispatch-decorator'; import { ViewSelectSnapshot } from '@ngxs-labs/select-snapshot'; import { classifyByProperty } from 'ngx-amvara-toolbox'; import { User } from '@store/actions/user.actions'; import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy'; +import { Store } from '@ngxs/store'; /** * BrowserSelectionComponent @@ -48,11 +48,12 @@ export class BrowserSelectionComponent implements OnInit { constructor( private _favouritePipe: BrowserFavouritedPipe, - private _platformSort: PlatformSortPipe + private _platformSort: PlatformSortPipe, + private _store: Store ) { } - testing_cloud = new FormControl('browserstack'); - browser = new FormControl(); + testing_cloud = new UntypedFormControl('browserstack'); + browser = new UntypedFormControl(); // Used for the loading screen loaded = new BehaviorSubject(false); @@ -124,21 +125,15 @@ export class BrowserSelectionComponent implements OnInit { document.querySelector(`.versions.${browserKey}`).classList.toggle('show_all'); } - @Dispatch() toggleFavourite(browser: BrowserstackBrowser) { - if (this._favouritePipe.transform(browser, this.favourites$)) { - // Remove favourite - return new User.RemoveBrowserFavourite(browser); - } else { - // Add favourite - return new User.AddBrowserFavourite(browser); - } + return this._favouritePipe.transform(browser, this.favourites$) ? + this._store.dispatch(new User.RemoveBrowserFavourite(browser)) : + this._store.dispatch(new User.AddBrowserFavourite(browser)); } - @Dispatch() deleteFavourite(fav: BrowserstackBrowser) { // Remove favourite - return new User.RemoveBrowserFavourite(fav); + return this._store.dispatch(new User.RemoveBrowserFavourite(fav)); } clickOnCategory(key, ev: MouseEvent) { diff --git a/front/src/app/components/data-driven-runs/data-driven-results/data-driven-results.component.html b/front/src/app/components/data-driven-runs/data-driven-results/data-driven-results.component.html new file mode 100755 index 00000000..10c9c210 --- /dev/null +++ b/front/src/app/components/data-driven-runs/data-driven-results/data-driven-results.component.html @@ -0,0 +1,143 @@ +
+
+
+ +
Main
+
+
+ + +
+
+ + +
+
+ +
+ shown when there is no charts +

This screen shows the results of your testruns.

Once you have more then 10 results, co.meta will show you a beautiful linechart with execution times and more.

+
+ +
+
Steps #
+
Browsers
+
Last test
+
+ +
+
{{ lastRun.total}}
+
{{ 1 }}
+
{{ passed ? 'OK' : 'NOK' }}
+
+
+
+ + + + + + +
+ +
+ Passed
+ +
Failed
+
+ + + + + + + + +
+
+ + + {{ row.result_date | amParse | amDateFormat:'MMMM d yyyy, HH:mm' }} + + + + {{ row.execution_time | secondsToHumanReadable }} + + + + {{ row.pixel_diff | pixelDifference }} + + + + + + + + +
+ Show archived items + +
+ + + +
Clear results
+ + + + + +
Options
+ + +
+
+ + + +
+

Please execute your first feature clicking the blue run-button.

+
+

If you just added a feature and it's executing now you will have to wait until it's finished.

+
+

These results are reloaded automatically.

+
+
\ No newline at end of file diff --git a/front/src/app/components/data-driven-runs/data-driven-results/data-driven-results.component.scss b/front/src/app/components/data-driven-runs/data-driven-results/data-driven-results.component.scss new file mode 100755 index 00000000..7550eab8 --- /dev/null +++ b/front/src/app/components/data-driven-runs/data-driven-results/data-driven-results.component.scss @@ -0,0 +1,445 @@ + +@import "color"; +@import "breakpoints"; + +:host { + display: flex; + flex-flow: column; + width: 100%; + overflow-y: auto; + max-height: calc(100vh - var(--header-height)); + height: calc(100vh - var(--header-height)); +} + +:host::ng-deep { + .mtx-grid{ + z-index: 0; + .mtx-grid-toolbar-content { + flex-grow: 1; + .custom_toolbar { + display: flex; + justify-content: space-between; + align-items: center; + margin-right: 5px; + } + button { + color: #000; + mat-icon { + font-size: 1.125rem; + width: 1.125rem; + height: 1.125rem; + margin-left: -4px; + margin-right: 8px; + display: inline-block; + } + } + } + table { + border-radius: 5px; + background-color: $table-background-opaque; + border-spacing: 0; + box-shadow: 0 3px 1px -2px $table-low-shadow, 0 2px 2px 0 $table-medium-shadow, 0 1px 5px 0 $table-high-shadow; + + thead { + background-color: #474747; + [role="columnheader"] { + span { + text-transform: uppercase; + font-weight: 400; + font-size: 16px; + color: #fff; + } + + svg { + display: none; + } + } + + .mat-sort-header-arrow div:not(.mat-sort-header-indicator) { + background-color: #fff; + } + } + + tbody tr { + height: 45px; + cursor: pointer; + &:nth-child(odd) { + background-color: $low-white; + } + &:nth-child(even) { + background-color: #f2f2f2; + } + &:hover { + background-color: #cecece; + } + &.selected { + background-color: $selected-black-opaque; + td { + &.name { + color: $blue; + } + } + } + } + + td { + font-weight: bold; + color: $dark; + font-family: 'CorpoS, sans-serif'; + font-size: 16px; + } + + // th .mat-header-cell-inner { + // justify-content: center; + // } + + // .aligned-center { + // text-align: center; + // } + // .aligned-right { + // text-align: right; + // } + + .mdc-icon-button { + color: #0000008a; + + &:hover { + color: var(--mdc-icon-button-icon-color, inherit); + } + } + } + thead .mat-table-sticky-right { + border-left-color: rgba(255,255,255,.2); + } + tbody .mat-table-sticky-right { + border-left-color: rgba(0,0,0,.2); + } + .mat-mdc-paginator-container { + background-color: $body-bg-color; + } + + .browser-icon { + width: 100%; + height: 20px; + display: block; + background-size: 20px; + background-repeat: no-repeat; + // background-position: center; + position: relative; + } + } +} + +.no-chart { + max-height: 400px; + padding: 10px 5%; + display: flex; + justify-content: center; + align-items: center; + img { + width: 400px; + max-width: 500px; + height: auto; + } + + p { + font-size: 3vw; + padding: 0 5%; + @include for-tablet-portrait-up { + font-size: 2vw; + } + @include for-super-large-desktop { + font-size: 1.5vw; + } + } +} + +.charts { + display: block; + width: 100%; + height: 340px; + .behave-charts { + height: 100%; + padding: 15px 0 0; + margin: 0 auto; + display: flex; + box-sizing: border-box; + text-align: center; + // @include maxWidth(700px) { + // padding: 40px 0 0; + // } + } +} + +.chart-section { + position: relative; + // height: 330px; + transition: .5s; + + @include maxWidth(750px) { + height: 430px; + + &.noChart { + height: 250px; + } + } +} + +:host::ng-deep .cell.show { + display: initial; +} + +:host::ng-deep .cell.hide { + display: none !important; +} + +:host::ng-deep .more { + height: 30px; + width: 30px; + opacity: .7; + background-image: url(^assets/icons/more-white.svg); + background-repeat: no-repeat; + background-size: 22px; + background-position: center; + transform: scale(1); + transition: transform .2s ease-in-out; + &:hover { + transform: scale(1.2); + } +} + +:host::ng-deep .status-bar { + flex: 0 5px; + max-width: 5px; + height: 100%; + &.success { + background-color: $good; + } + &.failed { + background-color: $bad; + } + @include for-tablet-landscape-up { + display: none !important; + } +} + +:host::ng-deep .all-options { + display: flex; + align-items: center; + justify-content: center; + margin-right: 5px; + margin-left: auto; + @include for-tablet-portrait-up { + padding-left: 35px; + } +} + +::ng-deep .cdk-header-item { + display: flex !important; + align-items: center; + padding-left: 10px !important; + &.cdk-drag-preview { + background-color: white; + } + &:hover .move-vertical { + opacity: .8; + } + .mat-checkbox { + display: flex; + flex: 1; + .mat-checkbox-layout { + display: flex; + flex: 1; + .mat-checkbox-label { + flex: 1; + } + } + } +} + +::ng-deep .move-vertical { + display: block; + height: 20px; + width: 25px; + margin-right: 10px; + background-image: url(^assets/move-row.svg); + background-size: 20px; + background-position: center; + background-repeat: no-repeat; + opacity: .5; +} + +.show-mobile { + white-space: nowrap; +} + +.current_filters { + display: flex; + flex-direction: column; + justify-content: flex-start; + // height: 85px; // Taking too much space when screen has 125%+ screen resolution. + margin: 15px 20px 0; + @include for-tablet-portrait-up-plus30 { + flex-direction: row; + } +} + +.mobile-info { + display: none; + @include maxWidth(750px) { + display: flex; + } + &.header { + .steps, .browser, .last_test { + color: #3782d8; + font-weight: bold; + font-size: 20px; + } + } + &:not(.header) { + font-size: 18px; + color: rgba(0,0,0,.5); + margin-bottom: 5px; + font-style: italic; + font-weight: bold; + } + .last_test .ok { + color: #00a99d; + } + .last_test .nok { + color: #d4145a; + } + .steps, .browser, .last_test { + flex: 1 50%; + text-align: center; + // padding: 0 0 10px 0; + } + .last-result-status { + &.success { + color: $good; + } + &.failed { + color: $bad; + } + } +} + +.view-mode { + // Allow the user to select text + // user-select: none; + position: fixed; + bottom: 80px; + color: $secondary-color; + cursor: pointer; + font-size: 23pt; + font-family: "CorpoS"; + line-height: 60px; + text-align: center; + right: 30px; + height: 60px; + width: 60px; + background-color: #343434; + border-radius: 50%; +} + +.firstLetterUppercase::first-letter { + text-transform: uppercase; +} + +:host::ng-deep .no-tests { + font-size: 13pt; + display: block; + text-align: center; + margin: 30px auto; + font-weight: bold; +} + +.greyed { + opacity: .5; +} + +::ng-deep .add-step-panel .mat-dialog-content { + @media (max-width: 700px) { + width: 350px; + } +} + +.pixel-difference { + position: relative; +} + +.view-pixel-difference { + display: block; + margin-left: 15px; + height: 20px; + width: 20px; + right: 20px; + top: 0; + bottom: 0; + margin: auto; + position: absolute; + background-image: url(^assets/eye-dark.svg); + background-size: contain; + background-repeat: no-repeat; + background-position: center; + opacity: .5; +} + +:host::ng-deep .skeleton-rows { + .skeleton-row:nth-child(odd) { + background-color: #e6e6e6; + border-bottom: 2px solid rgba(black, .05); + } + .skeleton-row:nth-child(even) { + background-color: #f2f2f2; + border-bottom: 2px solid rgba(black, .1); + } +} + +.table_rows { + height: 100%; +} + +.return { + position: absolute; + padding: 25px 0 0 20px; + box-sizing: border-box; + z-index: 2; + @include for-tablet-portrait-up { + flex-wrap: nowrap; + } + .return-item { + display: block; + align-items: center; + opacity: .6; + cursor: pointer; + // height: 50px; + // line-height: 50px; + white-space: nowrap; + @include for-tablet-portrait-up { + display: inline-block; + & + .return-item { + margin-left: 10px; + } + } + .return-text { + font-size: 14pt; + font-weight: bold; + color: rgba(0,0,0,0.6); + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + display: inline-block; + vertical-align: middle; + } + i { + height: 15px; + width: 15px; + display: inline-block; + margin-right: 10px; + background: url(^assets/left-arrow.svg) no-repeat; + background-size: contain; + vertical-align: middle; + } + } +} \ No newline at end of file diff --git a/front/src/app/components/data-driven-runs/data-driven-results/data-driven-results.component.ts b/front/src/app/components/data-driven-runs/data-driven-results/data-driven-results.component.ts new file mode 100755 index 00000000..e9eb451d --- /dev/null +++ b/front/src/app/components/data-driven-runs/data-driven-results/data-driven-results.component.ts @@ -0,0 +1,259 @@ +import { Component, OnInit, ChangeDetectionStrategy, ChangeDetectorRef } from '@angular/core'; +import { ActivatedRoute, Router } from '@angular/router'; +import { Observable } from 'rxjs'; +import { map } from 'rxjs/operators'; +import { Actions, Store, ofActionDispatched } from '@ngxs/store'; +import { MtxGridColumn } from '@ng-matero/extensions/grid'; +import { HttpClient } from '@angular/common/http'; +import { PageEvent } from '@angular/material/paginator'; +import { SharedActionsService } from '@services/shared-actions.service'; +import { Configuration } from '@store/actions/config.actions'; +import { MatDialog } from '@angular/material/dialog'; +import { MatSnackBar } from '@angular/material/snack-bar'; +import { VideoComponent } from '@dialogs/video/video.component'; +import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy'; +import { MatCheckboxChange } from '@angular/material/checkbox'; +import { PdfLinkPipe } from '@pipes/pdf-link.pipe'; +import { DownloadService } from '@services/download.service'; +import { InterceptorParams } from 'ngx-network-error'; +import { CommonModule } from '@angular/common'; +import { SharedModule } from '@modules/shared.module'; +import { WebSockets } from '@store/actions/results.actions'; + +@UntilDestroy() +@Component({ + selector: 'data-driven-results', + templateUrl: './data-driven-results.component.html', + styleUrls: ['./data-driven-results.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, + providers: [ + PdfLinkPipe + ], + imports: [CommonModule, SharedModule], + standalone: true +}) +export class DataDrivenResultsComponent implements OnInit { + +// @Select(CustomSelectors.GetConfigProperty('internal.showArchived')) showArchived$: Observable; + + columns: MtxGridColumn[] = [ + {header: 'Status', field: 'status', sortable: true, class: 'aligned-center'}, + {header: 'Execution Date', field: 'result_date', sortable: true, width: '230px', sortProp: { start: 'desc', id: 'result_date'}}, + {header: 'Total', field: 'total', sortable: true, class: 'aligned-center'}, + {header: 'OK', field: 'ok', sortable: true, class: 'aligned-center'}, + {header: 'NOK', field: 'fails', sortable: true, class: 'aligned-center'}, + {header: 'Skipped', field: 'skipped', class: 'aligned-center'}, + {header: 'Browser', field: 'browser', class: 'aligned-center'}, + {header: 'Browser Version', field: 'browser.browser_version', hide: true, sortable: true, class: 'aligned-center'}, + {header: 'Duration', field: 'execution_time', sortable: true, class: "aligned-right"}, + {header: 'Pixel Difference', field: 'pixel_diff', sortable: true, class: "aligned-right"}, + { + header: 'Options', + field: 'options', + width: '230px', + // pinned: 'right', + right: '0px', + type: 'button', + buttons: [ + { + type: 'icon', + text: 'replay', + icon: 'videocam', + tooltip: 'View results replay', + color: 'primary', + iif: (result: FeatureResult) => result.video_url ? true : false, + click: (result: FeatureResult) => this.openVideo(result), + }, + { + type: 'icon', + text: 'pdf', + icon: 'picture_as_pdf', + tooltip: 'Download result PDF', + color: 'primary', + click: (result: FeatureResult) => { + const pdfLink = this._pdfLinkPipe.transform(result.feature_result_id) + this._http.get(pdfLink, { + params: new InterceptorParams({ + skipInterceptor: true, + }), + responseType: 'text', + observe: 'response' + }).subscribe({ + next: (res) => { + this._downloadService.downloadFile(res, { + mime: 'application/pdf', + name: `${result.feature_name}_${result.feature_result_id}.pdf` + }) + }, + error: console.error + }) + }, + }, + { + type: 'icon', + text: 'archive', + icon: 'archive', + tooltip: 'Archive result', + color: 'accent', + click: (result: FeatureResult) => { + this._sharedActions.archive(result).subscribe(_ => this.getResults()) + }, + iif: (result: FeatureResult) => !result.archived + }, + { + type: 'icon', + text: 'unarchive', + icon: 'unarchive', + tooltip: 'Unarchive result', + color: 'accent', + click: (result: FeatureResult) => { + this._sharedActions.archive(result).subscribe(_ => this.getResults()) + }, + iif: (result: FeatureResult) => result.archived + }, + { + type: 'icon', + text: 'delete', + icon: 'delete', + tooltip: 'Delete result', + color: 'warn', + click: (result: FeatureResult) => { + this._sharedActions.deleteFeatureResult(result).subscribe(_ => this.getResults()) + }, + iif: (result: FeatureResult) => !result.archived + } + ] + } + ]; + + results = []; + total = 0; + isLoading = true; + showPagination = true; + latestFeatureResultId: number = 0; + + query = { + page: 0, + size: 10 + } + get params() { + const p = { ...this.query }; + p.page += 1; + return p + } + + constructor( + private _route: ActivatedRoute, + private _store: Store, + private _router: Router, + public _sharedActions: SharedActionsService, + private _http: HttpClient, + private cdRef: ChangeDetectorRef, + private _dialog: MatDialog, + private _snack: MatSnackBar, + private _actions: Actions, + private _pdfLinkPipe: PdfLinkPipe, + private _downloadService: DownloadService + ) { } + + runId$: Observable; + + openContent(feature_result: FeatureResult) { + this._router.navigate([ + 'data-driven', + this._route.snapshot.paramMap.get('id'), + 'step', + feature_result.feature_result_id + ]); + } + + getResults() { + this.isLoading = true; + this.runId$.subscribe(runId => { + this._http.get(`/backend/api/data_driven/results/${runId}`, { + params: { + ...this.params + } + }).subscribe({ + next: (res: any) => { + this.results = res.results + this.total = res.count + this.showPagination = this.total > 0 ? true : false + }, + error: (err) => { + console.error(err) + }, + complete: () => { + this.isLoading = false + this.cdRef.detectChanges(); + } + }) + }) + } + + updateData(e: PageEvent) { + this.query.page = e.pageIndex + this.query.size = e.pageSize + this.getResults() + + // create a localstorage session + localStorage.setItem('co_results_page_size', e.pageSize.toString()) + } + + /** + * Performs the overriding action through the Store + */ + setResultStatus(results: FeatureResult, status: 'Success' | 'Failed' | '') { + this._sharedActions.setResultStatus(results, status).subscribe(_ => { + this.getResults(); + }) + } + + openVideo(result: FeatureResult) { + this._sharedActions.loadingObservable( + this._sharedActions.checkVideo(result.video_url), + 'Loading video' + ).subscribe({ + next: _ => { + this._dialog.open(VideoComponent, { + backdropClass: 'video-player-backdrop', + panelClass: 'video-player-panel', + data: result + }) + }, + error: err => this._snack.open('An error ocurred', 'OK') + }) + } + + handleDeleteTemplateWithResults({ checked }: MatCheckboxChange) { + return this._store.dispatch(new Configuration.SetProperty('deleteTemplateWithResults', checked)); + } + + /** + * Enables or disables archived runs from checkbox + * @param change MatCheckboxChange + */ +// handleArchived = (change: MatCheckboxChange) => this._store.dispatch(new Configuration.SetProperty('internal.showArchived', change.checked)); + + ngOnInit() { + this.runId$ = this._route.paramMap.pipe( + map(params => +params.get('id')) + ) + this.query.size = parseInt(localStorage.getItem('co_results_page_size')) || 10; + this.getResults() + + // Reload current page of runs whenever a feature run completes + this._actions.pipe( + untilDestroyed(this), + ofActionDispatched(WebSockets.FeatureRunCompleted) + ).subscribe(_ => { + this.getResults() + }); + + } + + // return to v2 dashboard + returnToMain() { + this._router.navigate(['/']); + } +} diff --git a/front/src/app/components/data-driven-runs/data-driven-runs.component.html b/front/src/app/components/data-driven-runs/data-driven-runs.component.html new file mode 100755 index 00000000..1565d981 --- /dev/null +++ b/front/src/app/components/data-driven-runs/data-driven-runs.component.html @@ -0,0 +1,80 @@ + + + + + + +
+ +
+ Passed
+ +
Failed
+
+ + + + + + + + +
+
+ + + {{ row.date_time | amParse | amDateFormat:'MMMM d yyyy, HH:mm' }} + + + + {{ row.execution_time | secondsToHumanReadable }} + + + + {{ row.pixel_diff | pixelDifference }} + + + + +
+
+ Found {{ total }} Data Driven Runs +
+ +
+
+ + + + There are no Data Driven Tests as of now, please create a new Data Driven test. + \ No newline at end of file diff --git a/front/src/app/components/data-driven-runs/data-driven-runs.component.scss b/front/src/app/components/data-driven-runs/data-driven-runs.component.scss new file mode 100755 index 00000000..c54e7fde --- /dev/null +++ b/front/src/app/components/data-driven-runs/data-driven-runs.component.scss @@ -0,0 +1,139 @@ + +@import 'color'; +@import 'breakpoints'; + +:host { + width: 100%; + height: 0; + padding: 25px 20px 0 20px; + box-sizing: border-box; +} + +.blue { + color: $blue; + font-weight: bold; +} + +:host ::ng-deep .mtx-grid { + padding-bottom: 25px; + .mtx-grid-toolbar { + padding: 0; + } + .mtx-grid-toolbar-content { + flex-grow: 1; + .custom_toolbar { + display: flex; + justify-content: space-between; + align-items: center; + margin-right: 5px; + } + button { + mat-icon { + font-size: 1.125rem; + width: 1.125rem; + height: 1.125rem; + margin-left: -4px; + margin-right: 8px; + display: inline-block; + } + } + } + table { + border-radius: 5px; + background-color: $table-background-opaque; + border-spacing: 0; + box-shadow: 0 3px 1px -2px $table-low-shadow, 0 2px 2px 0 $table-medium-shadow, 0 1px 5px 0 $table-high-shadow; + thead tr { + height: 55px; + z-index: 10; + th { + white-space: nowrap; + font-size: 15px; + font-weight: bold; + color: $column-header-dark; + z-index: 4 !important; + &:not(:first-child):not(:last-child):hover { + background-color: $high-black-opaque; + cursor: pointer; + } + &.name { + border-right: none !important; + } + mat-icon { + display: none; + } + &:nth-child(3) { + z-index: 5 !important; + } + } + } + tbody tr { + height: 45px; + cursor: pointer; + &:nth-child(2n + 2) { + background-color: $low-black-opaque; + } + &:hover { + background-color: $high-black-opaque; + } + &.selected { + background-color: $selected-black-opaque; + td { + &.name { + color: $blue; + } + } + } + } + + td { + font-weight: bold; + color: $dark; + /* text-align: center; */ + &.name { + text-align: left; + border-right: none !important; + & > div { + display: flex; + align-items: center; + .mat-icon { + margin-right: 5px; + font-size: 22px; + line-height: 22px; + height: 22px; + width: 22px; + } + } + } + .time { + color: $text-pink; + } + } + th, td { + padding: 0 5px; + border-bottom-width: 1px; + border-bottom-style: solid; + border-bottom-color: $table-low-shadow; + } + th:first-of-type, + td:first-of-type { + padding-left: 5px; + &.running { + width: 40px; + &.disabled { + width: 5px; + } + } + .mat-icon { + width: 30px; + height: 30px; + font-size: 25px; + line-height: 30px; + } + } + th:last-of-type, + td:last-of-type { + padding-right: 5px; + } + } +} \ No newline at end of file diff --git a/front/src/app/components/data-driven-runs/data-driven-runs.component.ts b/front/src/app/components/data-driven-runs/data-driven-runs.component.ts new file mode 100755 index 00000000..e3b553f8 --- /dev/null +++ b/front/src/app/components/data-driven-runs/data-driven-runs.component.ts @@ -0,0 +1,124 @@ +import { HttpClient } from '@angular/common/http'; +import { Component, ChangeDetectionStrategy, ChangeDetectorRef, OnInit } from '@angular/core'; +import { MatDialog } from '@angular/material/dialog'; +import { PageEvent } from '@angular/material/paginator'; +import { Router } from '@angular/router'; +import { DataDrivenExecution } from '@dialogs/data-driven-execution/data-driven-execution.component'; +import { MtxGridColumn } from '@ng-matero/extensions/grid'; +import { SharedActionsService } from '@services/shared-actions.service'; + +@Component({ + selector: 'cometa-data-driven-runs', + templateUrl: './data-driven-runs.component.html', + styleUrls: ['./data-driven-runs.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class DataDrivenRunsComponent implements OnInit{ + constructor( + public _sharedActions: SharedActionsService, + private cdRef: ChangeDetectorRef, + private _router: Router, + private _http: HttpClient, + public _dialog: MatDialog, + ) { } + + columns: MtxGridColumn[] = [ + {header: 'Status', field: 'status', sortable: true}, + {header: 'File Name', field: 'file.name', sortable: true, class: 'name'}, + {header: 'Execution Date', field: 'date_time', sortable: true, width: '230px', sortProp: { start: 'desc', id: 'date_time'}}, + {header: 'Total', field: 'total', sortable: true}, + {header: 'OK', field: 'ok', sortable: true}, + {header: 'NOK', field: 'fails', sortable: true}, + {header: 'Skipped', field: 'skipped'}, + {header: 'Duration', field: 'execution_time', sortable: true}, + {header: 'Pixel Difference', field: 'pixel_diff', sortable: true}, + { + header: 'Options', + field: 'options', + // pinned: 'right', + right: '0px', + type: 'button', + buttons: [ + { + type: 'icon', + text: 'delete', + icon: 'delete', + tooltip: 'Delete result', + color: 'warn', + click: (result: DataDrivenRun) => { + // delete + }, + } + ] + } + ]; + + results = []; + total = 0; + isLoading = true; + showPagination = true; + latestFeatureResultId: number = 0; + + query = { + page: 0, + size: 10 + } + get params() { + const p = { ...this.query }; + p.page += 1; + return p + } + + openContent(run: DataDrivenRun) { + this._router.navigate([ + 'data-driven', + run.run_id + ]); + } + + getResults() { + this.isLoading = true; + this._http.get(`/backend/api/data_driven/`, { + params: { + ...this.params + } + }).subscribe({ + next: (res: any) => { + this.results = res.results + this.total = res.count + this.showPagination = this.total > 0 ? true : false + }, + error: (err) => { + console.error(err) + }, + complete: () => { + this.isLoading = false + this.cdRef.detectChanges(); + } + }) + } + + openNewDataDrivenRun() { + this._dialog.open(DataDrivenExecution, { + disableClose: true, + autoFocus: false, + panelClass: 'edit-feature-panel', + data: { } + }) + } + + updateData(e: PageEvent) { + this.query.page = e.pageIndex + this.query.size = e.pageSize + this.getResults() + + // create a localstorage session + localStorage.setItem('co_results_page_size', e.pageSize.toString()) + } + + ngOnInit(): void { + this.query.size = parseInt(localStorage.getItem('co_results_page_size')) || 10; + this.getResults() + } + +} diff --git a/front/src/app/components/data-driven-runs/data-driven-step-details/data-driven-step-details.component.html b/front/src/app/components/data-driven-runs/data-driven-step-details/data-driven-step-details.component.html new file mode 100755 index 00000000..e5a6cb2a --- /dev/null +++ b/front/src/app/components/data-driven-runs/data-driven-step-details/data-driven-step-details.component.html @@ -0,0 +1,67 @@ + + +
+
+ +
+ {{ result.result_date | amParse | amDateFormat:'MMMM d yyyy, HH:mm a' | firstLetterUppercase }} +
+
+
+ +
{{ stepResult.step_name }}
+
+
+
+ + + +
+ + {{ stepResult.success ? 'OK' : 'NOK' }} +
+
+ + {{ stepResult.execution_time | secondsToHumanReadable }} +
+
+ {{ (stepResult.pixel_diff | numeral) || 0 }} +
TOTAL PIXEL DIFFERENCE
+ +
+
+
+
+
+
Actual Screenshot
+
+
+ + +
+
+
+
+
Screenshot Template
+
+
+ + +
+
+
+
+
Screenshot difference
+
+
+ + +
+
+
+
+
+
+ + + diff --git a/front/src/app/components/data-driven-runs/data-driven-step-details/data-driven-step-details.component.scss b/front/src/app/components/data-driven-runs/data-driven-step-details/data-driven-step-details.component.scss new file mode 100755 index 00000000..388bdfd8 --- /dev/null +++ b/front/src/app/components/data-driven-runs/data-driven-step-details/data-driven-step-details.component.scss @@ -0,0 +1,376 @@ +@import 'breakpoints'; + +:host { + display: block; + width: 100%; + box-sizing: border-box; + overflow-x: hidden; + max-height: calc(100vh - var(--header-height)); +} + +.edit { + display: block; + position: absolute; + top: 110px; + z-index: 99; + right: 28px; + cursor: pointer; + height: 32px; + width: 32px; + background: url(^assets/internal/COM_Edit_Icon.svg) no-repeat; + background-size: contain; + transform: scale(1) rotate(0deg); + transition: transform .2s ease-in-out; + &:hover { + transform: scale(1.1) rotate(10deg); + } +} + +hr { + width: 100%; + height: 1px; + display: block; + background: rgba(black, .15); +} + +.inline-block { + display: inline-block, +} + +.direction-box { + position: absolute; + top: 0; + height: 100px; + width: 60px; + cursor: pointer; + bottom: 0; + margin: auto; + display: none; + @include for-tablet-portrait-up { + display: block; + } + background-image: url(^assets/arrow.svg); + background-repeat: no-repeat; + background-size: contain; + background-position: center; + transition: transform .3s ease-in-out; + opacity: .6; + z-index: 98; + &:hover { + opacity: 1; + } + &.previous { + transform: rotate(-180deg); + left: 30px; + &:hover { + transform: rotate(-180deg) translateX(15px) scale(1.1); + } + } + &.next { + right: 30px; + &:hover { + transform: translateX(15px) scale(1.1); + } + } +} + +.return { + padding: 20px; + box-sizing: border-box; + @include for-tablet-portrait-up { + flex-wrap: nowrap; + } + .return-item { + display: block; + align-items: center; + opacity: .6; + cursor: pointer; + + // creates unnecessary white space + // height: 50px; + // line-height: 50px; + + white-space: nowrap; + @include for-tablet-portrait-up { + display: inline-block; + & + .return-item { + margin-left: 10px; + } + } + .return-text { + font-size: 14pt; + font-weight: bold; + color: rgba(0,0,0,0.6); + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + display: inline-block; + vertical-align: middle; + } + i { + height: 15px; + width: 15px; + display: inline-block; + margin-right: 10px; + background: url(^assets/left-arrow.svg) no-repeat; + background-size: contain; + vertical-align: middle; + } + } +} + +.stats { + display: flex; + flex-wrap: wrap; + justify-content: space-between; + align-items: center; + padding: 0 50px; + margin-bottom: 50px; + box-sizing: border-box; + @include for-tablet-portrait-up { + max-width: 800px; + margin: 0 auto 50px; + padding: 0 20px; + flex-wrap: nowrap; + } + .col { + flex: 0 50%; + @include for-tablet-portrait-up { + flex: 1; + } + display: flex; + align-items: center; + justify-content: center; + &.ok { + i.tick { + background: url(^assets/internal/COM_OK_Icon.svg) no-repeat; + background-size: contain; + } + span.tick { + color: #00A99D; + } + } + &.nok { + i.tick { + background: url(^assets/internal/COM_NotOK_Icon.svg) no-repeat; + background-size: contain; + } + span.tick { + color: #E49033; + } + } + i.tick { + flex: 0 50px; + height: 50px; + margin-right: 15px; + } + span.tick { + font-weight: bold; + font-size: 40pt; + } + .result { + margin-left: 20px; + font-weight: bold; + font-size: 15pt; + } + .pixel_diff { + font-size: 45pt; + display: inline-block; + font-weight: bold; + color: #D4145A; + text-transform: uppercase; + } + .total_pixel_diff { + display: inline-block; + font-weight: bold; + font-size: 15pt; + width: 105px; + margin-left: 20px; + } + &.pixels { + flex: 1; + margin-top: 20px; + @include for-tablet-portrait-up { + margin-top: 0; + flex: 0 300px; + } + justify-content: space-between; + align-items: center; + } + i.pixels { + flex: 0 130px; + min-width: 130px; + height: 45px; + background-image: url(^assets/pixels_grafic.svg); + background-size: contain; + background-repeat: no-repeat; + background-position: center; + margin-left: 25px; + } + i.time { + flex: 0 50px; + width: 50px; + height: 50px; + background: url(^assets/internal/COM_Time_Icon.svg) no-repeat; + background-size: contain; + margin-right: 20px; + } + span.time { + color: #107A92; + font-size: 30pt; + font-weight: bold; + margin-right: 20px; + @include maxWidth(700px) { + margin-left: 0; + } + } + } +} + +.content { + text-align: center; + padding: 0 30px; + box-sizing: border-box; + .col { + display: inline-block; + width: 100%; + max-width: 350px; + height: 320px; + position: relative; + margin-bottom: 50px; + margin: 30px 0; + .image { + display: block; + height: calc(100% - 40px); + margin-top: 20px; + width: 100%; + position: relative; + background-color: rgba(black, .05); + background-size: contain; + background-repeat: no-repeat; + background-position: center; + border-radius: 3px; + &:hover .image-actions { + display: block; + } + .image-actions { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + display: block; + border-radius: 3px; + background-color: rgba(black, .3); + @include for-tablet-portrait-up { + display: none; + } + .action-btn { + display: block; + position: absolute; + margin: auto; + top: 0; + bottom: 0; + left: 0; + right: 0; + height: 55px; + width: 55px; + cursor: pointer; + border-radius: 50%; + background-color: rgba(black, .85); + background-position: center; + background-repeat: no-repeat; + transform: scale(1); + transition: transform .1s ease-in-out; + &:hover { + transform: scale(1.15); + } + } + .zoom { + right: 100px; + background-image: url(^assets/eye.svg); + background-size: 30px; + @include for-tablet-portrait-up { + right: 80px; + } + } + .remove { + left: 100px; + background-image: url(^assets/trash.svg); + background-size: 25px; + @include for-tablet-portrait-up { + left: 80px; + } + } + } + } + .image-type { + text-align: left; + font-weight: bold; + font-size: 1.1rem; + } + .image-type:before { + content: ""; + display: inline-block; + height: 14px; + width: 14px; + position: relative; + top: 1px; + margin-right: 15px; + border-radius: 50%; + } + &.red .image-type:before { + background: #FC0D1B; + } + &.orange .image-type:before { + background: #E49033; + } + &.green .image-type:before { + background: #6F9F4B; + } + } + .col:not(:first-child) { + margin-left: 70px; + @include maxWidth(700px) { + margin-left: 0; + } + } +} + +.current_filters { + // previous flexbox rules, broke layout by wrapping feature description text too much + // which caused contaner to push below contains below, thus altering view display constantly on screen resize + display: flex; + flex-direction: column; + justify-content: flex-start; + margin: 15px 20px 0; + height: 85px; + @include for-tablet-portrait-up-plus30 { + flex-direction: row; + } +} + +.zoomer { + display: block; + background-color: rgba(0,0,0,0.4); + width: 100%; + position: fixed; + height: calc(100% - 145px); + top: 85px; + left: 0; + z-index: 99; + .image { + position: fixed; + width: 80%; + height: calc(90% - 145px); + display: block; + margin: auto; + left: 0; + right: 0; + top: 85px; + bottom: 60px; + background-repeat: no-repeat; + background-size: contain; + background-position: center center; + } +} diff --git a/front/src/app/components/data-driven-runs/data-driven-step-details/data-driven-step-details.component.ts b/front/src/app/components/data-driven-runs/data-driven-step-details/data-driven-step-details.component.ts new file mode 100755 index 00000000..7a485172 --- /dev/null +++ b/front/src/app/components/data-driven-runs/data-driven-step-details/data-driven-step-details.component.ts @@ -0,0 +1,205 @@ +import { Component, OnInit, ChangeDetectionStrategy, Inject, HostListener } from '@angular/core'; +import { trigger, state, style, transition, animate } from '@angular/animations'; +import { ActivatedRoute, Router } from '@angular/router'; +import { ApiService } from '@services/api.service'; +import { MatLegacyDialog as MatDialog } from '@angular/material/legacy-dialog'; +import { SafeStyle, DomSanitizer } from '@angular/platform-browser'; +import { API_BASE } from 'app/tokens'; +import { ScreenshotComponent } from '@dialogs/screenshot/screenshot.component'; +import { Store } from '@ngxs/store'; +import { UserState } from '@store/user.state'; +import { BehaviorSubject, Observable } from 'rxjs'; +import { CustomSelectors } from '@others/custom-selectors'; +import { distinctUntilChanged, filter, map, switchMap, tap } from 'rxjs/operators'; +import { KEY_CODES } from '@others/enums'; +import { FeatureResults } from '@store/actions/feature_results.actions'; +import { ViewSelectSnapshot } from '@ngxs-labs/select-snapshot'; +import { AreYouSureData, AreYouSureDialog } from '@dialogs/are-you-sure/are-you-sure.component'; +import { CommonModule } from '@angular/common'; +import { SharedModule } from '@modules/shared.module'; + +@Component({ + selector: 'data-driven-step-details.component', + templateUrl: './data-driven-step-details.component.html', + styleUrls: ['./data-driven-step-details.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, + standalone: true, + imports: [CommonModule, SharedModule], + animations: [ + trigger('image1', [ + state('false', style({ + opacity: 0, + position: 'relative', + left: '-50px' + })), + state('true', style({ + opacity: 1, + position: 'relative', + left: '0' + })), + transition('false => true', animate('200ms 500ms ease-in-out')) + ]), + trigger('image2', [ + state('false', style({ + opacity: 0, + position: 'relative', + left: '-50px' + })), + state('true', style({ + opacity: 1, + position: 'relative', + left: '0' + })), + transition('false => true', animate('200ms 600ms ease-in-out')) + ]), + trigger('image3', [ + state('false', style({ + opacity: 0, + position: 'relative', + left: '-50px' + })), + state('true', style({ + opacity: 1, + position: 'relative', + left: '0' + })), + transition('false => true', animate('200ms 700ms ease-in-out')) + ]) + ] +}) +export class DataDrivenStepDetailViewComponent implements OnInit { + + @ViewSelectSnapshot(UserState.GetPermission('remove_screenshot')) canDeleteScreenshot: boolean; + + ready = new BehaviorSubject(false); + + currentStepResult$ = new BehaviorSubject(null); + + constructor( + private _acRouted: ActivatedRoute, + private _router: Router, + private _api: ApiService, + private _dialog: MatDialog, + private _sanitizer: DomSanitizer, + private _store: Store, + @Inject(API_BASE) private api_base: string + ) { } + + previous() { + // Goes to the previous step + const previousStepId = this.currentStepResult$.getValue().previous; + if (previousStepId) { + this._router.navigate(['../../detail', previousStepId], { relativeTo: this._acRouted }).then(() => window.scrollTo(0, 0)); + } + } + + next() { + // Goes to the next step + const nextStepId = this.currentStepResult$.getValue().next; + if (nextStepId) { + this._router.navigate(['../../detail', nextStepId], { relativeTo: this._acRouted }).then(() => window.scrollTo(0, 0)); + } + } + + @HostListener('document:keydown', ['$event']) handleKeyboardEvent(event: KeyboardEvent) { + // Handle keyboard arrows, for navigation of steps + // Only if user is not in zoom image view + if (this._dialog.openDialogs.length === 0) { + switch ( event.keyCode ) { + case KEY_CODES.RIGHT_ARROW: + this.next(); + break; + case KEY_CODES.LEFT_ARROW: + this.previous(); + break; + default: break; + } + } + } + + removeScreenshot(type: ScreenshotType) { + this._dialog.open(AreYouSureDialog, { + data: { + title: 'translate:you_sure.delete_item_title', + description: 'translate:you_sure.delete_item_desc' + } as AreYouSureData + }) + .afterClosed() + .pipe( + // Filter by Answer --> Yes + filter(remove => !!remove), + // Map to current step object value in this component + map(_ => this.currentStepResult$.getValue()), + // Perform removal in backend + switchMap(step => this._api.removeScreenshot(step.step_result_id, type).pipe( + filter(json => !!json.success), + map(_ => step) + )), + // Perform removal in current step without affecting pipe value + tap(step => { + const currentStep = { ...step }; + if (currentStep.screenshots[type]) { + currentStep.screenshots[type] = 'removed'; + } else { + currentStep[`screenshot_${type}`] = 'removed' + } + this.currentStepResult$.next(currentStep); + }), + ) + .subscribe(step => { + if (type === 'style') { + // Get template filename + const templateFile = step.screenshot_template || step.template_name || ''; + if (templateFile) { + // Remove template style image if is of type template and exists + this._api.removeTemplate(step.step_result_id, templateFile).subscribe(res => { + if (!res.success) { + console.log('An error ocurred while removing screenshot', res); + } + }, err => { + console.log(err); + }); + } + } + }); + } + + getIndexImage(type: ScreenshotType): SafeStyle { + const screenshot = this.currentStepResult$.getValue().screenshots[type]; + return this._sanitizer.bypassSecurityTrustStyle(`url(${this.api_base}screenshot/${screenshot}/)`); + } + + featureResult$: Observable; + + ngOnInit() { + // Get Feature Result info + this._acRouted.paramMap.pipe( + map(params => +params.get('feature_result_id')), + distinctUntilChanged(), + tap(featureResultId => this.featureResult$ = this._store.select(CustomSelectors.GetFeatureResultById(featureResultId))), + switchMap(featureResultId => this._store.dispatch( new FeatureResults.GetFeatureResult(featureResultId, true) )) + ).subscribe(_ => this.ready.next(true)); + // Get Step Result info + this._acRouted.paramMap.pipe( + map(params => +params.get('step_result_id')), + distinctUntilChanged(), + switchMap(stepResultId => this._api.getStepResult(stepResultId)) + ).subscribe(stepResult => this.currentStepResult$.next(stepResult)); + } + + makeZoom(screenshot) { + this._dialog.open(ScreenshotComponent, { + data: screenshot, + panelClass: 'screenshot-panel' + }); + } + + returnToSteps() { + this._router.navigate(['../../'], { relativeTo: this._acRouted }); + } + + returnToMain() { + this._router.navigate(['../../../../'], { relativeTo: this._acRouted }); + } + +} diff --git a/front/src/app/components/data-driven-runs/data-driven-steps/data-driven-steps.component.html b/front/src/app/components/data-driven-runs/data-driven-steps/data-driven-steps.component.html new file mode 100755 index 00000000..48556199 --- /dev/null +++ b/front/src/app/components/data-driven-runs/data-driven-steps/data-driven-steps.component.html @@ -0,0 +1,146 @@ + +
+
+ +
+ {{ test.result_date | amParse | amDateFormat:"MMMM d yyyy',' HH:mm a" | firstLetterUppercase }} +
+
+
+
+
+ +
{{ test.total | percentage:test.total }}
+
TOTAL TEST
+
+
+ +
{{ test.ok | percentage:test.total }}
+
OK
+
+
+ +
{{ test.fails | percentage:test.total }}
+
FAIL
+
+
+
+
+ {{ (test.pixel_diff | numeral) || 0 }} +
+
TOTAL PIXEL DIFFERENCE
+
+
+
+
+
+ + + + \ No newline at end of file diff --git a/front/src/app/components/data-driven-runs/data-driven-steps/data-driven-steps.component.scss b/front/src/app/components/data-driven-runs/data-driven-steps/data-driven-steps.component.scss new file mode 100755 index 00000000..244bc921 --- /dev/null +++ b/front/src/app/components/data-driven-runs/data-driven-steps/data-driven-steps.component.scss @@ -0,0 +1,425 @@ + +@import 'breakpoints'; +@import 'color'; + +:host { + display: flex; + flex-flow: column; + width: 100%; + box-sizing: border-box; + overflow-y: auto; + max-height: calc(100vh - var(--header-height)); + height: calc(100vh - var(--header-height)); +} + +.edit, .log { + display: block; + position: absolute; + top: 110px; + right: 28px; + cursor: pointer; + height: 32px; + width: 32px; + background: url(^assets/internal/COM_Edit_Icon.svg) no-repeat; + background-size: contain; + transform: scale(1) rotate(0deg); + transition: transform .2s ease-in-out; + z-index: 1; + &:hover { + transform: scale(1.1) rotate(10deg); + } +} + +.log{ + right: 172px; + background: transparent; + border: 1px solid; + border-radius: 50%; + font-size: 12px; + text-align: center; + line-height: 32px; +} + +.stats { + display: block; + width: 100%; + position: relative; + text-align: center; + padding: 30px 30px 60px; // table header will be in the same position in feature result component and step view component + box-sizing: border-box; + + @include for-tablet-portrait-up-plus30 { + padding: 30px 30px 83px; + } + + // comented because when screen is resized makes table header change position on y axis without any reason + // @include maxWidth(700px) { + // padding: 15px; + // } + + .return { + opacity: .6; + display: flex; + cursor: pointer; + height: 50px; + align-items: center; + .return-text { + flex: initial; + line-height: 50px; + font-size: 14pt; + font-weight: bold; + color: rgba(black, .6); + &:first-letter { + text-transform: uppercase; + } + } + i { + flex: initial; + height: 15px; + width: 15px; + margin-right: 20px; + background: url(^assets/left-arrow.svg) no-repeat; + background-size: contain; + } + } +} + +.current_filters { + // previous flex box rules made filter action icons have too much space between them + display: flex; + flex-direction: column; + justify-content: flex-start; + margin: 15px 20px 0; + height: 85px; + @include for-tablet-portrait-up-plus30 { + flex-direction: row; + } +} + +.result-stats { + display: flex; + flex-wrap: wrap; + flex-direction: column; + width: 100%; + max-width: 600px; + @include for-tablet-portrait-up { + flex-direction: row; + flex-wrap: nowrap; + margin: 0 auto; + justify-content: space-between; + } + .info { + display: flex; + justify-content: space-between; + align-items: center; + @include for-tablet-portrait-up { + flex: 0 400px; + } + .circular-progress { + flex: initial; + } + } +} + +.circular-progress { + position: relative; + display: flex; + flex-direction: column; + width: 110px; + .value { + position: absolute; + height: 25px; + width: 100%; + top: 35px; + font-size: 20pt; + font-weight: 500; + margin: auto; + text-align: center; + + &::after { + content: attr(data-value); + display: block; + font-size: 12pt; + font-weight: 100; + } + } + .title { + margin-top: 20px; + font-weight: bold; + color: rgba(0,0,0,0.6); + text-align: center; + } +} + +.info { + display: flex; + margin-top: 20px; + justify-content: space-evenly; +} +.total_pixel { + display: flex; + justify-content: space-between; + align-items: center; + font-weight: bold; + margin-top: 20px; + @include for-tablet-portrait-up { + flex: 0 105px; + max-width: 105px; + flex-direction: column; + } + .red { + flex: 0 33.33333%; + font-size: 40pt; + color: #D4145A; + text-transform: uppercase; + @include for-tablet-portrait-up { + flex: initial; + font-size: 30pt; + } + } + .total-pixel-title { + flex: 0 33.33333%; + padding: 0 15px; + box-sizing: border-box; + font-size: 18pt; + @include for-tablet-portrait-up { + flex: initial; + font-size: 15pt; + } + } + .pixel_graphic { + flex: 0 33.33333%; + height: 50px; + } +} + +.pixel_graphic { + display: block; + width: 130px; + height: 60px; + margin: 0 auto; + margin-top: 0px; + background: url(^assets/pixels_grafic.svg) no-repeat; + background-size: contain; + @include for-tablet-portrait-up { + margin-top: 20px; + } +} + +.run { + display: block; + position: absolute; + top: 110px; + right: 100px; + cursor: pointer; + height: 32px; + width: 32px; + background: url(^assets/play-button.svg) no-repeat; + background-size: contain; + transform: scale(1); + transition: transform .2s ease-in-out; + z-index: 1; + &:hover { + transform: scale(1.2); + } +} + +.running { + display: block; + position: absolute; + top: 105px; + right: 100px; + cursor: pointer; + height: 40px; + width: 40px; + background: url(^assets/running_loading_blue.svg) no-repeat; + background-size: contain; +} + +:host::ng-deep network-paginated-list { + .header { + flex: 1 100%; + display: flex; + align-items: center; + text-align: center; + background-color: #474747; + color: white; + text-transform: uppercase; + padding: 12px 0; + box-sizing: border-box; + padding-right: 10px; + + // commented because breaks syncronization between table header and table rows, column names do not coincide with their corresponding values + // @include for-tablet-portrait-up { + // padding-right: 85px; + // } + } + .name { + // commented because breaks syncronization between table header and table rows, column names do not coincide with their corresponding values + // flex: 0 60%; + // max-width: 60%; + // @include for-tablet-portrait-up { + flex: 0 40%; + max-width: 40%; + // } + } + .status, + .time, + .difference { + flex: 0 20%; + max-width: 20%; + text-align: center; + } + .difference-desktop { + flex: 0 20%; + max-width: 20%; + text-align: center; + display: none; + @include for-tablet-portrait-up { + display: initial; + } + } + .difference-mobile { + flex: 0 20%; + max-width: 20%; + // @include for-tablet-portrait-up-plus30 { + // display: none; + // } + } + .step-row { + display: flex; + align-items: center; + min-height: 53px; + box-sizing: border-box; + font-weight: bold; + cursor: pointer; + padding-right: 10px; + @include for-tablet-portrait-up { + padding-right: 0; + } + &:nth-child(odd) { + background-color: #e6e6e6; + border-bottom: 2px solid rgba(black, .05); + } + &:nth-child(even) { + background-color: #f2f2f2; + border-bottom: 2px solid rgba(black, .1); + } + &:hover .name { + color: $blue; + } + .status-bar { + flex: 0 5px; + max-width: 5px; + height: 100%; + &.success { + background-color: $good; + } + &.failed { + background-color: $bad; + } + @include for-tablet-portrait-up { + display: none; + } + } + .name { + padding-left: 10px; + box-sizing: border-box; + display: flex; + align-items: center; + justify-content: flex-start; + font-size: .9rem; + @include for-tablet-portrait-up { + padding-left: 20px; + font-size: 1rem; + } + .step-index { + margin-right: 15px; + color: $blue; + min-width: 30px; + text-align: center; + display: none; + @include for-tablet-portrait-up { + display: initial; + } + } + .step-content { + max-width: 100%; + // padding: 10px 0; prevents step row from increasing its height when custom error message has to be displayed, thus causing content overflow + .feature-reference, .step-error { + font-size: .8rem; + margin-bottom: 3px; + display: inline-flex; + position: relative; + transform: translate3d(0, 0, 0); + transition: transform .15s ease-in-out; + + } + + .feature-reference { + color: rgba(black, .55); + &:hover { + transform: translate3d(10px, 0, 0); + } + } + + .step-name, .step-error { word-break: break-all; } + + .step-error { + max-width: 100%; + color: $bad; + } + } + } + .item-options { + margin-right: 10px; + width: 80px; + display: flex; + justify-content: flex-end; + .option { + flex: 0 40px; + } + } + .status { + display: none; + @include for-tablet-portrait-up { + display: initial; + } + &.ok { + color: $good; + } + &.nok { + color: $medium; + } + } + .time { + color: $bad; + } + .item-options { + display: none; + @include for-tablet-portrait-up { + display: flex; + } + } + + + .screenshots-wrapper { + width: 24px; + height: 24px; + transform: translateX(150%); + } + } + .header { + .status { + display: none; + @include for-tablet-portrait-up { + display: initial; + } + } + } + .pagination { + // @include pagination-mobile { + // margin-top: 37px; + // } + } +} \ No newline at end of file diff --git a/front/src/app/components/data-driven-runs/data-driven-steps/data-driven-steps.component.ts b/front/src/app/components/data-driven-runs/data-driven-steps/data-driven-steps.component.ts new file mode 100755 index 00000000..6a323fa8 --- /dev/null +++ b/front/src/app/components/data-driven-runs/data-driven-steps/data-driven-steps.component.ts @@ -0,0 +1,169 @@ +import { Component, OnInit, ChangeDetectionStrategy, ViewChild } from '@angular/core'; +import { Router, ActivatedRoute } from '@angular/router'; +import { trigger, state, style, transition, query, stagger, animate } from '@angular/animations'; +import { Store } from '@ngxs/store'; +import { Observable, combineLatest } from 'rxjs'; +import { CustomSelectors } from '@others/custom-selectors'; +import { FeatureResults } from '@store/actions/feature_results.actions'; +import { distinctUntilChanged, map, shareReplay, switchMap, tap } from 'rxjs/operators'; +import { ApiService } from '@services/api.service'; +import { NetworkPaginatedListComponent } from '@components/network-paginated-list/network-paginated-list.component'; +import { SharedActionsService } from '@services/shared-actions.service'; +import { MatLegacyDialog as MatDialog } from '@angular/material/legacy-dialog'; +import { ScreenshotComponent } from '@dialogs/screenshot/screenshot.component'; +import { CommonModule } from '@angular/common'; +import { SharedModule } from '@modules/shared.module'; + +@Component({ + selector: 'data-driven-step-view', + templateUrl: './data-driven-steps.component.html', + styleUrls: ['./data-driven-steps.component.scss'], + standalone: true, + imports: [CommonModule, SharedModule], + changeDetection: ChangeDetectionStrategy.OnPush, + animations: [ + trigger('progressIn', [ + transition('* => *', [ + query(':enter', style({ opacity: 0, position: 'relative', left: '-50px' }), { optional: true }), + query(':enter', stagger('80ms', [ + animate('.2s 0ms ease-in', style({ opacity: 1, position: 'relative', left: '0' })) + ]), { optional: true }) + ]) + ]), + trigger('info', [ + state('false', style({ + opacity: 0, + position: 'relative', + bottom: '-20px' + })), + state('true', style({ + opacity: 1, + position: 'relative', + bottom: '0' + })), + transition('false => true', animate('200ms 300ms ease-in-out')) + ]), + trigger('chart', [ + state('false', style({ + opacity: 0, + position: 'relative', + top: '30px' + })), + state('true', style({ + opacity: 1, + position: 'relative', + top: '0' + })), + transition('false => true', animate('300ms 500ms ease-in-out')) + ]), + trigger('returnArrow', [ + state('false', style({ + opacity: 0, + position: 'relative', + left: '-30px' + })), + state('true', style({ + opacity: 1, + position: 'relative', + left: '0' + })), + transition('false => true', animate('200ms 0ms ease-in-out')) + ]), + trigger('returnText', [ + state('false', style({ + opacity: 0, + position: 'relative', + left: '-50px' + })), + state('true', style({ + opacity: 1, + position: 'relative', + left: '0' + })), + transition('false => true', animate('200ms 100ms ease-in-out')) + ]), + ] +}) +export class DataDrivenStepViewComponent implements OnInit { + + clickStepResult: number = null; + test$: Observable; + + stepResultsUrl$: Observable; + + constructor( + private _router: Router, + private _acRouted: ActivatedRoute, + private _store: Store, + private _api: ApiService, + public _sharedActions: SharedActionsService, + private _dialog: MatDialog, + ) { } + + featureResultId$: Observable; + + ngOnInit() { + this.featureResultId$ = this._acRouted.paramMap.pipe( + map(params => +params.get('feature_result_id')), + distinctUntilChanged(), + shareReplay({ bufferSize: 1, refCount: true }) // Share resulting value among subscribers + ) + this.test$ = this.featureResultId$.pipe( + tap(resultId => this.getFeatureResult(resultId)), + switchMap(resultId => this._store.select(CustomSelectors.GetFeatureResultById(resultId))), + ) + this.stepResultsUrl$ = this.featureResultId$.pipe( + map((resultId) => `feature_results/999/step_results/${resultId}/`) + ) + } + + getFeatureResult = resultId => this._store.dispatch(new FeatureResults.GetFeatureResult(resultId, true)); + + returnToMain() { + this._router.navigate([ + 'data-driven', + this._acRouted.snapshot.paramMap.get('id') + ]); + } + + goToDetail(step_id: number) { + // Save FeatureResult steps for later user in DetailView of clicked step + this._router.navigate(['detail', step_id], { relativeTo: this._acRouted }).then(() => window.scrollTo(0, 0)); + } + + @ViewChild(NetworkPaginatedListComponent) paginatedList: NetworkPaginatedListComponent; + + /** + * Performs the overriding action through the Store + */ + setStepStatus(item: StepResult, status: string) { + // If Default, compute original value + if (status === 'Default') { + status = ''; + } + if (this.paginatedList) { + // Launch Store action to process it + this._api.patchStepResult(item.step_result_id, { status }).subscribe(res => { + if (res.success) { + // Get current steps in view + const currentSteps = this.paginatedList.pagination$.getValue(); + // Get modifying step index + const stepIndex = currentSteps.results.findIndex(step => step.step_result_id === item.step_result_id); + // Update status value + currentSteps.results[stepIndex].status = status; + // Update view + this.paginatedList.pagination$.next(currentSteps); + } + }) + } + } + + loadImages (item) { + if(item) { + this._dialog.open(ScreenshotComponent, { + data: item, + panelClass: 'screenshot-panel' + }); + } + } +} diff --git a/front/src/app/components/data-driven/data-driven.component.html b/front/src/app/components/data-driven/data-driven.component.html new file mode 100644 index 00000000..f0f76b26 --- /dev/null +++ b/front/src/app/components/data-driven/data-driven.component.html @@ -0,0 +1,145 @@ + + + + Information + + Data Driven Test Information + + +
+ + Department + + + {{ dep.department_name }} + + + + + Application + + + {{ app.app_name }} + + + + + Environment + + + {{ env.environment_name }} + + + + + Name + + + + Description + + +
+
+ warning + Selecting Default department will make this feature visible to everyone, use it with caution! +
+
+ + + + + +
+
+
+ + + + + Files + + + + + + Upload/Download department files + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
File name +
+ + {{element.name}} +
+
File type {{element.type}} File size {{element.size | humanizeBytes}} Uploaded By {{element.uploaded_by.name}} Upload date {{ element.created_on | amParse | amDateFormat:'MMMM d yyyy, + HH:mm a'}} Actions + + + + + + +
+
+
+ +
+

Currently this department has no files, upload one by clicking upload button.

+
+
+
+ diff --git a/front/src/app/components/data-driven/data-driven.component.scss b/front/src/app/components/data-driven/data-driven.component.scss new file mode 100644 index 00000000..00c4d40b --- /dev/null +++ b/front/src/app/components/data-driven/data-driven.component.scss @@ -0,0 +1,28 @@ +.mat-expansion-panel { + margin-top : 35px; + margin-left: 10px; + margin-right: 10px; + + table { + width: 100%; + } + + .edit-data-driven-test { + display: flex; + + mat-form-field { + flex-grow: 1; + margin: 0 1px; + } + } + + .department-warning { + display: flex; + align-items: center; + margin-bottom: 18px; + + span { + margin-left: 3px; + } + } +} \ No newline at end of file diff --git a/front/src/app/components/data-driven/data-driven.component.ts b/front/src/app/components/data-driven/data-driven.component.ts new file mode 100644 index 00000000..4b9fbea3 --- /dev/null +++ b/front/src/app/components/data-driven/data-driven.component.ts @@ -0,0 +1,154 @@ +import { Component, OnInit } from "@angular/core"; +import { Select, Store } from "@ngxs/store"; +import { Observable } from "rxjs"; +import { FeaturesState } from "@store/features.state"; +import { CommonModule } from "@angular/common"; +import { SharedModule } from "@modules/shared.module"; +import { ViewSelectSnapshot } from "@ngxs-labs/select-snapshot"; +import { ConfigState } from "@store/config.state"; +import { FileUploadService } from "@services/file-upload.service"; +import { UserState } from "@store/user.state"; +import { MatSnackBar } from "@angular/material/snack-bar"; +import { ApplicationsState } from "@store/applications.state"; +import { EnvironmentsState } from "@store/environments.state"; + +@Component({ + selector: "cometa-data-driven", + imports: [CommonModule, SharedModule], + templateUrl: "./data-driven.component.html", + styleUrls: ["./data-driven.component.scss"], + standalone: true, +}) +export class DataDrivenComponent implements OnInit { + displayedColumns: string[] = [ + "name", + "mime", + "size", + "uploaded_by.name", + "created_on", + "actions", + ]; + + departments$ = this._store.selectSnapshot(UserState.RetrieveUserDepartments); + applications$ = this._store.selectSnapshot(ApplicationsState); + environments$ = this._store.selectSnapshot(EnvironmentsState); + + @Select(FeaturesState.GetFeaturesWithinFolder) features$: Observable< + ReturnType + >; + @ViewSelectSnapshot(ConfigState) config$!: Config; + @ViewSelectSnapshot(UserState) user!: UserInfo; + + department: Department; + application: Application; + environment: Environment; + + constructor( + private fileUpload: FileUploadService, + private _store: Store, + private _snackBar: MatSnackBar + ) { + + } + + ngOnInit() { + this.fillDropdownsOnInit(); // TODO: Change mode based on the edit or new. + } + + onUploadFile(ev) { + let formData: FormData = new FormData(); + let files = ev.target.files; + + for (let file of files) { + formData.append("files", file); + } + + let detpid = this.department.department_id.toString(); + formData.append("department_id", detpid); + + this.fileUpload.startUpload(files, formData, this.department, this.user); + } + + onDeleteFile(file: UploadedFile) { + this.fileUpload.deleteFile(file.id).subscribe((res) => { + if (res.success) this.fileUpload.updateFileState(file, this.department); + }); + } + + onRestoreFile(file: UploadedFile) { + let formData: FormData = new FormData(); + formData.append("restore", String(file.is_removed)); + + this.fileUpload.restoreFile(file.id, formData).subscribe((res) => { + if (res.success) this.fileUpload.updateFileState(file, this.department); + }); + } + + onDownloadFile(file: UploadedFile) { + // return if file is still uploading + if (file.status.toLocaleLowerCase() != "done") { + return; + } + + const downloading = this._snackBar.open( + "Generating file to download, please be patient.", + "OK", + { duration: 10000 } + ); + + this.fileUpload.downloadFile(file.id).subscribe({ + next: (res) => { + const blob = new Blob([this.base64ToArrayBuffer(res.body)], { + type: file.mime, + }); + this.fileUpload.downloadFileBlob(blob, file); + downloading.dismiss(); + }, + error: (err) => { + if (err.error) { + const errors = JSON.parse(err.error); + this._snackBar.open(errors.error, "OK"); + } + }, + }); + } + + base64ToArrayBuffer(data: string) { + const byteArray = atob(data); + const uint = new Uint8Array(byteArray.length); + for (let i = 0; i < byteArray.length; i++) { + let ascii = byteArray.charCodeAt(i); + uint[i] = ascii; + } + return uint; + } + + fillDropdownsOnInit(mode: string = 'new') { + switch(mode) { + case 'new': + this.preSelectedOrDefaultOptions(); + break; + case 'edit': + // TODO: Get data from the data-driven object + break; + default: + break; + } + } + + preSelectedOrDefaultOptions() { + const { + preselectDepartment, + preselectApplication, + preselectEnvironment + } = this.user.settings; + + const department: Department = this.departments$.find(d => d.department_id == preselectDepartment) || this.departments$[0]; + const application: Application = this.applications$.find(a => a.app_id == preselectApplication) || this.applications$[0] + const environment: Environment = this.environments$.find(e => e.environment_id == preselectEnvironment) || this.environments$[0] + + this.department = department; + this.application = application; + this.environment = environment; + } +} diff --git a/front/src/app/components/feature-actions/feature-actions.component.scss b/front/src/app/components/feature-actions/feature-actions.component.scss index c5859982..45ecf168 100755 --- a/front/src/app/components/feature-actions/feature-actions.component.scss +++ b/front/src/app/components/feature-actions/feature-actions.component.scss @@ -7,11 +7,12 @@ z-index: 98; flex: 0 80%; height: 32px; - margin: 0 auto; - justify-content: space-between; - @include for-tablet-portrait-up { + // creates too much space between action icons + // margin: 0 auto; + // justify-content: space-between; + // @include for-tablet-portrait-up { flex: initial; - } + // } & > div:not(:last-child) { margin-right: 30px; } @@ -133,8 +134,8 @@ ::ng-deep .options-menu-panel { min-width: 90px !important; .mat-menu-item { - padding-right: 16px; - padding-left: 16px; + // padding-right: 15px; + // padding-left: 15px; &:after { display: none; } diff --git a/front/src/app/components/feature-actions/feature-actions.component.ts b/front/src/app/components/feature-actions/feature-actions.component.ts index a89150e2..4e81c32f 100755 --- a/front/src/app/components/feature-actions/feature-actions.component.ts +++ b/front/src/app/components/feature-actions/feature-actions.component.ts @@ -1,9 +1,9 @@ -import { Component, ChangeDetectionStrategy, OnInit, Inject } from '@angular/core'; -import { MatDialog } from '@angular/material/dialog'; +import { Component, ChangeDetectionStrategy, OnInit, Inject, Input } from '@angular/core'; +import { MatLegacyDialog as MatDialog } from '@angular/material/legacy-dialog'; import { LogOutputComponent } from '@dialogs/log-output/log-output.component'; import { ApiService } from '@services/api.service'; import { LiveStepsComponent } from '@dialogs/live-steps/live-steps.component'; -import { MatSnackBar } from '@angular/material/snack-bar'; +import { MatLegacySnackBar as MatSnackBar } from '@angular/material/legacy-snack-bar'; import { Store } from '@ngxs/store'; import { ResultsState } from '@store/results.state'; import { debounceTime, distinctUntilChanged, filter, map, shareReplay, switchMap } from 'rxjs/operators'; @@ -13,7 +13,6 @@ import { SafeUrl, DomSanitizer } from '@angular/platform-browser'; import { API_BASE } from 'app/tokens'; import { Observable, fromEvent } from 'rxjs'; import { CustomSelectors } from '@others/custom-selectors'; -import { Dispatch } from '@ngxs-labs/dispatch-decorator'; import { KEY_CODES } from '@others/enums'; import { WebSockets } from '@store/actions/results.actions'; import { SharedActionsService } from '@services/shared-actions.service'; @@ -37,6 +36,8 @@ export class FeatureActionsComponent implements OnInit { featureResultId$: Observable; running$: Observable; + @Input() latestFeatureResultId: number = 0; + constructor( private _dialog: MatDialog, private _api: ApiService, @@ -129,10 +130,9 @@ export class FeatureActionsComponent implements OnInit { viewLog() { // Get feature result id from URL params let featureResultId = +this._ac.snapshot.paramMap.get('feature_result_id'); - if (!featureResultId) { + if (!featureResultId && this.latestFeatureResultId != 0) { // Or get id from last run object - const runs = this._store.selectSnapshot(PaginatedListsState.GetItems)('runs_' + this.getFeatureId()); - featureResultId = runs[0].feature_results[0].feature_result_id; + featureResultId = this.latestFeatureResultId; } // Open Log Dialog this._dialog.open(LogOutputComponent, { @@ -181,15 +181,13 @@ export class FeatureActionsComponent implements OnInit { }); } - @Dispatch() toggleNotification() { const featureStore = this._store.selectSnapshot(FeaturesState.GetFeatureInfo)(this.getFeatureId()); const notifications = this._store.selectSnapshot(ResultsState.GetNotifications); - if (notifications.includes(featureStore.feature_id)) { - return new WebSockets.RemoveNotificationID(featureStore.feature_id); - } else { - return new WebSockets.AddNotificationID(featureStore.feature_id); - } + + return notifications.includes(featureStore.feature_id) ? + this._store.dispatch(new WebSockets.RemoveNotificationID(featureStore.feature_id)) : + this._store.dispatch( new WebSockets.AddNotificationID(featureStore.feature_id)) } } \ No newline at end of file diff --git a/front/src/app/components/feature-list/feature-list.component.ts b/front/src/app/components/feature-list/feature-list.component.ts index 54e989e2..3c638171 100644 --- a/front/src/app/components/feature-list/feature-list.component.ts +++ b/front/src/app/components/feature-list/feature-list.component.ts @@ -7,12 +7,10 @@ */ import { ChangeDetectionStrategy, Component, Input } from '@angular/core'; -import { Dispatch } from '@ngxs-labs/dispatch-decorator'; import { Select, Store } from '@ngxs/store'; import { CustomSelectors } from '@others/custom-selectors'; import { FeatureFilledInfo } from '@pipes/fill-feature-info.pipe'; import { SharedActionsService } from '@services/shared-actions.service'; -import { Configuration } from '@store/actions/config.actions'; import { Observable } from 'rxjs'; @Component({ diff --git a/front/src/app/components/feature-run/feature-run.component.html b/front/src/app/components/feature-run/feature-run.component.html index 1ae0f74b..e3fee1d5 100644 --- a/front/src/app/components/feature-run/feature-run.component.html +++ b/front/src/app/components/feature-run/feature-run.component.html @@ -1,50 +1,55 @@ -
+
-
Passed
+
+ Passed
Failed
- + - - -
- {{ run.date_time | amParse | amDateFormat:'MMMM d yyyy, HH:mm a' }} + {{ test.result_date | amParse | amDateFormat:'MMMM d yyyy, HH:mm' }}
- {{ run.total }} + {{ test.total }}
- +
- +
- +
- {{ number | percentageField:run.total }} + {{ number | percentageField:test.total }} @@ -52,20 +57,15 @@
- {{ run.execution_time | secondsToHumanReadable }} + {{ test.execution_time | secondsToHumanReadable }}
- +
- {{ run.pixel_diff | pixelDifference }} + {{ test.pixel_diff | pixelDifference }}
- - -
- videocam -
-
@@ -75,98 +75,95 @@
- - -
- - -
+
-
Passed
Failed
- - - -
- -
+ +
{{ browser | browserComboText }}
- {{ test?.result_date | amParse | amDateFormat:'MMMM d yyyy, HH:mm a' | firstLetterUppercase }} + {{ run?.result_date | amParse | amDateFormat:'MMMM d yyyy, HH:mm a' | firstLetterUppercase }}
- {{ test.total }} + {{ run.total }}
-
- +
-
- +
-
- +
- - {{ number | percentageField:test.total }} + {{ number | percentageField:run.total }} - {{ number }}
- {{ test.execution_time | secondsToHumanReadable }} + {{ run.execution_time | secondsToHumanReadable }}
- {{ test.pixel_diff | pixelDifference }} + {{ run.pixel_diff | pixelDifference }}
- -
+ +
videocam
@@ -179,21 +176,18 @@
- - - - - - - - - + picture_as_pdfDownload PDF - - + insert_drive_file{{ file | downloadName }}
- - \ No newline at end of file + --> \ No newline at end of file diff --git a/front/src/app/components/feature-run/feature-run.component.scss b/front/src/app/components/feature-run/feature-run.component.scss index abbd2879..6089ab5b 100644 --- a/front/src/app/components/feature-run/feature-run.component.scss +++ b/front/src/app/components/feature-run/feature-run.component.scss @@ -85,7 +85,7 @@ align-items: center; justify-content: space-between; @include for-tablet-portrait-up { - flex: 0 250px; + flex: 0 200px; } i { height: 50px; @@ -101,7 +101,7 @@ flex: 1 35px; text-align: center; @include for-tablet-portrait-up { - flex: 0 100px; + flex: 0 90px; } } &.execution_time { diff --git a/front/src/app/components/feature-run/feature-run.component.ts b/front/src/app/components/feature-run/feature-run.component.ts index e7953224..b41e83cb 100644 --- a/front/src/app/components/feature-run/feature-run.component.ts +++ b/front/src/app/components/feature-run/feature-run.component.ts @@ -1,9 +1,9 @@ -import { Component, Input, ChangeDetectionStrategy, Optional, Host } from '@angular/core'; +import { Component, Input, ChangeDetectionStrategy, Optional, Host, OnInit } from '@angular/core'; import { Router, ActivatedRoute } from '@angular/router'; -import { MatDialog } from '@angular/material/dialog'; +import { MatLegacyDialog as MatDialog } from '@angular/material/legacy-dialog'; import { VideoComponent } from '@dialogs/video/video.component'; import { BehaviorSubject } from 'rxjs'; -import { MatSnackBar } from '@angular/material/snack-bar'; +import { MatLegacySnackBar as MatSnackBar } from '@angular/material/legacy-snack-bar'; import { switchMap } from 'rxjs/operators'; import { NetworkPaginatedListComponent } from '@components/network-paginated-list/network-paginated-list.component'; import { SharedActionsService } from '@services/shared-actions.service'; @@ -21,7 +21,7 @@ export class FeatureRunComponent { @Select(CustomSelectors.GetConfigProperty('percentMode')) percentMode$: Observable; - @Input() run: FeatureRun; + @Input() test: FeatureResult; show$ = new BehaviorSubject(false); @@ -34,24 +34,24 @@ export class FeatureRunComponent { @Optional() @Host() private _paginatedList: NetworkPaginatedListComponent ) { } - get browsers() { - if (this.run?.feature_results.length > 0) { - // Get unique browser icons - const browsers = []; - for (let i = 0; i < this.run.feature_results.length; i++) { - if (this.run.feature_results[i].browser) { - const browser = this.run.feature_results[i].browser.browser; - // Make sure of it's uniqueness - if (browser && !browsers.map(b => b.browser).includes(browser)) { - browsers.push(browser); - } - } - } - return browsers; - } else { - return []; - } - } + // get browsers() { + // if (this.run?.feature_results.length > 0) { + // // Get unique browser icons + // const browsers = []; + // for (let i = 0; i < this.run.feature_results.length; i++) { + // if (this.run.feature_results[i].browser) { + // const browser = this.run.feature_results[i].browser.browser; + // // Make sure of it's uniqueness + // if (browser && !browsers.map(b => b.browser).includes(browser)) { + // browsers.push(browser); + // } + // } + // } + // return browsers; + // } else { + // return []; + // } + // } openVideo(test: FeatureResult) { this._sharedActions.loadingObservable( @@ -67,47 +67,57 @@ export class FeatureRunComponent { } changeShow() { - // Go to Step View if we only have 1 result - if (this.run.feature_results.length === 1) { - this.stepView(this.run.run_id, this.run.feature_results[0]) - } else { - this.show$.next(!this.show$.getValue()); - } + // // Go to Step View if we only have 1 result + // // if (this.run.feature_results.length === 1) { + // this.stepView(this.run.run_id, this.run.feature_results[0]) + this.stepView(this.test) + // // } else { + // // this.show$.next(!this.show$.getValue()); + // // } } trackByFn(index, item: string) { return item; } - stepView(run_id: number, test: FeatureResult) { - this._router.navigate(['run', run_id, 'step', test.feature_result_id], { relativeTo: this._ac }).then(() => window.scrollTo(0, 0)); + // stepView(run_id: number, test: FeatureResult) { + stepView(test: FeatureResult) { + this._router.navigate(['step', test.feature_result_id], { relativeTo: this._ac }).then(() => window.scrollTo(0, 0)); } /** * Performs the overriding action through the Store */ - setResultStatus(result: FeatureResult, status: 'Success' | 'Failed' | '') { - this.reloadPageAfterAction( this._sharedActions.setResultStatus(result, status) ); + setResultStatus(test: FeatureResult, status: 'Success' | 'Failed' | '') { + this.reloadPageAfterAction( this._sharedActions.setResultStatus(test, status) ); } - /** - * Performs the overriding action through the Store - */ - setRunStatus(run: FeatureRun, status: 'Success' | 'Failed' | '') { - this.reloadPageAfterAction( this._sharedActions.setRunStatus(run, status) ); - } + // /** + // * Performs the overriding action through the Store + // */ + // setRunStatus(run: FeatureRun, status: 'Success' | 'Failed' | '') { + // this.reloadPageAfterAction( this._sharedActions.setRunStatus(run, status) ); + // } /** * Archives or unarchives a feature run or a feature result * @param run FeatureRun */ - archive(run: FeatureRun | FeatureResult) { - this.reloadPageAfterAction( this._sharedActions.archive(run) ); + archive(test: FeatureResult) { + this.reloadPageAfterAction( this._sharedActions.archive(test) ); } - deleteFeatureRun(run: FeatureRun) { - this.reloadPageAfterAction( this._sharedActions.deleteFeatureRun(run) ); - } + // /** + // * Archives or unarchives a feature run or a feature result + // * @param run FeatureRun + // */ + // archive(test: FeatureRun | FeatureResult) { + // this.reloadPageAfterAction( this._sharedActions.archive(test) ); + // } + + // deleteFeatureRun(run: FeatureRun) { + // this.reloadPageAfterAction( this._sharedActions.deleteFeatureRun(run) ); + // } deleteFeatureResult(test: FeatureResult) { this.reloadPageAfterAction( this._sharedActions.deleteFeatureResult(test) ); diff --git a/front/src/app/components/feature-titles/feature-titles.component.html b/front/src/app/components/feature-titles/feature-titles.component.html index a99ac0a3..7318f4db 100755 --- a/front/src/app/components/feature-titles/feature-titles.component.html +++ b/front/src/app/components/feature-titles/feature-titles.component.html @@ -1,5 +1,6 @@ - App: {{ app }} + Department: {{ dep }} + | Application: {{ app }} | Environment: {{ env }} | Test: {{ name }} \ No newline at end of file diff --git a/front/src/app/components/feature-titles/feature-titles.component.scss b/front/src/app/components/feature-titles/feature-titles.component.scss index f36d754b..54caa73f 100755 --- a/front/src/app/components/feature-titles/feature-titles.component.scss +++ b/front/src/app/components/feature-titles/feature-titles.component.scss @@ -6,7 +6,8 @@ flex: 1; line-height: 30px; margin-bottom: 20px; - @include for-tablet-portrait-up { + // will remove margin screen width is 630 or more + @include for-tablet-portrait-up-plus30 { margin-bottom: 0; } } \ No newline at end of file diff --git a/front/src/app/components/folder-item-tree/folder-item-tree.component.html b/front/src/app/components/folder-item-tree/folder-item-tree.component.html index 777713af..10b14b95 100644 --- a/front/src/app/components/folder-item-tree/folder-item-tree.component.html +++ b/front/src/app/components/folder-item-tree/folder-item-tree.component.html @@ -6,11 +6,15 @@ --> -
+ +
- keyboard_arrow_right + + keyboard_arrow_right -
+
domain @@ -19,9 +23,11 @@
{{ folder.name }}
-
+ +
- +
\ No newline at end of file diff --git a/front/src/app/components/folder-item-tree/folder-item-tree.component.scss b/front/src/app/components/folder-item-tree/folder-item-tree.component.scss index 430e51bb..65e239fc 100644 --- a/front/src/app/components/folder-item-tree/folder-item-tree.component.scss +++ b/front/src/app/components/folder-item-tree/folder-item-tree.component.scss @@ -15,11 +15,21 @@ color: $darker-step; padding: 0px 10px; box-sizing: border-box; + cursor: pointer; .item { - cursor: pointer; - display: flex; - align-items: center; - flex: 1; + width: 100%; + display: grid; + place-items: center start; + grid-template-columns: 30px calc(100% - var(--itemMinWidth)); + overflow: hidden; + + .folderName { + display: block; + max-width: calc(100% + var(--folderNameWidth)); + overflow: hidden; + text-overflow: ellipsis; + } + mat-icon { flex: 0 24px; margin-right: 8px; @@ -27,7 +37,6 @@ } mat-icon { flex: 0 20px; - cursor: unset; transition: transform .15s ease-in-out; } &:hover { @@ -39,8 +48,7 @@ background-color: rgba($blue, .1) !important; } -.selected .item{ - cursor: unset; +.selected .item { .folderName { color: $blue; } diff --git a/front/src/app/components/folder-item-tree/folder-item-tree.component.ts b/front/src/app/components/folder-item-tree/folder-item-tree.component.ts index f9c91a07..fab1d0dc 100644 --- a/front/src/app/components/folder-item-tree/folder-item-tree.component.ts +++ b/front/src/app/components/folder-item-tree/folder-item-tree.component.ts @@ -6,13 +6,14 @@ * @author: dph000 */ import { ChangeDetectionStrategy, Component, Input, OnInit } from '@angular/core'; -import { Dispatch } from '@ngxs-labs/dispatch-decorator'; import { Select, Store } from '@ngxs/store'; +import { SharedActionsService } from '@services/shared-actions.service'; import { CustomSelectors } from '@others/custom-selectors'; import { Configuration } from '@store/actions/config.actions'; import { Features } from '@store/actions/features.actions'; import { FeaturesState } from '@store/features.state'; import { BehaviorSubject, Observable } from 'rxjs'; +import { LogService } from '@services/log.service'; @Component({ selector: 'cometa-folder-item-tree', @@ -22,9 +23,13 @@ import { BehaviorSubject, Observable } from 'rxjs'; }) export class FolderItemTreeComponent implements OnInit { - constructor( - private _store: Store - ) { } + // stores state for each folder in hierarchy + folderState = {}; + + constructor(private _store: Store, public _sharedActions: SharedActionsService, private log: LogService) { + this.getOrSetDefaultFolderState(); + this.log.msg("1","Getting folder tree state...","folder-item-tree", this.folderState); + } @Input() folder: Folder; @Input() level: number; @@ -38,7 +43,6 @@ export class FolderItemTreeComponent implements OnInit { */ expanded$: BehaviorSubject; - // NgOnInit ngOnInit() { if (this.folder.folder_id === 0) { this.expanded$ = new BehaviorSubject(true); @@ -56,12 +60,15 @@ export class FolderItemTreeComponent implements OnInit { */ // Hides the sidenav - @Dispatch() hideSidenav = () => new Configuration.SetProperty('openedSidenav', false); + hideSidenav() { + this.log.msg("1","Expanding/Closing folder...","folder-item-tree"); + return this._store.dispatch(new Configuration.SetProperty('openedSidenav', false)); + } // Hides / shows the sidenav - @Dispatch() toggleSidenav() { + toggleSidenav() { const opened = this._store.selectSnapshot(CustomSelectors.GetConfigProperty('openedSidenav')); - return new Configuration.SetProperty('openedSidenav', !opened); + return this._store.dispatch(new Configuration.SetProperty('openedSidenav', !opened)); } /** @@ -71,8 +78,9 @@ export class FolderItemTreeComponent implements OnInit { * @date 06-10-21 * @lastModification 06-10-21 */ - @Dispatch() toggleSearch() { - return new Configuration.SetProperty('openedSearch', false); + toggleSearch() { + this.log.msg("1","Toggling folder state...","folder-item-tree"); + return this._store.dispatch(new Configuration.SetProperty('openedSearch', false)); } /** @@ -82,36 +90,45 @@ export class FolderItemTreeComponent implements OnInit { * @date 04-10-21 * @lastModification 05-10-21 */ - @Dispatch() toggleList() { this.toggleSidenav(); - return new Configuration.SetProperty('co_active_list', 'list', true); + return this._store.dispatch(new Configuration.SetProperty('co_active_list', 'list', true)); } /** * General functions */ - /** - * Function to toggle expanded state of current folder - * @param canClose If the current folder should close - */ - toggleRow(canClose: boolean) { + // reverses the folder state from true to false or viceversa + toggleRow() { let status = !this.expanded$.getValue(); - if (!canClose && !status) { - return - } this.expanded$.next(status); } - toggleExpandFromArrow(event: MouseEvent) { - this.toggleRow(true); - event.stopPropagation(); - } - - // Changes the current folder and closes every active expandable + // toggles clicked department/folder toggleExpand() { - this.toggleRow(false); // Don't close when clicking into folder icon or text + // update state incase it has been changed. + this.getOrSetDefaultFolderState(); + + // toggle folder (open/close) + this.toggleRow(); + + // modify existing folder state, or add new instance of folder with its state + this.folderState[this.folder.name] = { + open: this.expanded$.getValue() + }; + + // #3414 -------------------------------------------------start + // change browser url, add folder ids as params + this.log.msg("1","Setting folder id as url param...","folder-item-tree"); + this._sharedActions.set_url_folder_params(this.parent); + // #3414 ---------------------------------------------------end + + // refresh localstorage, so the next time this component view is rendered, it behaves correctly + this.log.msg("1","Saving folder tree state to localstorage...","folder-item-tree", this.folderState); + localStorage.setItem('co_folderState', JSON.stringify(this.folderState)); + + if (this.folder.folder_id == 0) { this._store.dispatch(new Features.ReturnToFolderRoute(0)); } else { @@ -122,4 +139,11 @@ export class FolderItemTreeComponent implements OnInit { this.toggleSearch(); } + getOrSetDefaultFolderState(): void { + // get folder hierarchy state from localstorage + // if localstorage is empty, then just set the comment + this.folderState = + JSON.parse(localStorage.getItem('co_folderState')) || { comment: "This object stores the state of whole folder hierarchy in localstorage" }; + } + } diff --git a/front/src/app/components/folder-tree/folder-tree.component.html b/front/src/app/components/folder-tree/folder-tree.component.html index 09cc9ae8..58c73f7e 100644 --- a/front/src/app/components/folder-tree/folder-tree.component.html +++ b/front/src/app/components/folder-tree/folder-tree.component.html @@ -6,13 +6,23 @@ --> -
+
home Home
+ +
+ format_indent_increase + Data Driven +
+ +
+ settings_input_component + Variables +
people diff --git a/front/src/app/components/folder-tree/folder-tree.component.ts b/front/src/app/components/folder-tree/folder-tree.component.ts index 0309757c..071cf56e 100644 --- a/front/src/app/components/folder-tree/folder-tree.component.ts +++ b/front/src/app/components/folder-tree/folder-tree.component.ts @@ -9,10 +9,12 @@ import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core'; import { Select, Store } from '@ngxs/store'; import { Observable } from 'rxjs'; import { CustomSelectors } from '@others/custom-selectors'; -import { Dispatch } from '@ngxs-labs/dispatch-decorator'; import { Configuration } from '@store/actions/config.actions'; import { Features } from '@store/actions/features.actions'; import { FeaturesState } from '@store/features.state'; +import { Router } from '@angular/router'; +import { LogService } from '@services/log.service'; +import { SharedActionsService } from '@services/shared-actions.service'; @Component({ selector: 'cometa-folder-tree', @@ -21,21 +23,30 @@ import { FeaturesState } from '@store/features.state'; changeDetection: ChangeDetectionStrategy.OnPush }) export class FolderTreeComponent implements OnInit { - - constructor(private _store: Store) { } + constructor(private _store: Store, private _router: Router, private log: LogService, private _sharedActions: SharedActionsService) { } @Select(CustomSelectors.GetConfigProperty('co_active_list')) activeList$: Observable; // Checks if the recent list is active @Select(FeaturesState.GetCurrentRouteNew) route$: Observable>; // Get the current route - /** - * Global variables - */ + // Global variables folders$: Observable; - // NgOnInit ngOnInit() { + this.log.msg("1","Inicializing component...","folder-tree"); + this.folders$ = this._store.select(CustomSelectors.GetDepartmentFolders()); // Get the list of departments available to the user this.activeList$.subscribe(value => localStorage.setItem('co_active_list', value)); // Initialize the recentList_active variable in the local storage + + // as soon as the view is loaded, get the route of the folder that was selected last + // the last selected folder route is saved in localstorage from store/actions/features.state.ts in a function called setFolderRoute + // it is actualized ever time any folder is clicked + const last_selected_folder = JSON.parse(localStorage.getItem('co_last_selected_folder_route')); + this.log.msg("1","Getting last selected folder route...","folder-tree", last_selected_folder); + + // dispach recieved route to features state manager to adapt the path of the currently selected folder accordingly + // this will load the same folder path that user was working on, before reloading browser window + this.log.msg("1","Dispatching last selected folder route to store...","folder-tree", last_selected_folder); + this._store.dispatch(new Features.SetFolderRoute(last_selected_folder)); } /** @@ -43,7 +54,10 @@ export class FolderTreeComponent implements OnInit { */ // Hides the sidenav - @Dispatch() hideSidenav = () => new Configuration.SetProperty('openedSidenav', false); + hideSidenav() { + this.log.msg("1","Hiding sidenav...","folder-tree"); + return this._store.dispatch(new Configuration.SetProperty('openedSidenav', false)); + } /** * Toggle the recent list variable in the store @@ -52,11 +66,15 @@ export class FolderTreeComponent implements OnInit { * @date 08-10-21 * @lastModification 08-10-21 */ - @Dispatch() toggleListType(listType: string) { - return new Configuration.SetProperty('co_active_list', listType, true); + this.log.msg("1","Navigating to root(home)...","folder-tree"); + this._sharedActions.set_url_folder_params(""); + + this._router.navigate(['/new']); + return this._store.dispatch(new Configuration.SetProperty('co_active_list', listType, true)); } + /** * Global functions */ diff --git a/front/src/app/components/folder/folder.component.ts b/front/src/app/components/folder/folder.component.ts index 0ebf4776..8118ab1e 100644 --- a/front/src/app/components/folder/folder.component.ts +++ b/front/src/app/components/folder/folder.component.ts @@ -1,11 +1,10 @@ import { Component, ChangeDetectionStrategy, Input } from '@angular/core'; import { Store } from '@ngxs/store'; -import { MatDialog } from '@angular/material/dialog'; +import { MatLegacyDialog as MatDialog } from '@angular/material/legacy-dialog'; import { ApiService } from '@services/api.service'; import { switchMap, tap } from 'rxjs/operators'; -import { MatSnackBar } from '@angular/material/snack-bar'; +import { MatLegacySnackBar as MatSnackBar } from '@angular/material/legacy-snack-bar'; import { Subscribe } from 'app/custom-decorators'; -import { Dispatch } from '@ngxs-labs/dispatch-decorator'; import { Features } from '@store/actions/features.actions'; import { AddFolderComponent } from '@dialogs/add-folder/add-folder.component'; import { SharedActionsService } from '@services/shared-actions.service'; @@ -28,7 +27,7 @@ export class FolderComponent { @Input() folder: Folder; - @Dispatch() open = () => new Features.AddFolderRoute(this.folder) + open = () => this._store.dispatch(new Features.AddFolderRoute(this.folder)) modify() { this._dialog.open(AddFolderComponent, { diff --git a/front/src/app/components/header/header.component.html b/front/src/app/components/header/header.component.html index 0595d306..c0a11991 100755 --- a/front/src/app/components/header/header.component.html +++ b/front/src/app/components/header/header.component.html @@ -22,12 +22,13 @@ \ No newline at end of file diff --git a/front/src/app/components/header/header.component.ts b/front/src/app/components/header/header.component.ts index 9cd26fcf..708a92a7 100755 --- a/front/src/app/components/header/header.component.ts +++ b/front/src/app/components/header/header.component.ts @@ -1,12 +1,12 @@ import { Component, ChangeDetectionStrategy } from '@angular/core'; import { trigger, state, style, animate, transition } from '@angular/animations'; -import { Dispatch } from '@ngxs-labs/dispatch-decorator'; import { UserState } from '@store/user.state'; import { SharedActionsService } from '@services/shared-actions.service'; import { ViewSelectSnapshot } from '@ngxs-labs/select-snapshot'; import { CustomSelectors } from '@others/custom-selectors'; import { Configuration } from '@store/actions/config.actions'; import { User } from '@store/actions/user.actions'; +import { Store } from '@ngxs/store'; @Component({ selector: 'header', @@ -36,12 +36,13 @@ export class HeaderComponent { @ViewSelectSnapshot(CustomSelectors.GetConfigProperty('internal.openedMenu')) openedMenu: boolean; constructor( - public _sharedActions: SharedActionsService + public _sharedActions: SharedActionsService, + private _store: Store ) { } - @Dispatch() closeMenu = () => new Configuration.SetProperty('internal.openedMenu', false); + closeMenu = () => this._store.dispatch(new Configuration.SetProperty('internal.openedMenu', false)); - @Dispatch() openMenu = () => new Configuration.SetProperty('internal.openedMenu', true); + openMenu = () => this._store.dispatch(new Configuration.SetProperty('internal.openedMenu', true)); - @Dispatch() logout = () => new User.Logout(); + logout = () => this._store.dispatch(new User.Logout()); } diff --git a/front/src/app/components/help/help.component.html b/front/src/app/components/help/help.component.html index 794ffe76..a0da6364 100755 --- a/front/src/app/components/help/help.component.html +++ b/front/src/app/components/help/help.component.html @@ -42,7 +42,7 @@
Actions
-

These are the actions available for use in any testplan:

+

These are short descriptions of the actions available for use in any testplan. A detailed documentation can be found in the GitHub Cometa-Rocks.

{{ action.action_name }}

diff --git a/front/src/app/components/l1-feature-item-list/l1-feature-item-list.component.ts b/front/src/app/components/l1-feature-item-list/l1-feature-item-list.component.ts index a15ac422..4cf8f8dd 100644 --- a/front/src/app/components/l1-feature-item-list/l1-feature-item-list.component.ts +++ b/front/src/app/components/l1-feature-item-list/l1-feature-item-list.component.ts @@ -14,12 +14,12 @@ import { observableLast, Subscribe } from 'ngx-amvara-toolbox'; import { NavigationService } from '@services/navigation.service'; import { ViewSelectSnapshot } from '@ngxs-labs/select-snapshot'; import { SharedActionsService } from '@services/shared-actions.service'; -import { Dispatch } from '@ngxs-labs/dispatch-decorator'; import { Features } from '@store/actions/features.actions'; import { AddFolderComponent } from '@dialogs/add-folder/add-folder.component'; import { ApiService } from '@services/api.service'; -import { MatDialog } from '@angular/material/dialog'; -import { MatSnackBar } from '@angular/material/snack-bar'; +import { MatLegacyDialog as MatDialog } from '@angular/material/legacy-dialog'; +import { MatLegacySnackBar as MatSnackBar } from '@angular/material/legacy-snack-bar'; +import { LogService } from '@services/log.service'; @Component({ selector: 'cometa-l1-feature-item-list', @@ -34,7 +34,9 @@ export class L1FeatureItemListComponent implements OnInit { public _sharedActions: SharedActionsService, private _dialog: MatDialog, private _api: ApiService, - private _snackBar: MatSnackBar + private _snackBar: MatSnackBar, + private log: LogService + ) { } // Receives the item from the parent component @@ -54,6 +56,8 @@ export class L1FeatureItemListComponent implements OnInit { // NgOnInit ngOnInit() { + this.log.msg("1","Inicializing component...","feature-item-list"); + this.feature$ = this._store.select(CustomSelectors.GetFeatureInfo(this.feature_id)); // Subscribe to the running state comming from NGXS this.featureRunning$ = this._store.select(CustomSelectors.GetFeatureRunningStatus(this.feature_id)); @@ -84,9 +88,16 @@ export class L1FeatureItemListComponent implements OnInit { */ // Go to the clicked folder - @Dispatch() goFolder(route: Folder[]) { - return new Features.SetFolderRoute(route); + this.log.msg("1","Opening folder...","feature-item-list", route); + // dispach the route of clicked folder + this._store.dispatch(new Features.SetFolderRoute(route)); + + // get absolute path of current route, including department + const currentRoute = this._store.snapshot().features.currentRouteNew; + + // add clicked folder's id hierarchy to url params + this._sharedActions.set_url_folder_params(currentRoute); } // Modify the clicked folder @@ -103,6 +114,7 @@ export class L1FeatureItemListComponent implements OnInit { // Delete the clicked folder @Subscribe() delete(folder: Folder) { + this.log.msg("1","Deleting folder...","feature-item-list", folder); return this._api.removeFolder(folder.folder_id).pipe( switchMap(_ => this._store.dispatch( new Features.GetFolders )), tap(_ => this._snackBar.open(`Folder ${folder.name} removed`, 'OK')) @@ -111,11 +123,13 @@ export class L1FeatureItemListComponent implements OnInit { // Moves the selected folder SAmoveFolder(folder: Folder) { + this.log.msg("1","Moving folder...","feature-item-list", folder); this._sharedActions.moveFolder(folder); } // Moves the selected feature SAmoveFeature(feature: Feature) { + this.log.msg("1","Moving feature...","feature-item-list", feature); this._sharedActions.moveFeature(feature); } } diff --git a/front/src/app/components/l1-feature-list/l1-feature-list.component.html b/front/src/app/components/l1-feature-list/l1-feature-list.component.html index 70ce0b13..d598a4e2 100644 --- a/front/src/app/components/l1-feature-list/l1-feature-list.component.html +++ b/front/src/app/components/l1-feature-list/l1-feature-list.component.html @@ -35,7 +35,7 @@ (rowClick)="openContent($event.rowData)" (page)="storePagination($event)" (sortChange)="saveSort($event)" - [cellTemplate]="{orderType: runningTpl, id: idTpl, name: nameTpl, status: statusTpl, date: dateTpl, time: timeTpl, total: totalTpl, department: departmentTpl, app: applicationTpl, environment: environmentTpl, browsers: browsersTpl, schedule: scheduleTpl, reference: optionsTpl}" + [cellTemplate]="{orderType: runningTpl, id: idTpl, name: nameTpl, status: statusTpl, date: dateTpl, time: timeTpl, total: totalTpl, ok: okTpl, fails: failsTpl, department: departmentTpl, app: applicationTpl, environment: environmentTpl, browsers: browsersTpl, schedule: scheduleTpl, reference: optionsTpl}" [pageSizeOptions]="[10, 25, 50, 100, 200]">
@@ -141,6 +141,28 @@ + + + + +
+ {{ row.ok }} +
+
+
+ + + + + + +
+ {{ row.fails }} +
+
+
+ + diff --git a/front/src/app/components/l1-feature-list/l1-feature-list.component.scss b/front/src/app/components/l1-feature-list/l1-feature-list.component.scss index 2cc2a3c7..3680b90e 100644 --- a/front/src/app/components/l1-feature-list/l1-feature-list.component.scss +++ b/front/src/app/components/l1-feature-list/l1-feature-list.component.scss @@ -67,6 +67,7 @@ } } } + td { font-weight: bold; color: $dark; diff --git a/front/src/app/components/l1-feature-list/l1-feature-list.component.ts b/front/src/app/components/l1-feature-list/l1-feature-list.component.ts index 4ab9ad72..00163569 100644 --- a/front/src/app/components/l1-feature-list/l1-feature-list.component.ts +++ b/front/src/app/components/l1-feature-list/l1-feature-list.component.ts @@ -10,20 +10,20 @@ import { ChangeDetectionStrategy, Component, EventEmitter, Input, OnInit, Output import { Select, Store } from '@ngxs/store'; import { SharedActionsService } from '@services/shared-actions.service'; import { BehaviorSubject, Observable, switchMap, tap } from 'rxjs'; -import { MatTableDataSource } from '@angular/material/table'; -import { MatPaginator } from '@angular/material/paginator'; +import { MatLegacyTableDataSource as MatTableDataSource } from '@angular/material/legacy-table'; +import { MatLegacyPaginator as MatPaginator } from '@angular/material/legacy-paginator'; import { MatSort } from '@angular/material/sort'; -import { Dispatch } from '@ngxs-labs/dispatch-decorator'; import { Features } from '@store/actions/features.actions'; import { Subscribe } from 'ngx-amvara-toolbox'; import { AddFolderComponent } from '@dialogs/add-folder/add-folder.component'; -import { MatDialog } from '@angular/material/dialog'; +import { MatLegacyDialog as MatDialog } from '@angular/material/legacy-dialog'; import { ApiService } from '@services/api.service'; -import { MatSnackBar } from '@angular/material/snack-bar'; +import { MatLegacySnackBar as MatSnackBar } from '@angular/material/legacy-snack-bar'; import { ViewSelectSnapshot } from '@ngxs-labs/select-snapshot'; import { UserState } from '@store/user.state'; import { CustomSelectors } from '@others/custom-selectors'; import { Configuration } from '@store/actions/config.actions'; +import { LogService } from '@services/log.service'; @Component({ selector: 'cometa-l1-feature-list', @@ -39,6 +39,7 @@ export class L1FeatureListComponent implements OnInit{ private _dialog: MatDialog, private _api: ApiService, private _snackBar: MatSnackBar, + private log: LogService ) { } @Input() data$: any; // Contains the new structure of the features / folders @@ -69,9 +70,20 @@ export class L1FeatureListComponent implements OnInit{ {header: 'Last run', field: 'date', sortable: true}, {header: 'Last duration', field: 'time', sortable: true}, {header: 'Last steps', field: 'total', sortable: true}, - {header: 'Department', field: 'department', sortable: true}, - {header: 'Application', field: 'app', sortable: true}, - {header: 'Environment', field: 'environment', sortable: true}, + + // #3427 -------------------------------------------------------- start + // hide department, application, environment and show successfull and files steps cuantity + {header: 'OK', field: 'ok', sortable: true}, + {header: 'NOK', field: 'fails', sortable: true}, + // #3427 ---------------------------------------------------------- end + + // #3427 -------------------------------------------------------- start + // by default these columns will not be shown + {header: 'Department', field: 'department', sortable: true, hide: true}, + {header: 'Application', field: 'app', sortable: true, hide: true}, + {header: 'Environment', field: 'environment', sortable: true, hide: true}, + // #3427 -------------------------------------------------------- start + {header: 'Browsers', field: 'browsers', sortable: true}, {header: 'Schedule', field: 'schedule', sortable: true}, {header: 'Options', field: 'reference'} @@ -90,9 +102,11 @@ export class L1FeatureListComponent implements OnInit{ // Creates a source for the data tableValues = new BehaviorSubject>(new MatTableDataSource([])); - // NgOnInit ngOnInit() { + this.log.msg("1","Inicializing component...","feature-list"); + // Initialize the co_features_pagination variable in the local storage + this.log.msg("1","Loading feature pagination...","feature-list"); this.featuresPagination$.subscribe(value => localStorage.setItem('co_features_pagination', value)); // load column settings @@ -110,10 +124,10 @@ export class L1FeatureListComponent implements OnInit{ * @date 15-10-21 * @lastModification 15-10-21 */ - @Dispatch() - storePagination(event) { - return new Configuration.SetProperty('co_features_pagination', event.pageSize, true); - } + storePagination(event) { + this.log.msg("1","Storing feature pagination in localstorage...","feature-list", event.pageSize); + return this._store.dispatch(new Configuration.SetProperty('co_features_pagination', event.pageSize, true)); + } /** * Saves current sort settings to localstorage @@ -123,6 +137,7 @@ export class L1FeatureListComponent implements OnInit{ */ saveSort(event) { // save to localstorage + this.log.msg("1","Saving chosen sort in localstorage...","feature-list", event); localStorage.setItem("co_feature_table_sort_v2", JSON.stringify(event)); } @@ -152,6 +167,8 @@ export class L1FeatureListComponent implements OnInit{ * @param event Looks something like this: [{label: "Type / Run", field: "orderType", show: false, header: "Type / Run", hide: true}, ...] */ saveColumnSettings(event){ + this.log.msg("1","Saving column settings...","feature-list", event); + // add missing keys for next reload event.forEach(column => { // get default properties for current column @@ -173,6 +190,8 @@ export class L1FeatureListComponent implements OnInit{ * @date 2021-12-28 */ getSavedColumnSettings() { + this.log.msg("1","Getting saved column settings...","feature-list"); + // check if co_feature_table_columns_v2 exists in localstorage if so import it into columns else use default this.columns = JSON.parse( localStorage.getItem('co_feature_table_columns_v2') ) // get value from localstorage || @@ -183,10 +202,20 @@ export class L1FeatureListComponent implements OnInit{ openContent(row:any) { switch(row.type) { case 'feature': + this.log.msg("1","opening feature with id...","feature-list", row.id); this._sharedActions.goToFeature(row.id); break; case 'folder': this.setFolder(row.route); + + // #3414 -------------------------------------------------start + // path to currently displayed folder + const currentRoute = this._store.snapshot().features.currentRouteNew; + + // change browser url, add folder id hierarchy as params + this._sharedActions.set_url_folder_params(currentRoute); + // #3414 ---------------------------------------------------end + // close add feature or folder menu this.closeAddButtons(); break; @@ -206,17 +235,19 @@ export class L1FeatureListComponent implements OnInit{ // Go to the clicked route setFolder(route: Folder[]) { + this.log.msg("1","Redirecting to route...","feature-list", route); this._store.dispatch(new Features.SetFolderRoute(route)); } // Go to the clicked folder - @Dispatch() goFolder(folder: Folder) { - return new Features.NewAddFolderRoute(folder); + this.log.msg("1","Opening folder...","feature-list", folder); + return this._store.dispatch(new Features.NewAddFolderRoute(folder)); } // Modify the clicked folder modify(folder: Folder) { + this.log.msg("1","Modifying folder...","feature-list", folder); this._dialog.open(AddFolderComponent, { autoFocus: true, data: { @@ -229,6 +260,7 @@ export class L1FeatureListComponent implements OnInit{ // Delete the clicked folder @Subscribe() delete(folder: Folder) { + this.log.msg("1","Deleting folder...","feature-list", folder); return this._api.removeFolder(folder.folder_id).pipe( switchMap(_ => this._store.dispatch( new Features.GetFolders )), tap(_ => this._snackBar.open(`Folder ${folder.name} removed`, 'OK')) @@ -241,40 +273,48 @@ export class L1FeatureListComponent implements OnInit{ // Opens a menu to create a new feature SAopenCreateFeature() { + this.log.msg("1","Opening create feature menu...","feature-list"); this._sharedActions.openEditFeature(); } // Runs the clicked feature SArunFeature(id: number) { + this.log.msg("1","Running feature with id...","feature-list", id); this._sharedActions.run(id); } // Edits the schedule of the clicked feature SAeditSchedule(id: number) { + this.log.msg("1","Editing shedule of feature with id...","feature-list", id); this._sharedActions.editSchedule(id); } // Opens the menu to edit the clicked feature SAopenEditFeature(id: number, mode) { + this.log.msg("1","Editing feature with id...","feature-list", id); this._sharedActions.openEditFeature(id, mode); } // Moves the selected feature SAmoveFeature(feature: Feature) { + this.log.msg("1","Moving feature...","feature-list", feature); this._sharedActions.moveFeature(feature); } // Handles the settings of the clicked feature SAhandleSetting(id: number, mode, event) { + this.log.msg("1","Handling setting of feature width id...","feature-list", id); this._sharedActions.handleSetting(id, mode, event); } SAdeleteFeature(id: number) { + this.log.msg("1","Deleting feature width id...","feature-list", id); this._sharedActions.deleteFeature(id); } // Moves the selected folder SAmoveFolder(folder: Folder) { + this.log.msg("1","Moving folder...","feature-recent-list", folder); this._sharedActions.moveFolder(folder); } } diff --git a/front/src/app/components/l1-feature-recent-list/l1-feature-recent-list.component.html b/front/src/app/components/l1-feature-recent-list/l1-feature-recent-list.component.html index 7f0c1e0d..026fb8d3 100644 --- a/front/src/app/components/l1-feature-recent-list/l1-feature-recent-list.component.html +++ b/front/src/app/components/l1-feature-recent-list/l1-feature-recent-list.component.html @@ -77,7 +77,7 @@
-
+
Running
diff --git a/front/src/app/components/l1-feature-recent-list/l1-feature-recent-list.component.ts b/front/src/app/components/l1-feature-recent-list/l1-feature-recent-list.component.ts index add5d6f4..6be27564 100644 --- a/front/src/app/components/l1-feature-recent-list/l1-feature-recent-list.component.ts +++ b/front/src/app/components/l1-feature-recent-list/l1-feature-recent-list.component.ts @@ -15,19 +15,19 @@ import { Select, Store } from '@ngxs/store'; import { CustomSelectors } from '@others/custom-selectors'; import { SharedActionsService } from '@services/shared-actions.service'; import { BehaviorSubject, Observable, switchMap, tap } from 'rxjs'; -import { MatTableDataSource } from '@angular/material/table'; -import { MatPaginator } from '@angular/material/paginator'; +import { MatLegacyTableDataSource as MatTableDataSource } from '@angular/material/legacy-table'; +import { MatLegacyPaginator as MatPaginator } from '@angular/material/legacy-paginator'; import { MatSort } from '@angular/material/sort'; -import { Dispatch } from '@ngxs-labs/dispatch-decorator'; import { Features } from '@store/actions/features.actions'; import { Subscribe } from 'ngx-amvara-toolbox'; import { AddFolderComponent } from '@dialogs/add-folder/add-folder.component'; -import { MatDialog } from '@angular/material/dialog'; +import { MatLegacyDialog as MatDialog } from '@angular/material/legacy-dialog'; import { ApiService } from '@services/api.service'; -import { MatSnackBar } from '@angular/material/snack-bar'; +import { MatLegacySnackBar as MatSnackBar } from '@angular/material/legacy-snack-bar'; import { ViewSelectSnapshot } from '@ngxs-labs/select-snapshot'; import { UserState } from '@store/user.state'; import { Configuration } from '@store/actions/config.actions'; +import { LogService } from '@services/log.service'; @Component({ selector: 'cometa-l1-feature-recent-list', @@ -43,6 +43,7 @@ export class L1FeatureRecentListComponent{ private _dialog: MatDialog, private _api: ApiService, private _snackBar: MatSnackBar, + private log: LogService ) { } // Contains the new structure of the features / folders @@ -107,9 +108,11 @@ export class L1FeatureRecentListComponent{ openContent(row:any) { switch(row.type) { case 'feature': + this.log.msg("1","opening feature with id...","feature-recent-list", row.id); this._sharedActions.goToFeature(row.id); break; case 'folder': + this.log.msg("1","opening folder with route...","feature-recent-list", row.route); this.setFolder(row.route); break; default: @@ -128,17 +131,19 @@ export class L1FeatureRecentListComponent{ // Go to the clicked route setFolder(route: Folder[]) { + this.log.msg("1","Setting folder route...","feature-recent-list", route); this._store.dispatch(new Features.SetFolderRoute(route)); } // Go to the clicked folder - @Dispatch() goFolder(folder: Folder) { - return new Features.NewAddFolderRoute(folder); + this.log.msg("1","redirecting to folder...","feature-recent-list", folder); + return this._store.dispatch(new Features.NewAddFolderRoute(folder)); } // Modify the clicked folder modify(folder: Folder) { + this.log.msg("1","Editing folder...","feature-recent-list", folder); this._dialog.open(AddFolderComponent, { autoFocus: true, data: { @@ -151,6 +156,7 @@ export class L1FeatureRecentListComponent{ // Delete the clicked folder @Subscribe() delete(folder: Folder) { + this.log.msg("1","Deleting folder...","feature-recent-list", folder); return this._api.removeFolder(folder.folder_id).pipe( switchMap(_ => this._store.dispatch( new Features.GetFolders )), tap(_ => this._snackBar.open(`Folder ${folder.name} removed`, 'OK')) @@ -164,9 +170,10 @@ export class L1FeatureRecentListComponent{ * * @return sets the current status of the sidenav on mobile */ - @Dispatch() toggleSidenav() { + toggleSidenav() { + this.log.msg("1","Toggling sidenav...","feature-recent-list"); const opened = this._store.selectSnapshot(CustomSelectors.GetConfigProperty('openedSidenav')); - return new Configuration.SetProperty('openedSidenav', !opened); + return this._store.dispatch(new Configuration.SetProperty('openedSidenav', !opened)); } /** @@ -175,40 +182,48 @@ export class L1FeatureRecentListComponent{ // Opens a menu to create a new feature SAopenCreateFeature() { + this.log.msg("1","Opening create feature menu...","feature-recent-list"); this._sharedActions.openEditFeature(); } // Runs the clicked feature SArunFeature(id: number) { + this.log.msg("1","Running feature with id...","feature-recent-list", id); this._sharedActions.run(id); } // Edits the schedule of the clicked feature SAeditSchedule(id: number) { + this.log.msg("1","Editing shedule of feature with id...","feature-recent-list", id); this._sharedActions.editSchedule(id); } // Opens the menu to edit the clicked feature SAopenEditFeature(id: number, mode) { + this.log.msg("1","Editing feature with id...","feature-recent-list", id); this._sharedActions.openEditFeature(id, mode); } // Moves the selected feature SAmoveFeature(feature: Feature, previousFolder?: number) { + this.log.msg("1","Moving feature...","feature-recent-list", feature); this._sharedActions.moveFeature(feature); } // Handles the settings of the clicked feature SAhandleSetting(id: number, mode, event) { + this.log.msg("1","Handling setting of feature width id...","feature-recent-list", id); this._sharedActions.handleSetting(id, mode, event); } SAdeleteFeature(id: number) { + this.log.msg("1","Deleting feature width id...","feature-recent-list", id); this._sharedActions.deleteFeature(id); } // Moves the selected folder SAmoveFolder(folder: Folder) { + this.log.msg("1","Moving folder...","feature-recent-list", folder); this._sharedActions.moveFolder(folder); } } \ No newline at end of file diff --git a/front/src/app/components/l1-filter/l1-filter.component.html b/front/src/app/components/l1-filter/l1-filter.component.html index 7839242a..71928593 100644 --- a/front/src/app/components/l1-filter/l1-filter.component.html +++ b/front/src/app/components/l1-filter/l1-filter.component.html @@ -7,7 +7,7 @@ -->
- +
-
+
diff --git a/front/src/app/components/l1-filter/l1-filter.component.scss b/front/src/app/components/l1-filter/l1-filter.component.scss index f96dee56..2afe34a8 100644 --- a/front/src/app/components/l1-filter/l1-filter.component.scss +++ b/front/src/app/components/l1-filter/l1-filter.component.scss @@ -27,12 +27,29 @@ i { height: 18px; width: 18px; - background-image: url(^assets/internal/COM_Menu_Icon_Sidemap.svg); + background-image: url(^assets/internal/COM_Menu_Icon_Sidemap_Arrow.svg); background-repeat: no-repeat; background-position: center; background-size: contain; display: inline-block; vertical-align: middle; + + // #3358 + //setting round border to folder toggle icon + border: 1px solid $darker-step; + border-radius: 50%; + + //rotate when in mobile mode, reason: sidebar closes automatically when it is displayed in mobile device, so icon must rotate to indicate 'open navbar' + @include for-phone-only { transform: rotate(180deg); } + + + &.sidebar-closed { + transform: rotate(180deg); + } + + &:hover { + background-color: rgba($blue, .1) !important; + } } } @@ -203,5 +220,5 @@ .toggle-search { display: none; - transition: display .15s ease-in-out + transition: display .15s ease-in-out; } \ No newline at end of file diff --git a/front/src/app/components/l1-filter/l1-filter.component.ts b/front/src/app/components/l1-filter/l1-filter.component.ts index fa57733e..3b3d08bb 100644 --- a/front/src/app/components/l1-filter/l1-filter.component.ts +++ b/front/src/app/components/l1-filter/l1-filter.component.ts @@ -10,18 +10,16 @@ */ import { ChangeDetectionStrategy, Component, HostListener, Input, OnInit } from '@angular/core'; -import { FormControl, FormGroup } from '@angular/forms'; +import { UntypedFormControl } from '@angular/forms'; +import { Router } from '@angular/router'; import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy'; -import { Dispatch } from '@ngxs-labs/dispatch-decorator'; import { Select, Store } from '@ngxs/store'; import { CustomSelectors } from '@others/custom-selectors'; +import { LogService } from '@services/log.service'; import { SharedActionsService } from '@services/shared-actions.service'; import { Configuration } from '@store/actions/config.actions'; import { Features } from '@store/actions/features.actions'; -import { ApplicationsState } from '@store/applications.state'; -import { EnvironmentsState } from '@store/environments.state'; import { FeaturesState } from '@store/features.state'; -import { UserState } from '@store/user.state'; import { BehaviorSubject, Observable } from 'rxjs'; @UntilDestroy() @@ -35,7 +33,9 @@ export class L1FilterComponent implements OnInit { constructor( public _sharedActions: SharedActionsService, - private _store: Store + private _store: Store, + private _router: Router, + private log: LogService ) { } /** @@ -48,7 +48,7 @@ export class L1FilterComponent implements OnInit { /** * Global variables */ - moreOrLessSteps = new FormControl('is'); + moreOrLessSteps = new UntypedFormControl('is'); finder = this._store.selectSnapshot(CustomSelectors.GetConfigProperty('openedSearch')); searchInput: string; dialogs = { @@ -70,10 +70,11 @@ export class L1FilterComponent implements OnInit { * Once the file loads, subscribes to the specified variables and updates them on change */ ngOnInit() { + this.log.msg("1","Inicializing component...","filter"); this.moreOrLessSteps.valueChanges.pipe( untilDestroyed(this) ).subscribe(value => { - this._store.dispatch( new Features.SetMoreOrLessSteps(value) ); + this._store.dispatch(new Features.SetMoreOrLessSteps(value)); }); this.openedSearch$.subscribe(value => this.finder = value); } @@ -84,10 +85,9 @@ export class L1FilterComponent implements OnInit { * * @returns parent_id of the current folder */ - - @Dispatch() returnParent() { - return new Features.ReturnToParentRoute(); + this.log.msg("1","Returning to parent folder...","filter"); + return this._store.dispatch(new Features.ReturnToParentRoute()); } /** @@ -95,10 +95,11 @@ export class L1FilterComponent implements OnInit { * * @returns root folder */ - @Dispatch() returnToRoot() { + this.log.msg("1","Returning to root directory...","filter"); + this._router.navigate(['/']); this.toggleListType('list'); - return new Features.ReturnToFolderRoute(0); + return this._store.dispatch(new Features.ReturnToFolderRoute(0)); } /** @@ -107,9 +108,19 @@ export class L1FilterComponent implements OnInit { * @param folder * @returns id of the clicked folder */ - @Dispatch() returnFolder(folder: Partial) { - return new Features.ReturnToFolderRoute(folder.folder_id); + this.log.msg("1","Changing current route path...","filter"); + // dispach folder path change + this._store.dispatch(new Features.ReturnToFolderRoute(folder.folder_id)); + + // #3414 -------------------------------------------------start + // path to currently displayed folder + const currentRoute = this._store.snapshot().features.currentRouteNew; + + this.log.msg("1","Setting url params, adding current folder's id...","filter"); + // change browser url, add folder id as params + this._sharedActions.set_url_folder_params(currentRoute); + // #3414 ---------------------------------------------------end } // Gets and sets the variable from config file to open/close the sidenav @@ -119,9 +130,11 @@ export class L1FilterComponent implements OnInit { * * @return sets the current status of the sidenav on mobile */ - @Dispatch() toggleSidenav() { - const opened = this._store.selectSnapshot(CustomSelectors.GetConfigProperty('openedSidenav')); - return new Configuration.SetProperty('openedSidenav', !opened); + toggleSidenav() { + // get current state of side navbar after clicking toggle arrow icon + let newSidebarState = this.getSidebarState() ? false : true; + this.log.msg("1","Toggling sidenav...","filter"); + return this._store.dispatch(new Configuration.SetProperty('openedSidenav', newSidebarState)); } /** @@ -129,9 +142,10 @@ export class L1FilterComponent implements OnInit { * * @return sets the current status of the search bar */ - @Dispatch() toggleSearch() { + toggleSearch() { + this.log.msg("1","Toggling searchbar...","filter"); this.finder = !this.finder; - return new Configuration.SetProperty('openedSearch', this.finder); + return this._store.dispatch(new Configuration.SetProperty('openedSearch', this.finder)); } /** @@ -140,9 +154,9 @@ export class L1FilterComponent implements OnInit { * @param filter * @return removes a filter */ - @Dispatch() removeFilter(filter: Filter) { - return new Features.RemoveFilter(filter); + this.log.msg("1","Removing searchbar filter term...","filter"); + return this._store.dispatch(new Features.RemoveFilter(filter)); } /** @@ -151,13 +165,12 @@ export class L1FilterComponent implements OnInit { * @param filter * @return removes a filter */ - @Dispatch() - removeSearchFilter() { - return new Features.RemoveSearchFilter(); - } + removeSearchFilter() { + this.log.msg("1","Clearing searchbar filter terms...","filter"); + return this._store.dispatch(new Features.RemoveSearchFilter()); + } // Checks which filter to add and if it's ok then add it - @Dispatch() addFilterOK(id: string, value?: any, value2?: any) { const filters = this._store.selectSnapshot(CustomSelectors.GetConfigProperty('filters')); let customFilter = { ...filters.find(filter => filter.id === id) }; @@ -178,14 +191,15 @@ export class L1FilterComponent implements OnInit { } // Check if filter requires a value if (customFilter.hasOwnProperty('value')) { - this.dialogs[id].next(false); + this.dialogs[id].next(false); } this.toggleSearch(); - return new Features.AddFilter(customFilter); + return this._store.dispatch(new Features.AddFilter(customFilter)); } // Adds a filter addFilter(filter: Filter) { + this.log.msg("1","Adding searchbar filter terms...","filter"); // Check if filter requires a value if (filter.hasOwnProperty('value')) { this.dialogs[filter.id].next(true); @@ -207,9 +221,14 @@ export class L1FilterComponent implements OnInit { */ searchFeature() { if (this.searchInput) { + this.log.msg("1","Searching feature...","filter", this.searchInput); + this.toggleListType('list'); this.addFilterOK('test', this.searchInput); this.searchInput = ""; + + // close search bar after click on search icon ---- #3461 + this.close_search(); } } @@ -217,6 +236,12 @@ export class L1FilterComponent implements OnInit { return item.feature_id; } + // returns sidebar state boolean true/false = open/closed + getSidebarState() { + this.log.msg("1","Getting sidebar state...","filter"); + return this._store.selectSnapshot(CustomSelectors.GetConfigProperty('openedSidenav')); + } + /** * If the search bar is closed, opens it and focuses on the search input */ @@ -243,23 +268,34 @@ export class L1FilterComponent implements OnInit { * @date 11-10-21 * @lastModification 11-10-21 */ - @Dispatch() - toggleListType(listType: string) { - return new Configuration.SetProperty('co_active_list', listType, true); - } + toggleListType(listType: string) { + return this._store.dispatch(new Configuration.SetProperty('co_active_list', listType, true)); + } /** * HotKey event listeners */ + // #3420 ------------------------------------------------ start // Hotkey Shift-Alt-f ... opens the finder @HostListener('document:keydown.Shift.Alt.f', ['$event']) hotkey_shift_alt_f(event: KeyboardEvent) { - if (this.filters$.length == 0) { - this.open_search(); - event.stopPropagation(); + + // rewrite browser shortcut + event.preventDefault(); + + // set searchterm to empty ---- #3461 + if(this.searchInput) this.searchInput = ""; + + // remove filter term if exists + if (this.filters$.length > 0) { + this.removeSearchFilter(); } + + // toggle searchbar + this.open_search(); } + // #3420 -------------------------------------------------- end // Hotkey Shift-Alt-h ... goes to root-Folder @HostListener('document:keydown.Shift.Alt.h', ['$event']) diff --git a/front/src/app/components/l1-landing/l1-landing.component.html b/front/src/app/components/l1-landing/l1-landing.component.html index bc751156..886200a8 100644 --- a/front/src/app/components/l1-landing/l1-landing.component.html +++ b/front/src/app/components/l1-landing/l1-landing.component.html @@ -14,7 +14,8 @@ This forms part of new landing together with the components "new-folder" + "folder-tree" --> -
+ +
@@ -36,6 +37,15 @@
Create feature +
@@ -51,26 +61,33 @@
-
+
-
+
Found {{ table.featureCount }} features and {{ table.folderCount }} folders
+
- + - + - + + + + + + +
@@ -115,7 +132,27 @@
- + + + + + + + + + + + + + + + + + + + + + diff --git a/front/src/app/components/l1-landing/l1-landing.component.scss b/front/src/app/components/l1-landing/l1-landing.component.scss index 6b211610..c835dcd1 100644 --- a/front/src/app/components/l1-landing/l1-landing.component.scss +++ b/front/src/app/components/l1-landing/l1-landing.component.scss @@ -18,6 +18,19 @@ height: 100%; } +edit-variables { + padding: 20px 0 0 10px; + height: 85%; + overflow: auto; + + ::ng-deep button { + border: 0; + &.mat-stroked-button { + border: 1px solid rgba(0, 0, 0, 0.12); + } + } +} + // Add-feature icon on mobile. The size of 21 pixels is due to the lack of padding on the icon .icon { cursor: pointer; @@ -40,59 +53,111 @@ width: 100%; height: 100%; background: $high-black; - z-index: 5; + // z-index: 5; opacity: 0; - pointer-events: none; + // pointer-events: none; @include for-phone-only{ + z-index: 1; + &.opened { pointer-events: unset; - opacity: 1; + opacity: 0; + z-index: -1; } } - transition: opacity .15s ease-in-out; + transition: opacity .5s ease-in-out; } + .searcher { display: block; - flex: 0 var(--sidenav-width); - min-width: var(--sidenav-width); - border-right: 1px solid $border-color; - min-height: 45px; + width: var(--sidenav-width); // fixed width of 300 px for sidenav by default height: 100%; - background: $body-bg-color; + min-height: 45px; max-height: calc(100vh - var(--header-height)); - transition: all .15s ease-in-out; + background: $body-bg-color; + border-right: 1px solid $border-color; box-sizing: border-box; - transform: translateX(-100vw); - position: absolute; + overflow: hidden; + white-space: nowrap; + transition: width 1s; + &.closed { width: 0%; } + + // width: 15vw; + // flex: 0 var(--sidenav-width); + // transition: transform 2.5s; + // transition-timing-function: cubic-bezier(0.1, 0.5, 1.0, 0.1); + // transform: translateX(-100vw); + // position: absolute; + @include for-phone-only { - width: 85vw; - transform: translateX(-100vw); + position: absolute; + width: 55vw; z-index: 10; - margin-left: unset; + // flex: 0 var(--sidenav-width); + // transform: translateX(-100vw); + // margin-left: unset; + // width: 55vw; // sidenav will take approximately 50% of current screen size, only when app is reproduced in small device + // transform: translateX(-100vw); + // &.opened { + // transform: translateX(0); + // margin-left: unset; + // } + // &.closed { + // transform: translateX(0); + // } + } + // &.closed { + // position: relative; + // transform: translateX(0); + // margin-left: unset; + // @include for-phone-only { + // position: absolute; + // transform: translateX(-100vw); + // } + // } + + // hide sidebar leaves icons visible + /*@include for-phone-only { + // when sidebar is open in mobile devices &.opened { - transform: translateX(0); - margin-left: unset; + + // adjust folder treen container width + .folder-tree { + width: 150px; + font-size: 0; + } } } + + // when sidebar is closed, in any device &.closed { - position: relative; - transform: translateX(0); - margin-left: unset; - @include for-phone-only { - position: absolute; - transform: translateX(-100vw); + + // reduce width, untill only icons are visible and hide overflow, so content doesnt overwrite dashboard + max-width: 57px; + overflow: hidden; + + // remove text from add button and leave only '+' logo + .add-from-sidenav { + .addIcon { + margin-left: 2px; + mat-icon { + margin-right: 7px; + } + } } - } + }*/ + .add-from-sidenav { - display: flex; - flex-wrap: wrap; - width: 70%; - align-items: center; - justify-content: flex-start; + display: inline-block; padding: 15px 15px; - box-sizing: border-box; - .addIcon, .addFeatureIcon, .addFolderIcon { + // display: flex; + // flex-wrap: wrap; + // width: 70%; + // align-items: center; + // justify-content: flex-start; + // box-sizing: border-box; + .addIcon, .addFeatureIcon, .addFolderIcon, .addDataDrivenIcon { width: auto; height: 40px; background: $blue; @@ -129,6 +194,30 @@ } } } + .addDataDrivenIcon { + position: absolute; + visibility: visible; + top: 180px; + transition: all .3s ease-in-out; + z-index: 102; + // Add-feature icon on desktop. The size of 21 pixels is due to the lack of padding on the icon + .icon { + outline: 0; + float: left; + margin-top: 8px; + padding-right: 5px; + i { + height: 21px; + width: 21px; + &.feature-icon { + background: url(^assets/internal/COM_Add_Icon2.svg) no-repeat; + background-size: contain; + stroke-width: 3; + height: 21px; + } + } + } + } button { color: $white-text; font-size: 1.2rem; @@ -171,9 +260,11 @@ } .viewer { + width: 100%; display: flex; flex-direction: column; flex: 1 100%; + overflow-y: scroll; .features_folders_count { padding-bottom: 20px; padding-left: 10px; @@ -216,7 +307,7 @@ p { display: flex; justify-content: space-between; align-items: center; - flex: 0 80px; + flex: 0 120px; height: 100%; border-radius: 5px; overflow: hidden; @@ -254,6 +345,10 @@ p { background-image: url(^assets/icons/list-view.svg); background-size: 20px; } + &.tree:after { + background-image: url(^assets/icons/tree-view.svg); + background-size: 20px; + } } } } diff --git a/front/src/app/components/l1-landing/l1-landing.component.ts b/front/src/app/components/l1-landing/l1-landing.component.ts index d9dfb87a..b2520b64 100644 --- a/front/src/app/components/l1-landing/l1-landing.component.ts +++ b/front/src/app/components/l1-landing/l1-landing.component.ts @@ -9,12 +9,12 @@ */ import { animate, query, stagger, state, style, transition, trigger } from '@angular/animations'; -import { ChangeDetectionStrategy, Component, OnInit, Renderer2 } from '@angular/core'; -import { FormControl, Validators } from '@angular/forms'; +import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core'; +import { UntypedFormControl, Validators } from '@angular/forms'; import { Store, Select } from '@ngxs/store'; +import { Router } from '@angular/router'; import { FeaturesState } from '@store/features.state'; -import { MatDialog } from '@angular/material/dialog'; -import { Dispatch } from '@ngxs-labs/dispatch-decorator'; +import { MatLegacyDialog as MatDialog } from '@angular/material/legacy-dialog'; import { UserState } from '@store/user.state'; import { Features } from '@store/actions/features.actions'; import { CustomSelectors } from '@others/custom-selectors'; @@ -24,6 +24,10 @@ import { Configuration } from '@store/actions/config.actions'; import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy'; import { SharedActionsService } from '@services/shared-actions.service'; import { Observable } from 'rxjs'; +import { ActivatedRoute } from '@angular/router'; +import { LogService } from '@services/log.service'; +import { User } from '@store/actions/user.actions'; + @UntilDestroy() @Component({ @@ -58,10 +62,13 @@ import { Observable } from 'rxjs'; export class L1LandingComponent implements OnInit { constructor( + private _router: Router, private _dialog: MatDialog, private _store: Store, - public _sharedActions: SharedActionsService - ) { + public _sharedActions: SharedActionsService, + private activatedRoute: ActivatedRoute, + private log: LogService + ) { const filtersStorage = localStorage.getItem('filters'); if (!!filtersStorage) { try { @@ -69,6 +76,16 @@ export class L1LandingComponent implements OnInit { this._store.dispatch(new Features.SetFilters(parsedFilters)); } catch (err) { } } + + // if user clicks on browser bookmark that saves location to certain folder or feature, it will not be loaded if active_list is not set to 'list', as 'list' renders the table + // that's why we check if there are params in url and if so, set active_list to 'list'. So user can directly navigate to saved bookmark location + if (this.url_has_params()) { + this._store.dispatch(new Configuration.SetProperty('co_active_list', 'list', true)); + } + + // forces the components content to reload when url parameters are changed manually + this._router.routeReuseStrategy.shouldReuseRoute = () => false; + } // Contains all the features and folders data @@ -88,23 +105,31 @@ export class L1LandingComponent implements OnInit { // Checks if the user has an active subscription @ViewSelectSnapshot(UserState.HasOneActiveSubscription) hasSubscription: boolean; + // Global variables - minADate = new FormControl('', Validators.required); - maxADate = new FormControl('', Validators.required); - moreOrLessSteps = new FormControl('is'); + minADate = new UntypedFormControl('', Validators.required); + maxADate = new UntypedFormControl('', Validators.required); + moreOrLessSteps = new UntypedFormControl('is'); openedAdd: boolean = false; // Checks if the add buttons are opened search: string; sidenavClosed = false; ngOnInit() { - this.moreOrLessSteps.valueChanges.pipe( - untilDestroyed(this) - ).subscribe(value => { + this.log.msg("1","Inicializing component...","landing"); + // #3414 -------------------------------------------------start + // check if there are folder ids in url params, if so redirect to that folder + this.redirect_with_url_params(); + // #3414 --------------------------------------------------end + + this.moreOrLessSteps.valueChanges.pipe(untilDestroyed(this)) + .subscribe(value => { this._store.dispatch( new Features.SetMoreOrLessSteps(value)); }); - this.aciveList$.pipe( - untilDestroyed(this) - ).subscribe(value => localStorage.setItem('co_active_list', value)); // Initialize the recentList_active variable in the local storage + + this.aciveList$.pipe(untilDestroyed(this)) + .subscribe(value => { + localStorage.setItem('co_active_list', value) // Initialize the recentList_active variable in the local storage + }); } /** @@ -112,14 +137,23 @@ export class L1LandingComponent implements OnInit { */ // Changes the type of view of the feature list (list / item) - @Dispatch() setView(type: string, view: FeatureViewTypes) { + this.log.msg("1","Changing feature list view type to...","landing", view); this.openedAdd = false; - return new Configuration.SetProperty(`featuresView.${type}`, view, true); + + return this._store.dispatch([ + new User.SetSetting({ 'featuresView.with': view }), + new Configuration.SetProperty(`featuresView.${type}`, view, true) + ]); } // Hides the sidenav - @Dispatch() hideSidenav = () => new Configuration.SetProperty('openedSidenav', false); + hideSidenav() { + let currentSidebarState = this._store.selectSnapshot(CustomSelectors.GetConfigProperty('openedSidenav')); + let newSidebarState = currentSidebarState ? false : true; + this.log.msg("1","Hiding sidenav...","landing"); + return this._store.dispatch(new Configuration.SetProperty('openedSidenav', newSidebarState)); + } /** * General functions @@ -132,18 +166,19 @@ export class L1LandingComponent implements OnInit { // Open the create folder dialog createFolder() { + this.log.msg("1","Opening create folder dialog...","landing"); const currentFolder = this._store.selectSnapshot(FeaturesState).currentRouteNew as Folder[]; let folder_id; if (currentFolder.length === 0) { folder_id = 0; } else { - folder_id = currentFolder[currentFolder.length - 1].folder_id + folder_id = currentFolder[currentFolder.length - 1] } this._dialog.open(AddFolderComponent, { autoFocus: true, data: { mode : 'new', - folder: { folder_id } + folder: folder_id } as IEditFolder }) } @@ -154,6 +189,92 @@ export class L1LandingComponent implements OnInit { // Opens a menu to create a new feature SAopenCreateFeature() { + this.log.msg("1","Opening create feature dialog...","landing"); this._sharedActions.openEditFeature(); } -} + + + // checks if current url contains params and returns corresponding boolean + url_has_params() { + let folderIdRoute = this.activatedRoute.snapshot.paramMap.get('breadcrumb'); + return folderIdRoute ? true : false; + } + + + // #3414 -----------------------------------------------------------------------------------------start + // generates a folder path with folder ids retrieved from url and redirect to there to show content + redirect_with_url_params() { + this.log.msg("1","Checking url params","landing"); + // get url params - which contains a path created with folder ids, like 2:13:15 for example + let folderIdRoute = this.activatedRoute.snapshot.paramMap.get('breadcrumb'); + + // if there are folder ids in browser path + if(folderIdRoute) { + // remove first ':' from url params + folderIdRoute = folderIdRoute.indexOf(":") == 0 ? folderIdRoute.slice(1) : folderIdRoute; + + // split the url string to get array or folder ids base on ':' + const folderIDS = folderIdRoute.split(":"); + + // checks if there is more than one id in url params + // if so it means that user is currently inside a folder within department, so we load that folders content + // if there is only one id it means user is currently in department, so we load all the folders that belong to that department + folderIDS.length > 1 ? this.show_folder_content(folderIDS) : this.show_department_content(folderIDS) + } else { + this.log.msg("1","No url params were found","landing"); + } + } + // #3414 ------------------------------------------------------------------------------------------end + + + // #3414 -----------------------------------------------------------------------------------------start + show_folder_content (folderIDS: any) { + // removes the first item from array, which is departmentId + folderIDS.shift(); + + let currentRoute = []; + + // get folders from state + let folders = this._store.snapshot().features.folders.folders; + + // filter folders with the first id of params + let folder = folders.filter(folder => folder.folder_id == folderIDS[0]); + + // array.prototype.filter returns an array, but we need to push an object in currentRoutes, so the final resut is array of objects, not array of arrays + // thats why we dont push folder array itself, but first and only item it has + currentRoute.push(folder[0]); + + // search recursively ids that are recieved from url params, search startpoint is the first folder + // the next filter is always performed on previus filter result (recursive filtering) + for(let i = 1; i folder.folder_id == folderIDS[i]); + currentRoute.push(folder[0]); + } + + // log folder id that app is redirected to + this.log.msg("1",`Folder id param found, redirectiong to folder with id ${currentRoute.slice(-1)[0].folder_id}`,"landing"); + + // save the final folder path in localstorage + localStorage.setItem('co_last_selected_folder_route', JSON.stringify(currentRoute)); + } + // #3414 ------------------------------------------------------------------------------------------end + + + // #3414 -----------------------------------------------------------------------------------------start + // filters folders to show only the ones that belong to department id present in url params + show_department_content(folderIDS: any) { + // log department id where app is redirected to + this.log.msg("1",`Department id param found, redirectiong to department with id ${folderIDS[0]}`,"landing"); + + let department = []; + this._store.select(CustomSelectors.GetDepartmentFolders()) + .subscribe( + data => { + department = data.filter(department => department.folder_id == Number(folderIDS[0])); + } + ); + localStorage.setItem('co_last_selected_folder_route', JSON.stringify(department)); + } + // #3414 ------------------------------------------------------------------------------------------end + } + diff --git a/front/src/app/components/l1-tree-view/l1-tree-view.component.html b/front/src/app/components/l1-tree-view/l1-tree-view.component.html new file mode 100755 index 00000000..dc56a84b --- /dev/null +++ b/front/src/app/components/l1-tree-view/l1-tree-view.component.html @@ -0,0 +1 @@ +
\ No newline at end of file diff --git a/front/src/app/components/l1-tree-view/l1-tree-view.component.scss b/front/src/app/components/l1-tree-view/l1-tree-view.component.scss new file mode 100755 index 00000000..51de78d9 --- /dev/null +++ b/front/src/app/components/l1-tree-view/l1-tree-view.component.scss @@ -0,0 +1,21 @@ + +@import 'color'; +@import 'breakpoints'; + +:host { + width: 100%; + height: 100%; +} + +#tree-view { + width: 100%; + height: 100%; +} + +::ng-deep .node-text { + font-size: 16px; +} + +::ng-deep .disabled { + filter: grayscale(1); +} \ No newline at end of file diff --git a/front/src/app/components/l1-tree-view/l1-tree-view.component.ts b/front/src/app/components/l1-tree-view/l1-tree-view.component.ts new file mode 100755 index 00000000..d4a2b9e7 --- /dev/null +++ b/front/src/app/components/l1-tree-view/l1-tree-view.component.ts @@ -0,0 +1,321 @@ +import { Component, ChangeDetectionStrategy, OnInit } from '@angular/core'; +import { Router } from '@angular/router'; +import { Select, Store } from '@ngxs/store'; +import { CustomSelectors } from '@others/custom-selectors'; +import { ApiService } from '@services/api.service'; +import { FeaturesState } from '@store/features.state'; +import * as d3 from 'd3'; +import { debounceTime, Observable } from 'rxjs'; + +@Component({ + selector: 'cometa-l1-tree-view', + templateUrl: './l1-tree-view.component.html', + styleUrls: ['./l1-tree-view.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class L1TreeViewComponent implements OnInit{ + + data = {} + viewingData = {} + @Select(FeaturesState.GetNewSelectionFolders) currentRoute$: Observable>; + + + widthChecker(text) { + const p = document.createElement("p"); + p.style.fontSize = "16px"; + p.style.position = "absolute"; + p.style.opacity = "0"; + p.innerHTML = text; + document.body.append(p); + const textWidth = p.clientWidth; + document.body.removeChild(p); + return textWidth; +} + + constructor( private _store: Store, private _api: ApiService, private _router: Router ) {} + + findEmbededObject(data: any, obj: any) { + // if data type is feature, add feature id as suffix in name + // if data type is not feature, just truncate name + if (data.type === 'feature' && !data.name.includes(" - " + data.id)) { + data.name = this.truncateString(data.name, 25) + ` - ${data.id}`; + } else { + data.name = this.truncateString(data.name, 25); + } + + let found = null; + if ( data.id == obj.id && data.name == obj.name && data.type == obj.type ) { + found = data; + } + if (data.children) { + data.children.forEach(child => { + const value = this.findEmbededObject(child, obj) + if (value) found = value; + }); + } + return found; + } + + truncateString(input: string, maxLength: number): string { + const ellipsis = '...'; + + if (input.length <= maxLength) { + return input; + } + + const prefixLength = Math.floor((maxLength - ellipsis.length) / 2); + const suffixLength = Math.ceil((maxLength - ellipsis.length) / 2); + + const prefix = input.slice(0, prefixLength); + const suffix = input.slice(-suffixLength); + + return prefix + ellipsis + suffix; + } + + dataFromCurrentRoute(currentRouteArray) { + // get the last object from the route + const elem: Folder = currentRouteArray.slice(-1).pop() + if (!elem) { + return this.data; + } + + const object = { + id: elem.folder_id, + name: elem.name, + type: elem.type == undefined ? 'folder' : elem.type + } + return this.findEmbededObject(this.data, object); + } + + draw() { + + const boundries = d3.select("#tree-view").node().getBoundingClientRect(); + // viewer width and height + const width = boundries.width; + const height = boundries.height; + + const imageSize = 20; + const textSpace = 15; + + const margins = {top: 0, right: 120, bottom: 0, left: 30}; + const dx = 30; // line height + const dy = width / 4; + + const tree = d3.tree().nodeSize([dx, dy]); + const diagonal = d3.linkHorizontal().x(d => { + let appendText = 0; + if (d.data) appendText = this.widthChecker(d.data.name) + textSpace + 5 + return d.y + appendText + }).y(d => d.x + 1); + + let root = d3.hierarchy(this.viewingData); + root.x0 = dy / 2; + root.y0 = 0; + root.descendants().forEach((d, i) => { + d.id = i; + }) + + const zoom = d3.zoom().scaleExtent([1, 10]) + .on('zoom', (event) => { + svg.attr("transform", event.transform) + }) + + const parent = d3.select("#tree-view").append("svg") + .attr("width", "100%") + .attr("height", "99%") + .style("font", "10px sans-serif") + .style("user-select", "none") + .call(zoom) + .on("dblclick.zoom", null); + const svg = parent.append("g"); + + const gLink = svg.append("g") + .attr("fill", "none") + .attr("stroke", "#555") + .attr("stroke-opacity", 0.4) + .attr("stroke-width", 1.5); + const gNode = svg.append("g") + .attr("cursor", "pointer") + .attr("pointer-events", "all"); + + const collapse = (d) => { + if (d.children) { + d._children = d.children; + d._children.forEach(collapse); + d.children = null; + } + } + + const expand = (d) => { + if (d._children) { + d.children = d._children; + d.children.forEach(expand); + d._children = null; + } + } + + const toggle = (d) => { + if (d._children) { + d.children = d._children; + d._children = null; + } else if (d.children) { + d._children = d.children; + d.children = null; + } + } + + function centerNode(source) { + const t = d3.zoomTransform(parent.node()); + const boundries = d3.selectAll('g').filter(d => d ? d.id == source.id : false).node().getBBox(); + let x = -source.y0; + let y = -source.x0; + x = x * t.k + (width / 2) - margins.left - (boundries.width / 2); + if (source.children) { + x = x - (dy / 2); + } + y = y * t.k - (dx * 2); // move upwards a little bit.... + d3.select('svg').transition().duration(250).call( zoom.transform, d3.zoomIdentity.translate(x,y).scale(t.k) ); + } + + const update = source => { + const duration = 250; + const nodes = root.descendants().reverse(); + const links = root.links(); + + // Compute the new tree layout. + tree(root); + + let left = root; + let right = root; + root.eachBefore(node => { + if (node.x < left.x) left = node; + if (node.x > right.x) right = node; + }); + + const height = right.x - left.x + margins.top + margins.bottom; + const transition = parent.transition() + .duration(duration) + .attr("viewBox", [-margins.left, left.x - margins.top, width, height ]) + .tween("resize", window.ResizeObserver ? null : () => () => svg.dispatch("toggle")); + + // Update the nodes… + const node = gNode.selectAll("g") + .data(nodes, d => d.id); + let feature: Feature + + // Enter any new nodes at the parent's previous position. + const nodeEnter = node.enter().append("g") + .attr("transform", d => `translate(${source.y0},${source.x0})`) + .attr("fill-opacity", 0) + .attr("stroke-opacity", 0) + .on("click", (event, d) => { + toggle(d); + update(d); + centerNode(d) + }) + .on("dblclick", (event, d) => { + + if (d.data.type == "feature") { + this._router.navigate(['/from/tree-view/', d.data.id]) + } + }) + + nodeEnter.append("text") + .attr("width", imageSize) + .attr("height", imageSize) + .style("font-family", 'Material Icons') + .style("transform", `translate(-${imageSize/2}px, ${imageSize/2}px)`) + .attr('font-size', "20px") + .attr('fill', d => d.data.type === "feature" && d.data.depends_on_others ? 'gray' : 'black') + .attr("class", d => d.data.type != "feature" && !d.children && !d._children ? 'disabled' : '') + .text(d => { + switch (d.data.type) { + case "department": + return "domain"; + case "folder": + return "folder icon"; + case "home": + return "home"; + case "feature": + return "description icon"; + case "variables": + case "variable": + return "settings_ethernet"; + } + }) + + nodeEnter.append("text") + .attr("dy", "0.40em") + .attr("x", textSpace) + .attr("text-anchor", "start") + .attr("class", d => `node-text node-text-${d.data.type}`) + .text(d => d.data.name) + + nodeEnter.append("circle") + .attr("r", d => d.parent ? 5 : 0) + .attr("fill", "gray") + .attr("transform", `translate(${-textSpace - 5}, 1)`) + + // Transition nodes to their new position. + const nodeUpdate = node.merge(nodeEnter).transition(transition) + .attr("transform", d => `translate(${d.y},${d.x})`) + .attr("fill-opacity", 1) + .attr("stroke-opacity", 1); + + // Transition exiting nodes to the parent's new position. + const nodeExit = node.exit().transition(transition).remove() + .attr("transform", d => `translate(${source.y},${source.x})`) + .attr("fill-opacity", 0) + .attr("stroke-opacity", 0); + + // Update the links… + const link = gLink.selectAll("path") + .data(links, d => d.target.id); + // Enter any new links at the parent's previous position. + const linkEnter = link.enter().append("path") + .attr("d", d => { + const o = {x: source.x0, y: source.y0}; + return diagonal({source: o, target: o}); + }); + // Transition links to their new position. + link.merge(linkEnter).transition(transition) + .attr("d", d => { + const target = { + x: d.target.x, + y: d.target.y - 15 + } + return diagonal({source: d.source, target: target}) + }); + // Transition exiting nodes to the parent's new position. + link.exit().transition(transition).remove() + .attr("d", d => { + const o = {x: source.x, y: source.y}; + return diagonal({source: o, target: o}); + }); + + // Stash the old positions for transition. + root.eachBefore(d => { + d.x0 = d.x; + d.y0 = d.y; + }); + + } + root.children.forEach(collapse); + update(root); + centerNode(root); + } + + async ngOnInit() { + + this.data = await this._api.getTreeView().toPromise(); + + this.currentRoute$.pipe(debounceTime(100)).subscribe(d => { + const data = this.dataFromCurrentRoute(d); + if (data) { + this.viewingData = data; + d3.select("svg").remove(); + this.draw(); + } + }) + } +} diff --git a/front/src/app/components/network-paginated-list/network-paginated-list.component.html b/front/src/app/components/network-paginated-list/network-paginated-list.component.html index fc055822..1f9b02c4 100644 --- a/front/src/app/components/network-paginated-list/network-paginated-list.component.html +++ b/front/src/app/components/network-paginated-list/network-paginated-list.component.html @@ -6,8 +6,7 @@
- - +
@@ -45,4 +44,4 @@ - \ No newline at end of file + diff --git a/front/src/app/components/network-paginated-list/network-paginated-list.component.scss b/front/src/app/components/network-paginated-list/network-paginated-list.component.scss index 5d609d67..0ddb82f8 100644 --- a/front/src/app/components/network-paginated-list/network-paginated-list.component.scss +++ b/front/src/app/components/network-paginated-list/network-paginated-list.component.scss @@ -1,18 +1,21 @@ - @import 'color'; .paginated-rows { - display: flex; - flex-wrap: wrap; + overflow-y: auto; +} + +.header { + height: 50px; } .pagination { display: flex; align-items: center; justify-content: center; - height: 56px; - margin-top: 10px; - margin-bottom: 20px; + // creates unnecessary white space below and above paginator, thus reducing the size of data table significantly + // height: 56px; + // margin-top: 10px; + // margin-bottom: 20px; font-size: 1.4rem; line-height: 40px; font-weight: bold; @@ -32,6 +35,10 @@ } } +:host{ + display: contents; +} + :host::ng-deep .loading-pagination { margin: 100px auto 0; } @@ -48,4 +55,15 @@ width: 100%; @include skeleton; } +} + +.viewport { + height: 315px; + width: 100%; + // width: 200px; + // border: 1px solid black; + } + +.item { + height: 50px; } \ No newline at end of file diff --git a/front/src/app/components/network-paginated-list/network-paginated-list.component.ts b/front/src/app/components/network-paginated-list/network-paginated-list.component.ts index fea861b8..5bab4264 100644 --- a/front/src/app/components/network-paginated-list/network-paginated-list.component.ts +++ b/front/src/app/components/network-paginated-list/network-paginated-list.component.ts @@ -1,6 +1,6 @@ import { HttpClient } from '@angular/common/http'; import { ChangeDetectionStrategy, Component, Input, OnChanges, SimpleChanges, TemplateRef } from '@angular/core'; -import { PageEvent } from '@angular/material/paginator'; +import { LegacyPageEvent as PageEvent } from '@angular/material/legacy-paginator'; import { ActivatedRoute, ParamMap, Router } from '@angular/router'; import { Store } from '@ngxs/store'; import { ApiService } from '@services/api.service'; diff --git a/front/src/app/components/search/search.component.ts b/front/src/app/components/search/search.component.ts index 9612cfe4..7cbfcb20 100755 --- a/front/src/app/components/search/search.component.ts +++ b/front/src/app/components/search/search.component.ts @@ -1,17 +1,16 @@ import { animate, query, stagger, state, style, transition, trigger } from '@angular/animations'; import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core'; -import { FormControl, Validators } from '@angular/forms'; +import { UntypedFormControl, Validators } from '@angular/forms'; import { Store, Select } from '@ngxs/store'; import { FeaturesState } from '@store/features.state'; import { ApplicationsState } from '@store/applications.state'; import { EnvironmentsState } from '@store/environments.state'; -import { MatDialog } from '@angular/material/dialog'; +import { MatLegacyDialog as MatDialog } from '@angular/material/legacy-dialog'; import { BehaviorSubject, Observable } from 'rxjs'; -import { Dispatch } from '@ngxs-labs/dispatch-decorator'; import { UserState } from '@store/user.state'; import { Features } from '@store/actions/features.actions'; import { CustomSelectors } from '@others/custom-selectors'; -import { PageEvent } from '@angular/material/paginator'; +import { LegacyPageEvent as PageEvent } from '@angular/material/legacy-paginator'; import { Paginations } from '@store/actions/paginations.actions'; import { AddFolderComponent } from '@dialogs/add-folder/add-folder.component'; import { ViewSelectSnapshot } from '@ngxs-labs/select-snapshot'; @@ -71,7 +70,7 @@ export class SearchComponent implements OnInit { @ViewSelectSnapshot(UserState.GetPermission('create_feature')) canCreateFeature: boolean; @ViewSelectSnapshot(UserState.HasOneActiveSubscription) hasSubscription: boolean; - moreOrLessSteps = new FormControl('is'); + moreOrLessSteps = new UntypedFormControl('is'); constructor( private _dialog: MatDialog, @@ -87,19 +86,17 @@ export class SearchComponent implements OnInit { } } - @Dispatch() setView(type: string, view: FeatureViewTypes) { - return new Configuration.SetProperty(`featuresView.${type}`, view, true); + return this._store.dispatch(new Configuration.SetProperty(`featuresView.${type}`, view, true)); } - @Dispatch() handlePageChange(paginationId: string, { pageIndex, pageSize }: PageEvent) { - return new Paginations.SetPagination(paginationId, { pageIndex, pageSize }) + return this._store.dispatch(new Paginations.SetPagination(paginationId, { pageIndex, pageSize })); } - sorting = new FormControl(localStorage.getItem('search_sorting') || 'execution'); - minADate = new FormControl('', Validators.required); - maxADate = new FormControl('', Validators.required); + sorting = new UntypedFormControl(localStorage.getItem('search_sorting') || 'execution'); + minADate = new UntypedFormControl('', Validators.required); + maxADate = new UntypedFormControl('', Validators.required); search: string; @@ -117,19 +114,16 @@ export class SearchComponent implements OnInit { ).subscribe(value => localStorage.setItem('search_sorting', value)) } - @Dispatch() goFolder(folder: Folder) { - return new Features.AddFolderRoute(folder); + return this._store.dispatch(new Features.AddFolderRoute(folder)); } - @Dispatch() returnToRoot() { - return new Features.ReturnToFolderRoute(0); + return this._store.dispatch(new Features.ReturnToFolderRoute(0)); } - @Dispatch() returnFolder(folder: Partial) { - return new Features.ReturnToFolderRoute(folder.folder_id); + return this._store.dispatch(new Features.ReturnToFolderRoute(folder.folder_id)); } getId(item: Feature) { @@ -159,7 +153,6 @@ export class SearchComponent implements OnInit { }) } - @Dispatch() addFilterOK(id: string, value?: any, value2?: any) { const filters = this._store.selectSnapshot(CustomSelectors.GetConfigProperty('filters')); let customFilter = { ...filters.find(filter => filter.id === id) }; @@ -182,12 +175,11 @@ export class SearchComponent implements OnInit { if (customFilter.hasOwnProperty('value')) { this.dialogs[id].next(false); } - return new Features.AddFilter(customFilter); + return this._store.dispatch(new Features.AddFilter(customFilter)); } - @Dispatch() removeFilter(filter: Filter) { - return new Features.RemoveFilter(filter); + return this._store.dispatch(new Features.RemoveFilter(filter)); } addFilter(filter: Filter) { diff --git a/front/src/app/components/snacks/loading/loading.snack.ts b/front/src/app/components/snacks/loading/loading.snack.ts index fb06fdf4..54dd36c6 100644 --- a/front/src/app/components/snacks/loading/loading.snack.ts +++ b/front/src/app/components/snacks/loading/loading.snack.ts @@ -1,5 +1,5 @@ import { ChangeDetectionStrategy, Component, Inject } from '@angular/core'; -import { MAT_SNACK_BAR_DATA } from '@angular/material/snack-bar'; +import { MAT_LEGACY_SNACK_BAR_DATA as MAT_SNACK_BAR_DATA } from '@angular/material/legacy-snack-bar'; /** * Snack used to show a loading state diff --git a/front/src/app/components/step-editor/step-editor.component.html b/front/src/app/components/step-editor/step-editor.component.html index 03077b17..a22e6048 100644 --- a/front/src/app/components/step-editor/step-editor.component.html +++ b/front/src/app/components/step-editor/step-editor.component.html @@ -17,7 +17,7 @@
Step Description
- +
unarchive @@ -53,7 +53,7 @@
-
+
{{ i+1 }}
@@ -66,10 +66,36 @@
-
- +
+ + + +
+ ${{variable.variable_name}} + ={{variable.variable_value}} +
+ +
+ No variable with this name +
+
+
+
+ @@ -85,7 +111,7 @@ + + \ No newline at end of file diff --git a/front/src/app/dialogs/data-driven-execution/data-driven-executed/data-driven-executed.component.scss b/front/src/app/dialogs/data-driven-execution/data-driven-executed/data-driven-executed.component.scss new file mode 100644 index 00000000..9832a814 --- /dev/null +++ b/front/src/app/dialogs/data-driven-execution/data-driven-executed/data-driven-executed.component.scss @@ -0,0 +1,6 @@ + +@import 'color'; + +h2 { + color: $good; +} \ No newline at end of file diff --git a/front/src/app/dialogs/data-driven-execution/data-driven-executed/data-driven-executed.component.ts b/front/src/app/dialogs/data-driven-execution/data-driven-executed/data-driven-executed.component.ts new file mode 100644 index 00000000..dd858dc0 --- /dev/null +++ b/front/src/app/dialogs/data-driven-execution/data-driven-executed/data-driven-executed.component.ts @@ -0,0 +1,27 @@ +import { ChangeDetectionStrategy, Component, Inject } from '@angular/core'; +import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; +import { Router } from '@angular/router'; + +@Component({ + selector: 'data-driven-executed', + templateUrl: 'data-driven-executed.component.html', + styleUrls: ['data-driven-executed.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush + }) + export class DataDrivenTestExecuted { + + constructor( + public dialogRef: MatDialogRef, + @Inject(MAT_DIALOG_DATA) public data: any, + private router: Router + ) { } + + onNoClick() { + this.dialogRef.close(); + } + + go() { + this.router.navigate(['data-driven', this.data.run_id]); + this.dialogRef.close(); + } + } \ No newline at end of file diff --git a/front/src/app/dialogs/data-driven-execution/data-driven-execution.component.html b/front/src/app/dialogs/data-driven-execution/data-driven-execution.component.html new file mode 100755 index 00000000..c588b351 --- /dev/null +++ b/front/src/app/dialogs/data-driven-execution/data-driven-execution.component.html @@ -0,0 +1,122 @@ + +

Overview of Data Driven Tests

+
+ + + + + + Information + + Data Driven Test Information + + +
+ + Department + + + {{ dep.department_name }} + + + +
+
+ warning + Selecting Default department will make all the files you upload visible to everyone, use it with caution! +
+
+
+ + + + + + Files + + Upload/Download department files + + + +
+ + +
+
+
+
+ + +
+
+ +
+ +
+
+ + + {{ row.created_on | amParse | amDateFormat:'MMMM d yyyy, HH:mm' }} + + + {{ row.size | humanizeBytes }} + + + + +
+ +
+
+ + + + + + + + + No Data Found. + + + + +

Currently this department has no files, upload one by clicking upload button.

+
\ No newline at end of file diff --git a/front/src/app/dialogs/data-driven-execution/data-driven-execution.component.scss b/front/src/app/dialogs/data-driven-execution/data-driven-execution.component.scss new file mode 100755 index 00000000..669d13b1 --- /dev/null +++ b/front/src/app/dialogs/data-driven-execution/data-driven-execution.component.scss @@ -0,0 +1,266 @@ + +@import 'color'; +@import 'breakpoints'; + +:host::ng-deep .edit-feature-info .mat-form-field .mat-form-field-infix { + width: 130px; +} + +.edit-feature-info { + @media (max-width: 600px) { + flex-wrap: wrap; + } + .mat-form-field { + @-moz-document url-prefix() { + textarea { + height: 15px; + } + } + + @media (max-width: 600px) { + flex: 1 100% !important; + } + } +} + + + +.table-container { + max-height: 300px; + overflow: auto; + tr.mat-header-row { + position: sticky; + z-index: 1; + top: 0; + } + table.mat-table { + width: 100%; + min-width: 700px; + } + + td.mat-cell, + th.mat-header-cell { + padding: 0; + } + + .removed { + td:not(:last-child) { + opacity: 0.6; + } + } + + .mat-column-name { + height: 50px; + div.name-wrapper { + display:flex; + align-items: center; + color: $blue; + font-weight: bold; + mat-spinner { + margin-right: 5px; + } + span.canDownload { + cursor: pointer; + } + } + } +} + + + +.no-clouds { + font-size: 1.05rem; + color: rgba(black, .75); +} + +a { + color: $blue; + text-decoration: none; +} + +.mat-radio-button { + margin: 0 10px; +} + +.mat-expansion-panel { + // overflow: unset; +} + +:host::ng-deep .mat-dialog-content { + padding: 0 10px 5px; + @include for-tablet-portrait-up { + padding: 0 24px 5px; + } +} + +:host::ng-deep .mat-expansion-panel-body { + padding: 0 15px 16px; + @include for-tablet-portrait-up { + padding: 0 24px 16px; + } +} + +.schedule-help { + margin-top: 10px; + cursor: pointer; + color: #0171BC; + font-size: 10pt; + &:hover { + text-decoration: underline; + } +} + +.run-now { + text-align: center; + margin: 20px 0 30px; +} + +.edit-feature-loader { + margin: 0 auto; +} + +.edit-variables { + margin-left: 0; + margin-top: 20px; + @include for-tablet-portrait-up { + margin-left: 20px; + margin-top: 0; + } +} + +.department-warning { + display: inline-flex; + align-items: center; + color: $blue; + margin-bottom: 15px; + .mat-icon { + font-size: 20px; + height: 20px; + margin-right: 5px; + } +} + +.depends { + display: flex; + align-items: flex-start; + flex-direction: column; + @include for-tablet-portrait-up { + flex-direction: row; + align-items: center; + .mat-checkbox:not(:first-child) { + margin-left: 20px; + } + } +} + +::ng-deep .options .mat-ripple-element { + background-color: rgba($color: $blue, $alpha: .1) !important +} + +.last-edition { + margin-top: 20px; + display: inline-block; +} + +h2 { + color: $blue; +} + +button { + font-weight: bold; +} + +.add { + cursor: pointer; + margin-left: 15px; + margin-top: 10px; + position: absolute; + top: 10px; + right: 20px; +} + +.remove_all { + cursor: pointer; + margin-left: 15px; + margin-top: 10px; + position: absolute; + top: 10px; + right: 130px; +} + +.mobile { + display: none; +} + +.edit-feature-info { + display: flex; + .edit-description { + flex: 1 60%; + } + .edit-app { + flex: 1 20%; + } + .edit-env { + flex: 1 20%; + } +} + +.schedule { + width: 100%; +} + +.schedule::ng-deep .mat-form-field-infix { + width: auto !important; + text-align: center; +} + +.mat-radio-button { + display: block; + margin-top: 15px; + @include for-tablet-portrait-up { + display: inline-block; + margin-top: 0; + } +} + +.edit-feature-help-icon { + position: absolute; + bottom: 10px; + right: 30px; +} + +.next_run { + line-height: 1.2rem; + li { + margin-left: 10px; + list-style: "\2192 "; + } +} + +.right { + display: flex; + justify-content: flex-end; +} + +:host ::ng-deep .mtx-grid { + padding-bottom: 25px; + .mtx-grid-toolbar { + padding: 0; + } + .mtx-grid-toolbar-content { + flex-grow: 1; + .custom_toolbar { + display: flex; + justify-content: flex-end; + align-items: center; + margin-right: 5px; + + button { + font-weight: 100; + } + } + } + .no-expand .mtx-grid-row-expand-button { + visibility: hidden; + } +} \ No newline at end of file diff --git a/front/src/app/dialogs/data-driven-execution/data-driven-execution.component.ts b/front/src/app/dialogs/data-driven-execution/data-driven-execution.component.ts new file mode 100755 index 00000000..e1f915f9 --- /dev/null +++ b/front/src/app/dialogs/data-driven-execution/data-driven-execution.component.ts @@ -0,0 +1,295 @@ +import { ChangeDetectorRef, Component, HostListener, OnInit } from "@angular/core"; +import { Select } from "@ngxs/store"; +import { Observable } from "rxjs"; +import { FeaturesState } from "@store/features.state"; +import { ViewSelectSnapshot } from "@ngxs-labs/select-snapshot"; +import { ConfigState } from "@store/config.state"; +import { FileUploadService } from "@services/file-upload.service"; +import { UserState } from "@store/user.state"; +import { MatSnackBar } from "@angular/material/snack-bar"; +import { KEY_CODES } from "@others/enums"; +import { MtxGridColumn } from "@ng-matero/extensions/grid"; +import { HttpClient } from "@angular/common/http"; +import { InterceptorParams } from "ngx-network-error"; +import { DataDrivenTestExecuted } from "./data-driven-executed/data-driven-executed.component"; +import { DepartmentsState } from "@store/departments.state"; +import { MatDialog, MatDialogRef } from "@angular/material/dialog"; + +@Component({ + selector: "data-driven-execution", + templateUrl: "./data-driven-execution.component.html", + styleUrls: ["./data-driven-execution.component.scss"], +}) +export class DataDrivenExecution implements OnInit { + columns: MtxGridColumn[] = [ + {header: 'Status', field: 'status', showExpand: true, class: (row: UploadedFile, col: MtxGridColumn) => { + if (row && row.status !== 'Done') return 'no-expand'; + else return ''; + }}, + {header: 'File Name', field: 'name', sortable: true, class: 'name'}, + {header: 'Size', field: 'size', sortable: true}, + {header: 'Uploaded By', field: 'uploaded_by.name', sortable: true}, + {header: 'Uploaded On', field: 'created_on', sortable: true}, + { + header: 'Options', + field: 'options', + // pinned: 'right', + right: '0px', + type: 'button', + buttons: [ + { + type: 'icon', + text: 'play_circle_fill', + icon: 'play_circle_fill', + tooltip: 'Execute this file', + color: 'primary', + click: (result: UploadedFile) => { + this.execute_data_driven(result, this) + }, + }, + { + type: 'icon', + text: 'delete', + icon: 'delete', + tooltip: 'Delete result', + color: 'warn', + click: (result: UploadedFile) => { + this.onDeleteFile(result); + }, + } + ] + } + ]; + + @Select(DepartmentsState) departments$: Observable; + + @Select(FeaturesState.GetFeaturesWithinFolder) features$: Observable< + ReturnType + >; + @ViewSelectSnapshot(ConfigState) config$!: Config; + @ViewSelectSnapshot(UserState) user!: UserInfo; + + department: Department; + + constructor( + private fileUpload: FileUploadService, + private _snackBar: MatSnackBar, + private _http: HttpClient, + private cdRef: ChangeDetectorRef, + private _dialog: MatDialog, + public dialogRef: MatDialogRef, + ) { + + } + + ngOnInit() { + this.fillDropdownsOnInit(); // TODO: Change mode based on the edit or new. + } + + onUploadFile(ev) { + let formData: FormData = new FormData(); + let files = ev.target.files; + + for (let file of files) { + formData.append("files", file); + } + + let detpid = this.department.department_id.toString(); + formData.append("department_id", detpid); + + this.fileUpload.startUpload(files, formData, this.department, this.user); + } + + onDeleteFile(file: UploadedFile) { + this.fileUpload.deleteFile(file.id).subscribe((res) => { + if (res.success) this.fileUpload.updateFileState(file, this.department); + }); + } + + onRestoreFile(file: UploadedFile) { + let formData: FormData = new FormData(); + formData.append("restore", String(file.is_removed)); + + this.fileUpload.restoreFile(file.id, formData).subscribe((res) => { + if (res.success) this.fileUpload.updateFileState(file, this.department); + }); + } + + onDownloadFile(file: UploadedFile) { + // return if file is still uploading + if (file.status.toLocaleLowerCase() != "done") { + return; + } + + const downloading = this._snackBar.open( + "Generating file to download, please be patient.", + "OK", + { duration: 10000 } + ); + + this.fileUpload.downloadFile(file.id).subscribe({ + next: (res) => { + const blob = new Blob([this.base64ToArrayBuffer(res.body)], { + type: file.mime, + }); + this.fileUpload.downloadFileBlob(blob, file); + downloading.dismiss(); + }, + error: (err) => { + if (err.error) { + const errors = JSON.parse(err.error); + this._snackBar.open(errors.error, "OK"); + } + }, + }); + } + + base64ToArrayBuffer(data: string) { + const byteArray = atob(data); + const uint = new Uint8Array(byteArray.length); + for (let i = 0; i < byteArray.length; i++) { + let ascii = byteArray.charCodeAt(i); + uint[i] = ascii; + } + return uint; + } + + fillDropdownsOnInit(mode: string = 'new') { + switch(mode) { + case 'new': + this.preSelectedOrDefaultOptions(); + break; + case 'edit': + // TODO: Get data from the data-driven object + break; + default: + break; + } + } + + preSelectedOrDefaultOptions() { + const { + preselectDepartment, + } = this.user.settings; + + this.departments$.subscribe(deps => { + this.department = deps.find(d => d.department_id == (this.department ? this.department.department_id : preselectDepartment)) || deps[0]; + }); + } + + // Handle keyboard keys + @HostListener('document:keydown', ['$event']) handleKeyboardEvent(event: KeyboardEvent) { + // only execute switch case if child dialog is closed + switch (event.keyCode) { + case KEY_CODES.ESCAPE: + this.dialogRef.close(); + } + } + + closeDialog() { + this.dialogRef.close(); + } + + execute_data_driven(file: UploadedFile, parent) { + this._http.post('/backend/exec_data_driven/', { + file_id: file.id + }, { + params: new InterceptorParams({ + skipInterceptor: true, + }) + }).subscribe({ + next(res: any) { + res = JSON.parse(res) + if (res.success) { + parent._dialog.open(DataDrivenTestExecuted, { + minWidth: '500px', + panelClass: 'edit-feature-panel', + data: { + run_id: res.run_id, + file_name: file.name + } + }); + parent.dialogRef.close(); + } + + }, + error(err) { + if (err.status >= 400 && err.status < 500) { + const error = JSON.parse(err.error); + parent._snackBar.open(`Error: ${error.error}. Please review the data and try again.`, 'OK', { + duration: 30000 + }); + } + } + }) + } + + getFileData(row) { + row.isLoading = true; + this._http.get(`/backend/api/data_driven/file/${row.id}`, { + params: new InterceptorParams({ + skipInterceptor: true, + }, { + size: row.params.size, + page: row.params.page + 1 + }) + }).subscribe({ + next: (res: any) => { + res = JSON.parse(res); + row.file_data = res.results.map(d => d.data) + row.total = res.count + row.showPagination = row.total > 0 ? true : false + + // generate headers for the data + if (row.file_data.length > 0) { + const d = row.file_data[0] + const keys = Object.keys(d) + + row.columns = [] + keys.forEach(key => { + row.columns.push({ + header: key, field: key + }) + }) + } + }, + error: (err) => { + if (err.status >= 400 && err.status < 500) { + const error = JSON.parse(err.error) + row.file_data = [{'status': err.status, 'error': error}] + row.total = 1; + row.columns = [ + {header: 'Status Code', field: 'status'}, + {header: 'Error', field: 'error.error'} + ] + row.showPagination = false; + } + }, + complete: () => { + row.isLoading = false + this.cdRef.detectChanges(); + } + }) + } + + updateData(e, row) { + row.params.page = e.pageIndex; + row.params.size = e.pageSize; + this.getFileData(row); + } + + expand(event) { + if (event.expanded) { + const row = event.data; + if (!row.file_data) { + // set some params + row.params = { + page: 0, + size: 10 + } + // fetch data + this.getFileData(row) + } + } + } +} diff --git a/front/src/app/dialogs/edit-feature/edit-feature.component.html b/front/src/app/dialogs/edit-feature/edit-feature.component.html index 5509687e..1e7270c2 100755 --- a/front/src/app/dialogs/edit-feature/edit-feature.component.html +++ b/front/src/app/dialogs/edit-feature/edit-feature.component.html @@ -54,7 +54,7 @@

Clone feature

-
+
info Selecting Default department will make this feature visible to everyone, use it with caution!
@@ -68,7 +68,7 @@

Clone feature

{{ 'need_help.ask_for' | translate }} - +
@@ -113,6 +113,94 @@

Clone feature

+ + + + + + + Files + + + + + + Upload/Download department files + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
File name +
+ + {{element.name}} +
+
File type {{element.mime}} File size {{element.size | humanizeBytes}} Uploaded By {{element.uploaded_by.name}} Upload date {{ element.created_on | amParse | amDateFormat:'MMMM d yyyy, HH:mm a'}} Actions + + + + + + +
+
+
+ +
+

Currently this department has no files, upload one by clicking upload button.

+
+
+
+
+ + + + + @@ -141,7 +229,7 @@

Clone feature

Edit the steps for the current feature (applies to all selected browsers)
- +
@@ -196,6 +284,16 @@
How to format a cron sche +
+ +

Note: Your feature will be scheduled in UTC timezone.

+

Here are some executions (in "{{timezone}}" timezone) based on the crontab expression:

+
  • Next Run: {{nextRun}}
  • +
    + +

    Error: Found error while parsing crontab expression, please check.

    +
    +
    diff --git a/front/src/app/dialogs/edit-feature/edit-feature.component.scss b/front/src/app/dialogs/edit-feature/edit-feature.component.scss index c798277b..53dd9f0f 100755 --- a/front/src/app/dialogs/edit-feature/edit-feature.component.scss +++ b/front/src/app/dialogs/edit-feature/edit-feature.component.scss @@ -11,12 +11,63 @@ flex-wrap: wrap; } .mat-form-field { + @-moz-document url-prefix() { + textarea { + height: 15px; + } + } + @media (max-width: 600px) { flex: 1 100% !important; } } } + + +.table-container { + max-height: 300px; + overflow: auto; + tr.mat-header-row { + position: sticky; + z-index: 1; + top: 0; + } + table.mat-table { + width: 100%; + min-width: 700px; + } + + td.mat-cell, + th.mat-header-cell { + padding: 0; + } + + .removed { + td:not(:last-child) { + opacity: 0.6; + } + } + + .mat-column-name { + height: 50px; + div.name-wrapper { + display:flex; + align-items: center; + color: $blue; + font-weight: bold; + mat-spinner { + margin-right: 5px; + } + span.canDownload { + cursor: pointer; + } + } + } +} + + + .no-clouds { font-size: 1.05rem; color: rgba(black, .75); @@ -31,6 +82,10 @@ a { margin: 0 10px; } +.mat-expansion-panel { + // overflow: unset; +} + :host::ng-deep .mat-dialog-content { padding: 0 10px 5px; @include for-tablet-portrait-up { @@ -172,4 +227,12 @@ button { position: absolute; bottom: 10px; right: 30px; +} + +.next_run { + line-height: 1.2rem; + li { + margin-left: 10px; + list-style: "\2192 "; + } } \ No newline at end of file diff --git a/front/src/app/dialogs/edit-feature/edit-feature.component.ts b/front/src/app/dialogs/edit-feature/edit-feature.component.ts index d50e8162..67620985 100755 --- a/front/src/app/dialogs/edit-feature/edit-feature.component.ts +++ b/front/src/app/dialogs/edit-feature/edit-feature.component.ts @@ -1,13 +1,15 @@ -import { Component, OnInit, Inject, ViewChild, ChangeDetectionStrategy, HostListener, OnDestroy } from '@angular/core'; +import { Component, OnInit, Inject, ViewChild, ChangeDetectionStrategy, HostListener, OnDestroy, ChangeDetectorRef } from '@angular/core'; import { ApiService } from '@services/api.service'; +import { FileUploadService } from '@services/file-upload.service' import { COMMA, ENTER } from '@angular/cdk/keycodes'; -import { MatDialogRef, MAT_DIALOG_DATA, MatDialog } from '@angular/material/dialog'; -import { MatSnackBar } from '@angular/material/snack-bar'; -import { FormControl, FormGroup, Validators, FormBuilder } from '@angular/forms'; -import { MatCheckboxChange } from '@angular/material/checkbox'; +import { API_URL } from 'app/tokens'; +import { MatLegacyDialogRef as MatDialogRef, MAT_LEGACY_DIALOG_DATA as MAT_DIALOG_DATA, MatLegacyDialog as MatDialog } from '@angular/material/legacy-dialog'; +import { MatLegacySnackBar as MatSnackBar } from '@angular/material/legacy-snack-bar'; +import { UntypedFormControl, UntypedFormGroup, Validators, UntypedFormBuilder } from '@angular/forms'; +import { MatLegacyCheckboxChange as MatCheckboxChange } from '@angular/material/legacy-checkbox'; import { StepEditorComponent } from '@components/step-editor/step-editor.component'; import { BrowserSelectionComponent } from '@components/browser-selection/browser-selection.component'; -import { MatChipListChange } from '@angular/material/chips'; +import { MatLegacyChipListChange as MatChipListChange } from '@angular/material/legacy-chips'; import { ApplicationsState } from '@store/applications.state'; import { Select, Store } from '@ngxs/store'; import { EnvironmentsState } from '@store/environments.state'; @@ -18,7 +20,6 @@ import { BehaviorSubject, Observable, of } from 'rxjs'; import { FeatureCreated } from '@dialogs/edit-feature/feature-created/feature-created.component'; import { ScheduleHelp } from '@dialogs/edit-feature/schedule-help/schedule-help.component'; import { KEY_CODES } from '@others/enums'; -import { Dispatch } from '@ngxs-labs/dispatch-decorator'; import { CustomSelectors } from '@others/custom-selectors'; import { ViewSelectSnapshot } from '@ngxs-labs/select-snapshot'; import { noWhitespaceValidator, deepClone } from 'ngx-amvara-toolbox'; @@ -29,6 +30,9 @@ import { finalize, switchMap } from 'rxjs/operators'; import { EmailTemplateHelp } from './email-template-help/email-template-help.component'; import { AreYouSureData, AreYouSureDialog } from '@dialogs/are-you-sure/are-you-sure.component'; import { Configuration } from '@store/actions/config.actions'; +import { parseExpression } from 'cron-parser'; +import { DepartmentsState } from '@store/departments.state'; +import { VariablesState } from '@store/variables.state'; @Component({ selector: 'edit-feature', @@ -37,6 +41,7 @@ import { Configuration } from '@store/actions/config.actions'; changeDetection: ChangeDetectionStrategy.OnPush }) export class EditFeature implements OnInit, OnDestroy { + displayedColumns: string[] = ['name','mime','size','uploaded_by.name','created_on', 'actions']; @ViewSelectSnapshot(ConfigState) config$ !: Config; /** @@ -50,14 +55,26 @@ export class EditFeature implements OnInit, OnDestroy { departments$ !: Department[]; @ViewSelectSnapshot(UserState) user !: UserInfo; @ViewSelectSnapshot(UserState.HasOneActiveSubscription) hasSubscription: boolean; + @Select(DepartmentsState) allDepartments$: Observable; + @Select(VariablesState) variableState$: Observable; saving$ = new BehaviorSubject(false); departmentSettings$: Observable + variable_dialog_isActive: boolean = false; steps$: Observable; + // next runs an array of next executions + nextRuns = []; + // parse error + parseError = { + "error": false, + "msg": "" + } + // get user timezone + timezone = Intl.DateTimeFormat().resolvedOptions().timeZone; browserstackBrowsers = new BehaviorSubject([]); @@ -65,6 +82,8 @@ export class EditFeature implements OnInit, OnDestroy { selected_department; selected_application; selected_environment; + department; + variables !: VariablePair[]; readonly separatorKeysCodes: number[] = [ENTER, COMMA]; @@ -73,7 +92,7 @@ export class EditFeature implements OnInit, OnDestroy { // COTEMP -- Used to check the state data status @Select(FeaturesState.GetStateDAta) state$: Observable>; - featureForm: FormGroup; + featureForm: UntypedFormGroup; constructor( public dialogRef: MatDialogRef, @@ -82,7 +101,10 @@ export class EditFeature implements OnInit, OnDestroy { private _snackBar: MatSnackBar, private _store: Store, private _dialog: MatDialog, - private _fb: FormBuilder + private _fb: UntypedFormBuilder, + private cdr: ChangeDetectorRef, + private fileUpload: FileUploadService, + @Inject(API_URL) public api_url: string, ) { // Create the fields within FeatureForm this.featureForm = this._fb.group({ @@ -102,6 +124,7 @@ export class EditFeature implements OnInit, OnDestroy { 'need_help': [false], 'send_mail_on_error': [false], 'continue_on_failure': [false], + 'uploaded_files': [[]], 'video': [true], 'minute': ['0', Validators.compose([Validators.required, Validators.pattern('^[0-9,-/*]+$')])], 'hour': ['0', Validators.compose([Validators.required, Validators.pattern('^[0-9,-/*]+$')])], @@ -120,12 +143,37 @@ export class EditFeature implements OnInit, OnDestroy { this.selected_department = route.length > 0 ? route[0].name : this.departments$[0].department_name; this.selected_application = this.applications$[0].app_name; this.selected_environment = this.environments$[0].environment_name; + + this.featureForm.valueChanges.subscribe(values => { + const { minute, hour, day_month, month, day_week } = values; + this.parseSchedule({ minute, hour, day_month, month, day_week }); + }) } - @Dispatch() ngOnDestroy() { // When Edit Feature Dialog is closed, clear temporal steps - return new StepDefinitions.ClearNewFeature(); + return this._store.dispatch(new StepDefinitions.ClearNewFeature()); + } + + parseSchedule(expression) { + // ignore if schedule is disabled + if (!this.featureForm.value.run_now) return; + + try { + // parse cron expression + let parser = parseExpression(Object.values(expression).join(" "), {utc: true}); + // reset errors + this.parseError.error = false; + // reset nextRuns arrays + this.nextRuns = []; + for(let i = 0; i<5; i++) { this.nextRuns.push(parser.next().toDate().toLocaleString()); } + } catch (error) { + this.nextRuns = []; + this.parseError = { + "error": true, + "msg": error.message + } + } } changeSchedule({ checked }: MatCheckboxChange) { @@ -163,11 +211,21 @@ export class EditFeature implements OnInit, OnDestroy { editVariables() { const environmentId = this.environments$.find(env => env.environment_name === this.featureForm.get('environment_name').value).environment_id; const departmentId = this.departments$.find(dep => dep.department_name === this.featureForm.get('department_name').value).department_id; + const feature = this.feature.getValue(); + + this.variable_dialog_isActive = true; this._dialog.open(EditVariablesComponent, { data: { + feature_id: feature.feature_id, environment_id: environmentId, - department_id: departmentId - } + department_id: departmentId, + department_name: this.featureForm.get('department_name').value, + environment_name: this.featureForm.get('environment_name').value, + feature_name: this.featureForm.get('feature_name').value + }, + panelClass: 'edit-variable-panel' + }).afterClosed().subscribe(res => { + this.variable_dialog_isActive = false; }); } @@ -187,6 +245,8 @@ export class EditFeature implements OnInit, OnDestroy { // Handle keyboard keys @HostListener('document:keydown', ['$event']) handleKeyboardEvent(event: KeyboardEvent) { + // only execute switch case if child dialog is closed + if (this.variable_dialog_isActive) return switch (event.keyCode) { case KEY_CODES.ESCAPE: // Check if form has been modified before closing @@ -203,6 +263,10 @@ export class EditFeature implements OnInit, OnDestroy { } else { this.dialogRef.close(); } + break; + case KEY_CODES.V: + if (event.ctrlKey && event.altKey) this.editVariables(); + break; } } @@ -309,6 +373,12 @@ export class EditFeature implements OnInit, OnDestroy { @ViewChild(BrowserSelectionComponent, { static: false }) _browserSelection: BrowserSelectionComponent; ngOnInit() { + this.featureForm.valueChanges.subscribe(() => { + this.variableState$.subscribe(data => { + this.variables = this.getFilteredVariables(data) + }) + }) + if (this.data.mode === 'edit' || this.data.mode === 'clone') { // Code for editing feautre const featureInfo = this.data.info; @@ -331,19 +401,54 @@ export class EditFeature implements OnInit, OnDestroy { } // Try to save all possible feature properties in the form using the same property names for (const key in featureInfo) { - if (this.featureForm.get(key) instanceof FormControl) { + if (this.featureForm.get(key) instanceof UntypedFormControl) { this.featureForm.get(key).setValue(featureInfo[key]); } } this.stepsOriginal = this.data.steps; } else { // Code for creating a feature + // set user preselect options this.feature.next(this.data.feature); + this.preSelectedOptions() } // @ts-ignore if (!this.feature) this.feature = { feature_id: 0 }; const featureId = this.data.mode === 'clone' ? 0 : this.data.feature.feature_id; this.steps$ = this._store.select(CustomSelectors.GetFeatureSteps(featureId)) + + this.featureForm.get('department_name').valueChanges.subscribe(department_name => { + this.allDepartments$.subscribe(data => { + this.department = data.find(dep => dep.department_name === department_name); + this.fileUpload.validateFileUploadStatus(this.department); + this.cdr.detectChanges(); + }) + }) + } + + /** + * Select user specified selections if any. + */ + preSelectedOptions() { + const { + preselectDepartment, + preselectApplication, + preselectEnvironment, + recordVideo } = this.user.settings; + + this.departments$.find(d => { + if (d.department_id == preselectDepartment) this.selected_department = d.department_name; + }) + this.applications$.find(a => { + if (a.app_id == preselectApplication) this.selected_application = a.app_name; + }) + this.environments$.find(e => { + if (e.environment_id == preselectEnvironment) this.selected_environment = e.environment_name + }) + this.featureForm.patchValue({ + video: recordVideo != undefined ? recordVideo : true + // ... add addition properties here. + }) } stepsOriginal: FeatureStep[] = []; @@ -580,7 +685,7 @@ export class EditFeature implements OnInit, OnDestroy { */ moveFeatureToCurrentFolder(featureId: number): Observable { // Get current folder route - const currentRoute = this._store.selectSnapshot(FeaturesState.GetSelectionFolders); + const currentRoute = this._store.selectSnapshot(FeaturesState.GetSelectionFolders).filter(route => route.type != 'department'); // Check if changing folder of created feature is necessary if (currentRoute.length > 0) { // Get current folder id @@ -616,8 +721,99 @@ export class EditFeature implements OnInit, OnDestroy { * @date 21/11/02 * @lastModification 21/11/02 */ - @Dispatch() toggleWelcome(){ - return new Configuration.SetProperty('co_first_time_cometa', 'false', true); + return this._store.dispatch(new Configuration.SetProperty('co_first_time_cometa', 'false', true)); } + + // adds each selected file into formControl array + onUploadFile(ev) { + let formData: FormData = new FormData; + let files = ev.target.files + + for (let file of files) { + formData.append("files", file) + } + formData.append("department_id", this.department.department_id); + + this.fileUpload.startUpload(files, formData, this.department, this.user); + } + + onDownloadFile(file: UploadedFile) { + // return if file is still uploading + if(file.status.toLocaleLowerCase() != 'done') { + return; + } + + const downloading = this._snackBar.open('Generating file to download, please be patient.', 'OK', { duration: 10000 }) + + this.fileUpload.downloadFile(file.id).subscribe({ + next: (res) => { + const blob = new Blob([this.base64ToArrayBuffer(res.body)], { type: file.mime }); + this.fileUpload.downloadFileBlob(blob, file); + downloading.dismiss(); + }, + error: (err) => { + if (err.error) { + const errors = JSON.parse(err.error); + this._snackBar.open(errors.error, 'OK'); + } + } + }) + } + + base64ToArrayBuffer(data: string) { + const byteArray = atob(data); + const uint = new Uint8Array(byteArray.length) + for (let i = 0; i < byteArray.length; i++) { + let ascii = byteArray.charCodeAt(i); + uint[i] = ascii; + } + return uint; + } + + onDeleteFile(file: UploadedFile) { + this.fileUpload.deleteFile(file.id).subscribe(res => { + if (res.success) this.fileUpload.updateFileState(file, this.department); + }); + } + + onRestoreFile(file: UploadedFile) { + let formData: FormData = new FormData; + formData.append("restore", String(file.is_removed) ); + + this.fileUpload.restoreFile(file.id, formData).subscribe(res => { + if (res.success) this.fileUpload.updateFileState(file, this.department); + }); + } + + public onFilePathCopy(successful: boolean): void { + const duration = 2000; + successful ? this._snackBar.open("File upload path has been copied", "OK", { duration: duration }) : + this._snackBar.open("File upload path could not be copied", "OK", { duration: duration }) + } + + + getFilteredVariables(variables: VariablePair[]) { + const environmentId = this.environments$.find(env => env.environment_name === this.featureForm.get('environment_name').value)?.environment_id; + const departmentId = this.departments$.find(dep => dep.department_name === this.featureForm.get('department_name').value)?.department_id; + + let feature = this.feature.getValue(); + let reduced = variables.reduce((filtered_variables: VariablePair[], current:VariablePair) => { + // stores variables, if it's id coincides with received department id and it is based on department + const byDeptOnly = current.department === departmentId && current.based == 'department' ? current : null; + + // stores variable if department id coincides with received department id and + // environment or feature ids coincide with received ones, additionally if feature id coincides variable must be based on feature. If environment id coincides, variables must be based on environment. + const byEnv = current.department === departmentId && ((current.environment === environmentId && current.based == 'environment') || + (current.feature === feature.feature_id && current.based == 'feature')) ? current : null; + + // pushes stored variables into array if they have value + byDeptOnly ? filtered_variables.push(byDeptOnly) : null; + byEnv ? filtered_variables.push(byEnv) : null; + + // removes duplicated variables and returs set like array + return filtered_variables.filter((value, index, self) => index === self.findIndex((v) => (v.id === value.id))) + }, []) + return reduced; + } } \ No newline at end of file diff --git a/front/src/app/dialogs/edit-feature/feature-created/feature-created.component.ts b/front/src/app/dialogs/edit-feature/feature-created/feature-created.component.ts index 46f36047..7fbaeb65 100644 --- a/front/src/app/dialogs/edit-feature/feature-created/feature-created.component.ts +++ b/front/src/app/dialogs/edit-feature/feature-created/feature-created.component.ts @@ -1,6 +1,6 @@ import { ChangeDetectionStrategy, Component, Inject } from '@angular/core'; import { Router } from '@angular/router'; -import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog'; +import { MatLegacyDialogRef as MatDialogRef, MAT_LEGACY_DIALOG_DATA as MAT_DIALOG_DATA } from '@angular/material/legacy-dialog'; @Component({ selector: 'feature-created', diff --git a/front/src/app/dialogs/edit-feature/schedule-help/schedule-help.component.ts b/front/src/app/dialogs/edit-feature/schedule-help/schedule-help.component.ts index 5b245502..893bc839 100644 --- a/front/src/app/dialogs/edit-feature/schedule-help/schedule-help.component.ts +++ b/front/src/app/dialogs/edit-feature/schedule-help/schedule-help.component.ts @@ -1,5 +1,5 @@ import { Component, HostListener } from "@angular/core"; -import { MatDialogRef } from "@angular/material/dialog"; +import { MatLegacyDialogRef as MatDialogRef } from "@angular/material/legacy-dialog"; @Component({ selector: 'schedule-help', diff --git a/front/src/app/dialogs/edit-integration/edit-integration.component.ts b/front/src/app/dialogs/edit-integration/edit-integration.component.ts index 3be1ee9a..3e219fc3 100644 --- a/front/src/app/dialogs/edit-integration/edit-integration.component.ts +++ b/front/src/app/dialogs/edit-integration/edit-integration.component.ts @@ -1,6 +1,6 @@ import { Component, ChangeDetectionStrategy, Inject, OnInit } from '@angular/core'; -import { FormBuilder, FormGroup, Validators } from '@angular/forms'; -import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog'; +import { UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms'; +import { MatLegacyDialogRef as MatDialogRef, MAT_LEGACY_DIALOG_DATA as MAT_DIALOG_DATA } from '@angular/material/legacy-dialog'; import { ViewSelectSnapshot } from '@ngxs-labs/select-snapshot'; import { Store } from '@ngxs/store'; import { ApiService } from '@services/api.service'; @@ -20,12 +20,12 @@ export class EditIntegrationDialog implements OnInit { @ViewSelectSnapshot(UserState.RetrieveUserDepartments) departments: Department[]; @ViewSelectSnapshot(UserState.RetrieveIntegrationApps) integrations: string[]; - intForm: FormGroup; + intForm: UntypedFormGroup; constructor( private dialogRef: MatDialogRef, @Inject(MAT_DIALOG_DATA) public data: IntegrationDialogData, - private _fb: FormBuilder, + private _fb: UntypedFormBuilder, private _api: ApiService, private _sharedActions: SharedActionsService, private _store: Store diff --git a/front/src/app/dialogs/edit-schedule/edit-schedule.component.html b/front/src/app/dialogs/edit-schedule/edit-schedule.component.html index 72d3b2e2..e4b19d13 100755 --- a/front/src/app/dialogs/edit-schedule/edit-schedule.component.html +++ b/front/src/app/dialogs/edit-schedule/edit-schedule.component.html @@ -1,7 +1,8 @@ -
    +

    Edit schedule

    -
    + +
    Minute @@ -28,13 +29,85 @@

    Edit schedule

    Invalid format
    + +
    +
    +
    + + Minute + + Invalid format + +
    +
    Example: Setting the minute to "0,15,30,45" executes the feature every 15 minutes.
    +
    +
    +
    + + Hour + + Invalid format + +
    +
    Setting the hour to "*/2" executes every second hour combined with the minute setting.
    +
    +

    +
    +
    + + Day of Month + + Invalid format + +
    +
    Setting the day to "1-31" executes the feature from the first to the 31th day of the respective month.
    +
    +
    +
    + + Month + + Invalid format + +
    +
    Using the "*" is a placeholder that meets any condition, like any month, day our hour. The above mentioned input value can also be used here.
    +
    +
    +
    + + Day of week + + Invalid format + +
    +
    Can use "*" as a wildcard. Or SUN, MON, ... to execute on one or more days of the week.
    +
    +
    +
    + +

    Note: Your feature will be scheduled in UTC timezone.

    +

    Here are some executions (in "{{timezone}}" timezone) based on the crontab expression:

    +
  • Next Run: {{nextRun}}
  • +
    + +

    Error: Found error while parsing crontab expression, please check.

    +
    +
    +
    Enable schedule
    - infoSchedule format +
    +
    + infoSchedule format +
    +
    + {{formLayoutIcon}}Switch to {{formLayoutTextSelected}} layout +
    +
    - \ No newline at end of file + diff --git a/front/src/app/dialogs/edit-schedule/edit-schedule.component.scss b/front/src/app/dialogs/edit-schedule/edit-schedule.component.scss index 9bf61bbd..256c7114 100755 --- a/front/src/app/dialogs/edit-schedule/edit-schedule.component.scss +++ b/front/src/app/dialogs/edit-schedule/edit-schedule.component.scss @@ -7,6 +7,21 @@ margin-bottom: 10px; } +.grid-schedule-layout-2 { + margin-bottom: 10px; + .inputcontainer { + margin-bottom: 25px; + display: grid; + grid-template-columns: 1fr 1fr; + } +} + +.helpContainer { + display: grid; + grid-template-columns: 1fr 1fr; +} + + :host::ng-deep .grid-schedule .mat-form-field-label-wrapper { text-align: center } @@ -31,4 +46,11 @@ input { &:hover { text-decoration: underline; } +} +.next_run { + line-height: 1.2rem; + li { + margin-left: 10px; + list-style: "\2192 "; + } } \ No newline at end of file diff --git a/front/src/app/dialogs/edit-schedule/edit-schedule.component.ts b/front/src/app/dialogs/edit-schedule/edit-schedule.component.ts index 439178fa..4daed0b3 100755 --- a/front/src/app/dialogs/edit-schedule/edit-schedule.component.ts +++ b/front/src/app/dialogs/edit-schedule/edit-schedule.component.ts @@ -1,15 +1,17 @@ import { Component, Inject, ChangeDetectionStrategy } from '@angular/core'; -import { MatDialogRef, MAT_DIALOG_DATA, MatDialog } from '@angular/material/dialog'; +import { MatLegacyDialogRef as MatDialogRef, MAT_LEGACY_DIALOG_DATA as MAT_DIALOG_DATA, MatLegacyDialog as MatDialog } from '@angular/material/legacy-dialog'; import { ApiService } from '@services/api.service'; -import { MatSnackBar } from '@angular/material/snack-bar'; +import { MatLegacySnackBar as MatSnackBar } from '@angular/material/legacy-snack-bar'; import { ScheduleHelp } from '@dialogs/edit-feature/schedule-help/schedule-help.component'; -import { MatSlideToggleChange } from '@angular/material/slide-toggle'; +import { MatLegacySlideToggleChange as MatSlideToggleChange } from '@angular/material/legacy-slide-toggle'; import { Store } from '@ngxs/store'; import { FeaturesState } from '@store/features.state'; -import { FormGroup, FormBuilder, Validators } from '@angular/forms'; +import { UntypedFormGroup, UntypedFormBuilder, Validators } from '@angular/forms'; import { BehaviorSubject } from 'rxjs'; import { Features } from '@store/actions/features.actions'; import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy'; +import { parseExpression } from 'cron-parser'; +import { LogService } from '@services/log.service'; @UntilDestroy() @Component({ @@ -20,7 +22,26 @@ import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy'; }) export class EditSchedule { - schedule: FormGroup; + schedule: UntypedFormGroup; + + // formLayout is a variable to steer the layout of the schedule edit form + // ... value: "1" ... means the first layout + // ... value: "2" ... second layout proposed from Cosimo + formLayout : Number + // The text shown in the link to toggle the layout + formLayoutTextSelected : String + // The Icon shown in front of the link etxt + formLayoutIcon = "rotate_right" + + // next runs an array of next executions + nextRuns = []; + // parse error + parseError = { + "error": false, + "msg": "" + } + // get user timezone + timezone = Intl.DateTimeFormat().resolvedOptions().timeZone; feature: Feature; @@ -33,8 +54,18 @@ export class EditSchedule { private snackBar: MatSnackBar, private _dialog: MatDialog, private _store: Store, - private _fb: FormBuilder + private _fb: UntypedFormBuilder, + private log: LogService ) { + + this.log.msg("1","Edit Schedule Constuctor","edit-schedule") + + // set the default layout of the form for editing schedule + // "1" is the crontab like layout + // "2" is the vertical form layout + this.formLayout = 2 + this.formLayoutTextSelected = "horizontal" + // Create empty form this.schedule = this._fb.group({}); // Iterate each cron field and add control in form @@ -55,9 +86,9 @@ export class EditSchedule { } else { // Initialize form with default values this.schedule.setValue({ - minute: '0', - hour: '0', - day: '1', + minute: '0,15,30,45', + hour: '*/2', + day: '1-31', month: '*', dayWeek: '*' }); @@ -66,18 +97,57 @@ export class EditSchedule { untilDestroyed(this) ).subscribe(enable => { if (enable) { + this.log.msg("1","Enable Schedule","edit-schedule") this.schedule.enable(); + this.parseSchedule(this.schedule.getRawValue()); } else { + this.log.msg("1","Disable Schedule","edit-schedule") this.schedule.disable(); } this.schedule.updateValueAndValidity(); }); + + this.schedule.valueChanges.subscribe((expression) => { + this.parseSchedule(expression); + }); } getHelp() { this._dialog.open(ScheduleHelp, { panelClass: 'help-schedule-panel' }); } + parseSchedule(expression) { + // ignore if schedule is disabled + if (!this.schedule.enable) return; + + try { + // parse cron expression + let parser = parseExpression(Object.values(expression).join(" "), {utc: true}); + // reset errors + this.parseError.error = false; + // reset nextRuns arrays + this.nextRuns = []; + for(let i = 0; i<5; i++) { this.nextRuns.push(parser.next().toDate().toLocaleString()); } + } catch (error) { + this.nextRuns = []; + this.parseError = { + "error": true, + "msg": error.message + } + } + } + + // triggers the toggle of the layout + switchFormLayout() { + // toggle the layout between layout 1 and layout 2 + this.formLayout = (this.formLayout == 1) ? 2 : 1; + // Toggle the string to click on + this.formLayoutTextSelected = (this.formLayout == 1) ? 'vertical' : 'horizontal' + // Toggle the Icon + this.formLayoutIcon = (this.formLayout == 1) ? 'rotate_right' : 'rotate_left' + + } + updateSchedule() { let schedule = [ this.schedule.value.minute, diff --git a/front/src/app/dialogs/edit-variables/edit-variables.component.html b/front/src/app/dialogs/edit-variables/edit-variables.component.html index 604a3a63..44024206 100644 --- a/front/src/app/dialogs/edit-variables/edit-variables.component.html +++ b/front/src/app/dialogs/edit-variables/edit-variables.component.html @@ -1,46 +1,178 @@ -

    Edit environment variables

    +
    +

    Edit variables

    +
    + +

    Edit variables

    +
    +
    +
    + Environment: + {{data.environment_name}} +
    +
    + Department: + {{data.department_name}} +
    +
    + - - -
    -
    Key
    -
    Value
    -
    Encrypt
    -
    -
    - - -
    - -
    - -
    - -
    - -
    - -
    - - - - + + + Search + search + + +
    + + +
    + + {{c.name}} +
    - -
    - -
    -
    -
    + +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Name + + Variable name is required. + Special characters and space are not allowed. + Value + + Variable value is required. + Encrypted + + + Based on + + + {{base}} + + + Environment +

    {{element?.environment_name}}

    +
    Feature +

    {{element?.feature_name}}

    +
    Department +

    {{element.department_name}}

    +
    Created by +

    {{element?.created_by_name}}

    +
    Created on +

    {{element?.created_on ? (element?.created_on | amParse | amDateFormat:'MMMM d yyyy, HH:mm a') : element?.created_on }}

    +
    Last updated by +

    {{element?.updated_by_name}}

    +
    Last updated on +

    {{element?.updated_on ? (element?.updated_on | amParse | amDateFormat:'MMMM d yyyy, HH:mm a') : element?.updated_on }}

    +
    +
    +
    + +
    +
    + +
    + +
    + +
    +
    +
    + +
    +
    +
    +
    This environment has no variables yet.
    - - - - + +
    + +
    +
    \ No newline at end of file diff --git a/front/src/app/dialogs/edit-variables/edit-variables.component.scss b/front/src/app/dialogs/edit-variables/edit-variables.component.scss index 29fa3853..6aaedfb6 100644 --- a/front/src/app/dialogs/edit-variables/edit-variables.component.scss +++ b/front/src/app/dialogs/edit-variables/edit-variables.component.scss @@ -1,81 +1,134 @@ - @import 'color'; -.variable-header { - display: flex; - align-items: center; - justify-content: space-between; - flex-wrap: nowrap; - .key { - margin-right: 5px; - } - .value { - margin-left: 5px; - } - .key, - .value { - flex: 0 50%; - max-width: 50%; - color: $blue; - font-weight: bold; - padding-left: 5px; - margin-bottom: 10px; - box-sizing: border-box; - } - .secret { - flex: 0 60px; - margin-left: 10px; - color: $blue; - font-weight: bold; - } - .remove { - flex: 0 50px; - margin-left: 10px; +.mat-dialog-title { + margin: 0; + height: 40px; +} + +.mat-dialog-content { + display: unset; +} + +.search-wrapper { + display: block; + + mat-icon { + margin-right: 15px; } } +.table-wrapper.component { + height: calc(100% - 275px); +} -.variable-row { +.columns-button-wrapper { + text-align: end; + padding-bottom: 20px; +} + +.env-info { display: flex; - align-items: center; - justify-content: space-between; - flex-wrap: nowrap; - margin-bottom: 10px; - .key { - margin-right: 5px; - } - .value { - margin-left: 5px; + + .env-item { + margin-right: 20px; + .env-title { margin-right: 3px; } + .env-value { color: $blue; } } - .key, - .value { - flex: 0 50%; - max-width: 50%; +} + +.table-wrapper { + height: 40vh; + overflow-y: auto; + scroll-behavior: smooth; + + table { + width: 100%; + + .var-row:hover { + background-color: rgb(242, 242, 242); + } + + .var-row.edit { + cursor: pointer; + td.mat-cell { + border-bottom: 1px solid $blue; + } + } + + td.mat-cell { + small { + display: block; + font-size: 10px; + } + } + + th.mat-header-cell { + font-size: 13px; + font-weight: bold; + color: $blue; + } + + td.mat-cell, + th.mat-header-cell { + padding-right: 20px; + } + + tr.mat-header-row { + position: sticky; + z-index: 1; + top: 0; + } + input { background-color: transparent; - border: 1px solid rgba(black, .1); - border-radius: 3px; - padding: 10px; + border: 0; box-sizing: border-box; width: 100%; outline: 0; + pointer-events: none; + &.edit { + pointer-events: all; + } + } + + .validation-error { + color: $blue; + } + + .actions-wrapper { + display: flex; + + .action-wrapper { + height: 22px; + line-height: 22px; + button { + padding: 0; + height: 22px !important; + line-height: 22px !important; + cursor: pointer; + + mat-icon { + margin-right: 0 !important; + } + } + } } - } - .secret { - margin-left: 10px; - flex: 0 60px; - text-align: center; - } - &:hover .remove { - opacity: .9; - } - .remove { - flex: 0 50px; - margin-left: 10px; - opacity: .45; } } -.add { - margin-top: 20px; +.add-variable { + display: inline-block; +} + +.mat-elevation-z8 { + box-shadow: none; +} + +p.inuse { + display: inline; + color: $bad; font-weight: bold; +} + +mat-dialog-actions { + gap: 8px; } \ No newline at end of file diff --git a/front/src/app/dialogs/edit-variables/edit-variables.component.ts b/front/src/app/dialogs/edit-variables/edit-variables.component.ts index ddd67ecd..4449c1c0 100644 --- a/front/src/app/dialogs/edit-variables/edit-variables.component.ts +++ b/front/src/app/dialogs/edit-variables/edit-variables.component.ts @@ -1,22 +1,25 @@ -import { Component, Inject, ChangeDetectionStrategy, ChangeDetectorRef } from '@angular/core'; -import { MatDialog, MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog'; -import { Store } from '@ngxs/store'; +import { Component, Inject, ChangeDetectionStrategy, ChangeDetectorRef, OnInit, ViewChild, OnDestroy, ElementRef } from '@angular/core'; +import { MatLegacyDialog as MatDialog, MAT_LEGACY_DIALOG_DATA as MAT_DIALOG_DATA } from '@angular/material/legacy-dialog'; +import { Select, Store } from '@ngxs/store'; import { ApiService } from '@services/api.service'; -import { MatSnackBar } from '@angular/material/snack-bar'; +import { MatLegacySnackBar as MatSnackBar } from '@angular/material/legacy-snack-bar'; import { UserState } from '@store/user.state'; -import { switchMap, map, finalize, filter } from 'rxjs/operators'; -import { Observable, of } from 'rxjs'; +import { Observable, Subject, takeUntil } from 'rxjs'; import { VariablesState } from '@store/variables.state'; -import { deepClone } from 'ngx-amvara-toolbox'; import { Variables } from '@store/actions/variables.actions'; -import { SelectSnapshot, ViewSelectSnapshot } from '@ngxs-labs/select-snapshot'; -import { MatCheckboxChange } from '@angular/material/checkbox'; -import { FormArray, FormBuilder, Validators } from '@angular/forms'; +import { ViewSelectSnapshot } from '@ngxs-labs/select-snapshot'; import { AreYouSureData, AreYouSureDialog } from '@dialogs/are-you-sure/are-you-sure.component'; +import { MatSort, Sort } from '@angular/material/sort'; +import { MatLegacyTableDataSource as MatTableDataSource } from '@angular/material/legacy-table'; +import { MatLegacyCheckboxChange as MatCheckboxChange } from '@angular/material/legacy-checkbox'; interface PassedData { environment_id: number; department_id: number; + feature_id: number; + department_name: string; + environment_name: string; + feature_name: string; } @Component({ @@ -25,132 +28,350 @@ interface PassedData { styleUrls: ['./edit-variables.component.scss'], changeDetection: ChangeDetectionStrategy.OnPush }) -export class EditVariablesComponent { +export class EditVariablesComponent implements OnInit, OnDestroy { + allColumns: VariableColumns[] = [ + { name: 'Name', activated: true, value: 'variable_name' }, + { name: 'Value', activated: true, value: 'variable_value' }, + { name: 'Encrypted', activated: true, value: 'encrypted' }, + { name: 'Based on', activated: true, value: 'based' }, + { name: 'Department', activated: true, value: 'department_name' }, + { name: 'Environment', activated: true, value: 'environment_name' }, + { name: 'Feature', activated: true, value: 'feature_name' }, + { name: 'Created by', activated: false, value: 'created_by_name' }, + { name: 'Last updated by', activated: false, value: 'updated_by_name' }, + { name: 'Created on', activated: false, value: 'created_on' }, + { name: 'Last updated on', activated: false, value: 'updated_on' }, + { name: 'Actions', activated: true, value: 'actions' } + ]; + displayedColumns: string[] = []; + bases: string[] = ['feature','environment','department']; + isEditing: boolean = false; + errors = { name: null, value: null }; + variables: VariablePair[]; + variable_backup: VariablePair; + destroy$ = new Subject(); + searchTerm: string = ""; + isDialog: boolean = false; + dataSource; + + + @ViewChild('tableWrapper') tableWrapper: ElementRef; + @ViewChild(MatSort) sort: MatSort; + @Select(VariablesState) variableState$: Observable; @ViewSelectSnapshot(UserState.GetPermission('create_variable')) canCreate: boolean; @ViewSelectSnapshot(UserState.GetPermission('edit_variable')) canEdit: boolean; @ViewSelectSnapshot(UserState.GetPermission('delete_variable')) canDelete: boolean; - @SelectSnapshot(UserState.RetrieveEncryptionPrefix) encryptionPrefix: string; - - variablesForm: FormArray = this._fb.array([]); constructor( - private dialogRef: MatDialogRef, @Inject(MAT_DIALOG_DATA) public data: PassedData, private _store: Store, private _snack: MatSnackBar, private _api: ApiService, - private _fb: FormBuilder, private _cdr: ChangeDetectorRef, - private _dialog: MatDialog - ) { - const variables = deepClone(this._store.selectSnapshot(VariablesState.GetVariables)(this.data.environment_id, this.data.department_id)) as VariablePair[]; - variables.forEach(variable => { - this.variablesForm.push( - this._fb.group({ - variable_name: [variable.variable_name, this.nameValidator], - variable_value: variable.variable_value, - encrypted: variable.encrypted || false, - department: variable.department, - environment: variable.environment, - loading: false - }) - ) + private _dialog: MatDialog, + ) {} + + ngOnInit(): void { + // get displayed columns from localstorage + const storedColumns = JSON.parse(localStorage.getItem('co_edit_variable_displayed_columns')); + + // if result is not null, load columns from localstorage, else load default columns + storedColumns != null ? this.loadStoredColumns(storedColumns) : this.onColumnDisplayChange(); + + // department_id is received only when component is opened as dialog + this.isDialog = this.data?.department_id ? true : false; + + this.variableState$.pipe(takeUntil(this.destroy$)) + .subscribe(data => { + this.variables = this.isDialog ? this.getFilteredVariables(data) : this.getAllVariables(data); + this.dataSource = new MatTableDataSource(this.variables); + + // Inbuilt MatTableDataSource.filterPredicate determines which columns filter term must be applied to + this.applyFilterPredicate(); + + // apply current searchTerm + this.applyFilter(); + }) + } + + ngOnDestroy(): void { + // destroy subscription + this.destroy$.next(); + this.destroy$.complete(); + + // when this dialog is closed, remove any variable with id 0 from variable state. + this.variables.forEach(variable => { + if (variable.id === 0) { + this._store.dispatch(new Variables.DeleteVariable(variable.id)) + } }) } - nameValidator = Validators.pattern(/^[^\n ]*$/) + // loads colums received from localstorage if length of the array has items. If array is loads default columns + loadStoredColumns(storedColumns: string[]) { + if(storedColumns.length > 0) { + this.allColumns.map(item => item.activated = storedColumns.includes(item.value)) + this.onColumnDisplayChange(); + return + } + + this.onColumnDisplayChange(); + } + + // removes or adds clicked column from displayed columns + onColumnDisplayChange() { + this.displayedColumns = this.allColumns.filter(item => item.activated) + .map(item => item.value); - trackIndex = index => index; + localStorage.setItem('co_edit_variable_displayed_columns', JSON.stringify(this.displayedColumns)); + } - add() { - // Add new variable row - this.variablesForm.push( - this._fb.group({ - variable_name: ['', this.nameValidator], - variable_value: '', - encrypted: false, - department_id: this.data.department_id, - environment_id: this.data.environment_id, - loading: false - }) - ) - this.updateFormView(); - } - - handleSecretChange(index: number, { checked }: MatCheckboxChange) { - // Grab variable value - const value = (this.variablesForm.at(index).value as VariablePair).variable_value - let request: Observable = of(value); - // Check if current value needs decryption - if (value.startsWith(this.encryptionPrefix) && !checked) { - // Show loading on encryption checkbox - this.variablesForm.at(index).patchValue({ loading: true }) - this.updateFormView(); - request = this._dialog.open(AreYouSureDialog, { + onEditVar(variable: VariablePair) { + // if user is currently editing some variable row and double click is performed on different variable row, give feedback message and return + // this snack feedback message will only be provided if user tries to edit variable with double click mouse event, while another variable is being edited. + if (this.isEditing && variable.disabled) { + this._snack.open('Please save changes in order to edit another variable.', 'OK'); + return; + } + + // return if user double clicks on a row that she/he is already editing + if (this.isEditing && !variable.disabled) { + return; + } + + // save backup of current state of variable in case user wants to cancel changes + this.variable_backup = {...variable} + + // notify view that currently there is a variable that is being edited and enable table row that variable is located at. + this.isEditing = true; + variable.disabled = false; + + // focus enabled row + this.focusElement(`name-${variable.id}`) + } + + onSaveVar(variable: VariablePair) { + // If input validators are invalid, return + // this condition will only be fired when user tries to save variable with ENTER keyboard event + if (this.errors.name || this.errors.value){ + return; + } + + // removes from state variable with id 0 (if it exists) + this._store.dispatch(new Variables.DeleteVariable(0)) + + // Depending on if user is creating new variable or is patching existing one + // next piece of code updates existing variable in state or replaces the removed variable(with id 0) with the one that is received from XHR + let action = variable.id === 0 ? this.createVariable(variable) : this.patchVariable(variable); + action.subscribe(this.safeSubscriber('save')) + } + + onDeleteVar(variable: VariablePair) { + // defines action to be realized, in this case > Delete + let action = this.deleteVariable(variable.id); + + // opens confirmation dialog, to prevent accidental elimination + const confirmDialog = this._dialog.open(AreYouSureDialog, { + data: { + title: 'translate:you_sure.delete_item_title', + description: 'translate:you_sure.delete_item_desc' + } as AreYouSureData + }); + + // if dialogs result is 'yes/true', removes variable completes action to eliminate variable + confirmDialog.afterClosed().subscribe(res => { + if (res) { + action.subscribe(this.safeSubscriber('delete', variable)); + return; + } + }) + } + + onEncryptCheckboxChange(event: MatCheckboxChange, variable: VariablePair) { + if (!event.checked && variable.variable_value.startsWith("U2FsdGVkX1")) { + // opens confirmation dialog, to prevent accidental elimination of encrypted variable value + const confirmDialog = this._dialog.open(AreYouSureDialog, { data: { title: 'translate:you_sure.decrypt_title', description: 'translate:you_sure.decrypt_desc' } as AreYouSureData - }).afterClosed().pipe( - finalize(() => { - // Hide loading on encryption checkbox - this.variablesForm.at(index).patchValue({ loading: false }) - this.updateFormView(); - }), - filter(res => !!res), - map(_ => '') - ) + }); + + // if dialogs result is 'yes/true', resets the value of variable. But it is still not saved, user can revert it by hitting ESC key or Cancel button + confirmDialog.afterClosed().subscribe(res => { + if (res) { + variable.variable_value = ""; + this.setInputStatus({required: true}, 'value'); + this._cdr.markForCheck(); + + // focus enabled row's variable-value input + this.focusElement(`value-${variable.id}`) + return; + } + + // if dialog result is no/false. go back to previous state of value encryption and focus variable-name input + variable.encrypted = true; + this.focusElement(`name-${variable.id}`) + this._cdr.markForCheck(); + }) } - // Retrieve encrypted/decrypted value - request.subscribe(decrypted => { - // Update value field - this.variablesForm.at(index).patchValue({ variable_value: decrypted, encrypted: checked }) - this.updateFormView(); - }) } - updateFormView() { - // Update view - this.variablesForm.updateValueAndValidity(); - this._cdr.detectChanges(); + onCancelVar(variable: VariablePair, event: Event) { + // stop propagation if user cancels modification by clicking ESC key, this will prevent whole popup from closing + if(event) event.stopImmediatePropagation(); + + // if users is cancelling the process of creation of new variable, dispaches delete event to remove it from state + // if user is cancelling the process of modification of already existing variable, dispatches update event to set variable to its previous state + variable.id === 0 ? this._store.dispatch(new Variables.DeleteVariable(variable.id)) : this._store.dispatch(new Variables.UpdateOrCreateVariable(this.variable_backup)); + this.variable_backup = null; + + // Nulify validatrs for newly created variables + this.nullifyValidators(); + + // notify view that user is no longer editing any variable + this.isEditing = false; } - save() { - // Remove empty variable pairs - let variables = this.variablesForm.value as VariablePair[]; - variables = variables.filter((variable, i) => { - const remove = !variable.variable_name.length || !variable.variable_value.length - // Also remove them from the FormArray - if (remove) this.variablesForm.removeAt(i); - return !remove; - }) - this.updateFormView(); - // Save on backend - this._api.setEnvironmentVariables(this.data.environment_id, this.data.department_id, variables) - .pipe( - // Save on store state - switchMap(res => this._store.dispatch( new Variables.GetVariables() ).pipe( - map(_ => res) - )) - ) - .subscribe(res => { - if (res.success) { - this._snack.open('Variables saved!', 'OK'); - this.dialogRef.close(); - } else if (res.handled) { - this.dialogRef.close(); - } else { - this._snack.open('Oops, something went wrong.', 'OK'); - } - }); + // fired when user clicks on Add Variable button + onAddVar() { + // creates and dispatches new temporary variable object to state, so it gets rendered in view + this.createNewVarInstance(); + this.applyValidators(); + this.isEditing = true; + + // scrolls up to top and focus new variable row + setTimeout(() => { + this.tableWrapper.nativeElement.scrollTo(0,0); + document.getElementById("name-0").focus(); + },0); + } + + // is fired when, sorting of any column on table is changed + announceSortChange(sortState: Sort) { + this.dataSource.sort = this.sort; + } + + // Receives string from search input and adds it to dataSource as filterTerm. + applyFilter() { + this.dataSource.filter = this.searchTerm.trim().toLowerCase(); + } + + // determines which columns filter must be applied to + applyFilterPredicate() { + this.dataSource.filterPredicate = (row: VariablePair, filter: string) => { + return row.variable_name.toLocaleLowerCase().includes(filter) || row.based.toLocaleLowerCase().includes(filter) || row.department_name.toLocaleLowerCase().includes(filter); + }; + } + + // binded to (input) event. Actualizes input's validator status every time user focuses out of input + setInputStatus(errors: any, control: string) { + this.errors[control] = errors; + } + + // create variable XHR + createVariable(variable: VariablePair) { + return this._api.setVariable(variable) + } + + // update variable XHR + patchVariable(variable: VariablePair) { + return this._api.patchVariable(variable); + } + + // delete variable XHR + deleteVariable(id: number) { + return this._api.deleteVariable(id); } - deleteVar(index: number) { - // Delete variable row - this.variablesForm.removeAt(index); - this.updateFormView(); + // set validators for newly created temporary variables + applyValidators() { + this.errors = { name: {required : true}, value: {required: true} }; } + // nulifies validators, after editmode cancel + nullifyValidators() { + this.errors = { name: null, value: null }; + } + + // provides filter mechanism for variables requested in #3985 + getFilteredVariables(variables: VariablePair[]) { + let reduced = variables.reduce((filtered_variables: VariablePair[], current:VariablePair) => { + // stores variables, if it's id coincides with received department id and it is based on department + const byDeptOnly = current.department === this.data.department_id && current.based == 'department' ? current : null; + + // stores variable if department id coincides with received department id and + // environment or feature ids coincide with received ones, additionally if feature id coincides variable must be based on feature. If environment id coincides, variables must be based on environment. + const byEnv = current.department === this.data.department_id && ((current.environment === this.data.environment_id && current.based == 'environment') || + (current.feature === this.data.feature_id && current.based == 'feature')) ? current : null; + + // pushes stored variables into array if they have value + byDeptOnly ? filtered_variables.push(byDeptOnly) : null; + byEnv ? filtered_variables.push(byEnv) : null; + + // removes duplicated variables and returs set like array + return filtered_variables.filter((value, index, self) => index === self.findIndex((v) => (v.id === value.id))) + }, []) + + // disables every table row, except the one that is newly created in is still not saved in db + const clone = reduced.map((item: VariablePair) => { return {...item, disabled: item.id === 0 ? false : true} }) + return clone; + } + + // just disables table rows, filters are not applied. This only happens when template is displayed as child component instead of dialog + getAllVariables(variables: VariablePair[]) { + return variables.map((item: VariablePair) => { return {...item, disabled: true} }) + } + + // focuses dom element that carries id attribute equal received id + focusElement(id: string) { + setTimeout(() => { + document.getElementById(id).focus(); + },0); + } + + // creates temporary variable object and dispatches it to state, so it is displayed in view and user can fill input fields + createNewVarInstance() { + const new_var = {}; + + new_var.id = 0; + new_var.department = this.data.department_id + new_var.environment = this.data.environment_id; + new_var.department_name = this.data.department_name + new_var.environment_name = this.data.environment_name; + new_var.feature_name = this.data.feature_name; + new_var.feature = this.data.feature_id === 0 ? null : this.data.feature_id; + new_var.variable_name = ""; + new_var.variable_value = ""; + new_var.encrypted = false; + new_var.based = this.data.feature_id === 0 ? 'department' : 'feature'; + new_var.in_use = []; + new_var.disabled = false; + + this._store.dispatch(new Variables.UpdateOrCreateVariable(new_var)) + this.isEditing = true; + } + + // subscribes to XHR actions and treats returned infromation + safeSubscriber(action: string, variable?: VariablePair) { + return { + next: (response) => { + let res = JSON.parse(response); + if (res.success) { + action === 'save' ? this._store.dispatch(new Variables.UpdateOrCreateVariable(res['data'] as VariablePair)) : this._store.dispatch(new Variables.DeleteVariable(variable.id)) + this._snack.open('Action has been completed successfully!', 'OK'); + } + }, + error: (err) => { + this._snack.open(JSON.parse(err.error).error, 'OK'); + this.isEditing = false; + }, + complete: () => { + this.isEditing = false; + this._cdr.detectChanges(); + } + } + } } diff --git a/front/src/app/dialogs/enter-value/enter-value.component.ts b/front/src/app/dialogs/enter-value/enter-value.component.ts index 08e88b47..c4e45ffa 100755 --- a/front/src/app/dialogs/enter-value/enter-value.component.ts +++ b/front/src/app/dialogs/enter-value/enter-value.component.ts @@ -1,6 +1,6 @@ import { Component, Inject, ChangeDetectionStrategy } from '@angular/core'; -import { FormBuilder, FormGroup } from '@angular/forms'; -import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog'; +import { UntypedFormBuilder, UntypedFormGroup } from '@angular/forms'; +import { MatLegacyDialogRef as MatDialogRef, MAT_LEGACY_DIALOG_DATA as MAT_DIALOG_DATA } from '@angular/material/legacy-dialog'; import { ApiService } from '@services/api.service'; @Component({ @@ -14,7 +14,7 @@ export class EnterValueComponent { constructor( private dialogRef: MatDialogRef, @Inject(MAT_DIALOG_DATA) public data: any, - private fb: FormBuilder, + private fb: UntypedFormBuilder, private _api: ApiService ) { this.rForm = this.fb.group({ @@ -22,7 +22,7 @@ export class EnterValueComponent { }); } - rForm: FormGroup; + rForm: UntypedFormGroup; getReturn() { return { diff --git a/front/src/app/dialogs/error/error.dialog.ts b/front/src/app/dialogs/error/error.dialog.ts index ba66182e..641dc9a6 100644 --- a/front/src/app/dialogs/error/error.dialog.ts +++ b/front/src/app/dialogs/error/error.dialog.ts @@ -1,5 +1,5 @@ import { ChangeDetectionStrategy, Component, Inject } from '@angular/core'; -import { MAT_DIALOG_DATA } from '@angular/material/dialog'; +import { MAT_LEGACY_DIALOG_DATA as MAT_DIALOG_DATA } from '@angular/material/legacy-dialog'; @Component({ selector: 'cometa-error', diff --git a/front/src/app/dialogs/html-diff/html-diff.component.ts b/front/src/app/dialogs/html-diff/html-diff.component.ts index 8df3d5b6..98e00b72 100755 --- a/front/src/app/dialogs/html-diff/html-diff.component.ts +++ b/front/src/app/dialogs/html-diff/html-diff.component.ts @@ -1,5 +1,5 @@ import { Component, Inject, ChangeDetectionStrategy, HostListener } from '@angular/core'; -import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog'; +import { MatLegacyDialogRef as MatDialogRef, MAT_LEGACY_DIALOG_DATA as MAT_DIALOG_DATA } from '@angular/material/legacy-dialog'; @Component({ selector: 'html-diff', diff --git a/front/src/app/dialogs/import-json/import-json.component.ts b/front/src/app/dialogs/import-json/import-json.component.ts index 0f822aa6..af3872f9 100755 --- a/front/src/app/dialogs/import-json/import-json.component.ts +++ b/front/src/app/dialogs/import-json/import-json.component.ts @@ -1,5 +1,5 @@ import { Component, ChangeDetectionStrategy } from '@angular/core'; -import { MatDialogRef } from '@angular/material/dialog'; +import { MatLegacyDialogRef as MatDialogRef } from '@angular/material/legacy-dialog'; @Component({ selector: 'import-json', diff --git a/front/src/app/dialogs/invite-user/invite-user.component.ts b/front/src/app/dialogs/invite-user/invite-user.component.ts index e3283027..80384a07 100644 --- a/front/src/app/dialogs/invite-user/invite-user.component.ts +++ b/front/src/app/dialogs/invite-user/invite-user.component.ts @@ -1,9 +1,9 @@ import { COMMA, ENTER } from '@angular/cdk/keycodes'; import { Component, ChangeDetectionStrategy, OnInit } from '@angular/core'; -import { FormBuilder, FormGroup, Validators } from '@angular/forms'; -import { MatChipListChange } from '@angular/material/chips'; -import { MatDialogRef } from '@angular/material/dialog'; -import { MatSnackBar } from '@angular/material/snack-bar'; +import { UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms'; +import { MatLegacyChipListChange as MatChipListChange } from '@angular/material/legacy-chips'; +import { MatLegacyDialogRef as MatDialogRef } from '@angular/material/legacy-dialog'; +import { MatLegacySnackBar as MatSnackBar } from '@angular/material/legacy-snack-bar'; import { ViewSelectSnapshot } from '@ngxs-labs/select-snapshot'; import { ApiService } from '@services/api.service'; import { UserState } from '@store/user.state'; @@ -19,11 +19,11 @@ export class InviteUserDialog implements OnInit { @ViewSelectSnapshot(UserState.RetrieveUserDepartments) departments: Department[]; - inviteForm: FormGroup; + inviteForm: UntypedFormGroup; constructor( private dialogRef: MatDialogRef, - private _fb: FormBuilder, + private _fb: UntypedFormBuilder, private _api: ApiService, private _snackBar: MatSnackBar ) { diff --git a/front/src/app/dialogs/live-steps/live-step/live-step.component.ts b/front/src/app/dialogs/live-steps/live-step/live-step.component.ts index a208050c..178c0ae1 100644 --- a/front/src/app/dialogs/live-steps/live-step/live-step.component.ts +++ b/front/src/app/dialogs/live-steps/live-step/live-step.component.ts @@ -3,7 +3,7 @@ import { Store } from '@ngxs/store'; import { tap } from 'rxjs/operators'; import { LiveStepsComponent } from '../live-steps.component'; import { DomSanitizer } from '@angular/platform-browser'; -import { MatDialog } from '@angular/material/dialog'; +import { MatLegacyDialog as MatDialog } from '@angular/material/legacy-dialog'; import { ScreenshotComponent } from '@dialogs/screenshot/screenshot.component'; import { BehaviorSubject, Observable } from 'rxjs'; import { CustomSelectors } from '@others/custom-selectors'; diff --git a/front/src/app/dialogs/live-steps/live-steps.component.html b/front/src/app/dialogs/live-steps/live-steps.component.html index 35df3e2a..032b36ae 100755 --- a/front/src/app/dialogs/live-steps/live-steps.component.html +++ b/front/src/app/dialogs/live-steps/live-steps.component.html @@ -68,7 +68,7 @@

    Live Steps Viewer: {{ feature.feature_name }}

    - +
    #
    Step definition
    diff --git a/front/src/app/dialogs/live-steps/live-steps.component.ts b/front/src/app/dialogs/live-steps/live-steps.component.ts index c6cc33d5..ee0c21c8 100755 --- a/front/src/app/dialogs/live-steps/live-steps.component.ts +++ b/front/src/app/dialogs/live-steps/live-steps.component.ts @@ -1,17 +1,16 @@ import { Component, Inject, ChangeDetectionStrategy, OnInit, OnDestroy } from '@angular/core'; -import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog'; +import { MatLegacyDialogRef as MatDialogRef, MAT_LEGACY_DIALOG_DATA as MAT_DIALOG_DATA } from '@angular/material/legacy-dialog'; import { ApiService } from '@services/api.service'; import { Store, Actions, ofActionCompleted } from '@ngxs/store'; import { Subscribe } from 'app/custom-decorators'; -import { MatSnackBar } from '@angular/material/snack-bar'; +import { MatLegacySnackBar as MatSnackBar } from '@angular/material/legacy-snack-bar'; import { distinctUntilKeyChanged, filter, map, shareReplay, tap } from 'rxjs/operators'; -import { MatCheckboxChange } from '@angular/material/checkbox'; +import { MatLegacyCheckboxChange as MatCheckboxChange } from '@angular/material/legacy-checkbox'; import { Observable } from 'rxjs'; import { CustomSelectors } from '@others/custom-selectors'; import { WebSockets } from '@store/actions/results.actions'; import { StepDefinitions } from '@store/actions/step_definitions.actions'; import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy'; -import { Dispatch } from '@ngxs-labs/dispatch-decorator'; import { getBrowserKey } from '@services/tools'; @UntilDestroy() @@ -47,7 +46,7 @@ export class LiveStepsComponent implements OnInit, OnDestroy { } // Cleanup old or unused runs info on close - @Dispatch() ngOnDestroy = () => new WebSockets.CleanupFeatureResults(this.feature_id); + ngOnDestroy = () => this._store.dispatch(new WebSockets.CleanupFeatureResults(this.feature_id)); trackBrowserFn(index, item) { return item.key; @@ -131,7 +130,7 @@ export class LiveStepsComponent implements OnInit, OnDestroy { // if so we don't have access to live session if (data.browser_info.cloud != 'local') return false; // array of status on which not to show the live icon - const notToShowOn = ['Initializing', 'Timeout', 'Completed']; + const notToShowOn = ['Queued', 'Initializing', 'Timeout', 'Completed']; return !notToShowOn.includes(data.status); } diff --git a/front/src/app/dialogs/log-output/log-output.component.ts b/front/src/app/dialogs/log-output/log-output.component.ts index ece08ac2..eb6029cd 100755 --- a/front/src/app/dialogs/log-output/log-output.component.ts +++ b/front/src/app/dialogs/log-output/log-output.component.ts @@ -1,6 +1,5 @@ import { Component, Inject, ChangeDetectionStrategy, HostListener, OnInit } from '@angular/core'; -import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog'; -import { Dispatch } from '@ngxs-labs/dispatch-decorator'; +import { MatLegacyDialogRef as MatDialogRef, MAT_LEGACY_DIALOG_DATA as MAT_DIALOG_DATA } from '@angular/material/legacy-dialog'; import { Store } from '@ngxs/store'; import { CustomSelectors } from '@others/custom-selectors'; import { Logs } from '@store/actions/logs.actions'; @@ -29,9 +28,8 @@ export class LogOutputComponent implements OnInit { log$: Observable; - @Dispatch() ngOnInit() { - return new Logs.GetLogs(this.id); + return this._store.dispatch(new Logs.GetLogs(this.id)); } } diff --git a/front/src/app/dialogs/modify-department-timeout/modify-department-timeout.component.html b/front/src/app/dialogs/modify-department-timeout/modify-department-timeout.component.html new file mode 100755 index 00000000..43f7064e --- /dev/null +++ b/front/src/app/dialogs/modify-department-timeout/modify-department-timeout.component.html @@ -0,0 +1,33 @@ + + +

    Modify all timeouts of department

    +
    +
    +
    + + From + + seconds + +
    +
    + + To + + seconds + +
    +
    +
    +
    + Globally modify timeout of steps in all the features of department. + Have in mind that update will only be applied to steps that currently exist and will not affect in any way steps created in future. +
    +
    +
    +
    +
    + + + + \ No newline at end of file diff --git a/front/src/app/dialogs/modify-department-timeout/modify-department-timeout.component.scss b/front/src/app/dialogs/modify-department-timeout/modify-department-timeout.component.scss new file mode 100755 index 00000000..5febe7a9 --- /dev/null +++ b/front/src/app/dialogs/modify-department-timeout/modify-department-timeout.component.scss @@ -0,0 +1,12 @@ +@import 'color'; + +.global-timeout-modifier { + display: grid; + grid-template-columns: 150px 150px; + place-items: center start; + gap: 5px; +} + +h2 { + color: $blue; +} \ No newline at end of file diff --git a/front/src/app/dialogs/modify-department-timeout/modify-department-timeout.component.ts b/front/src/app/dialogs/modify-department-timeout/modify-department-timeout.component.ts new file mode 100755 index 00000000..516c5556 --- /dev/null +++ b/front/src/app/dialogs/modify-department-timeout/modify-department-timeout.component.ts @@ -0,0 +1,67 @@ +import { Component, Inject, ChangeDetectionStrategy } from '@angular/core'; +import { UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms'; +import { MAT_LEGACY_DIALOG_DATA as MAT_DIALOG_DATA } from '@angular/material/legacy-dialog'; +import { MatLegacySnackBar as MatSnackBar } from '@angular/material/legacy-snack-bar'; +import { ApiService } from '@services/api.service'; + +@Component({ + selector: 'modify-department-timeout', + templateUrl: './modify-department-timeout.component.html', + styleUrls: ['./modify-department-timeout.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class ModifyDepartmentTimeoutComponent { + + timeoutForm: UntypedFormGroup; + loading: boolean = false; + + constructor( @Inject(MAT_DIALOG_DATA) private department_id: number, + private _api: ApiService, + private snack: MatSnackBar, + private fb: UntypedFormBuilder) { + + this.timeoutForm = this.fb.group({ + 'step_timeout_from': ['', Validators.compose([Validators.min(1), Validators.max(7205), Validators.maxLength(4)])], + 'step_timeout_to': ['', Validators.compose([Validators.min(1), Validators.max(7205), Validators.maxLength(4)])], + }); + } + + applyGlobalTimeout(ev: Event) { + const options = this.timeoutForm.value; + // prevents whole popup from closing + ev.preventDefault(); + + // disable button while http post is processed + this.loading = true; + this._api.applyDepartmentStepsTimeout(this.department_id, options).subscribe({ + next: (res) => { + let result = JSON.parse(res); + + // if timeout modification XHR was successfull, show user how many steps and features were modified + if (result.success) { + result.total_steps_updated === 0 ? this.snack.open("No steps with specified timeout", 'OK') : + this.snack.open(`features updated:${result.total_features_updated}, steps updated:${result.total_steps_updated}`, 'OK'); + } + // enable button again + this.loading = false; + // reset input values + this.resetGlobalTimeoutInputs(); + }, + error: (err) => { + let error = JSON.parse(err.error); + + // show user the cause of error + this.snack.open(error.error, 'OK') + this.loading = false; + } + }) + } + + + // resets the input value for global timeout modifiers + resetGlobalTimeoutInputs() { + this.timeoutForm.get('step_timeout_from').setValue(''); + this.timeoutForm.get('step_timeout_to').setValue(''); + } + +} diff --git a/front/src/app/dialogs/modify-department/modify-department.component.html b/front/src/app/dialogs/modify-department/modify-department.component.html index 49dbcb72..9ea9257b 100755 --- a/front/src/app/dialogs/modify-department/modify-department.component.html +++ b/front/src/app/dialogs/modify-department/modify-department.component.html @@ -1,33 +1,49 @@ -

    Modify department

    -
    + - - Name - - -
    - -
    - Continue on failure -
    By activating this option all features within the department won't stop when a step fails.
    -
    -
    -
    -
    - Results housekeeping - - Delete after - - days - -
    Set the amount of days after which the results will be automatically deleted. An email will be sent 10 days before deletion.
    - Please use the Archive option to avoid deletion. -
    -
    -
    + +

    Modify department

    + + Name + + +
    + +
    + Continue on failure +
    By activating this option all features within the department won't stop when a step fails.
    +
    +
    +
    +
    + Results housekeeping + + Delete after + + days + +
    Set the amount of days after which the results will be automatically deleted. An email will be sent 10 days before deletion.
    + Please use the Archive option to avoid deletion. +
    +
    +
    +
    +
    +
    Default step timeout
    +
    + + Timeout + + seconds + +
    + Set the default step timeout which will be applied to all the features that belongs to this department. + Have in mind that Modifying default timeout, will not update steps that have been created before timeout modification. +
    +
    + +
    - - - \ No newline at end of file + + \ No newline at end of file diff --git a/front/src/app/dialogs/modify-department/modify-department.component.scss b/front/src/app/dialogs/modify-department/modify-department.component.scss index f05eec9d..2e6b921c 100755 --- a/front/src/app/dialogs/modify-department/modify-department.component.scss +++ b/front/src/app/dialogs/modify-department/modify-department.component.scss @@ -1,5 +1,6 @@ @import 'color'; +@import 'breakpoints'; h2 { color: $blue; @@ -18,4 +19,20 @@ button.submit { color: $blue; margin-left: 15px; margin-top: 10px; +} + +.global-timeout-modifier { + display: grid; + grid-template-columns: 150px 150px; + place-items: center start; + gap: 5px; + + button { + height: 40px; + display: inline-block; + } + + @include for-tablet-landscape-only { + grid-template-columns: 150px 150px 50px; + } } \ No newline at end of file diff --git a/front/src/app/dialogs/modify-department/modify-department.component.ts b/front/src/app/dialogs/modify-department/modify-department.component.ts index c4abe3f7..a8400b6f 100755 --- a/front/src/app/dialogs/modify-department/modify-department.component.ts +++ b/front/src/app/dialogs/modify-department/modify-department.component.ts @@ -1,15 +1,15 @@ import { Component, Inject, ChangeDetectionStrategy } from '@angular/core'; -import { FormGroup, FormBuilder, Validators } from '@angular/forms'; +import { UntypedFormGroup, UntypedFormBuilder, Validators } from '@angular/forms'; import { ApiService } from '@services/api.service'; -import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog'; -import { MatSnackBar } from '@angular/material/snack-bar'; +import { MatLegacyDialogRef as MatDialogRef, MAT_LEGACY_DIALOG_DATA as MAT_DIALOG_DATA } from '@angular/material/legacy-dialog'; +import { MatLegacySnackBar as MatSnackBar } from '@angular/material/legacy-snack-bar'; import { SelectSnapshot, ViewSelectSnapshot } from '@ngxs-labs/select-snapshot'; import { DepartmentsState } from '@store/departments.state'; import { Store } from '@ngxs/store'; import { UserState } from '@store/user.state'; import { Departments } from '@store/actions/departments.actions'; import { BehaviorSubject } from 'rxjs'; -import { MatCheckboxChange } from '@angular/material/checkbox'; +import { MatLegacyCheckboxChange as MatCheckboxChange } from '@angular/material/legacy-checkbox'; @Component({ selector: 'modify-department', @@ -19,7 +19,9 @@ import { MatCheckboxChange } from '@angular/material/checkbox'; }) export class ModifyDepartmentComponent { - rForm: FormGroup; + rForm: UntypedFormGroup; + timeoutForm: UntypedFormGroup; + loading: boolean = false; expireDaysChecked$ = new BehaviorSubject(false); @@ -33,7 +35,7 @@ export class ModifyDepartmentComponent { private dialogRef: MatDialogRef, @Inject(MAT_DIALOG_DATA) private department_id: number, private _api: ApiService, - private fb: FormBuilder, + private fb: UntypedFormBuilder, private snack: MatSnackBar, private _store: Store ) { @@ -41,9 +43,14 @@ export class ModifyDepartmentComponent { const expireDays = this.department.settings?.result_expire_days ? parseInt(this.department.settings.result_expire_days, 10) : 90; this.rForm = this.fb.group({ 'department_name': [this.department.department_name, Validators.required], - 'continue_on_failure': [this.department.settings.continue_on_failure], + 'continue_on_failure': [{value: this.department.settings.continue_on_failure, disabled: this.account?.settings?.continue_on_failure}], + 'step_timeout': [this.department.settings?.step_timeout || 60, [Validators.required, Validators.compose([Validators.min(1), Validators.max(7205), Validators.maxLength(4)])]], 'result_expire_days': [expireDays] }); + this.timeoutForm = this.fb.group({ + 'step_timeout_from': ['', Validators.compose([Validators.min(1), Validators.max(7205), Validators.maxLength(4)])], + 'step_timeout_to': ['', Validators.compose([Validators.min(1), Validators.max(7205), Validators.maxLength(4)])], + }); this.expireDaysChecked$.next(!!this.department.settings.result_expire_days) } @@ -63,18 +70,18 @@ export class ModifyDepartmentComponent { settings: { ...this.department.settings, continue_on_failure: values.continue_on_failure, + step_timeout: values.step_timeout, result_expire_days: this.expireDaysChecked$.getValue() ? values.result_expire_days : null } } this._api.modifyDepartment(this.department_id, payload).subscribe(res => { if (res.success) { this._store.dispatch( new Departments.UpdateDepartment(this.department_id, payload) ); - this.dialogRef.close(); + // this.dialogRef.close(); this.snack.open('Department modified successfully!', 'OK'); } else { this.snack.open('An error ocurred', 'OK'); } }, () => this.snack.open('An error ocurred', 'OK')); } - } diff --git a/front/src/app/dialogs/modify-password/modify-password.component.ts b/front/src/app/dialogs/modify-password/modify-password.component.ts index 75258029..ae7bfc62 100755 --- a/front/src/app/dialogs/modify-password/modify-password.component.ts +++ b/front/src/app/dialogs/modify-password/modify-password.component.ts @@ -1,8 +1,8 @@ import { Component, Inject, ChangeDetectionStrategy } from '@angular/core'; -import { FormGroup, FormBuilder, Validators } from '@angular/forms'; +import { UntypedFormGroup, UntypedFormBuilder, Validators } from '@angular/forms'; import { ApiService } from '@services/api.service'; -import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog'; -import { MatSnackBar } from '@angular/material/snack-bar'; +import { MatLegacyDialogRef as MatDialogRef, MAT_LEGACY_DIALOG_DATA as MAT_DIALOG_DATA } from '@angular/material/legacy-dialog'; +import { MatLegacySnackBar as MatSnackBar } from '@angular/material/legacy-snack-bar'; @Component({ selector: 'modify-password', @@ -12,13 +12,13 @@ import { MatSnackBar } from '@angular/material/snack-bar'; }) export class ModifyPasswordComponent { - rForm: FormGroup; + rForm: UntypedFormGroup; constructor( private dialogRef: MatDialogRef, @Inject(MAT_DIALOG_DATA) public data: { account: IAccount }, private _api: ApiService, - private fb: FormBuilder, + private fb: UntypedFormBuilder, private snack: MatSnackBar ) { this.rForm = this.fb.group({ diff --git a/front/src/app/dialogs/modify-user/modify-user.component.ts b/front/src/app/dialogs/modify-user/modify-user.component.ts index 0bed8936..5ca6ce00 100755 --- a/front/src/app/dialogs/modify-user/modify-user.component.ts +++ b/front/src/app/dialogs/modify-user/modify-user.component.ts @@ -1,8 +1,8 @@ import { Component, Inject, ChangeDetectionStrategy } from '@angular/core'; import { ApiService } from '@services/api.service'; -import { FormBuilder, FormGroup, Validators } from '@angular/forms'; -import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog'; -import { MatSnackBar } from '@angular/material/snack-bar'; +import { UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms'; +import { MatLegacyDialogRef as MatDialogRef, MAT_LEGACY_DIALOG_DATA as MAT_DIALOG_DATA } from '@angular/material/legacy-dialog'; +import { MatLegacySnackBar as MatSnackBar } from '@angular/material/legacy-snack-bar'; import { Select } from '@ngxs/store'; import { DepartmentsState } from '@store/departments.state'; import { Observable } from 'rxjs'; @@ -19,13 +19,13 @@ export class ModifyUserComponent { @Select(DepartmentsState) departments$: Observable; @Select(UserState.GetPermissionTypes) permissions$: Observable; - rForm: FormGroup; + rForm: UntypedFormGroup; constructor( private dialogRef: MatDialogRef, @Inject(MAT_DIALOG_DATA) public data: { account: IAccount }, private _api: ApiService, - private fb: FormBuilder, + private fb: UntypedFormBuilder, private snack: MatSnackBar ) { this.rForm = this.fb.group({ diff --git a/front/src/app/dialogs/move-feature/move-folder-item/move-folder-item.component.ts b/front/src/app/dialogs/move-feature/move-folder-item/move-folder-item.component.ts index a8793bf9..e6ef6ae3 100644 --- a/front/src/app/dialogs/move-feature/move-folder-item/move-folder-item.component.ts +++ b/front/src/app/dialogs/move-feature/move-folder-item/move-folder-item.component.ts @@ -1,15 +1,14 @@ import { Component, ChangeDetectionStrategy, Input } from '@angular/core'; import { ApiService } from '@services/api.service'; import { Store } from '@ngxs/store'; -import { MatDialog } from '@angular/material/dialog'; +import { MatLegacyDialog as MatDialog } from '@angular/material/legacy-dialog'; import { EnterValueComponent } from '@dialogs/enter-value/enter-value.component'; import { filter, map, switchMap, tap } from 'rxjs/operators'; import { Subscribe } from 'app/custom-decorators'; -import { MatSnackBar } from '@angular/material/snack-bar'; +import { MatLegacySnackBar as MatSnackBar } from '@angular/material/legacy-snack-bar'; import { ConfigService } from '@services/config.service'; import { Features } from '@store/actions/features.actions'; import { AddFolderComponent } from '@dialogs/add-folder/add-folder.component'; -import { AreYouSureDialog } from '@dialogs/are-you-sure/are-you-sure.component'; @Component({ selector: 'cometa-move-folder-item', diff --git a/front/src/app/dialogs/move-feature/move-item.component.ts b/front/src/app/dialogs/move-feature/move-item.component.ts index bdff3801..b09ffa54 100644 --- a/front/src/app/dialogs/move-feature/move-item.component.ts +++ b/front/src/app/dialogs/move-feature/move-item.component.ts @@ -1,11 +1,17 @@ +// # ######################################## # +// # Changelog: +// # 2022-05-26 TONY ADDED - move feature dialog. Local reference: 1.1 ticket: #3460 +// # ######################################## # + import { Component, Inject, ChangeDetectionStrategy } from '@angular/core'; -import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog'; +import { MatLegacyDialog as MatDialog, MatLegacyDialogRef as MatDialogRef, MAT_LEGACY_DIALOG_DATA as MAT_DIALOG_DATA } from '@angular/material/legacy-dialog'; import { Store } from '@ngxs/store'; import { FeaturesState } from '@store/features.state'; import { ApiService } from '@services/api.service'; import { Observable, NEVER } from 'rxjs'; import { ConfigService } from '@services/config.service'; import { CustomSelectors } from '@others/custom-selectors'; +import { AreYouSureData, AreYouSureDialog } from '@dialogs/are-you-sure/are-you-sure.component'; @Component({ selector: 'cometa-move-item', @@ -26,7 +32,8 @@ export class MoveItemDialog { @Inject(MAT_DIALOG_DATA) public data: IMoveData, public _config: ConfigService, private _store: Store, - private _api: ApiService + private _api: ApiService, + private dialog: MatDialog ) { // If statement that decides which data to send to the backend (see backend's views.py:1883 for more info about old_folder and new_folder) // Move a feature @@ -88,28 +95,71 @@ export class MoveItemDialog { newFeature.department_id = folderData.department; newFeature.department_name = folderData.name; } + // Removes useless variables in the patch delete newFeature.created_by; delete newFeature.last_edited; delete newFeature.info; delete newFeature.steps; - // Patch and move the testcase - req = this._api.patchFeatureV2(newFeature.feature_id, newFeature, this.previousFolderId, folderData.id || 0, newFeature.department_id); + + // changelog reference: 1.1, ticket #3460 -------------------------------------------------------------------- start + // reason of implementation: unaccessible environment variables + // testcase: If user moves a feature that uses environment variables from deparment 'X' to department 'Y' + // the feature in question will no longer have access to the content of department 'X's environment variables + // causing the test to fail, unless department 'Y' also has environment variables that the feature uses + // for this reason we implement are you sure dialog, to advert user about possible consequences of this action + + // if destination's department is not the same as origin's department + if(folderData.department !== this.originFolder.department) { + // open dialog + this.dialog.open(AreYouSureDialog, { + data: { + title: 'translate:you_sure.move_feature_title', + description: 'translate:you_sure.move_feature' + } as AreYouSureData + }).afterClosed().subscribe(move => { + if(move) { + // if user clicks on 'yes' in dialog, move will be set to true + // Patch and move the testcase + req = this._api.patchFeatureV2(newFeature.feature_id, newFeature, this.previousFolderId, folderData.id || 0, folderData.department); + + // close folder picker dialog + this.closedialogRef(req); + } + }); + } else { + // if user attempts to move feature in the same department, then just move it without 'are you sure' dialog + // Patch and move the testcase + req = this._api.patchFeatureV2(newFeature.feature_id, newFeature, this.previousFolderId, folderData.id || 0, folderData.department); + + // close folder picker dialog + this.closedialogRef(req); + } + // changelog reference: 1.1, ticket #3460 -------------------------------------------------------------------- end break; + case 'folder': - req = this._api.modifyFolder({ + // save payload to an object + let payload = { folder_id: this.data.folder.folder_id, parent_id: folderData.id || null - }) + } as any; + // check if folderData is of type department if so send department value as well + if (folderData.type == 'department') payload.department = folderData.department; + req = this._api.modifyFolder(payload) + this.closedialogRef(req); break; default: req = NEVER; + this.closedialogRef(req); } + } + + closedialogRef(req: Observable) { req.subscribe(_ => { // Commented as it forces two requests to folders api // this._store.dispatch( new Features.GetFolders ); this.dialogRef.close(); }); } - } diff --git a/front/src/app/dialogs/offer-tour/offer-tour.component.ts b/front/src/app/dialogs/offer-tour/offer-tour.component.ts index c1d5309f..bdc70978 100644 --- a/front/src/app/dialogs/offer-tour/offer-tour.component.ts +++ b/front/src/app/dialogs/offer-tour/offer-tour.component.ts @@ -1,5 +1,5 @@ import { Component, ChangeDetectionStrategy, Inject } from '@angular/core'; -import { MAT_DIALOG_DATA } from '@angular/material/dialog'; +import { MAT_LEGACY_DIALOG_DATA as MAT_DIALOG_DATA } from '@angular/material/legacy-dialog'; import { Tour } from '@services/tours'; @Component({ diff --git a/front/src/app/dialogs/screenshot/screenshot.component.ts b/front/src/app/dialogs/screenshot/screenshot.component.ts index 2b3aec78..581c5fab 100644 --- a/front/src/app/dialogs/screenshot/screenshot.component.ts +++ b/front/src/app/dialogs/screenshot/screenshot.component.ts @@ -1,5 +1,5 @@ import { Component, ChangeDetectionStrategy, Inject } from '@angular/core'; -import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; +import { MAT_LEGACY_DIALOG_DATA as MAT_DIALOG_DATA, MatLegacyDialogRef as MatDialogRef } from '@angular/material/legacy-dialog'; import { SafeStyle, DomSanitizer } from '@angular/platform-browser'; @Component({ diff --git a/front/src/app/dialogs/sure-remove-feature/sure-remove-feature.component.ts b/front/src/app/dialogs/sure-remove-feature/sure-remove-feature.component.ts index 41e0b545..4a74a9f1 100755 --- a/front/src/app/dialogs/sure-remove-feature/sure-remove-feature.component.ts +++ b/front/src/app/dialogs/sure-remove-feature/sure-remove-feature.component.ts @@ -1,7 +1,7 @@ import { Component, Inject, ChangeDetectionStrategy } from '@angular/core'; import { ApiService } from '@services/api.service'; -import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog'; -import { MatSnackBar } from '@angular/material/snack-bar'; +import { MatLegacyDialogRef as MatDialogRef, MAT_LEGACY_DIALOG_DATA as MAT_DIALOG_DATA } from '@angular/material/legacy-dialog'; +import { MatLegacySnackBar as MatSnackBar } from '@angular/material/legacy-snack-bar'; import { BehaviorSubject, of } from 'rxjs'; import { finalize, map, switchMap } from 'rxjs/operators'; import { SharedActionsService } from '@services/shared-actions.service'; diff --git a/front/src/app/dialogs/video/video.component.scss b/front/src/app/dialogs/video/video.component.scss index 362c0871..1b934f98 100644 --- a/front/src/app/dialogs/video/video.component.scss +++ b/front/src/app/dialogs/video/video.component.scss @@ -1,13 +1,6 @@ @import 'color'; @import 'breakpoints'; -:host { - display: block; - margin-bottom: -1px; - margin-right: -1px; - min-width: 300px; -} - video { width: 100%; height: auto; diff --git a/front/src/app/dialogs/video/video.component.ts b/front/src/app/dialogs/video/video.component.ts index fea89c36..1ee61b01 100644 --- a/front/src/app/dialogs/video/video.component.ts +++ b/front/src/app/dialogs/video/video.component.ts @@ -1,5 +1,5 @@ import { Component, ChangeDetectionStrategy, Inject } from '@angular/core'; -import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog'; +import { MatDialogRef as MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog'; import { DomSanitizer, SafeUrl } from '@angular/platform-browser'; import { BehaviorSubject } from 'rxjs'; diff --git a/front/src/app/dialogs/whats-new/whats-new.component.ts b/front/src/app/dialogs/whats-new/whats-new.component.ts index 07890e78..03e64b19 100644 --- a/front/src/app/dialogs/whats-new/whats-new.component.ts +++ b/front/src/app/dialogs/whats-new/whats-new.component.ts @@ -1,5 +1,5 @@ import { Component, ChangeDetectionStrategy, Inject } from '@angular/core'; -import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog'; +import { MatLegacyDialogRef as MatDialogRef, MAT_LEGACY_DIALOG_DATA as MAT_DIALOG_DATA } from '@angular/material/legacy-dialog'; import { DomSanitizer, SafeUrl } from '@angular/platform-browser'; import { SelectSnapshot, ViewSelectSnapshot } from '@ngxs-labs/select-snapshot'; import { CustomSelectors } from '@others/custom-selectors'; @@ -36,8 +36,6 @@ export class WhatsNewDialog { } poster: SafeUrl; - - } interface DialogChanges { diff --git a/front/src/app/modules/about.module.ts b/front/src/app/modules/about.module.ts index 31e0e8f4..30f8bf4d 100644 --- a/front/src/app/modules/about.module.ts +++ b/front/src/app/modules/about.module.ts @@ -10,6 +10,7 @@ import { createTranslateLoader } from 'app/app.module'; const routes: Routes = [ { path: '', + title: 'About', component: AboutComponent } ]; diff --git a/front/src/app/modules/admin.module.ts b/front/src/app/modules/admin.module.ts index 821170fb..a9427c74 100755 --- a/front/src/app/modules/admin.module.ts +++ b/front/src/app/modules/admin.module.ts @@ -19,7 +19,9 @@ import { ModifyUserComponent } from '@dialogs/modify-user/modify-user.component' import { ModifyPasswordComponent } from '@dialogs/modify-password/modify-password.component'; import { PermissionGuard } from '@guards/permission.guard'; import { ModifyDepartmentComponent } from '@dialogs/modify-department/modify-department.component'; +import { ModifyDepartmentTimeoutComponent } from '@dialogs/modify-department-timeout/modify-department-timeout.component'; import { TranslateModule } from '@ngx-translate/core'; +import { AccountsDialog } from '@dialogs/accounts-dialog/accounts-dialog.component'; const routes: Routes = [ { @@ -29,6 +31,7 @@ const routes: Routes = [ { path: '', pathMatch: 'full', + component: AdminWrapperComponent, canActivate: [PermissionGuard], data: { require_permission: 'view_admin_panel' @@ -37,6 +40,7 @@ const routes: Routes = [ { path: 'departments', component: DepartmentsComponent, + title: 'Departments - Admin', canActivate: [PermissionGuard], data: { require_permission: 'view_departments_panel' @@ -45,6 +49,7 @@ const routes: Routes = [ { path: 'applications', component: ApplicationsComponent, + title: 'Applications - Admin', canActivate: [PermissionGuard], data: { require_permission: 'view_applications_panel' @@ -53,6 +58,7 @@ const routes: Routes = [ { path: 'environments', component: EnvironmentsComponent, + title: 'Environments - Admin', canActivate: [PermissionGuard], data: { require_permission: 'view_environments_panel' @@ -61,6 +67,7 @@ const routes: Routes = [ { path: 'browsers', component: BrowsersComponent, + title: 'Browsers - Admin', canActivate: [PermissionGuard], data: { require_permission: 'view_browsers_panel' @@ -69,6 +76,7 @@ const routes: Routes = [ { path: 'features', component: FeaturesComponent, + title: 'Features - Admin', canActivate: [PermissionGuard], data: { require_permission: 'view_features_panel' @@ -77,6 +85,7 @@ const routes: Routes = [ { path: 'accounts', component: AccountsComponent, + title: 'Accounts - Admin', canActivate: [PermissionGuard], data: { require_permission: 'view_accounts_panel' @@ -99,6 +108,7 @@ const routes: Routes = [ ModifyUserComponent, ModifyPasswordComponent, ModifyDepartmentComponent, + ModifyDepartmentTimeoutComponent, DepartmentComponent, ApplicationComponent, AccountComponent, @@ -111,7 +121,8 @@ const routes: Routes = [ EnvironmentsComponent, BrowsersComponent, FeaturesComponent, - AccountsComponent + AccountsComponent, + AccountsDialog ] }) export class AdminModule { } diff --git a/front/src/app/modules/details.module.ts b/front/src/app/modules/details.module.ts index a17c3b8e..5041d665 100644 --- a/front/src/app/modules/details.module.ts +++ b/front/src/app/modules/details.module.ts @@ -1,6 +1,6 @@ -import { NgModule } from '@angular/core'; +import { NgModule, inject } from '@angular/core'; import { CommonModule } from '@angular/common'; -import { Routes, RouterModule } from '@angular/router'; +import { Routes, RouterModule, ResolveFn, ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router'; import { SharedModule } from './shared.module'; import { MainViewComponent } from '../views/main-view/main-view.component'; import { StepViewComponent } from '../views/step-view/step-view.component'; @@ -9,7 +9,6 @@ import { FeatureTitlesComponent } from '@components/feature-titles/feature-title import { FeatureActionsComponent } from '@components/feature-actions/feature-actions.component'; import { HighchartsChartModule } from 'highcharts-angular'; import { NumeralPipe } from '@pipes/numeral.pipe'; -import { PixelDifferencePipe } from '@pipes/pixel-difference.pipe'; import { FirstLetterUppercasePipe } from '@pipes/first-letter-uppercase.pipe'; import { RoundProgressModule } from 'angular-svg-round-progressbar'; import { EditSchedule } from '@dialogs/edit-schedule/edit-schedule.component'; @@ -31,24 +30,39 @@ import { DownloadNamePipe } from '@pipes/download-name.pipe'; import { MainViewHeaderComponent } from '../views/main-view/main-view-header/main-view-header.component'; import { ScreenshotBgPipe } from '../pipes/screenshot-bg.pipe'; import { RunColumnDirective } from '../directives/run-column.directive'; +import { Store } from '@ngxs/store'; +import { CustomSelectors } from '@others/custom-selectors'; + +const resolveFeatureTitle: ResolveFn = (route: ActivatedRouteSnapshot, state: RouterStateSnapshot) => { + const featureId = route.paramMap.get('feature'); + const feature = inject(Store).selectSnapshot(CustomSelectors.GetFeatureInfo(parseInt(featureId))) + return `${feature.feature_name} (${feature.feature_id})` +} const routes: Routes = [ { path: '', + title: resolveFeatureTitle, component: MainViewComponent }, { - path: 'run/:run/step/:feature_result_id', + path: 'step/:feature_result_id', children: [ { path: '', + title: resolveFeatureTitle, component: StepViewComponent }, { path: 'detail/:step_result_id', + title: 'Step Details', component: DetailViewComponent } ] + }, + { + path: 'run/:run/step/:feature_result_id', + redirectTo: 'step/:feature_result_id' } ]; @@ -57,18 +71,17 @@ const routes: Routes = [ TranslateModule.forChild({ extend: true }), - HighchartsChartModule, - RoundProgressModule, + // HighchartsChartModule, + // RoundProgressModule, RouterModule.forChild(routes), SharedModule, CommonModule ], declarations: [ /* Pipes */ - NumeralPipe, + // NumeralPipe, ArchivedRunsPipe, - PixelDifferencePipe, - FirstLetterUppercasePipe, + // FirstLetterUppercasePipe, PdfLinkPipe, SumByPropertyPipe, DownloadLinkPipe, @@ -76,7 +89,7 @@ const routes: Routes = [ /* Components */ FeatureTitlesComponent, EditSchedule, - BehaveChartTestComponent, + // BehaveChartTestComponent, VideoComponent, FeatureRunComponent, LogOutputComponent, @@ -90,7 +103,7 @@ const routes: Routes = [ FeatureRunPassedPipe, FeatureResultPassedPipe, MainViewHeaderComponent, - ScreenshotBgPipe, + // ScreenshotBgPipe, RunColumnDirective ] }) diff --git a/front/src/app/modules/help.module.ts b/front/src/app/modules/help.module.ts index 0c9c2295..ae8d6d20 100644 --- a/front/src/app/modules/help.module.ts +++ b/front/src/app/modules/help.module.ts @@ -7,6 +7,7 @@ import { HelpComponent } from '@components/help/help.component'; const routes: Routes = [ { path: '', + title: 'Help', component: HelpComponent } ]; diff --git a/front/src/app/modules/newlanding.module.ts b/front/src/app/modules/newlanding.module.ts index 88adad41..8fab9b97 100644 --- a/front/src/app/modules/newlanding.module.ts +++ b/front/src/app/modules/newlanding.module.ts @@ -13,11 +13,18 @@ import { L1FeatureRecentListComponent } from '../components/l1-feature-recent-li import { L1FeatureStarredListComponent } from '../components/l1-feature-starred-list/l1-feature-starred-list.component'; import { L1FeatureTrashbinListComponent } from '../components/l1-feature-trashbin-list/l1-feature-trashbin-list.component'; import { L1FeatureTeamListComponent } from '../components/l1-feature-team-list/l1-feature-team-list.component'; +import { L1TreeViewComponent } from '../components/l1-tree-view/l1-tree-view.component'; import { WelcomeComponent } from '@components/welcome/welcome.component'; +import { DataDrivenRunsComponent } from '@components/data-driven-runs/data-driven-runs.component' const routes: Routes = [ { path: '', + title: 'Home', + component: L1LandingComponent + }, + { + path: ':breadcrumb', component: L1LandingComponent } ]; @@ -42,7 +49,9 @@ const routes: Routes = [ L1FeatureStarredListComponent, L1FeatureTrashbinListComponent, L1FeatureTeamListComponent, - WelcomeComponent + L1TreeViewComponent, + WelcomeComponent, + DataDrivenRunsComponent ] }) export class NewlandingModule { } \ No newline at end of file diff --git a/front/src/app/modules/search.module.ts b/front/src/app/modules/search.module.ts index 133cef1e..05721736 100644 --- a/front/src/app/modules/search.module.ts +++ b/front/src/app/modules/search.module.ts @@ -11,6 +11,7 @@ import { EasterEggComponent } from '../components/easter-egg/easter-egg.componen const routes: Routes = [ { path: '', + title: 'Home', component: SearchComponent } ]; diff --git a/front/src/app/modules/shared.module.ts b/front/src/app/modules/shared.module.ts index af632727..d539e65f 100755 --- a/front/src/app/modules/shared.module.ts +++ b/front/src/app/modules/shared.module.ts @@ -1,37 +1,38 @@ import { NgModule, ModuleWithProviders } from '@angular/core'; import { CommonModule } from '@angular/common'; import { DragDropModule } from '@angular/cdk/drag-drop'; -import { MatTabsModule } from '@angular/material/tabs'; +import { MatLegacyTabsModule as MatTabsModule } from '@angular/material/legacy-tabs'; import { MatIconModule } from '@angular/material/icon'; -import { MatDialogModule } from '@angular/material/dialog'; -import { MatProgressSpinnerModule } from '@angular/material/progress-spinner'; +import { MatLegacyDialogModule as MatDialogModule } from '@angular/material/legacy-dialog'; +import { MatLegacyProgressSpinnerModule as MatProgressSpinnerModule } from '@angular/material/legacy-progress-spinner'; import { MatNativeDateModule } from '@angular/material/core'; -import { MatRadioModule } from '@angular/material/radio'; -import { MatFormFieldModule } from '@angular/material/form-field'; -import { MatInputModule } from '@angular/material/input'; -import { MatTooltipModule } from '@angular/material/tooltip'; -import { MatCheckboxModule } from '@angular/material/checkbox'; -import { MatMenuModule } from '@angular/material/menu'; -import { MatSlideToggleModule } from '@angular/material/slide-toggle'; -import { MatSelectModule } from '@angular/material/select'; -import { MatButtonModule } from '@angular/material/button'; +import { MatLegacyRadioModule as MatRadioModule } from '@angular/material/legacy-radio'; +import { MatLegacyFormFieldModule as MatFormFieldModule } from '@angular/material/legacy-form-field'; +import { MatLegacyInputModule as MatInputModule } from '@angular/material/legacy-input'; +import { MatLegacyTooltipModule as MatTooltipModule } from '@angular/material/legacy-tooltip'; +import { MatLegacyCheckboxModule as MatCheckboxModule } from '@angular/material/legacy-checkbox'; +import { MatLegacyMenuModule as MatMenuModule } from '@angular/material/legacy-menu'; +import { MatLegacySlideToggleModule as MatSlideToggleModule } from '@angular/material/legacy-slide-toggle'; +import { MatLegacySelectModule as MatSelectModule } from '@angular/material/legacy-select'; +import { MatLegacyButtonModule as MatButtonModule } from '@angular/material/legacy-button'; import { MatExpansionModule } from '@angular/material/expansion'; import { MatDividerModule } from '@angular/material/divider'; -import { MatAutocompleteModule } from '@angular/material/autocomplete'; +import { MatLegacyAutocompleteModule as MatAutocompleteModule } from '@angular/material/legacy-autocomplete'; import { ReactiveFormsModule, FormsModule } from '@angular/forms'; import { EnterValueComponent } from '@dialogs/enter-value/enter-value.component'; import { AddFolderComponent } from '@dialogs/add-folder/add-folder.component'; -import { MatSnackBarModule } from '@angular/material/snack-bar'; -import { MatChipsModule } from '@angular/material/chips'; -import { MatPaginatorModule } from '@angular/material/paginator'; +import { MatLegacySnackBarModule as MatSnackBarModule } from '@angular/material/legacy-snack-bar'; +import { MatLegacyChipsModule as MatChipsModule } from '@angular/material/legacy-chips'; +import { MatLegacyPaginatorModule as MatPaginatorModule } from '@angular/material/legacy-paginator'; +import { ClipboardModule } from '@angular/cdk/clipboard' import { AmParsePipe } from '@pipes/am-parse.pipe'; import { AmDateFormatPipe } from '@pipes/am-date-format.pipe'; import { PlatformSortPipe } from '@pipes/platform-sort.pipe'; import { OfferTourComponent } from '@dialogs/offer-tour/offer-tour.component'; import { EditVariablesComponent } from '@dialogs/edit-variables/edit-variables.component'; import { ErrorDialog } from '@dialogs/error/error.dialog'; -import { MAT_SNACK_BAR_DEFAULT_OPTIONS } from '@angular/material/snack-bar'; -import { MAT_DIALOG_DEFAULT_OPTIONS } from '@angular/material/dialog'; +import { MAT_LEGACY_SNACK_BAR_DEFAULT_OPTIONS as MAT_SNACK_BAR_DEFAULT_OPTIONS } from '@angular/material/legacy-snack-bar'; +import { MAT_LEGACY_DIALOG_DEFAULT_OPTIONS as MAT_DIALOG_DEFAULT_OPTIONS } from '@angular/material/legacy-dialog'; import { NetworkPaginatedListComponent } from '@components/network-paginated-list/network-paginated-list.component'; /* Pipes */ @@ -59,6 +60,7 @@ import { HasPermissionPipe } from '../pipes/has-permission.pipe'; import { SafeHtmlPipe } from '../pipes/safe-html.pipe'; import { AddLatestPipe } from '../pipes/add-latest.pipe'; import { SafeUrlPipe } from '@pipes/safe-url.pipe'; +import { HumanizeBytesPipe } from '@pipes/humanize-bytes.pipe'; /* Directives */ import { StopPropagationDirective } from '@directives/stop-propagation.directive'; @@ -93,9 +95,23 @@ import { DepartmentNamePipe } from '@pipes/department-name.pipe'; import { NewFolderComponent } from '@components/new-folder/new-folder.component'; import { MoveFolderItemComponent } from '@dialogs/move-feature/move-folder-item/move-folder-item.component'; import { MatSortModule } from '@angular/material/sort'; -import { MatTableModule } from '@angular/material/table'; -import { MaterialExtensionsModule } from '@ng-matero/extensions'; +import { MatLegacyTableModule as MatTableModule } from '@angular/material/legacy-table'; +import { MatLegacyListModule as MatListModule } from '@angular/material/legacy-list'; +// import { MaterialExtensionsModule } from '@ng-matero/extensions'; +import { MtxGridModule } from '@ng-matero/extensions/grid'; + +// virtual scrolling module for extended lists of feature results +import {ScrollingModule} from '@angular/cdk/scrolling'; + import { FeatureRunningPipe } from '../pipes/feature-running.pipe'; +import { PixelDifferencePipe } from '@pipes/pixel-difference.pipe'; +import { BehaveChartTestComponent } from '@components/behave-charts/behave-chart.component'; +import { HighchartsChartModule } from 'highcharts-angular'; +import { FirstLetterUppercasePipe } from '@pipes/first-letter-uppercase.pipe'; +import { NumeralPipe } from '@pipes/numeral.pipe'; +import { RoundProgressModule } from 'angular-svg-round-progressbar'; +import { ScreenshotBgPipe } from '@pipes/screenshot-bg.pipe'; +import { AvailableFilesPipe } from '@pipes/available-files.pipe'; const components = [ @@ -111,15 +127,18 @@ const components = [ L1FeatureListComponent, FolderComponent, NewFolderComponent, - MoveFolderItemComponent + MoveFolderItemComponent, + BehaveChartTestComponent ]; const materialModules = [ + ClipboardModule, MatTabsModule, MatIconModule, MatDialogModule, MatSnackBarModule, MatProgressSpinnerModule, + MatListModule, MatNativeDateModule, MatRadioModule, MatFormFieldModule, @@ -138,7 +157,10 @@ const materialModules = [ MatDividerModule, MatTableModule, MatSortModule, - MaterialExtensionsModule + MtxGridModule, + ScrollingModule, + HighchartsChartModule, + RoundProgressModule ]; const snacks = [ @@ -181,7 +203,13 @@ const pipes = [ AlreadyTakenFilterPipe, FilterTextPipe, DepartmentNamePipe, - FeatureRunningPipe + FeatureRunningPipe, + HumanizeBytesPipe, + PixelDifferencePipe, + FirstLetterUppercasePipe, + NumeralPipe, + ScreenshotBgPipe, + AvailableFilesPipe ]; const dialogs = [ diff --git a/front/src/app/modules/user.module.ts b/front/src/app/modules/user.module.ts index d7a24271..8ab0273d 100755 --- a/front/src/app/modules/user.module.ts +++ b/front/src/app/modules/user.module.ts @@ -8,6 +8,7 @@ import { TranslateModule } from '@ngx-translate/core'; const routes: Routes = [ { path: '', + title: 'Profile', component: UserComponent } ]; diff --git a/front/src/app/others/custom-selectors.ts b/front/src/app/others/custom-selectors.ts index 1a185759..1d5be507 100644 --- a/front/src/app/others/custom-selectors.ts +++ b/front/src/app/others/custom-selectors.ts @@ -136,7 +136,8 @@ export class CustomSelectors { } // Filter accordingly too where steps are required const typeToFilter = showOnlyOriginal ? 'subfeature' : 'substep'; - return steps.filter(step => step.step_type === 'normal' || step.step_type === typeToFilter || !step.hasOwnProperty('step_type')); + const addLoops = typeToFilter === 'subfeature'; + return steps.filter(step => step.step_type === 'normal' || step.step_type === typeToFilter || !step.hasOwnProperty('step_type') || (addLoops && step.step_type === 'loop')); }) } diff --git a/front/src/app/others/enums.ts b/front/src/app/others/enums.ts index 96226ee9..51ad4dc6 100644 --- a/front/src/app/others/enums.ts +++ b/front/src/app/others/enums.ts @@ -8,5 +8,6 @@ export enum KEY_CODES { L = 76, S = 83, R = 82, - N = 78 + N = 78, + V = 86 } \ No newline at end of file diff --git a/front/src/app/others/interfaces.d.ts b/front/src/app/others/interfaces.d.ts index f8b66c87..a12509e4 100755 --- a/front/src/app/others/interfaces.d.ts +++ b/front/src/app/others/interfaces.d.ts @@ -149,6 +149,7 @@ interface StepResult { screenshot_style: string; screenshot_difference: string; screenshot_template: string; + error: null | string; } interface BelongsTo { @@ -233,6 +234,20 @@ interface FeatureRun { pixel_diff: number; } +interface DataDrivenRun { + run_id: number; + date_time: string; + archived: boolean; + status: string; + total: number; + fails: number; + ok: number; + skipped: number; + execution_time: number; + pixel_diff: number; + file: File; +} + // Applications interface Application { @@ -257,8 +272,10 @@ interface BrowserResult { interface Department { department_id: number; department_name: string; + files?: [], slug?: string; settings?: any; + users?: IAccount[]; } // Steps @@ -342,7 +359,7 @@ interface FeatureStep { timeout?: number; } -declare type StepType = 'normal' | 'subfeature' | 'substep'; +declare type StepType = 'normal' | 'subfeature' | 'substep' | 'loop'; interface GroupContentObject { screenshot?: boolean; @@ -523,7 +540,7 @@ interface FeatureViewItems { [view: string]: FeatureViewTypes; } -type FeatureViewTypes = 'tiles' | 'list'; +type FeatureViewTypes = 'tiles' | 'list' | 'tree'; interface ResultHeader { enable: boolean; @@ -557,11 +574,43 @@ interface ServerInfo { interface Toggles { hideInformation: boolean; hideBrowsers: boolean; + hideUploadedFiles: boolean; hideSteps: boolean; hideSchedule: boolean; hideSendMail: boolean; } +interface UploadedFile { + created_on?: string, + id?: number, + md5sum?: string, + mime?: string, + name?: string, + department?: string, + size?: number, + type?: string, + is_removed?: boolean; + uploadPath?: string, + uploaded_by?: Uploader, + error?: FileUploadError, + status?: 'Unknown' | 'Processing' | 'Scanning' | 'Encrypting' | 'Done' | 'Error' +} + +interface Uploader { + email?: string, + name?: string, + user_id?: number +} + +interface FileUploadError { + description?: string, + status?: string; +} +interface FileAction { + icon: string; + tooltip: string; +} + interface LiveStep { id: number; name: string; @@ -650,13 +699,45 @@ interface AccountRole { department: number; } + +interface VariableInsertionData { + currentStepIndex?: number | null; + selectionIndex?: number; + stepValue?: string; + quoteIndexes?: QuoteIndexes; + strToReplace?: string; + strWithoutQuotes?: string; +} + +interface QuoteIndexes { + next?: number; + prev?: number; +} + +interface VariableColumns { + name: string; + activated: boolean; + value: string; +} + interface VariablePair { + id: number; + department: number; + department_name: string; + environment: number; + feature: number | null; variable_name: string; variable_value: string; encrypted: boolean; - id?: number; - department: Department; - environment: Environment; + environment_name: string; + feature_name?: string; + based: string; + in_use: number[]; + created_by_name: string; + updated_by_name: string; + created_on: Date; + updated_on: Date; + disabled?: boolean; } interface Cloud { diff --git a/front/src/app/pipes/available-files.pipe.ts b/front/src/app/pipes/available-files.pipe.ts new file mode 100644 index 00000000..49ee181e --- /dev/null +++ b/front/src/app/pipes/available-files.pipe.ts @@ -0,0 +1,12 @@ +import { Pipe, PipeTransform } from '@angular/core'; + +@Pipe({ + name: 'availableFiles' +}) +export class AvailableFilesPipe implements PipeTransform { + + transform(files): string { + return files.filter(f => !f.is_removed); + } + +} diff --git a/front/src/app/pipes/humanize-bytes.pipe.ts b/front/src/app/pipes/humanize-bytes.pipe.ts new file mode 100644 index 00000000..90ce8d1d --- /dev/null +++ b/front/src/app/pipes/humanize-bytes.pipe.ts @@ -0,0 +1,22 @@ +import { Pipe, PipeTransform } from '@angular/core'; + +@Pipe({ + name: 'humanizeBytes' +}) +export class HumanizeBytesPipe implements PipeTransform { + + constructor( ) { } + + transform(bytes: number): String { + const kiloBytes = bytes/1024; + if (kiloBytes < 1) return `${bytes} B`; + const megaBytes = kiloBytes/1024; + if (megaBytes < 1) return `${kiloBytes.toFixed(2)} KB`; + const gigaBytes = megaBytes/1024; + if (gigaBytes < 1) return `${megaBytes.toFixed(2)} MB`; + const teraByte = gigaBytes/1024; + if (teraByte < 1) return `${gigaBytes.toFixed(2)} GB`; + return `${teraByte.toFixed(2)} TB` + } + +} diff --git a/front/src/app/pipes/percentage.pipe.ts b/front/src/app/pipes/percentage.pipe.ts index 0fb30d51..3b2e3831 100755 --- a/front/src/app/pipes/percentage.pipe.ts +++ b/front/src/app/pipes/percentage.pipe.ts @@ -6,9 +6,15 @@ import { Pipe, PipeTransform } from '@angular/core'; export class PercentagePipe implements PipeTransform { transform(part: number, total: number, sign: boolean = true): string { - if (part === 0 && total === 0) return '0' + (sign ? ' %' : ''); - if (part === 0) return '0' + (sign ? ' %' : ''); - return ((part * 100) / total).toFixed(0) + (sign ? ' %' : ''); + const signValue = sign ? '%' : ''; + if (part === 0 && total === 0) return '0' + signValue; + if (part === 0) return '0' + signValue; + const percentage = (part * 100) / total; + if (percentage == 100) { + return `${percentage}${signValue}` + } else { + return `${percentage.toFixed(2)}${signValue}` + } } } diff --git a/front/src/app/pipes/pixel-difference.pipe.ts b/front/src/app/pipes/pixel-difference.pipe.ts index 8a86a1a6..fbfd41c7 100755 --- a/front/src/app/pipes/pixel-difference.pipe.ts +++ b/front/src/app/pipes/pixel-difference.pipe.ts @@ -7,7 +7,7 @@ export class PixelDifferencePipe implements PipeTransform { transform(value: number): string { if (value) { - return value.toString().replace(/\B(?=(\d{3})+(?!\d))/g, '.'); + return value.toLocaleString(); } else { return '-'; } diff --git a/front/src/app/plugins/angular-captcha/correct-captcha.directive.ts b/front/src/app/plugins/angular-captcha/correct-captcha.directive.ts index 011efe8e..0d58fa16 100644 --- a/front/src/app/plugins/angular-captcha/correct-captcha.directive.ts +++ b/front/src/app/plugins/angular-captcha/correct-captcha.directive.ts @@ -1,4 +1,4 @@ -import { Directive, forwardRef, HostListener, Inject } from '@angular/core'; +import { Directive, forwardRef, HostListener } from '@angular/core'; import { NG_ASYNC_VALIDATORS, AbstractControl, Validator } from '@angular/forms'; import { CaptchaService } from './captcha.service'; diff --git a/front/src/app/plugins/ngx-joyride/joyride.module.ts b/front/src/app/plugins/ngx-joyride/joyride.module.ts index 7ef306b0..da38211a 100644 --- a/front/src/app/plugins/ngx-joyride/joyride.module.ts +++ b/front/src/app/plugins/ngx-joyride/joyride.module.ts @@ -29,7 +29,6 @@ export const routerModuleForChild: ModuleWithProviders = RouterMo JoyrideButtonComponent, JoyrideCloseButtonComponent ], - entryComponents: [JoyrideStepComponent], exports: [ JoyrideDirective ] diff --git a/front/src/app/routing.module.ts b/front/src/app/routing.module.ts index ab5e82a2..7e94ace8 100644 --- a/front/src/app/routing.module.ts +++ b/front/src/app/routing.module.ts @@ -41,8 +41,28 @@ const routes: Routes = [ loadChildren: () => import('@modules/newlanding.module').then(m => m.NewlandingModule) }, { - path: 'new', - loadChildren: () => import('@modules/newlanding.module').then(m => m.NewlandingModule) + path: 'data-driven', + loadComponent: () => import('@components/data-driven/data-driven.component').then(m => m.DataDrivenComponent), + }, + { + path: 'data-driven/:id', + children: [{ + path: '', + title: 'Data-Driven Run', + loadComponent: () => import('@components/data-driven-runs/data-driven-results/data-driven-results.component').then(m => m.DataDrivenResultsComponent), + },{ + path: 'step/:feature_result_id', + children: [{ + path: '', + title: 'Data-Driven Result', + loadComponent: () => import('@components/data-driven-runs/data-driven-steps/data-driven-steps.component').then(m => m.DataDrivenStepViewComponent) + }, + { + path: 'detail/:step_result_id', + title: 'Step Details', + loadComponent: () => import('@components/data-driven-runs/data-driven-step-details/data-driven-step-details.component').then(m => m.DataDrivenStepDetailViewComponent) + }] + }], } ]; diff --git a/front/src/app/services/api.service.ts b/front/src/app/services/api.service.ts index 38f198a0..5d1d826a 100755 --- a/front/src/app/services/api.service.ts +++ b/front/src/app/services/api.service.ts @@ -5,7 +5,7 @@ import { Observable, of } from 'rxjs'; import { filter, map, switchMap } from 'rxjs/operators'; import { encodeURLParams } from 'ngx-amvara-toolbox'; import { InterceptorParams } from 'ngx-network-error'; -import { MatDialog } from '@angular/material/dialog'; +import { MatLegacyDialog as MatDialog } from '@angular/material/legacy-dialog'; import { AreYouSureData, AreYouSureDialog } from '@dialogs/are-you-sure/are-you-sure.component'; @Injectable() @@ -50,6 +50,7 @@ export class ApiService { // Get Folders of features getFolders = () => this._http.get(`${this.api}folders/`); + getTreeView = () => this._http.get(`${this.api}folders/?tree`); // Create Folder on the backend for the current logged user and inside another folder createFolder(name: string, department_id: number, parent_id: number = 0) { @@ -338,6 +339,14 @@ export class ApiService { }); } + applyDepartmentStepsTimeout(department_id: number, options: {step_timeout_from: number, step_timeout_to: number}) { + return this._http.post(`${this.base}departments/${department_id}/updateStepTimeout/`, options, { + params: new InterceptorParams({ + skipInterceptor: true, + }) + }); + } + modifyDepartment(department_id: number, newOptions: Partial) { return this._http.patch(`${this.api}departments/${department_id}/`, newOptions); } @@ -465,11 +474,27 @@ export class ApiService { * @param department ID of existing department * @param values Variables */ - setEnvironmentVariables(environment: number, department: number, values: VariablePair[]) { - return this._http.post(`${this.api}variables/`, { - environment_id: environment, - department_id: department, - variables: values + setVariable(variable: VariablePair) { + return this._http.post(`${this.api}variables/`, variable, { + params: new InterceptorParams({ + skipInterceptor: true, + }) + }); + } + + patchVariable(variable: VariablePair) { + return this._http.patch(`${this.api}variables/${variable.id}/`, variable, { + params: new InterceptorParams({ + skipInterceptor: true, + }) + }); + } + + deleteVariable(id: number) { + return this._http.delete(`${this.api}variables/${id}`, { + params: new InterceptorParams({ + skipInterceptor: true, + }) }); } @@ -570,4 +595,25 @@ export class ApiService { }) } + uploadFiles(formData: FormData) { + return this._http.post(`${this.api}uploads/`, formData); + } + + updateFile(file_id: number, formdata: FormData) { + return this._http.put(`${this.api}uploads/${file_id}/`, formdata); + } + + deleteFile(file_id: number) { + return this._http.delete(`${this.api}uploads/${file_id}/`); + } + + downloadFile(file_id: number) { + return this._http.get(`${this.api}uploads/${file_id}/`, { + params: new InterceptorParams({ + skipInterceptor: true, + }), + responseType: 'text', + observe: 'response' + }); + } } diff --git a/front/src/app/services/download.service.ts b/front/src/app/services/download.service.ts new file mode 100644 index 00000000..ace4776b --- /dev/null +++ b/front/src/app/services/download.service.ts @@ -0,0 +1,46 @@ +import { HttpClient } from '@angular/common/http'; +import { Injectable, Inject } from '@angular/core'; +import { MatSnackBar } from '@angular/material/snack-bar'; +import { API_BASE, API_URL } from 'app/tokens'; + +@Injectable() +export class DownloadService { + + constructor( + private _http: HttpClient, + @Inject(API_URL) public api: string, + @Inject(API_BASE) public base: string, + private _snack: MatSnackBar + ) { } + + downloadFile(response, file: UploadedFile) { + const downloading = this._snack.open('Generating file to download, please be patient.', 'OK', { duration: 1000 }) + const blob = new Blob([this.base64ToArrayBuffer(response.body)], { type: file.mime }); + this.downloadFileBlob(blob, file); + } + + base64ToArrayBuffer(data: string) { + let byteArray; + try { + byteArray = atob(data); + } catch(DOMException) { + byteArray = data; + } + const uint = new Uint8Array(byteArray.length) + for (let i = 0; i < byteArray.length; i++) { + let ascii = byteArray.charCodeAt(i); + uint[i] = ascii; + } + return uint; + } + + downloadFileBlob(blob: Blob, file: UploadedFile) { + const url = window.URL.createObjectURL(blob); + const link = document.createElement('a'); + + link.href = url; + link.download = file.name; + link.click(); + } + +} \ No newline at end of file diff --git a/front/src/app/services/file-upload.service.ts b/front/src/app/services/file-upload.service.ts new file mode 100644 index 00000000..e240ac81 --- /dev/null +++ b/front/src/app/services/file-upload.service.ts @@ -0,0 +1,149 @@ +/** + * File upload service based on websockets + * File upload template, allows multiple file upload + * Websocket service returns resoponses for one file at a time, if user uploads more than 1 file and we render response from websockets, + * template will not render both file, but first the first one and then second one + * In order to prevent above said behaviour, when user uploads varios files we dispatch temporari files into department state which are then updated one by one, depending on the response from websocket + * To observe how to files are updated, check 'department.state.ts > updateFileStatus()' + * Each file will be avaliable for download, once websocket communication for this certain file has been finished + * File will not be uploaded if backend side detects that it already exists or is malicious +**/ + + +import { Injectable } from '@angular/core'; +import { MatLegacySnackBar as MatSnackBar } from '@angular/material/legacy-snack-bar'; +import { Store } from '@ngxs/store'; +import { Departments } from '@store/actions/departments.actions'; +import { ApiService } from './api.service'; + +@Injectable({ + providedIn: 'root' +}) + +export class FileUploadService { + constructor(private _store: Store, private _api: ApiService, private _snack: MatSnackBar) { } + + startUpload(files: File[], formData: FormData, department: Department, user) { + this.setTempFiles([...files], department, user); + + // starts websocket comunication for each uploaded file + this._api.uploadFiles(formData).subscribe(); + } + + // temporarilty inserts uploaded files into department state, temporary files contain information received from file input event + private setTempFiles(files: File[], department: Department, user) { + const payload = { + files: [ + ...this.getTempFilesInfo(files, user), + ...department.files + ] + } as any; + + // dispatches temporary files into department state + this._store.dispatch( new Departments.UpdateDepartment(department.department_id, payload) ); + } + + // gets information of file input for each file + private getTempFilesInfo(files: File[], user) { + const uploadedFiles: UploadedFile[] = []; + + files.forEach(file => { + // for each file, generates object and pushed it into array + uploadedFiles.push(this.getTempFileObject(file, user)); + }) + + return uploadedFiles; + } + + // sets up temporary file object + private getTempFileObject(file: File, user) { + const uploadedFile = {}; + const { name, size, type } = file; + + uploadedFile.name = name; + uploadedFile.size = size; + uploadedFile.mime = type; + uploadedFile.is_removed = false; + uploadedFile.uploaded_by = { + name: user.name + } + uploadedFile.created_on = new Date().toJSON(); + uploadedFile.status = 'Unknown'; + + return uploadedFile; + } + + // constantly observes if specific department contains any file with property 'error', if so: informs user with snackbar, removes file and actualizes department's state + validateFileUploadStatus(department: Department) { + department.files.forEach((file: UploadedFile) => { + if (file.error) { + this.informAndRemoveFile(file, department); + } + }); + } + + // Opens informative snackbar when file user intends to upload but file can't be uploaded because of duplication or potential virus + private informAndRemoveFile(file: UploadedFile, department: Department) { + let snack = this._snack.open(file.error.description, 'OK', { duration: 10000 }); + + // removes invalid file from department state if snackbar is dissmissed because of timeout or click event on another component of the body + snack.afterDismissed().subscribe(() => { + this.removeFile(file, department) + }); + + // removes invalid file from department state if snackbar is dissmised because of action click event + snack.onAction().subscribe(() => { + this.removeFile(file, department) + }); + } + + // removes recieved file from recieved department's files array and actualises the department state + removeFile(file: UploadedFile, department: Department) { + const files = department.files.filter((f: UploadedFile) => f.id != null) + const payload = { + files: [ + ...files + ] + } as any; + + // dispatches new files array that does not contain invalid file into department state + this._store.dispatch( new Departments.UpdateDepartment(department.department_id, payload) ); + } + + updateFileState(file: UploadedFile, department: Department) { + const files = department.files.filter((f: UploadedFile) => f.id != file.id) + + const updatedfile = { ...file, is_removed: !file.is_removed }; + + const payload = { + files: [ + ...files, + updatedfile + ] + } as any; + + this._store.dispatch( new Departments.UpdateDepartment(department.department_id, payload) ); + } + + downloadFileBlob(blob: Blob, file: UploadedFile) { + const url = window.URL.createObjectURL(blob); + const link = document.createElement('a'); + + link.href = url; + link.download = file.name; + link.click(); + } + + + deleteFile(file_id: number) { + return this._api.deleteFile(file_id); + } + + restoreFile(file_id: number, formData: FormData) { + return this._api.updateFile(file_id, formData); + } + + downloadFile(file_id: number) { + return this._api.downloadFile(file_id); + } +} \ No newline at end of file diff --git a/front/src/app/services/loading.interceptor.ts b/front/src/app/services/loading.interceptor.ts index 78e272a7..7be423df 100644 --- a/front/src/app/services/loading.interceptor.ts +++ b/front/src/app/services/loading.interceptor.ts @@ -2,7 +2,7 @@ import { Injectable } from '@angular/core'; import { HttpEvent, HttpInterceptor, HttpHandler, HttpRequest } from '@angular/common/http'; import { finalize } from 'rxjs/operators'; import { Observable } from 'rxjs'; -import { MatSnackBar, MatSnackBarRef } from '@angular/material/snack-bar'; +import { MatLegacySnackBar as MatSnackBar, MatLegacySnackBarRef as MatSnackBarRef } from '@angular/material/legacy-snack-bar'; import { LoadingSnack } from '@components/snacks/loading/loading.snack'; import { TranslateService } from '@ngx-translate/core'; diff --git a/front/src/app/services/log.service.ts b/front/src/app/services/log.service.ts new file mode 100644 index 00000000..05865449 --- /dev/null +++ b/front/src/app/services/log.service.ts @@ -0,0 +1,53 @@ +import { Injectable } from '@angular/core'; +import { formatDate } from "@angular/common"; + +@Injectable({ + providedIn: 'root' +}) +export class LogService { + + constructor() { + + } + + // trace(msg: any, tag: string) { this.msg("1", msg, tag) } + // error(msg: any, tag: string) { this.msg("2", msg, tag) } + // warn(msg: any, tag: string) { this.msg("3", msg, tag) } + // info(msg: any, tag: string) { this.msg("4", msg, tag) } + // general(msg: any, tag: string) { this.msg("5", msg, tag) } + + msg ( lvl : string, msg : any, tag : string, data : any = '') { + + // read from localstorage + // co_loglvl + co_logtag + // 1 = trace + // 2 = error + // 3 = warn + // 4 = info + // 5 = general + const ls_lvl = localStorage.getItem("co_loglvl") || "" + + // Reading ls_tag + const ls_tag = localStorage.getItem("co_logtag") || "none" + + // just get out of here, if ls_tag is "none" ... we do not want any logging in that case + if (ls_tag == "none") {return} + + // check ls_lvl is smaller than lvl coming in to print that message + if (ls_lvl <= lvl) { + // check that tag is matched to print message + // tag = * ...... print anything + // tag = none ... never print + // tag = foo .... only print foo messages + if ( ls_tag == "*" || ls_tag.match(tag) != null ) { + const currentDate = Date.now(); + const format = 'dd/MM/yyyy hh:mm:ss:SSS'; + const zone = 'en-US'; + + const parsedDate = formatDate( currentDate,format, zone); + console.log(parsedDate, `[${lvl}-${tag}]`, msg, data); + } + } + + } +} diff --git a/front/src/app/services/paginator-intl.ts b/front/src/app/services/paginator-intl.ts index 3b020438..cb1b304d 100644 --- a/front/src/app/services/paginator-intl.ts +++ b/front/src/app/services/paginator-intl.ts @@ -1,5 +1,5 @@ import { Injectable } from '@angular/core'; -import { MatPaginatorIntl } from '@angular/material/paginator'; +import { MatLegacyPaginatorIntl as MatPaginatorIntl } from '@angular/material/legacy-paginator'; import { TranslateParser, TranslateService } from '@ngx-translate/core'; /** diff --git a/front/src/app/services/shared-actions.service.ts b/front/src/app/services/shared-actions.service.ts index 305bb48c..3eab39da 100644 --- a/front/src/app/services/shared-actions.service.ts +++ b/front/src/app/services/shared-actions.service.ts @@ -1,9 +1,10 @@ import { HttpResponse } from '@angular/common/http'; import { Injectable } from '@angular/core'; -import { MatCheckboxChange } from '@angular/material/checkbox'; -import { MatDialog } from '@angular/material/dialog'; -import { MatSnackBar } from '@angular/material/snack-bar'; +import { MatLegacyCheckboxChange as MatCheckboxChange } from '@angular/material/legacy-checkbox'; +import { MatLegacyDialog as MatDialog } from '@angular/material/legacy-dialog'; +import { MatLegacySnackBar as MatSnackBar } from '@angular/material/legacy-snack-bar'; import { Router } from '@angular/router'; +import { Location } from '@angular/common'; import { LoadingSnack } from '@components/snacks/loading/loading.snack'; import { AreYouSureData, AreYouSureDialog } from '@dialogs/are-you-sure/are-you-sure.component'; import { EditFeature } from '@dialogs/edit-feature/edit-feature.component'; @@ -13,7 +14,6 @@ import { HtmlDiffDialog } from '@dialogs/html-diff/html-diff.component'; import { LiveStepsComponent } from '@dialogs/live-steps/live-steps.component'; import { MoveItemDialog } from '@dialogs/move-feature/move-item.component'; import { SureRemoveFeatureComponent } from '@dialogs/sure-remove-feature/sure-remove-feature.component'; -import { Dispatch } from '@ngxs-labs/dispatch-decorator'; import { Store } from '@ngxs/store'; import { CustomSelectors } from '@others/custom-selectors'; import { Features } from '@store/actions/features.actions'; @@ -41,12 +41,46 @@ export class SharedActionsService { private _api: ApiService, private _snackBar: MatSnackBar, private _router: Router, + private _location: Location, private _snack: MatSnackBar, private _socket: SocketService ) { this._store.select(CustomSelectors.RetrieveResultHeaders(false)).subscribe(headers => this.headers$.next(headers)); } + // #3414 -----------------------------------start + // adds the ids of folders to browser url each time folders in foldertree or breadcrum are clicked + set_url_folder_params (currentRoute: any = "") { + + // folder url base + let folderUrl = "/new/"; + + // go to newLanding if there are no folder id params in currentRoute + if (!currentRoute) { + this._location.go(folderUrl); + return; + } + + // concat folder ids to create path to clicked folder + currentRoute.forEach(folder => { + folderUrl += `:${folder.folder_id}`; + }) + + // change url without redirection + this._location.go(folderUrl); + } + // #3414 ------------------------------------end + + + + // #3397 -----------------------------------start + // clears localstorage corresponding to searchFilters(see it at ctrl + f11/features/filters) + removeSearchFilter() { + return this._store.dispatch(new Features.RemoveSearchFilter()); + } + // #3397 ------------------------------------end + + dialogActive = false; goToFeature(featureId: number) { const feature = this._store.selectSnapshot(CustomSelectors.GetFeatureInfo(featureId)) @@ -55,6 +89,11 @@ export class SharedActionsService { feature.environment_name, feature.feature_id ]); + + // #3397 -----------------------------------start + // remove search filter when acceding to any features + this.removeSearchFilter(); + // #3397 -------------------------------------end } editSchedule(featureId: number) { @@ -65,11 +104,10 @@ export class SharedActionsService { }).afterClosed().subscribe(_ => this.dialogActive = false); } - @Dispatch() handleSetting(featureId: number, type: string, event: MatCheckboxChange) { switch (type) { case 'need_help': - return new Features.PatchFeature(featureId, { need_help: event.checked }); + return this._store.dispatch(new Features.PatchFeature(featureId, { need_help: event.checked })); default: return null; } @@ -119,11 +157,11 @@ export class SharedActionsService { } else { // Check if the feature has at least 1 browser selected, if not, show a warning if (feature.browsers.length > 0) { - this._store.dispatch( new LoadingActions.SetLoading(featureId, true) ) + this._store.dispatch(new LoadingActions.SetLoading(featureId, true)) this._api.runFeature(feature.feature_id, false).pipe( filter(json => !!json.success), - switchMap(_ => this._store.dispatch( new WebSockets.FeatureTaskQueued(featureId))), - finalize(() => this._store.dispatch( new LoadingActions.SetLoading(featureId, false) )) + switchMap(_ => this._store.dispatch(new WebSockets.FeatureTaskQueued(featureId))), + finalize(() => this._store.dispatch(new LoadingActions.SetLoading(featureId, false))) ).subscribe(_ => { this._snackBar.open(`Feature ${feature.feature_name} is running...`, 'OK'); // Make view live steps popup optional @@ -149,7 +187,7 @@ export class SharedActionsService { // Get data of feature and steps this._api.getFeatureSteps(featureId, { loading: 'translate:tooltips.loading_feature' }).subscribe(steps => { // Save steps into NGXS Store - this._store.dispatch( new StepDefinitions.SetStepsForFeature(mode === 'clone' ? 0 : featureId, steps) ); + this._store.dispatch(new StepDefinitions.SetStepsForFeature(mode === 'clone' ? 0 : featureId, steps)); // Open Edit Feature this._dialog.open(EditFeature, { disableClose: true, @@ -204,7 +242,7 @@ export class SharedActionsService { sequentialStoreDispatch(actions: any[]) { return from(actions).pipe( // Convert each action to Store Action - concatMap(action => this._store.dispatch( action )), + concatMap(action => this._store.dispatch(action)), // Merge all actions toArray() ) diff --git a/front/src/app/services/socket.service.ts b/front/src/app/services/socket.service.ts index 07019b15..50b20c21 100755 --- a/front/src/app/services/socket.service.ts +++ b/front/src/app/services/socket.service.ts @@ -38,10 +38,8 @@ export class SocketService { path: '/socket.io/', reconnection: true, reconnectionAttempts: Infinity, - forceNew: true, - transports: ['websocket'], - query: { - user: JSON.stringify(this.user) + auth: { + user: this.user } }); // Bind listeners to socket diff --git a/front/src/app/services/success-handler.interceptor.ts b/front/src/app/services/success-handler.interceptor.ts index f6d5eb2e..904af8b3 100644 --- a/front/src/app/services/success-handler.interceptor.ts +++ b/front/src/app/services/success-handler.interceptor.ts @@ -1,7 +1,7 @@ import { Injectable } from '@angular/core'; import { HttpEvent, HttpInterceptor, HttpHandler, HttpRequest, HttpResponse } from '@angular/common/http'; import { filter, map, tap } from 'rxjs/operators'; -import { MatDialog } from '@angular/material/dialog'; +import { MatLegacyDialog as MatDialog } from '@angular/material/legacy-dialog'; import { Observable } from 'rxjs'; import { ErrorDialog } from '@dialogs/error/error.dialog'; diff --git a/front/src/app/services/titles/cometa-title.service.ts b/front/src/app/services/titles/cometa-title.service.ts new file mode 100644 index 00000000..a844e831 --- /dev/null +++ b/front/src/app/services/titles/cometa-title.service.ts @@ -0,0 +1,19 @@ +import { Injectable } from '@angular/core'; +import { Title } from '@angular/platform-browser'; +import { RouterStateSnapshot, TitleStrategy } from '@angular/router'; + +@Injectable({ + providedIn: 'root' +}) +export class CometaTitleStrategyService extends TitleStrategy { + constructor(private title: Title) { + super() + } + + override updateTitle(snapshot: RouterStateSnapshot): void { + const title = this.buildTitle(snapshot); + if (title) { + this.title.setTitle(`${title} - Complete Meta Test Automation`) + } + } +} diff --git a/front/src/app/services/tools.ts b/front/src/app/services/tools.ts index d21139c9..c45a6483 100755 --- a/front/src/app/services/tools.ts +++ b/front/src/app/services/tools.ts @@ -82,7 +82,7 @@ export function getBrowserKey(browser: BrowserstackBrowser) { } export function ownFeature(feature: Feature, user: UserInfo, departments: Department[]) { - return feature.created_by === user.user_id || departments.map(d => d.department_name).includes(feature.department_name); + return feature?.created_by === user.user_id || departments.map(d => d?.department_name).includes(feature?.department_name); } export function getDescendantProp(obj, desc) { diff --git a/front/src/app/services/tour.service.ts b/front/src/app/services/tour.service.ts index 65f5955d..d2cea4c2 100644 --- a/front/src/app/services/tour.service.ts +++ b/front/src/app/services/tour.service.ts @@ -3,13 +3,11 @@ import { JoyrideService } from '@plugins/ngx-joyride/services/joyride.service'; import { Tour, TourDefinition, Tours } from '@services/tours'; import { DOCUMENT } from '@angular/common'; import { BehaviorSubject } from 'rxjs'; -import { Dispatch } from '@ngxs-labs/dispatch-decorator'; import { SelectSnapshot } from '@ngxs-labs/select-snapshot'; import { UserState } from '@store/user.state'; -import { MatDialog } from '@angular/material/dialog'; +import { MatLegacyDialog as MatDialog } from '@angular/material/legacy-dialog'; import { OfferTourComponent } from '@dialogs/offer-tour/offer-tour.component'; import { filter, tap } from 'rxjs/operators'; -import { ApiService } from './api.service'; import { Configuration } from '@store/actions/config.actions'; import { Store } from '@ngxs/store'; import { User } from '@store/actions/user.actions'; @@ -22,16 +20,14 @@ export class TourService { private readonly _joyrideService: JoyrideService, private _dialog: MatDialog, private _tours: Tours, - private _api: ApiService, private _store: Store, @Inject(DOCUMENT) private readonly document: Document ) { } @SelectSnapshot(UserState) user: UserInfo; - @Dispatch() private sidebarOpen(open: boolean) { - return new Configuration.SetProperty('openedMenu', open); + return this._store.dispatch(new Configuration.SetProperty('openedMenu', open)); } private setTourMode(tourMode: boolean) { diff --git a/front/src/app/services/tours.ts b/front/src/app/services/tours.ts index 5d3c6092..3aa22a48 100644 --- a/front/src/app/services/tours.ts +++ b/front/src/app/services/tours.ts @@ -68,6 +68,7 @@ export class Tours { position: 'bottom', attachTo: '.edit-feature-panel .mat-expansion-panel:first-child' }, + // Explain to check continue on failure { name: 'feature_continue', title: 'Continue on failure', @@ -77,6 +78,16 @@ export class Tours { position: 'bottom', attachTo: '.edit-feature-panel [formcontrolname="continue_on_failure"]' }, + // Explain where to upload files + { + name: 'file_upload', + title: 'Upload Files', + description: 'Here you can upload files of your desire.', + nextFn: emptyFn, + previousFn: emptyFn, + position: 'bottom', + attachTo: '.edit-feature-panel .upload-file' + }, // Explain to select which browsers to run the feature with { name: 'feature_browsers', @@ -85,7 +96,7 @@ export class Tours { nextFn: emptyFn, previousFn: emptyFn, position: 'top', - attachTo: '.edit-feature-panel .mat-expansion-panel:nth-child(2)' + attachTo: '.edit-feature-panel .mat-expansion-panel:nth-child(3)' }, // Explain to fill the steps { @@ -95,7 +106,7 @@ export class Tours { nextFn: emptyFn, previousFn: emptyFn, position: 'top', - attachTo: '.edit-feature-panel .mat-expansion-panel:nth-child(3)' + attachTo: '.edit-feature-panel .mat-expansion-panel:nth-child(4)' }, // Explain to set the schedule { @@ -105,7 +116,7 @@ export class Tours { nextFn: emptyFn, previousFn: emptyFn, position: 'top', - attachTo: '.edit-feature-panel .mat-expansion-panel:nth-child(4)' + attachTo: '.edit-feature-panel .mat-expansion-panel:nth-child(5)' }, // Explain how to finally create the feature { diff --git a/front/src/app/services/whats-new.service.ts b/front/src/app/services/whats-new.service.ts index b8403151..2341041f 100644 --- a/front/src/app/services/whats-new.service.ts +++ b/front/src/app/services/whats-new.service.ts @@ -1,5 +1,5 @@ import { Injectable } from '@angular/core'; -import { MatDialog } from '@angular/material/dialog'; +import { MatLegacyDialog as MatDialog } from '@angular/material/legacy-dialog'; import { WhatsNewDialog } from '@dialogs/whats-new/whats-new.component'; import { SelectSnapshot } from '@ngxs-labs/select-snapshot'; import { CustomSelectors } from '@others/custom-selectors'; diff --git a/front/src/app/store/actions/departments.actions.ts b/front/src/app/store/actions/departments.actions.ts index f5408c19..71d2704f 100644 --- a/front/src/app/store/actions/departments.actions.ts +++ b/front/src/app/store/actions/departments.actions.ts @@ -41,4 +41,35 @@ export namespace Departments { static readonly type = '[Departments] Remove Admin Department'; constructor( public department_id: number) { } } + + // file upload process websockets + export class FileUploadStatusUknown { + static readonly type = '[Files] Unknown'; + constructor( public file: UploadedFile ) { } + } + + export class FileUploadStatusProcessing { + static readonly type = '[Files] Processing'; + constructor( public file: UploadedFile ) { } + } + + export class FileUploadStatusScanning { + static readonly type = '[Files] Scanning'; + constructor( public file: UploadedFile ) { } + } + + export class FileUploadStatusEncrypting { + static readonly type = '[Files] Encrypting'; + constructor( public file: UploadedFile ) { } + } + + export class FileUploadStatusDone { + static readonly type = '[Files] Done'; + constructor( public file: UploadedFile ) { } + } + + export class FileUploadStatusError { + static readonly type = '[Files] Error'; + constructor( public file: UploadedFile, public error: FileUploadError ) { } + } } \ No newline at end of file diff --git a/front/src/app/store/actions/results.actions.ts b/front/src/app/store/actions/results.actions.ts index 63faa777..aed2b8fc 100644 --- a/front/src/app/store/actions/results.actions.ts +++ b/front/src/app/store/actions/results.actions.ts @@ -5,6 +5,26 @@ */ export namespace WebSockets { + /** + * @description A feature has been queued + * @param {number} feature_id Feature ID + * @param {number} run_id Run ID + * @param {number} feature_result_id Feature Result ID + * @param {BrowserstackBrowser} browser_info Browser object + * @param {string} datetime Current datetime + */ + export class FeatureQueued { + static readonly type = '[WebSockets] Feature Queued'; + constructor( + public feature_id: number, + public run_id: number, + public feature_result_id: number, + public browser_info: BrowserstackBrowser, + public datetime: string + ) { } + } + + /** * @description A feature has just started initializing * @param {number} feature_id Feature ID diff --git a/front/src/app/store/actions/variables.actions.ts b/front/src/app/store/actions/variables.actions.ts index 2b4e7159..61a42177 100644 --- a/front/src/app/store/actions/variables.actions.ts +++ b/front/src/app/store/actions/variables.actions.ts @@ -30,11 +30,22 @@ export namespace Variables { * @description Update one environment variable * @param {VariablePair} variable Variable object to update */ - export class UpdateVariable { + export class UpdateOrCreateVariable { static readonly type = '[Variables] Update Variable'; constructor( public variable: VariablePair ) { } } + + /** + * @description Update one environment variable + * @param {VariablePair} variable Variable object to update + */ + export class DeleteVariable { + static readonly type = '[Variables] Delete Variable'; + constructor( + public id: number + ) { } + } } \ No newline at end of file diff --git a/front/src/app/store/browsers.state.ts b/front/src/app/store/browsers.state.ts index e9bb9062..0db56352 100644 --- a/front/src/app/store/browsers.state.ts +++ b/front/src/app/store/browsers.state.ts @@ -54,5 +54,4 @@ export class BrowsersState { static getBrowserJsons(state: BrowserResultObject[]) { return state.map(browser => browser.browser_json); } - } diff --git a/front/src/app/store/config.state.ts b/front/src/app/store/config.state.ts index cdcca0bf..17c2d6bb 100644 --- a/front/src/app/store/config.state.ts +++ b/front/src/app/store/config.state.ts @@ -43,9 +43,11 @@ export class ConfigState { configFile.percentMode = localStorage.getItem('percentMode') === 'true' || false; configFile.toggles.hideInformation = localStorage.getItem('hideInformation') === 'true' || configFile.toggles.hideInformation; configFile.toggles.hideBrowsers = localStorage.getItem('hideBrowsers') === 'true' || configFile.toggles.hideBrowsers; + configFile.toggles.hideUploadedFiles = localStorage.getItem('hideUploadedFiles') === 'true' || configFile.toggles.hideUploadedFiles; configFile.toggles.hideSteps = localStorage.getItem('hideSteps') === 'true' || configFile.toggles.hideSteps; configFile.toggles.hideSchedule = localStorage.getItem('hideSchedule') === 'true' || configFile.toggles.hideSchedule; configFile.toggles.hideSendMail = localStorage.getItem('hideSendMail') === 'true' || configFile.toggles.hideSendMail; + // decide to useNewDashboard either from localStorage or from global configFile configFile.useNewDashboard = localStorage.getItem('useNewDashboard') === 'true' || configFile.useNewDashboard; configFile.sorting = localStorage.getItem('search_sorting') || configFile.sorting; configFile.reverse = localStorage.getItem('search_sorting_reverse') === 'true' || configFile.reverse; @@ -55,7 +57,7 @@ export class ConfigState { // Configuration to change the features view const featuresViewWith = localStorage.getItem('featuresView.with'); if (featuresViewWith !== null) { - if (['tiles','list'].includes(featuresViewWith)) { + if (['tiles','list', 'tree'].includes(featuresViewWith)) { configFile.featuresView.with = featuresViewWith; } else { localStorage.removeItem('featuresView.with'); @@ -64,7 +66,7 @@ export class ConfigState { // Configuration to change the related features view const featuresViewWithout = localStorage.getItem('featuresView.without'); if (featuresViewWithout !== null) { - if (['tiles','list'].includes(featuresViewWithout)) { + if (['tiles','list', 'tree'].includes(featuresViewWithout)) { configFile.featuresView.without = featuresViewWithout; } else { localStorage.removeItem('featuresView.without'); diff --git a/front/src/app/store/departments.state.ts b/front/src/app/store/departments.state.ts index 62170092..13fd8b91 100644 --- a/front/src/app/store/departments.state.ts +++ b/front/src/app/store/departments.state.ts @@ -18,41 +18,100 @@ export class DepartmentsState { constructor( private _api: ApiService ) { } - @Action(Departments.GetAdminDepartments) - getAdminDepartments({ setState }: StateContext) { - return this._api.getDepartments().pipe( - map(json => json.results), - tap(departments => setState(departments)) - ); - } + @Action(Departments.GetAdminDepartments) + getAdminDepartments({ setState }: StateContext) { + return this._api.getDepartments().pipe( + map(json => json.results), + tap(departments => setState(departments)) + ); + } - @Action(Departments.AddAdminDepartment) - setAdminDepartment({ setState, getState }: StateContext, { department }: Departments.AddAdminDepartment) { - // Add department only if doesn't exist already - if (!getState().some(dept => dept.department_id === department.department_id)) { - // Add new department - setState([ ...getState(), department ]); - } + @Action(Departments.AddAdminDepartment) + setAdminDepartment({ setState, getState }: StateContext, { department }: Departments.AddAdminDepartment) { + // Add department only if doesn't exist already + if (!getState().some(dept => dept.department_id === department.department_id)) { + // Add new department + setState([ ...getState(), department ]); } + } - @Action(Departments.UpdateDepartment) - updateDepartment({ setState, getState }: StateContext, { departmentId, options }: Departments.UpdateDepartment) { - setState( - produce(getState(), (ctx: Department[]) => { - const index = ctx.findIndex(dept => dept.department_id === departmentId); - if (index !== -1) { - ctx[index] = { - ...ctx[index], - ...options - } + @Action(Departments.UpdateDepartment) + updateDepartment({ setState, getState }: StateContext, { departmentId, options }: Departments.UpdateDepartment) { + setState( + produce(getState(), (ctx: Department[]) => { + const index = ctx.findIndex(dept => dept.department_id === departmentId); + if (index !== -1) { + ctx[index] = { + ...ctx[index], + ...options } - }) - ) - } + } + }) + ) + } - @Action(Departments.RemoveAdminDepartment) - removeAdminDepartment({ setState, getState }: StateContext, { department_id }: Departments.RemoveAdminDepartment) { - setState(getState().filter(dept => dept.department_id !== department_id)); - } + @Action(Departments.RemoveAdminDepartment) + removeAdminDepartment({ setState, getState }: StateContext, { department_id }: Departments.RemoveAdminDepartment) { + setState(getState().filter(dept => dept.department_id !== department_id)); + } + + + // file upload status actions + @Action(Departments.FileUploadStatusUknown) + fileUploadStatusUknown({ setState, getState }: StateContext, { file }: Departments.FileUploadStatusUknown) { + this.updateFileStatus(setState, getState, file); + } + + @Action(Departments.FileUploadStatusProcessing) + fileUploadStatusProcessing({ setState, getState }: StateContext, { file }: Departments.FileUploadStatusProcessing) { + this.updateFileStatus(setState, getState, file); + } + + @Action(Departments.FileUploadStatusScanning) + fileUploadStatusScanning({ setState, getState }: StateContext, { file }: Departments.FileUploadStatusScanning) { + this.updateFileStatus(setState, getState, file); + } + + @Action(Departments.FileUploadStatusEncrypting) + fileUploadStatusEncrypting({ setState, getState }: StateContext, { file }: Departments.FileUploadStatusEncrypting) { + this.updateFileStatus(setState, getState, file); + } -} + @Action(Departments.FileUploadStatusDone) + fileUploadStatusDone({ setState, getState }: StateContext, { file }: Departments.FileUploadStatusDone) { + this.updateFileStatus(setState, getState, file); + } + + @Action(Departments.FileUploadStatusError) + fileUploadStatusError({ setState, getState }: StateContext, { file, error }: Departments.FileUploadStatusError) { + this.updateFileStatus(setState, getState, file, error); + } + + private updateFileStatus(setState, getState, file: UploadedFile, error?: FileUploadError) { + setState( + produce(getState(), (ctx: Department[]) => { + const index = ctx.findIndex(dept => dept.department_id == Number(file.department)); + if (error) file.error = error; + + if (index !== -1) { + // find files of department + let files = ctx[index].files as UploadedFile[]; + + // get current file index in files array + let tmpFileIndex = files.findIndex(f => f.name === file.name) + + // set file properties received from websocket + files[tmpFileIndex] = file; + + const payload = { files: files } as any; + + // refresh state + ctx[index] = { + ...ctx[index], + ...payload + } + } + }) + ) + } +} \ No newline at end of file diff --git a/front/src/app/store/features.state.ts b/front/src/app/store/features.state.ts index 2c53565b..abf373c0 100644 --- a/front/src/app/store/features.state.ts +++ b/front/src/app/store/features.state.ts @@ -179,7 +179,7 @@ export class FeaturesState { * @lastModification 14-10-21 */ @Action(Features.RemoveSearchFilter) - removeSearchFilter({ setState }: StateContext, { }: Features.RemoveSearchFilter) { + removeSearchFilter({ setState }: StateContext, { }: Features.RemoveSearchFilter) { setState( produce((ctx: IFeaturesState) => { const filters = []; @@ -254,7 +254,7 @@ export class FeaturesState { ctx.currentRoute.push(folder); }) ); - dispatch( new Paginations.ResetPagination(['search_with_depends', 'search_without_depends']) ); + dispatch(new Paginations.ResetPagination(['search_with_depends', 'search_without_depends'])); } /** @@ -264,16 +264,16 @@ export class FeaturesState { newAddFolderRoute({ setState, dispatch }: StateContext, { folder }: Features.NewAddFolderRoute) { setState( produce((ctx: IFeaturesState) => { - let departmentsList = this._store.select(CustomSelectors.GetDepartmentFolders()); // Get the list of existing departments - let department; - // Adds the department variable - departmentsList.subscribe(val => department = val.filter(department => department.department == folder.department)); - if (ctx.currentRouteNew.length == 0) { - // Pushes the department folder when the current route length is 0, meaning the user is at root directory - ctx.currentRouteNew.push(department[0]); - } - // Pushes the folder to the current route new state - ctx.currentRouteNew.push(folder); + let departmentsList = this._store.select(CustomSelectors.GetDepartmentFolders()); // Get the list of existing departments + let department; + // Adds the department variable + departmentsList.subscribe(val => department = val.filter(department => department.department == folder.department)); + if (ctx.currentRouteNew.length == 0) { + // Pushes the department folder when the current route length is 0, meaning the user is at root directory + ctx.currentRouteNew.push(department[0]); + } + // Pushes the folder to the current route new state + ctx.currentRouteNew.push(folder); }) ); } @@ -304,9 +304,9 @@ export class FeaturesState { let department; let path; // Check if the department path already exists, if not add it - if (!folder[0].type) { + if (!folder[0]?.type) { // Adds the department variable - departmentsList.subscribe(val => department = val.filter(department => department.department == folder[0].department)); + departmentsList.subscribe(val => department = val.filter(department => department.department == folder[0]?.department)); // Empties the folder array to save space department.folders = []; // Adds the department to the first position of the folder array @@ -320,13 +320,18 @@ export class FeaturesState { ctx.currentRouteNew = path || []; // Set the new current route }) ); + + // #3399 ----------------------------------------------------------------------------start + // save the last selected folder's path in localstorage in order to maintain it on reload, on view destory, etc... + this.saveLastFolderPath(folder); + // #3399 ------------------------------------------------------------------------------en } @Action(Features.ReturnToFolderRoute) returnToFolderRoute({ patchState, getState, dispatch }: StateContext, { folderId }: Features.ReturnToFolderRoute) { // Removing a folder from the folders array makes no sense, // therefore is preferable to return to a previous folder name and remove the subsequent folders - let currentRoute = [ ...getState().currentRoute ]; + let currentRoute = [...getState().currentRoute]; // Check folder id if (folderId) { const indexOfName = currentRoute.findIndex(route => route.folder_id === folderId); @@ -339,14 +344,14 @@ export class FeaturesState { patchState({ currentRoute: [...currentRoute] }); - dispatch( new Paginations.ResetPagination(['search_with_depends', 'search_without_depends']) ); + dispatch(new Paginations.ResetPagination(['search_with_depends', 'search_without_depends'])); } @Action(Features.ReturnToFolderRoute) newReturnToFolderRoute({ patchState, getState, dispatch }: StateContext, { folderId }: Features.ReturnToFolderRoute) { // Removing a folder from the folders array makes no sense, // therefore is preferable to return to a previous folder name and remove the subsequent folders - let currentRouteNew = [ ...getState().currentRouteNew ]; + let currentRouteNew = [...getState().currentRouteNew]; // Check folder id if (folderId) { const indexOfName = currentRouteNew.findIndex(route => route.folder_id === folderId); @@ -359,7 +364,13 @@ export class FeaturesState { patchState({ currentRouteNew: [...currentRouteNew] }); - dispatch( new Paginations.ResetPagination(['search_with_depends', 'search_without_depends']) ); + + // #3399 ----------------------------------------------------------------------------start + // save the last selected folder's path in localstorage in order to maintain it on reload, on view destory, etc... + this.saveLastFolderPath(currentRouteNew); + // #3399 ------------------------------------------------------------------------------en + + dispatch(new Paginations.ResetPagination(['search_with_depends', 'search_without_depends'])); } @Action(Features.SetDepartmentFilter) @@ -450,6 +461,14 @@ export class FeaturesState { localStorage.setItem('filters', JSON.stringify(filters)); } + // #3399 ------------------------------start + saveLastFolderPath(folders: Partial[]) { + // save the last selected folder's path in localstorage in order to maintain it on reload, on view destory, etc... + // this instance of localstorage is used in folder-tree.component.ts in ngOnInit(). + localStorage.setItem('co_last_selected_folder_route', JSON.stringify(folders)); + } + // #3399 --------------------------------end + @Selector() @ImmutableSelector() static GetFeatureInfo(state: IFeaturesState) { @@ -482,7 +501,7 @@ export class FeaturesState { @ImmutableSelector() static GetSelectionFolders(state: IFeaturesState) { // FIXME find a better way to do this - return location.hash != '#/new' ? state.currentRoute : state.currentRouteNew; + return ! location.hash.includes('#/new') ? state.currentRoute : state.currentRouteNew; } /** @@ -541,18 +560,18 @@ export class FeaturesState { // Filter features by name features = features.filter(feature => details[feature].feature_name.toLowerCase().includes(filter.value.toString().toLowerCase())); break; - case 'steps': - case 'ok': - case 'fails': - case 'skipped': - case 'execution_time': - case 'pixel_diff': - // Filter features by given "greater, equal or smaller than" filters - features = this.computeEvaluation(filter.id, features, details, filter.value, filter.more); - break; - case 'help': - // Filter by "Asking for Help" - features = features.filter(id => details[id]?.need_help) + case 'steps': + case 'ok': + case 'fails': + case 'skipped': + case 'execution_time': + case 'pixel_diff': + // Filter features by given "greater, equal or smaller than" filters + features = this.computeEvaluation(filter.id, features, details, filter.value, filter.more); + break; + case 'help': + // Filter by "Asking for Help" + features = features.filter(id => details[id]?.need_help) } }); return features; @@ -610,9 +629,9 @@ export class FeaturesState { let search = state.filters; // Get the currently existing filters // If there are no filters, get the features and folders from the current directory if (search.length == 0) { - // Get the data in the current directory - folders = this.getCurrentDirectoryData(state, folders); - // If there is any filter, filter the folders and features by it + // Get the data in the current directory + folders = this.getCurrentDirectoryData(state, folders); + // If there is any filter, filter the folders and features by it } else { // Process resulting features with selected filters folders = this.newProcessFilters(state, search, folders); @@ -637,14 +656,14 @@ export class FeaturesState { * @date 04-10-21 * @lastModification 08-10-21 */ - static getRecentFeatures(state: IFeaturesState, user_id: number): FoldersResponse { + static getRecentFeatures(state: IFeaturesState, user_id: number): FoldersResponse { let features: Feature[] = Object.values(JSON.parse(JSON.stringify(state.details))); // Get all the features // Filter the data rows by the modification user id, removing the rows that are not equal to the current user's id features = features.filter(val => val.last_edited?.user_id === user_id); // Sorts the features by modification date - let sorted: any = features.sort(function(a: any, b: any) { return (b.last_edited_date < a.last_edited_date) ? -1 : 1}); + let sorted: any = features.sort(function (a: any, b: any) { return (b.last_edited_date < a.last_edited_date) ? -1 : 1 }); sorted = (sorted.length > 10) ? sorted.slice(0, 10) : sorted; // Limit the results to 10 rows - let result = {folders: [], features: sorted}; + let result = { folders: [], features: sorted }; result.features = sorted.map(val => val.feature_id); // Store only the id of each feature return result; } @@ -683,10 +702,17 @@ export class FeaturesState { * @author dph000 */ static transformCurrentDirectoryData(result, state: IFeaturesState, folders: FoldersResponse) { + // return if there are no folders + if (!folders) return; + + for (const id of folders.features) { let columns: any = {}; // Variable to store the feature values let feature = state.details[id]; // Variable with the feature data + //return if there are no features + if (!feature) continue; + // Gets the needed variables and inserts them into the columns variable columns.type = "feature"; // Type of data row columns.orderType = feature.depends_on_others ? '3' : '2'; // set order type, makes it easy to sort groups. @@ -698,9 +724,23 @@ export class FeaturesState { columns.date = feature.info?.result_date; // Date of the last execution columns.time = feature.info?.execution_time; // Elapsed time of the last execution columns.total = feature.info?.total; // Amount of total steps of the last execution + + // #3427 ----------------------------------------------- start + // as per ticket description we intend to hide department, environment and application columns in feature data-grid + // and show successfully executes steps and failed steps + columns.ok = feature.info?.ok; // cuantity of successful steps of features + columns.fails = feature.info?.total - feature.info?.ok; // cuantity of failed steps of features + // #3427 ------------------------------------------------- end + + // #3427 ----------------------------------------------- start + // For now we hide these properties, but there is no need to eliminate them from here, we remove them from html where they are used + // this way we can use them whenever needed in future columns.department = feature.department_name; // Name of the department - columns.app = feature.app_name; // Name of the application columns.environment = feature.environment_name; // Name of the environment + columns.app = feature.app_name; // Name of the application + // #3427 ------------------------------------------------- end + + columns.browsers = feature.browsers; // List of browsers used to test the feature columns.schedule = feature.schedule; // Schedule to said feature columns.depends_on_others = feature.depends_on_others; @@ -726,6 +766,13 @@ export class FeaturesState { columns.date = null; columns.time = null; columns.total = null; + + // #3427 ----------------------------------------------- start + // set default value to null + columns.ok = null; + columns.fails = null; + // #3427 ------------------------------------------------- end + columns.department = folder.department; // Name of the department columns.app = null; columns.environment = null; @@ -749,14 +796,17 @@ export class FeaturesState { * @lastModification 01-10-21 */ static getRecursiveData(state: IFeaturesState, results, folder: Folder, filter_name: string, route: Folder[]) { + // Recursivity that goes over all the folders folder.folders.forEach(currentFolder => { let currentRoute: Folder[] = [...route, currentFolder]; // Add the current route to the parent route (if exists) results = this.getRecursiveData(state, results, currentFolder, filter_name, currentRoute); }); + + // Get the features data as an [] folder.features.forEach(val => { - if (state.details[val].feature_name.toLowerCase().includes(filter_name) || state.details[val].feature_id.toString().includes(filter_name)) { + if (state.details[val]?.feature_name.toLowerCase().includes(filter_name) || state.details[val]?.feature_id.toString().includes(filter_name)) { results.features.push(val); } }); @@ -882,5 +932,4 @@ export class FeaturesState { return features; } } - -} +} \ No newline at end of file diff --git a/front/src/app/store/results.state.ts b/front/src/app/store/results.state.ts index 3c2d84f4..c48a1dfe 100644 --- a/front/src/app/store/results.state.ts +++ b/front/src/app/store/results.state.ts @@ -1,9 +1,9 @@ import { State, Action, StateContext, Selector, Store } from '@ngxs/store'; import { Injectable, NgZone } from '@angular/core'; import { getBrowserComboText, getBrowserKey } from '@services/tools'; -import { MatSnackBar } from '@angular/material/snack-bar'; +import { MatLegacySnackBar as MatSnackBar } from '@angular/material/legacy-snack-bar'; import { Router } from '@angular/router'; -import { MatDialog } from '@angular/material/dialog'; +import { MatLegacyDialog as MatDialog } from '@angular/material/legacy-dialog'; import { produce } from 'immer'; import { ImmutableSelector } from '@ngxs-labs/immer-adapter'; import { WebSockets } from './actions/results.actions'; @@ -187,6 +187,21 @@ export class ResultsState { ); } + @Action(WebSockets.FeatureQueued) + setFeatureQueued( + { setState }: StateContext, + { feature_id, run_id, browser_info, feature_result_id}: WebSockets.FeatureQueued + ) { + this.resetTimeout(feature_id, run_id, browser_info); + setState( + produce((ctx: IResults) => { + this.verifyAndFixMainKeys(ctx, feature_id, run_id, browser_info, 'Queued', feature_result_id); + ctx[feature_id].status = 'Feature Queued'; + ctx[feature_id].running = true; + }) + ); + } + @Action(WebSockets.FeatureInitializing) setFeatureInitializing( { setState }: StateContext, diff --git a/front/src/app/store/user.state.ts b/front/src/app/store/user.state.ts index 241f250b..4b275b77 100644 --- a/front/src/app/store/user.state.ts +++ b/front/src/app/store/user.state.ts @@ -29,6 +29,24 @@ export class UserState { getUser({ patchState, dispatch }: StateContext) { return this._api.doOIDCLogin().pipe( tap(account => { + + // set up localstorage instance for each existing property in account settings object + // does the same as below commented >>>> localstorage.setItem + Object.keys(account.settings).forEach(key => { + localStorage.setItem(key, account.settings[key]); + }); + + + // localStorage.setItem('useNewDashboard', account.settings?.useNewDashboard); + // localStorage.setItem('logWebsockets', account.settings?.logWebsockets); + // localStorage.setItem('percentMode', account.settings?.percentMode); + // localStorage.setItem('da', account.settings?.disableAnimations); + // localStorage.setItem('hideInformation', account.settings?.hideInformation); + // localStorage.setItem('hideSendMail', account.settings?.hideSendMail); + // localStorage.setItem('hideBrowsers', account.settings?.hideBrowsers); + // localStorage.setItem('hideSteps', account.settings?.hideSteps); + // localStorage.setItem('hideSchedule', account.settings?.hideSchedule); + if (typeof account.favourite_browsers === 'object') account.favourite_browsers = JSON.stringify(account.favourite_browsers); // Cross-join available clouds with subscriptions if (account.requires_payment) { @@ -43,7 +61,7 @@ export class UserState { for (const cloud of account.clouds) { switch (cloud.name) { case 'local': - dispatch( new Browsers.GetBrowsers ); + dispatch(new Browsers.GetBrowsers); break; case 'browserstack': dispatch(new Browserstack.GetBrowserstack); @@ -64,7 +82,10 @@ export class UserState { userLogout() { // Do nothing with the state, just redirect to logout.html localStorage.removeItem('@@STATE'); - window.location.href = '/callback?logout=/logout.html'; + // generate logout URL + const logoutURL = `/callback?logout=${location.protocol}//${location.hostname}/logout.html`; + console.log({logoutURL}); + window.location.href = logoutURL; } @Action(User.SetBrowserFavourites) diff --git a/front/src/app/store/variables.state.ts b/front/src/app/store/variables.state.ts index b3fffad8..b3fa17ec 100644 --- a/front/src/app/store/variables.state.ts +++ b/front/src/app/store/variables.state.ts @@ -1,6 +1,6 @@ import { State, Action, StateContext, Selector } from '@ngxs/store'; import { ApiService } from '@services/api.service'; -import { tap } from 'rxjs/operators'; +import { map, tap } from 'rxjs/operators'; import { Injectable } from '@angular/core'; import { ImmutableSelector } from '@ngxs-labs/immer-adapter'; import produce from 'immer'; @@ -25,20 +25,24 @@ export class VariablesState { return this._api.getVariables().pipe( tap(vars => setState(vars)) ); - } + } - @Action(Variables.SetVariables) - setVariables({ setState, getState }: StateContext, { environment_name, department_name, variables }: Variables.SetVariables) { - const vars = getState().filter(v => !(v.environment.environment_name === environment_name && v.department.department_name === department_name)); - setState([ ...vars, ...variables ]) + @Action(Variables.DeleteVariable) + deleteVariable({ setState, getState }: StateContext, { id }: Variables.DeleteVariable) { + setState( + produce(getState(), (ctx: VariablePair[]) => { + const index = ctx.findIndex(v => v.id === id); + if(index != -1) ctx.splice(index, 1); + }) + ) } - @Action(Variables.UpdateVariable) - updateVariable({ setState, getState }: StateContext, { variable }: Variables.UpdateVariable) { + @Action(Variables.UpdateOrCreateVariable) + UpdateOrCreateVariable({ setState, getState }: StateContext, { variable }: Variables.UpdateOrCreateVariable) { setState( produce(getState(), (ctx: VariablePair[]) => { - const index = ctx.findIndex(v => v.id === variable.id); - ctx[index] = variable; + let index = ctx.findIndex(v => v.id === variable.id); + index == -1 ? ctx.unshift(variable) : ctx[index] = variable; }) ) } @@ -47,8 +51,7 @@ export class VariablesState { @ImmutableSelector() static GetVariables(state: VariablePair[]) { return (environment_id: number, department_id: number) => { - return sortBy(state.filter(v => v.environment.environment_id === environment_id && v.department.department_id === department_id), 'variable_name'); + return sortBy(state.filter(v => v.environment === environment_id && v.department === department_id), 'variable_name'); }; } - } diff --git a/front/src/app/views/detail-view/detail-view.component.html b/front/src/app/views/detail-view/detail-view.component.html index 315739a8..170d18e0 100755 --- a/front/src/app/views/detail-view/detail-view.component.html +++ b/front/src/app/views/detail-view/detail-view.component.html @@ -68,4 +68,4 @@ - \ No newline at end of file + diff --git a/front/src/app/views/detail-view/detail-view.component.scss b/front/src/app/views/detail-view/detail-view.component.scss index b110401e..388bdfd8 100755 --- a/front/src/app/views/detail-view/detail-view.component.scss +++ b/front/src/app/views/detail-view/detail-view.component.scss @@ -1,4 +1,3 @@ - @import 'breakpoints'; :host { @@ -86,8 +85,11 @@ hr { align-items: center; opacity: .6; cursor: pointer; - height: 50px; - line-height: 50px; + + // creates unnecessary white space + // height: 50px; + // line-height: 50px; + white-space: nowrap; @include for-tablet-portrait-up { display: inline-block; @@ -331,17 +333,21 @@ hr { margin-left: 70px; @include maxWidth(700px) { margin-left: 0; - } + } } } .current_filters { + // previous flexbox rules, broke layout by wrapping feature description text too much + // which caused contaner to push below contains below, thus altering view display constantly on screen resize + display: flex; + flex-direction: column; + justify-content: flex-start; margin: 15px 20px 0; - @include for-tablet-portrait-up { - margin: 20px 30px 0; + height: 85px; + @include for-tablet-portrait-up-plus30 { + flex-direction: row; } - display: flex; - flex-wrap: wrap; } .zoomer { @@ -367,4 +373,4 @@ hr { background-size: contain; background-position: center center; } -} \ No newline at end of file +} diff --git a/front/src/app/views/detail-view/detail-view.component.ts b/front/src/app/views/detail-view/detail-view.component.ts index ca473d88..26e16184 100755 --- a/front/src/app/views/detail-view/detail-view.component.ts +++ b/front/src/app/views/detail-view/detail-view.component.ts @@ -2,7 +2,7 @@ import { Component, OnInit, ChangeDetectionStrategy, Inject, HostListener } from import { trigger, state, style, transition, animate } from '@angular/animations'; import { ActivatedRoute, Router } from '@angular/router'; import { ApiService } from '@services/api.service'; -import { MatDialog } from '@angular/material/dialog'; +import { MatLegacyDialog as MatDialog } from '@angular/material/legacy-dialog'; import { SafeStyle, DomSanitizer } from '@angular/platform-browser'; import { API_BASE } from 'app/tokens'; import { ScreenshotComponent } from '@dialogs/screenshot/screenshot.component'; @@ -195,7 +195,7 @@ export class DetailViewComponent implements OnInit { } returnToMain() { - this._router.navigate(['../../../../../../'], { relativeTo: this._acRouted }); + this._router.navigate(['../../../../'], { relativeTo: this._acRouted }); } } diff --git a/front/src/app/views/main-view/main-view-header/main-view-header.component.html b/front/src/app/views/main-view/main-view-header/main-view-header.component.html index 8f7870b0..612ae734 100644 --- a/front/src/app/views/main-view/main-view-header/main-view-header.component.html +++ b/front/src/app/views/main-view/main-view-header/main-view-header.component.html @@ -3,31 +3,39 @@
    STATUS
    -
    +
    {{ 'MAIN_VIEW.DATE' | translate }}
    -
    +
    steps #
    -
    +
    ok {{ percentMode ? '%' : '#' }}
    -
    +
    nok {{ percentMode ? '%' : '#' }}
    -
    +
    skipped {{ percentMode ? '%' : '#' }}
    ok / nok / skip. {{ percentMode ? '%' : '#' }}
    -
    +
    time
    -
    +
    browser
    -
    +
    pixel difference
    @@ -37,15 +45,21 @@
    -
    @@ -60,12 +74,9 @@
    Options
    diff --git a/front/src/app/views/main-view/main-view-header/main-view-header.component.ts b/front/src/app/views/main-view/main-view-header/main-view-header.component.ts index 3924ec34..9c5711c1 100644 --- a/front/src/app/views/main-view/main-view-header/main-view-header.component.ts +++ b/front/src/app/views/main-view/main-view-header/main-view-header.component.ts @@ -1,9 +1,8 @@ import { CdkDragDrop, moveItemInArray } from '@angular/cdk/drag-drop'; import { ChangeDetectionStrategy, Component, Host, OnInit, Optional } from '@angular/core'; -import { MatCheckboxChange } from '@angular/material/checkbox'; -import { MatSnackBar } from '@angular/material/snack-bar'; +import { MatLegacyCheckboxChange as MatCheckboxChange } from '@angular/material/legacy-checkbox'; +import { MatLegacySnackBar as MatSnackBar } from '@angular/material/legacy-snack-bar'; import { ActivatedRoute } from '@angular/router'; -import { Dispatch } from '@ngxs-labs/dispatch-decorator'; import { Select, Store } from '@ngxs/store'; import { CustomSelectors } from '@others/custom-selectors'; import { Configuration } from '@store/actions/config.actions'; @@ -81,9 +80,8 @@ export class MainViewHeaderComponent implements OnInit { return `${item.id}_${index}`; } - @Dispatch() handleDeleteTemplateWithResults({ checked }: MatCheckboxChange) { - return new Configuration.SetProperty('deleteTemplateWithResults', checked); + return this._store.dispatch(new Configuration.SetProperty('deleteTemplateWithResults', checked)); } changeSort(prop: string) { @@ -140,7 +138,6 @@ export class MainViewHeaderComponent implements OnInit { * Enables or disables archived runs from checkbox * @param change MatCheckboxChange */ - @Dispatch() - handleArchived = (change: MatCheckboxChange) => new Configuration.SetProperty('internal.showArchived', change.checked); + handleArchived = (change: MatCheckboxChange) => this._store.dispatch(new Configuration.SetProperty('internal.showArchived', change.checked)); } diff --git a/front/src/app/views/main-view/main-view.component.html b/front/src/app/views/main-view/main-view.component.html index 0ff67ff8..eef49b08 100755 --- a/front/src/app/views/main-view/main-view.component.html +++ b/front/src/app/views/main-view/main-view.component.html @@ -1,52 +1,148 @@
    - +
    - -
    -
    - - + +
    +
    +
    + +
    Main
    - -
    -
    Steps #
    -
    Browsers
    -
    Last test
    -
    - -
    -
    {{ lastRun.feature_results | sumByProperty:'total' }}
    -
    {{ lastRun.feature_results.length }}
    -
    {{ passed ? 'OK' : 'NOK' }}
    -
    - - - - - - - - - - -
    -

    Please execute your first feature clicking the blue run-button.

    -
    -

    If you just added a feature and it's executing now you will have to wait until it's finished.

    -
    -

    These results are reloaded automatically.

    + + +
    +
    + +
    +
    + +
    + shown when there is no charts +

    This screen shows the results of your testruns.

    Once you have more then 10 results, co.meta will show you a beautiful linechart with execution times and more.

    +
    + +
    +
    Steps #
    +
    Browsers
    +
    Last test
    +
    + +
    +
    {{ lastRun.total}}
    +
    {{ 1 }}
    +
    {{ passed ? 'OK' : 'NOK' }}
    +
    +
    +
    + + + + + + +
    + +
    + Passed
    + +
    Failed
    - \ No newline at end of file + + + + + + + + +
    +
    + + + {{ row.result_date | amParse | amDateFormat:'MMMM d yyyy, HH:mm' }} + + + + {{ row.execution_time | secondsToHumanReadable }} + + + + {{ row.pixel_diff | pixelDifference }} + + + + + + + + +
    + Show archived items + +
    + + + +
    Clear results
    + + + + + +
    Options
    + + +
    +
    + + + +
    +

    Please execute your first feature clicking the blue run-button.

    +
    +

    If you just added a feature and it's executing now you will have to wait until it's finished.

    +
    +

    These results are reloaded automatically.

    +
    +
    \ No newline at end of file diff --git a/front/src/app/views/main-view/main-view.component.scss b/front/src/app/views/main-view/main-view.component.scss index a5db1fde..7550eab8 100755 --- a/front/src/app/views/main-view/main-view.component.scss +++ b/front/src/app/views/main-view/main-view.component.scss @@ -3,25 +3,184 @@ @import "breakpoints"; :host { - display: block; + display: flex; + flex-flow: column; width: 100%; - overflow-x: hidden; + overflow-y: auto; max-height: calc(100vh - var(--header-height)); + height: calc(100vh - var(--header-height)); +} + +:host::ng-deep { + .mtx-grid{ + z-index: 0; + .mtx-grid-toolbar-content { + flex-grow: 1; + .custom_toolbar { + display: flex; + justify-content: space-between; + align-items: center; + margin-right: 5px; + } + button { + color: #000; + mat-icon { + font-size: 1.125rem; + width: 1.125rem; + height: 1.125rem; + margin-left: -4px; + margin-right: 8px; + display: inline-block; + } + } + } + table { + border-radius: 5px; + background-color: $table-background-opaque; + border-spacing: 0; + box-shadow: 0 3px 1px -2px $table-low-shadow, 0 2px 2px 0 $table-medium-shadow, 0 1px 5px 0 $table-high-shadow; + + thead { + background-color: #474747; + [role="columnheader"] { + span { + text-transform: uppercase; + font-weight: 400; + font-size: 16px; + color: #fff; + } + + svg { + display: none; + } + } + + .mat-sort-header-arrow div:not(.mat-sort-header-indicator) { + background-color: #fff; + } + } + + tbody tr { + height: 45px; + cursor: pointer; + &:nth-child(odd) { + background-color: $low-white; + } + &:nth-child(even) { + background-color: #f2f2f2; + } + &:hover { + background-color: #cecece; + } + &.selected { + background-color: $selected-black-opaque; + td { + &.name { + color: $blue; + } + } + } + } + + td { + font-weight: bold; + color: $dark; + font-family: 'CorpoS, sans-serif'; + font-size: 16px; + } + + // th .mat-header-cell-inner { + // justify-content: center; + // } + + // .aligned-center { + // text-align: center; + // } + // .aligned-right { + // text-align: right; + // } + + .mdc-icon-button { + color: #0000008a; + + &:hover { + color: var(--mdc-icon-button-icon-color, inherit); + } + } + } + thead .mat-table-sticky-right { + border-left-color: rgba(255,255,255,.2); + } + tbody .mat-table-sticky-right { + border-left-color: rgba(0,0,0,.2); + } + .mat-mdc-paginator-container { + background-color: $body-bg-color; + } + + .browser-icon { + width: 100%; + height: 20px; + display: block; + background-size: 20px; + background-repeat: no-repeat; + // background-position: center; + position: relative; + } + } +} + +.no-chart { + max-height: 400px; + padding: 10px 5%; + display: flex; + justify-content: center; + align-items: center; + img { + width: 400px; + max-width: 500px; + height: auto; + } + + p { + font-size: 3vw; + padding: 0 5%; + @include for-tablet-portrait-up { + font-size: 2vw; + } + @include for-super-large-desktop { + font-size: 1.5vw; + } + } } .charts { display: block; width: 100%; - height: 350px; + height: 340px; .behave-charts { height: 100%; - padding: 50px 0 0; + padding: 15px 0 0; margin: 0 auto; display: flex; box-sizing: border-box; text-align: center; - @include maxWidth(700px) { - padding: 40px 0 0; + // @include maxWidth(700px) { + // padding: 40px 0 0; + // } + } +} + +.chart-section { + position: relative; + // height: 330px; + transition: .5s; + + @include maxWidth(750px) { + height: 430px; + + &.noChart { + height: 250px; } } } @@ -115,14 +274,13 @@ } .current_filters { - margin: 15px 20px 0; - @include for-tablet-portrait-up { - margin: 20px 30px 0; - } display: flex; - flex-wrap: wrap; - br { - line-height: 25px; + flex-direction: column; + justify-content: flex-start; + // height: 85px; // Taking too much space when screen has 125%+ screen resolution. + margin: 15px 20px 0; + @include for-tablet-portrait-up-plus30 { + flex-direction: row; } } @@ -141,7 +299,7 @@ &:not(.header) { font-size: 18px; color: rgba(0,0,0,.5); - margin-bottom: 20px; + margin-bottom: 5px; font-style: italic; font-weight: bold; } @@ -154,7 +312,7 @@ .steps, .browser, .last_test { flex: 1 50%; text-align: center; - padding: 0 0 10px 0; + // padding: 0 0 10px 0; } .last-result-status { &.success { @@ -236,4 +394,52 @@ background-color: #f2f2f2; border-bottom: 2px solid rgba(black, .1); } +} + +.table_rows { + height: 100%; +} + +.return { + position: absolute; + padding: 25px 0 0 20px; + box-sizing: border-box; + z-index: 2; + @include for-tablet-portrait-up { + flex-wrap: nowrap; + } + .return-item { + display: block; + align-items: center; + opacity: .6; + cursor: pointer; + // height: 50px; + // line-height: 50px; + white-space: nowrap; + @include for-tablet-portrait-up { + display: inline-block; + & + .return-item { + margin-left: 10px; + } + } + .return-text { + font-size: 14pt; + font-weight: bold; + color: rgba(0,0,0,0.6); + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + display: inline-block; + vertical-align: middle; + } + i { + height: 15px; + width: 15px; + display: inline-block; + margin-right: 10px; + background: url(^assets/left-arrow.svg) no-repeat; + background-size: contain; + vertical-align: middle; + } + } } \ No newline at end of file diff --git a/front/src/app/views/main-view/main-view.component.ts b/front/src/app/views/main-view/main-view.component.ts index 48756889..97c7f1b8 100755 --- a/front/src/app/views/main-view/main-view.component.ts +++ b/front/src/app/views/main-view/main-view.component.ts @@ -1,107 +1,297 @@ -import { Component, OnInit, ChangeDetectionStrategy, ViewChild, AfterViewInit } from '@angular/core'; -import { ActivatedRoute } from '@angular/router'; +import { Component, OnInit, ChangeDetectionStrategy, ChangeDetectorRef } from '@angular/core'; +import { ActivatedRoute, Router } from '@angular/router'; import { Actions, ofActionDispatched, Select } from '@ngxs/store'; import { combineLatest, Observable, fromEvent } from 'rxjs'; import { CustomSelectors } from '@others/custom-selectors'; -import { NetworkPaginatedListComponent } from '@components/network-paginated-list/network-paginated-list.component'; -import { WebSockets } from '@store/actions/results.actions'; import { map } from 'rxjs/operators'; import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy'; -import { SharedActionsService } from '@services/shared-actions.service'; -import { startWith } from 'rxjs/operators'; import { Store } from '@ngxs/store'; -import { User } from '@store/actions/user.actions'; -import { MainViewFieldsDesktop, MainViewFieldsMobile, MainViewFieldsTabletLandscape, MainViewFieldsTabletPortrait } from '@others/variables'; +import { MtxGridColumn } from '@ng-matero/extensions/grid'; +import { HttpClient } from '@angular/common/http'; +import { PageEvent } from '@angular/material/paginator'; +import { SharedActionsService } from '@services/shared-actions.service'; +import { WebSockets } from '@store/actions/results.actions'; +import { Configuration } from '@store/actions/config.actions'; +import { MatDialog } from '@angular/material/dialog'; +import { MatSnackBar } from '@angular/material/snack-bar'; +import { VideoComponent } from '@dialogs/video/video.component'; +import { ApiService } from '@services/api.service'; +import { LoadingSnack } from '@components/snacks/loading/loading.snack'; +import { MatCheckboxChange } from '@angular/material/checkbox'; +import { PdfLinkPipe } from '@pipes/pdf-link.pipe'; +import { DownloadService } from '@services/download.service'; +import { InterceptorParams } from 'ngx-network-error'; @UntilDestroy() @Component({ selector: 'main-view', templateUrl: './main-view.component.html', styleUrls: ['./main-view.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush + changeDetection: ChangeDetectionStrategy.OnPush, + providers: [ + PdfLinkPipe + ], }) -export class MainViewComponent implements OnInit, AfterViewInit { - - @ViewChild(NetworkPaginatedListComponent, { static: false }) paginatedList: NetworkPaginatedListComponent; +export class MainViewComponent implements OnInit { @Select(CustomSelectors.GetConfigProperty('internal.showArchived')) showArchived$: Observable; + columns: MtxGridColumn[] = [ + {header: 'Status', field: 'status', sortable: true, class: 'aligned-center'}, + {header: 'Execution Date', field: 'result_date', sortable: true, width: '230px', sortProp: { start: 'desc', id: 'result_date'}}, + {header: 'Total', field: 'total', sortable: true, class: 'aligned-center'}, + {header: 'OK', field: 'ok', sortable: true, class: 'aligned-center'}, + {header: 'NOK', field: 'fails', sortable: true, class: 'aligned-center'}, + {header: 'Skipped', field: 'skipped', class: 'aligned-center'}, + {header: 'Browser', field: 'browser', class: 'aligned-center'}, + {header: 'Browser Version', field: 'browser.browser_version', hide: true, sortable: true, class: 'aligned-center'}, + {header: 'Duration', field: 'execution_time', sortable: true, class: "aligned-right"}, + {header: 'Pixel Difference', field: 'pixel_diff', sortable: true, class: "aligned-right"}, + { + header: 'Options', + field: 'options', + width: '230px', + // pinned: 'right', + right: '0px', + type: 'button', + buttons: [ + { + type: 'icon', + text: 'replay', + icon: 'videocam', + tooltip: 'View results replay', + color: 'primary', + iif: (result: FeatureResult) => result.video_url ? true : false, + click: (result: FeatureResult) => this.openVideo(result), + }, + { + type: 'icon', + text: 'pdf', + icon: 'picture_as_pdf', + tooltip: 'Download result PDF', + color: 'primary', + click: (result: FeatureResult) => { + const pdfLink = this._pdfLinkPipe.transform(result.feature_result_id) + this._http.get(pdfLink, { + params: new InterceptorParams({ + skipInterceptor: true, + }), + responseType: 'text', + observe: 'response' + }).subscribe({ + next: (res) => { + this._downloadService.downloadFile(res, { + mime: 'application/pdf', + name: `${result.feature_name}_${result.feature_result_id}.pdf` + }) + }, + error: console.error + }) + }, + }, + { + type: 'icon', + text: 'archive', + icon: 'archive', + tooltip: 'Archive result', + color: 'accent', + click: (result: FeatureResult) => { + this._sharedActions.archive(result).subscribe(_ => this.getResults()) + }, + iif: (result: FeatureResult) => !result.archived + }, + { + type: 'icon', + text: 'unarchive', + icon: 'unarchive', + tooltip: 'Unarchive result', + color: 'accent', + click: (result: FeatureResult) => { + this._sharedActions.archive(result).subscribe(_ => this.getResults()) + }, + iif: (result: FeatureResult) => result.archived + }, + { + type: 'icon', + text: 'delete', + icon: 'delete', + tooltip: 'Delete result', + color: 'warn', + click: (result: FeatureResult) => { + this._sharedActions.deleteFeatureResult(result).subscribe(_ => this.getResults()) + }, + iif: (result: FeatureResult) => !result.archived + } + ] + } + ]; + + results = []; + total = 0; + isLoading = true; + showPagination = true; + latestFeatureResultId: number = 0; + + query = { + page: 0, + size: 10 + } + get params() { + const p = { ...this.query }; + p.page += 1; + return p + } + constructor( private _route: ActivatedRoute, private _actions: Actions, - private _sharedActions: SharedActionsService, - private _store: Store + private _store: Store, + private _router: Router, + public _sharedActions: SharedActionsService, + private _http: HttpClient, + private cdRef: ChangeDetectorRef, + private _dialog: MatDialog, + private _snack: MatSnackBar, + private _api: ApiService, + private _pdfLinkPipe: PdfLinkPipe, + private _downloadService: DownloadService ) { } - featureRunsUrl$: Observable; featureId$: Observable; + openContent(feature_result: FeatureResult) { + this._router.navigate([ + this._route.snapshot.paramMap.get('app'), + this._route.snapshot.paramMap.get('environment'), + this._route.snapshot.paramMap.get('feature'), + 'step', + feature_result.feature_result_id + ]); + } + + getResults() { + this.isLoading = true; + combineLatest([this.featureId$,this.showArchived$]).subscribe(([featureId, archived]) => { + this._http.get(`/backend/api/feature_results_by_featureid/`, { + params: { + feature_id: featureId, + archived: archived, + ...this.params + } + }).subscribe({ + next: (res: any) => { + this.results = res.results + this.total = res.count + this.showPagination = this.total > 0 ? true : false + + // set latest feature id + if (this.showPagination) this.latestFeatureResultId = this.results[0].feature_result_id; + }, + error: (err) => { + console.error(err) + }, + complete: () => { + this.isLoading = false + this.cdRef.detectChanges(); + } + }) + }) + } + + updateData(e: PageEvent) { + this.query.page = e.pageIndex + this.query.size = e.pageSize + this.getResults() + + // create a localstorage session + localStorage.setItem('co_results_page_size', e.pageSize.toString()) + } + + /** + * Performs the overriding action through the Store + */ + setResultStatus(results: FeatureResult, status: 'Success' | 'Failed' | '') { + this._sharedActions.setResultStatus(results, status).subscribe(_ => { + this.getResults(); + }) + } + + openVideo(result: FeatureResult) { + this._sharedActions.loadingObservable( + this._sharedActions.checkVideo(result.video_url), + 'Loading video' + ).subscribe({ + next: _ => { + this._dialog.open(VideoComponent, { + backdropClass: 'video-player-backdrop', + panelClass: 'video-player-panel', + data: result + }) + }, + error: err => this._snack.open('An error ocurred', 'OK') + }) + } + + /** + * Clears runs depending on the type of clearing passed + * @param clearing ClearRunsType + * @returns void + */ + clearRuns(clearing: ClearRunsType) { + // Open Loading Snack + const loadingRef = this._snack.openFromComponent(LoadingSnack, { + data: 'Clearing history...', + duration: 60000 + }); + const featureId = +this._route.snapshot.params.feature; + const deleteTemplateWithResults = this._store.selectSnapshot(CustomSelectors.GetConfigProperty('deleteTemplateWithResults')); + this._api.removeMultipleFeatureRuns(featureId, clearing, deleteTemplateWithResults).subscribe({ + next: _ => { + this.getResults(); + }, + error: (err) => { + console.error(err) + }, + complete: () => { + // Close loading snack + loadingRef.dismiss(); + // Show completed snack + this._snack.open('History cleared', 'OK', { + duration: 5000 + }); + } + }) + } + + handleDeleteTemplateWithResults({ checked }: MatCheckboxChange) { + return this._store.dispatch(new Configuration.SetProperty('deleteTemplateWithResults', checked)); + } + + /** + * Enables or disables archived runs from checkbox + * @param change MatCheckboxChange + */ + handleArchived = (change: MatCheckboxChange) => this._store.dispatch(new Configuration.SetProperty('internal.showArchived', change.checked)); + ngOnInit() { this.featureId$ = this._route.paramMap.pipe( map(params => +params.get('feature')) ) - // Subscribe to URL params - this.featureRunsUrl$ = combineLatest([ - // Get featureId parameter - this.featureId$, - // Get latest value from archived$ - this.showArchived$ - ]).pipe( - // Return endpointUrl for paginated list - map(([featureId, archived]) => `feature_results/?feature_id=${featureId}&archived=${archived}`) - ) + this.query.size = parseInt(localStorage.getItem('co_results_page_size')) || 10; + this.getResults() + // Reload current page of runs whenever a feature run completes this._actions.pipe( untilDestroyed(this), ofActionDispatched(WebSockets.FeatureRunCompleted) ).subscribe(_ => { - if (this.paginatedList) this.paginatedList.reloadCurrentPage().subscribe() + this.getResults() }); + } - ngAfterViewInit() { - // Change the run elements' visibility whenever the user changes the window size - combineLatest([ - this._store.select(CustomSelectors.RetrieveResultHeaders(true)).pipe( - // Add some virtual headers - map(headers => ([ - { id: 'bar', enable: true }, - // @ts-ignore - ...headers, - { id: 'video', enable: true}, - { id: 'options', enable: true } - ])) - ), - fromEvent(window, 'resize').pipe( - map((event: Event) => (event.target as Window).innerWidth), - startWith(window.innerWidth) - ) - ]).pipe( - untilDestroyed(this) - ).subscribe(([headers, windowWidth]) => { - var showVariables = []; - if (windowWidth < 600) { - // Mobile - showVariables = MainViewFieldsMobile; - } else if (windowWidth < 900) { - // Tablet Portrait - showVariables = MainViewFieldsTabletPortrait; - } else if (windowWidth < 1200) { - // Tablet Landscape - showVariables = MainViewFieldsTabletLandscape; - } else { - // Desktop - showVariables = MainViewFieldsDesktop; - } - for (let i = 0; i < headers.length; i++) { - if (showVariables.includes(headers[i].id)) { - document.documentElement.style.setProperty(`--${headers[i].id}-display`, headers.find(header => header.id === headers[i].id).enable ? 'flex' : 'none'); - document.documentElement.style.setProperty(`--${headers[i].id}-order`, (headers.findIndex(header => header.id === headers[i].id) + 1).toString()); - } else { - document.documentElement.style.setProperty(`--${headers[i].id}-display`, 'none'); - document.documentElement.style.setProperty(`--${headers[i].id}-order`, '0'); - } - } - }) + // return to v2 dashboard + returnToMain() { + this._router.navigate(['/']); } } diff --git a/front/src/app/views/step-view/step-view.component.html b/front/src/app/views/step-view/step-view.component.html index 56e7f180..9321f751 100755 --- a/front/src/app/views/step-view/step-view.component.html +++ b/front/src/app/views/step-view/step-view.component.html @@ -13,23 +13,23 @@
    - -
    {{ test.total }}
    +
    {{ test.total | percentage:test.total }}
    TOTAL TEST
    - -
    {{ test.ok| percentage:test.total:false }}%
    +
    {{ test.ok | percentage:test.total }}
    OK
    - -
    {{ test.fails | percentage:test.total:false }}%
    +
    {{ test.fails | percentage:test.total }}
    FAIL
    @@ -66,15 +66,14 @@ class="feature-reference">From feature: {{ item.belongs_to.feature_name }}
    {{ item?.step_name }}
    + Custom error: {{item?.error}}
    -
    {{ passed ? 'Passed' : 'Failed' }}
    +
    + {{ passed ? 'Passed' : 'Failed' }} +
    @@ -91,7 +90,17 @@
    {{ item?.execution_time | secondsToHumanReadable }}
    -
    {{ item?.pixel_diff | pixelDifference }}
    +
    {{ item?.pixel_diff | pixelDifference }} +
    +
    +
    + insert_photo +
    +
    @@ -130,7 +139,8 @@
    RESULT
    TIME
    PIXEL DIFF
    -
    PIXEL DIFFERENCE
    + +
    SCREENSHOTS
    diff --git a/front/src/app/views/step-view/step-view.component.scss b/front/src/app/views/step-view/step-view.component.scss index 70c3e9fc..244bc921 100755 --- a/front/src/app/views/step-view/step-view.component.scss +++ b/front/src/app/views/step-view/step-view.component.scss @@ -3,11 +3,13 @@ @import 'color'; :host { - display: block; + display: flex; + flex-flow: column; width: 100%; box-sizing: border-box; - overflow-x: hidden; + overflow-y: auto; max-height: calc(100vh - var(--header-height)); + height: calc(100vh - var(--header-height)); } .edit, .log { @@ -43,11 +45,18 @@ width: 100%; position: relative; text-align: center; - padding: 30px 30px 50px; + padding: 30px 30px 60px; // table header will be in the same position in feature result component and step view component box-sizing: border-box; - @include maxWidth(700px) { - padding: 15px; + + @include for-tablet-portrait-up-plus30 { + padding: 30px 30px 83px; } + + // comented because when screen is resized makes table header change position on y axis without any reason + // @include maxWidth(700px) { + // padding: 15px; + // } + .return { opacity: .6; display: flex; @@ -76,15 +85,14 @@ } .current_filters { - margin: 15px 20px 0; - @include for-tablet-portrait-up { - margin: 20px 30px 0; - flex-wrap: nowrap; - } + // previous flex box rules made filter action icons have too much space between them display: flex; - flex-wrap: wrap; - br { - line-height: 25px; + flex-direction: column; + justify-content: flex-start; + margin: 15px 20px 0; + height: 85px; + @include for-tablet-portrait-up-plus30 { + flex-direction: row; } } @@ -122,11 +130,18 @@ position: absolute; height: 25px; width: 100%; - top: 40px; + top: 35px; font-size: 20pt; font-weight: 500; margin: auto; text-align: center; + + &::after { + content: attr(data-value); + display: block; + font-size: 12pt; + font-weight: 100; + } } .title { margin-top: 20px; @@ -183,9 +198,12 @@ width: 130px; height: 60px; margin: 0 auto; - margin-top: 20px; + margin-top: 0px; background: url(^assets/pixels_grafic.svg) no-repeat; background-size: contain; + @include for-tablet-portrait-up { + margin-top: 20px; + } } .run { @@ -230,17 +248,20 @@ padding: 12px 0; box-sizing: border-box; padding-right: 10px; - @include for-tablet-portrait-up { - padding-right: 85px; - } + + // commented because breaks syncronization between table header and table rows, column names do not coincide with their corresponding values + // @include for-tablet-portrait-up { + // padding-right: 85px; + // } } .name { - flex: 0 60%; - max-width: 60%; - @include for-tablet-portrait-up { + // commented because breaks syncronization between table header and table rows, column names do not coincide with their corresponding values + // flex: 0 60%; + // max-width: 60%; + // @include for-tablet-portrait-up { flex: 0 40%; max-width: 40%; - } + // } } .status, .time, @@ -261,12 +282,11 @@ .difference-mobile { flex: 0 20%; max-width: 20%; - @include for-tablet-portrait-up { - display: none; - } + // @include for-tablet-portrait-up-plus30 { + // display: none; + // } } .step-row { - flex: 1 100%; display: flex; align-items: center; min-height: 53px; @@ -324,21 +344,30 @@ } } .step-content { - padding: 10px 0; - .feature-reference { - color: rgba(black, .55); + max-width: 100%; + // padding: 10px 0; prevents step row from increasing its height when custom error message has to be displayed, thus causing content overflow + .feature-reference, .step-error { font-size: .8rem; margin-bottom: 3px; display: inline-flex; position: relative; transform: translate3d(0, 0, 0); transition: transform .15s ease-in-out; + + } + + .feature-reference { + color: rgba(black, .55); &:hover { transform: translate3d(10px, 0, 0); } } - .step-name { - word-break: break-all; + + .step-name, .step-error { word-break: break-all; } + + .step-error { + max-width: 100%; + color: $bad; } } } @@ -372,6 +401,13 @@ display: flex; } } + + + .screenshots-wrapper { + width: 24px; + height: 24px; + transform: translateX(150%); + } } .header { .status { @@ -382,8 +418,8 @@ } } .pagination { - @include pagination-mobile { - margin-top: 37px; - } + // @include pagination-mobile { + // margin-top: 37px; + // } } } \ No newline at end of file diff --git a/front/src/app/views/step-view/step-view.component.ts b/front/src/app/views/step-view/step-view.component.ts index a7fcc115..387fc8f8 100755 --- a/front/src/app/views/step-view/step-view.component.ts +++ b/front/src/app/views/step-view/step-view.component.ts @@ -9,7 +9,8 @@ import { distinctUntilChanged, map, shareReplay, switchMap, tap } from 'rxjs/ope import { ApiService } from '@services/api.service'; import { NetworkPaginatedListComponent } from '@components/network-paginated-list/network-paginated-list.component'; import { SharedActionsService } from '@services/shared-actions.service'; -import { Dispatch } from '@ngxs-labs/dispatch-decorator'; +import { MatLegacyDialog as MatDialog } from '@angular/material/legacy-dialog'; +import { ScreenshotComponent } from '@dialogs/screenshot/screenshot.component'; @Component({ selector: 'step-view', @@ -81,6 +82,7 @@ import { Dispatch } from '@ngxs-labs/dispatch-decorator'; }) export class StepViewComponent implements OnInit { + clickStepResult: number = null; test$: Observable; stepResultsUrl$: Observable; @@ -90,7 +92,8 @@ export class StepViewComponent implements OnInit { private _acRouted: ActivatedRoute, private _store: Store, private _api: ApiService, - public _sharedActions: SharedActionsService + public _sharedActions: SharedActionsService, + private _dialog: MatDialog, ) { } featureId$: Observable; @@ -119,7 +122,7 @@ export class StepViewComponent implements OnInit { ) } - @Dispatch() getFeatureResult = resultId => new FeatureResults.GetFeatureResult(resultId, true); + getFeatureResult = resultId => this._store.dispatch(new FeatureResults.GetFeatureResult(resultId, true)); returnToMain() { this._router.navigate([ @@ -160,4 +163,13 @@ export class StepViewComponent implements OnInit { }) } } + + loadImages (item) { + if(item) { + this._dialog.open(ScreenshotComponent, { + data: item, + panelClass: 'screenshot-panel' + }); + } + } } diff --git a/front/src/app/views/user/user.component.html b/front/src/app/views/user/user.component.html index 77b801b2..e9bd2391 100755 --- a/front/src/app/views/user/user.component.html +++ b/front/src/app/views/user/user.component.html @@ -99,25 +99,62 @@

    SETTINGS


    Continue on failure

    - Disable Animations + Disable Animations

    - Show percent values in details + Show percent values in details

    - Use new dashboard by default + Use new dashboard by default

    - I can help + + I can help +

    +
    + Log WebSockets into Console

    - Log WebSockets into Console +
    + Pre-select default options for department, application, environment and record video when creating a new feature. +
      +
    • + + + Preselect Department + + {{ department.department_name }} + + +
    • +
    • + + + Preselect Application + + {{ application.app_name }} + + +
    • +
    • + + + Preselect Environment + + {{ environment.environment_name }} + + +
    • +
    • Record Video
    • +
    +


    Select which sections of Add/Edit feature are closed by default.
      -
    • Information
    • -
    • Email
    • -
    • Browser selection
    • -
    • Steps definition
    • -
    • Schedule
    • +
    • Information
    • +
    • Email
    • +
    • Browser selection
    • +
    • Uploaded files
    • +
    • Steps definition
    • +
    • Schedule
    diff --git a/front/src/app/views/user/user.component.ts b/front/src/app/views/user/user.component.ts index 8b9ab150..26850bab 100755 --- a/front/src/app/views/user/user.component.ts +++ b/front/src/app/views/user/user.component.ts @@ -1,25 +1,26 @@ import { Component, ChangeDetectionStrategy, OnInit } from '@angular/core'; import { UserState } from '@store/user.state'; import { ConfigState } from '@store/config.state'; -import { MatCheckboxChange } from '@angular/material/checkbox'; +import { MatLegacyCheckboxChange as MatCheckboxChange } from '@angular/material/legacy-checkbox'; import { Tour, TourExtended, Tours } from '@services/tours'; import { ViewSelectSnapshot } from '@ngxs-labs/select-snapshot'; import { Observable } from 'rxjs'; import { Select, Store } from '@ngxs/store'; import { map } from 'rxjs/operators'; import { TourService } from '@services/tour.service'; -import { MatDialog } from '@angular/material/dialog'; +import { MatLegacyDialog as MatDialog } from '@angular/material/legacy-dialog'; import { InviteUserDialog } from '@dialogs/invite-user/invite-user.component'; import { SharedActionsService } from '@services/shared-actions.service'; import { IntegrationsState } from '@store/integrations.state'; import { ApiService } from '@services/api.service'; -import { MatSnackBar } from '@angular/material/snack-bar'; +import { MatLegacySnackBar as MatSnackBar } from '@angular/material/legacy-snack-bar'; import { Integrations } from '@store/actions/integrations.actions'; -import { Dispatch } from '@ngxs-labs/dispatch-decorator'; import { Configuration } from '@store/actions/config.actions'; import { User } from '@store/actions/user.actions'; import { Router } from '@angular/router'; import { TranslateService } from '@ngx-translate/core'; +import { ApplicationsState } from '@store/applications.state'; +import { EnvironmentsState } from '@store/environments.state'; @Component({ selector: 'account-settings', @@ -30,6 +31,8 @@ import { TranslateService } from '@ngx-translate/core'; export class UserComponent implements OnInit { @Select(UserState) account$: Observable; + @Select(ApplicationsState) applications$: Observable + @Select(EnvironmentsState) environments$: Observable @ViewSelectSnapshot(ConfigState) config: Config; @Select(UserState.RetrieveSettings) settings$: Observable; @Select(UserState.IsDefaultDepartment) isDefaultDepartment$: Observable; @@ -56,23 +59,23 @@ export class UserComponent implements OnInit { this.tours$ = this.settings$.pipe( map(settings => { return Object.entries(this._tours) - // Remove injected services - .filter(entry => !entry[0].startsWith('_')) - // Map to value - .map(entry => entry[1]) - // Add custom properties - .map((tour: Tour) => { - let completed - try { - completed = settings.tours_completed[tour.id] >= tour.version - } catch (err) { - completed = false - } - return { - ...tour, - completed: completed - } - }) + // Remove injected services + .filter(entry => !entry[0].startsWith('_')) + // Map to value + .map(entry => entry[1]) + // Add custom properties + .map((tour: Tour) => { + let completed + try { + completed = settings.tours_completed[tour.id] >= tour.version + } catch (err) { + completed = false + } + return { + ...tour, + completed: completed + } + }) }) ) } @@ -93,7 +96,7 @@ export class UserComponent implements OnInit { this._api.deleteIntegration(id), 'Deleting integration' ).subscribe({ - next: () => this._store.dispatch( new Integrations.RemoveOne(id) ), + next: () => this._store.dispatch(new Integrations.RemoveOne(id)), error: () => this._snack.open('An error ocurred', 'OK') }) } @@ -106,12 +109,11 @@ export class UserComponent implements OnInit { }) } - @Dispatch() setLang(code: string) { localStorage.setItem('lang', code); this._translate.use(code); this._snack.open('Language changed successfully!', 'OK'); - return new Configuration.SetProperty('language', code); + return this._store.dispatch(new Configuration.SetProperty('language', code)); } reloadLang() { @@ -119,9 +121,12 @@ export class UserComponent implements OnInit { this._snack.open('Language reloaded successfully!', 'OK'); } - @Dispatch() toggleLogWebsockets(event: MatCheckboxChange) { - return new Configuration.SetProperty('logWebsockets', event.checked, true); + // save log websockets value in user setting and send it to backend to make it persistent + return this._store.dispatch([ + new User.SetSetting({ logWebsockets: event.checked }), + new Configuration.SetProperty('logWebsockets', event.checked, true) + ]); } inviteUser() { @@ -132,55 +137,77 @@ export class UserComponent implements OnInit { this._tourService.startTourById(tour.id, true) } - @Dispatch() - handleDisableAnimations(e: MatCheckboxChange) { - localStorage.setItem('da', e.checked ? 'yes' : 'no'); - return new Configuration.SetProperty('disableAnimations', e.checked); + handleDisableAnimations(event: MatCheckboxChange) { + localStorage.setItem('da', event.checked ? 'yes' : 'no'); + + // save disable animation value in user setting and send it to backend to make it persistent + return this._store.dispatch([ + new User.SetSetting({ disableAnimations: event.checked }), + new Configuration.SetProperty('disableAnimations', event.checked) + ]); + } + + handlePercentMode(event: MatCheckboxChange) { + // save percent mode in user setting and send it to backend to make it persistent + return this._store.dispatch([ + new User.SetSetting({ percentMode: event.checked }), + new Configuration.ChangePercentMode() + ]); } - @Dispatch() - handlePercentMode() { - return new Configuration.ChangePercentMode(); + handleToggle(event: MatCheckboxChange, prop) { + // creates js object in format ex: {hidesteps: true} {hidesteps: false} + let toggleSetting = {}; + toggleSetting[prop] = event.checked; + + // save toggle settings in user settings and send it to backend to make it persistent + return this._store.dispatch([ + new User.SetSetting(toggleSetting), + new Configuration.ToggleCollapsible(prop, event.checked) + ]); } - @Dispatch() - handleToggle(ev: MatCheckboxChange, prop) { - return new Configuration.ToggleCollapsible(prop, ev.checked); + preselectSave(prop: string, value: string) { + let preselectSettings = {} + preselectSettings[prop] = value; + + return this._store.dispatch(new User.SetSetting(preselectSettings)); } // Sets the new landing as default dashboard - @Dispatch() toggleNewDashboard(event: MatCheckboxChange) { let routerConfig = this._router.config; // Toggles all the home redirects to /new or /search routerConfig[0].redirectTo = event.checked ? 'new' : 'search'; - return [ + + // save useNewDashboard value in user setting and send it to backend to make it persistent + return this._store.dispatch([ + new User.SetSetting({ useNewDashboard: event.checked }), new Configuration.SetProperty('useNewDashboard', event.checked, true) - ] + ]) } - @Dispatch() handleAccountSetting(ev: any, prop) { // Handle any change in any option if (ev.hasOwnProperty('checked')) { // Add exception if for Budgets if (prop === 'enable_budget' && ev.checked) { - return new User.SetSetting({ - budget: this._store.selectSnapshot(UserState.RetrieveSettings).budget || 0, - enable_budget: ev.checked, - budget_schedule_behavior: this._store.selectSnapshot(UserState.RetrieveSettings).budget_schedule_behavior || 'prevent' - }) + return this._store.dispatch(new User.SetSetting({ + budget: this._store.selectSnapshot(UserState.RetrieveSettings).budget || 0, + enable_budget: ev.checked, + budget_schedule_behavior: this._store.selectSnapshot(UserState.RetrieveSettings).budget_schedule_behavior || 'prevent' + })) } // Handle as checkbox - return new User.SetSetting({ [prop]: ev.checked }); + return this._store.dispatch(new User.SetSetting({ [prop]: ev.checked })); } else if (ev.hasOwnProperty('value')) { // Handle as radio - return new User.SetSetting({ [prop]: ev.value }); + return this._store.dispatch(new User.SetSetting({ [prop]: ev.value })); } else { // Handle as input let value: any = ev.target.value; value = isNaN(value) ? value : parseFloat(value); - return new User.SetSetting({ [prop]: value }); + return this._store.dispatch(new User.SetSetting({ [prop]: value })); } } } diff --git a/front/src/assets/config.json b/front/src/assets/config.json index f0b0de7b..517f8d38 100755 --- a/front/src/assets/config.json +++ b/front/src/assets/config.json @@ -1,5 +1,5 @@ { - "version": "2.8.270", + "version": "2.8.316", "language": "en", "appTitle": "co.meta", "scenario": "dev", @@ -63,7 +63,8 @@ "hideBrowsers": false, "hideSteps": false, "hideSchedule": false, - "hideSendMail": false + "hideSendMail": false, + "hideUploadedFiles": false }, "websocketsTimeout": 30000, "flaticon": [ @@ -189,6 +190,458 @@ "Dream it. Believe it. Build it." ], "changelog": [ + { + "version": "2.8.316", + "date": "2023-10-20", + "features": [ + "Data Driven Test Implemented." + ] + }, + { + "version": "2.8.315", + "date": "2023-10-17", + "features": [ + "New step to highlight elements in screenshot." + ], + "bugfixes": [ + "Feature id repeated twice in tree view." + ] + }, + { + "version": "2.8.314", + "date": "2023-07-19", + "bugfixes": [ + "Feature results are deleted after feature execution has been fixed." + ] + }, + { + "version": "2.8.313", + "date": "2023-07-05", + "bugfixes": [ + "Fixed if uploaded file after decryption was not removed it threw an error on the next executions.", + "Feature results are not removed if feature run is removed, feature runs are no longer in use.", + "Added logging to gunicorn process." + ] + }, + { + "version": "2.8.312", + "date": "2023-07-04", + "bugfixes": [ + "Fixed max step timeout value from 1000s to 7200s.", + "Now showing warning when step timeout is invalid." + ] + }, + { + "version": "2.8.311", + "date": "2023-06-30", + "bugfixes": [ + "Updated the logic for the 'I cannot see element' step." + ] + }, + { + "version": "2.8.310", + "date": "2023-06-28", + "features": [ + { + "title": "New Step: Double Click", + "description": "Double click on elements is now possible!" + } + ] + }, + { + "version": "2.8.309", + "date": "2023-06-26", + "bugfixes": [ + "Department variables were not being picked up." + ] + }, + { + "version": "2.8.308", + "date": "2023-06-21", + "bugfixes": [ + "Added link to public github documentation in help section." + ] + }, + { + "version": "2.8.307", + "date": "2023-06-07", + "bugfixes": [ + "When uploading a file the filename should be the same as the file saved in the Upload tab." + ] + }, + { + "version": "2.8.306", + "date": "2023-05-31", + "features": [ + { + "title": "New Step: Custom Error Message", + "description": "Set custom error messages that will show up on the live preview, step view as well as in PDF report." + } + ] + }, + { + "version": "2.8.305", + "date": "2023-05-24", + "features": [ + { + "title": "Screenshot open on pixel difference click.", + "description": "Clicking on the pixel difference will open up the pixel difference screenshot in the step view." + }, + { + "title": "Return to Main view", + "description": "Added 'Return to Main view' in feature result view." + } + ] + }, + { + "version": "2.8.304", + "date": "2023-05-23", + "bugfixes": [ + "Updated translations for 'Integrations' field." + ] + }, + { + "version": "2.8.303", + "date": "2023-05-22", + "features": [ + { + "title": "Screenshot icon on step view.", + "description": "Screenshot icon has been added next to the pixel difference in step view." + } + ] + }, + { + "version": "2.8.302", + "date": "2023-05-19", + "bugfixes": [ + "Adding feature id as prefix to the feature name in tree view.", + "Feature icon will be set to gray color if feature is checked with depends on other features." + ] + }, + { + "version": "2.8.301", + "date": "2023-05-18", + "features": [ + { + "title": "Screenshot of failed step.", + "description": "Screenshot will be taken on failed steps even though checkbox is not marked." + } + ], + "bugfixes": [ + "Browser does not stop if time limit is exceeded while loading the page.", + "Changed the max timout value in front from 1000s to 7200s (2 hours)." + ] + }, + { + "version": "2.8.300", + "date": "2023-05-04", + "features": [ + { + "title": "Undefined Steps", + "description": "Undefined steps now throw an error in Live Preview." + }, + { + "title": "Step Timeouts", + "description": "Test Managers now can update step timeouts of a department in batch." + } + ] + }, + { + "version": "2.8.299", + "date": "2023-05-02", + "features": [ + { + "title": "Welcome tour", + "description": "Fixed - welcome tour presentation didn't stop on ESC key press" + } + ] + }, + { + "version": "2.8.298", + "date": "2023-05-02", + "features": [ + { + "title": "Default feature step", + "description": "From now on, whenever user creates new feature, default step - 'StartBrowser and call URL {url}' will be automatically inserted into it's steps." + } + ] + }, + { + "version": "2.8.297", + "date": "2023-04-28", + "features": [ + { + "title": "Variable creation", + "description": "Users can now create variable even if feature creation is not finished, but in this case variable can only be based on department or environment." + } + ] + }, + { + "version": "2.8.296", + "date": "2023-04-28", + "features": [ + { + "title": "Folder tree", + "description": "Fixed - Folder tree hierarchy layout is broken if name is too long." + } + ] + }, + { + "version": "2.8.295", + "date": "2023-04-19", + "features": [ + { + "title": "Variable table column selection", + "description": "Users can now choose which columns must be displayed in variables table" + } + ] + }, + { + "version": "2.8.294", + "date": "2023-04-19", + "features": [ + { + "title": "Variable insertion into new feature", + "description": "User can no make use of variable insertion even when currently edit feature is still not created" + } + ] + }, + { + "version": "2.8.293", + "date": "2023-04-19", + "features": [ + { + "title": "Variable replacement", + "description": "User can now insert variable into step by clicking on desired variable in flyout, or alternatively by pressing enter on selected variable" + } + ] + }, + { + "version": "2.8.292", + "date": "2023-04-13", + "features": [ + { + "title": "Variable filter popup", + "description": "User can now make use of live variable filter popup to accurately insert variable names in steps. Popup will appear once user replaces '{anything}' with '$'" + } + ] + }, + { + "version": "2.8.291", + "date": "2023-04-12", + "features": [ + { + "title": "New step: I cannot see", + "description": "With this new step we can easily assert negative assumptions. So, not seeing a given selector is positive. For examaple, use this for testing the absence of elements when checking on access-rights." + } + ] + }, + { + "version": "2.8.290", + "date": "2023-03-12", + "features": [ + { + "title": "Preselect Feature Options", + "description": "You can preselect feature options such as department, application, environment and record video when ever creating new features. These options can be changed in User settings." + }, + { + "title": "New step: I cannot see", + "description": "With this new step we can easily assert negative assumptions. So, not seeing a given selector is positiv. For exmaple, use this for testing the absense of elements when checking on access-rights." + } + ], + "bugfixes": [ + "Typo fix from the step context menu." + ] + }, + { + "version": "2.8.289", + "date": "2023-03-10", + "features": [ + { + "title": "Upload Files", + "description": "You can now upload files to co.meta and use these files in upload file steps." + }, + { + "title": "Added step: Open {excelfile} and compare the number of rows in the {column} column, starting from row {starting_row}, to ensure that there are {total_rows} rows with option {option}", + "description": "Assert the number of rows to match the number of rows specified, possible options are: do not count empty cells or include empty cells." + }, + { + "title": "Javascript function return variable", + "description": "If the Javascript function step has a return value, this value can now be used in co.meta using '%js_return' parameter." + } + ], + "bugfixes": [ + "#3931 - Fixed sub-folders were not being created inside the selected folder." + ] + }, + { + "version": "2.8.288", + "date": "2022-12-17", + "features": [ + { + "title": "Added step: Add a timestamp to the {prefix} and save it to {variable_name}", + "description": "Adds timestamp to the prefix specified so the string becomes unique." + }, + { + "title": "Added step: Create a string of random {x} numbers and save to {variable_name}", + "description": "Create a string of x random numbers." + } + ], + "bugfixes": [ + "#3828 - Fixed height issues when showing paginated rows.", + "#3828 - Fixed 'View Logs' button not working.", + "#3826 - Test list step now generated a CSV file even thought selector was empty or not found.", + "#3826 - Fixed Test list step to show correctly the values when using partial as option." + ] + },{ + "version": "2.8.287", + "date": "2022-07-25", + "features": [ + { + "title": "Updated step: Edit {file} and set {value} to {cell}", + "description": "Updated edit excel step to now edit csv files as well. Supported extensions are: XLSX, XLS & CSV. CSV only supports UTF-8." + } + ] + }, + { + "version": "2.8.286", + "date": "2022-07-12", + "features": [ + { + "title": "New step: Attach {filename} from Downloads folder to current execution results", + "description": "Added a new step to attach files to the execution results fo later investigation. Please read the documentation for more details. https://github.com/cometa-rocks/cometa_documentation/blob/main/cometa_actions.md" + } + ] + }, + { + "version": "2.8.285", + "date": "2022-07-09", + "bugfixes": [ + "Fixed upload of multiple files by separating files with semicolon." + ] + }, + { + "version": "2.8.284", + "date": "2022-07-08", + "features": [ + { + "title": "New steps: Open Excel and assert, Open Excel and set environment variable to value from cell", + "description": "Added new step to updated an excel file. Please read the documentation for more details. https://github.com/cometa-rocks/cometa_documentation/blob/main/cometa_actions.md" + } + ] + }, + { + "version": "2.8.283", + "date": "2022-06-22", + "features": [ + { + "title": "New steps: Edit Excel Files", + "description": "Added new step to updated an excel file. Please read the documentation for more details." + } + ] + }, + { + "version": "2.8.282", + "date": "2022-05-10", + "features": [ + { + "title": "New steps: Download a file + Upload a File", + "description": "Added exiting new steps to upload and download files. Please read the documentation for more details." + } + ] + }, + { + "version": "2.8.281", + "date": "2022-05-10", + "features": [ + { + "title": "Multiple http request", + "description": "Fixed step edit component realizing multiple http requests when it has another feature dependency step" + } + ] + }, + { + "version": "2.8.280", + "date": "2022-05-09", + "features": [ + { + "title": "Folder tree", + "description": "Fixed folder tree's unpersistent behaviour" + } + ] + }, + { + "version": "2.8.279", + "date": "2022-03-22", + "features": [ + { + "title": "Sidebar", + "description": "New Animation for MAIN V.2 landing sidebar navigation" + } + ] + }, + { + "version": "2.8.278", + "date": "2022-03-21", + "bugfixes": [ + "#3426 - Fixed chart diagram is displayed from the starting from first feature result, now it will be displayed when there are at least 10" + ] + }, + { + "version": "2.8.277", + "date": "2022-03-20", + "bugfixes": [ + "#3449 - Fixed default settings in user panel are not persistent" + ] + }, + { + "version": "2.8.276", + "date": "2022-03-19", + "bugfixes": [ + "#3461 - Fixed searchbar doesnt close when searching via click on search icon." + ] + }, + { + "version": "2.8.275", + "date": "2022-03-18", + "bugfixes": [ + "#3470 - Fixed When new dashboard in is deactivated, a click on every folder tree item apart from departments and its content, redirects to /search.", + "#3470 - Fixed browser bookmarks not working if the last click before closing the app was performed on home, recent, starred, trash bin or team drives." + ] + }, + { + "version": "2.8.274", + "date": "2022-03-17", + "bugfixes": [ + "#3467 - Fixed department id not updating when moving feature from department to another department's folder.", + "#3467 - Fixed when moving feature from folder to another, it is not moved but copied(creates duplicate)", + "#3467 - Fixed when moving feature from folder to department, action is not realized" + ] + }, + { + "version": "2.8.273", + "date": "2022-03-16", + "features": [ + { + "title": "Department users", + "description": "Users with permissions to view the department panel are now able to view users inside each departments." + } + ] + }, + { + "version": "2.8.272", + "date": "2021-03-04", + "bugfixes": [ + "#xxxx - New step 'Search for \"{something}\" in IBM Cognos and click on first result'" + ] + }, + { + "version": "2.8.271", + "date": "2021-03-01", + "bugfixes": [ + "#xxxx - New step for hit OK button on popup message like alert, cofirm or prompt window." + ] + }, { "version": "2.8.270", "date": "2021-12-27", diff --git a/front/src/assets/i18n/de.json b/front/src/assets/i18n/de.json index c376d3fd..2829f735 100755 --- a/front/src/assets/i18n/de.json +++ b/front/src/assets/i18n/de.json @@ -29,7 +29,7 @@ }, "user": { "tour_description": "Co.meta hat eine ReiseleiterIn - sie zeigt Dir neue Funktionen. So kannst Du Schritt für Schritt den Umgang mit co.meta erlernen. Bewege den Mauszeiger über den Tour-Knopf um eine kurze Beschreibung zu erhalten.", - "webhooks_desc": "Integrieren Sie co.meta mit einigen Ihrer bevorzugten Tools, um schneller zu bauen, kontinuierlich zu testen und die Qualität mit der gesamten Abteilung zu kommunizieren." + "webhooks_desc": "Durch die Integration von co.meta mit deinen bevorzugten Tools kannst du deinen Entwicklungsprozess optimieren. Durch die nahtlose Verbindung mit Co.Meta kannst du die Fertigstellung von Projekten beschleunigen, kontinuierliche Testpraktiken implementieren und Qualitätsupdates effektiv mit deinem gesamten Team teilen." }, "websockets": { "Initializing": "Initializing feature", @@ -92,7 +92,9 @@ "budget_exceeded_title": "Budget überschritten", "budget_exceeded_desc": "Die Funktion, die Sie ausführen wollen, wird Ihr konfiguriertes Budget überschreiten. Möchten Sie fortfahren?", "budget_ahead_title": "Niedriges Budget", - "budget_ahead_desc": "Die Funktion, die Sie ausführen wollen, wird wahrscheinlich Ihr konfiguriertes Budget überschreiten. Möchten Sie fortfahren?" + "budget_ahead_desc": "Die Funktion, die Sie ausführen wollen, wird wahrscheinlich Ihr konfiguriertes Budget überschreiten. Möchten Sie fortfahren?", + "move_feature_title": "Verschieben", + "move_feature": "Wenn ein Test in eine andere Abteilung verschoben wird, werden die Umgebungsvariablen nicht mit übertragen. Soll der Test verschoben werden?" }, "integrations": { "add": "Integration hinzufügen", diff --git a/front/src/assets/i18n/en.json b/front/src/assets/i18n/en.json index fa872b00..1647008c 100755 --- a/front/src/assets/i18n/en.json +++ b/front/src/assets/i18n/en.json @@ -42,7 +42,7 @@ }, "user": { "tour_description": "In co.meta you can learn about features interactive using the virtual tour guide. The virtual tour guide will show you around and explain step by step the how's and where's. Move your mouse over the tour buttons to see a short description.", - "webhooks_desc": "Integrate co.meta with some of your favorite tools to build faster, test continuously, and communicate quality with the entire department." + "webhooks_desc": "Integrating co.meta with your preferred tools empowers you to optimize your development process. By seamlessly connecting Co.Meta, you can accelerate project completion, implement continuous testing practices, and effectively share quality updates with your entire department." }, "connected": "Connected", "not_connected": "Not connected", @@ -114,8 +114,11 @@ "budget_exceeded_title": "Budget exceed", "budget_exceeded_desc": "The feature you are about to run will exceed your configured budget. Do you want to continue?", "budget_ahead_title": "Low budget", - "budget_ahead_desc": "The feature you are about to run will likely exceed your configured budget. Do you want to continue?" + "budget_ahead_desc": "The feature you are about to run will likely exceed your configured budget. Do you want to continue?", + "move_feature_title": "Move feature", + "move_feature": "You are attempting to move feature to different department, environment variables that the feature in question is currently using, will no longer be usable. Do you want to continue?" }, + "integrations": { "add": "Add integration", "send_on": "Send on", diff --git a/front/src/assets/i18n/es.json b/front/src/assets/i18n/es.json index f8b54c28..88f68aef 100755 --- a/front/src/assets/i18n/es.json +++ b/front/src/assets/i18n/es.json @@ -42,7 +42,7 @@ }, "user": { "tour_description": "In co.meta you can learn about features interactive using the virtual tour guide. The virtual tour guide will show you around and explain step by step the how's and where's. Move your mouse over the tour buttons to see a short description.", - "webhooks_desc": "Integre co.meta con algunas de sus herramientas favoritas para construir más rápido, probar continuamente y comunicar la calidad con todo el departamento." + "webhooks_desc": "Integrar co.meta con tus herramientas favoritas te permite optimizar tu proceso de desarrollo. Al conectar Co.Meta de manera fluida, puedes acelerar la finalización de proyectos, implementar prácticas de pruebas continuas y compartir actualizaciones de calidad de manera efectiva con todo tu departamento." }, "connected": "Connected", "not_connected": "Not connected", @@ -104,12 +104,14 @@ "quit_desc": "Tienes cambios sin guardar.", "decrypt_title": "¿Estás seguro de que quieres desencriptar esta variable?", "decrypt_desc": "Al descifrar una variable se restablece el valor de la variable de entorno.", - "remove_all_title": "Remove everything", - "remove_all": "Are you sure you want to remove all the steps?", + "remove_all_title": "Eliminar todo", + "remove_all": "¿Estas seguro que quieres eliminar todos los pasos?", "budget_exceeded_title": "Presupuesto superado", "budget_exceeded_desc": "El feature que va a ejecutar superará el presupuesto configurado. ¿Deseas continuar?", "budget_ahead_title": "Bajo presupuesto", - "budget_ahead_desc": "Es probable que el feature que va a ejecutar supere el presupuesto configurado. ¿Deseas continuar?" + "budget_ahead_desc": "Es probable que el feature que va a ejecutar supere el presupuesto configurado. ¿Deseas continuar?", + "move_feature_title": "Mover Feature", + "move_feature": "Mover feature a diferente departamento provocara que feature deje de tener acceso al contenido de variables nativos del departamento actual. ¿Deseas continuar?" }, "integrations": { "add": "Añadir integración", diff --git a/front/src/assets/icons/tree-view.svg b/front/src/assets/icons/tree-view.svg new file mode 100644 index 00000000..bae583cb --- /dev/null +++ b/front/src/assets/icons/tree-view.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/front/src/assets/internal/COM_Menu_Icon_Sidemap_Arrow.svg b/front/src/assets/internal/COM_Menu_Icon_Sidemap_Arrow.svg new file mode 100644 index 00000000..ef417384 --- /dev/null +++ b/front/src/assets/internal/COM_Menu_Icon_Sidemap_Arrow.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/front/src/index.html b/front/src/index.html index fec66ab5..8465f927 100755 --- a/front/src/index.html +++ b/front/src/index.html @@ -3,11 +3,12 @@ co.meta - Complete Meta Test Automation - + +