diff --git a/.github/workflows/package.yml b/.github/workflows/package.yml index 1a2e219bb..9ede3a352 100644 --- a/.github/workflows/package.yml +++ b/.github/workflows/package.yml @@ -30,40 +30,6 @@ jobs: name: testrun_package path: testrun*.deb - install_package_20: - permissions: {} - needs: create_package - name: Install on Ubuntu 20.04 - runs-on: ubuntu-20.04 - timeout-minutes: 15 - steps: - - name: Checkout source - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - - name: Download package - uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8 - with: - name: testrun_package - - name: Install dependencies - shell: bash {0} - run: sudo cmd/prepare - - name: Install package - shell: bash {0} - run: sudo apt install ./testrun*.deb - - name: Start testrun - shell: bash {0} - run: sudo testrun > >(tee testrun_output.log) 2>&1 & - - name: Verify testrun started - shell: bash {0} - run: | - sleep 5 - if grep -q "API waiting for requests" testrun_output.log; then - echo "Testrun started successfully." - else - echo "Testrun did not start correctly." - cat testrun_output.log - exit 1 - fi - install_package_22: permissions: {} needs: create_package diff --git a/.github/workflows/testing.yml b/.github/workflows/testing.yml index 4e78f18b5..d8da3ada0 100644 --- a/.github/workflows/testing.yml +++ b/.github/workflows/testing.yml @@ -7,6 +7,25 @@ on: - cron: '0 13 * * *' jobs: + testrun_tests: + permissions: {} + name: Tests + runs-on: ubuntu-22.04 + timeout-minutes: 30 + steps: + - name: Checkout source + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + - name: Install dependencies + shell: bash {0} + run: cmd/prepare + - name: Install Testrun + shell: bash {0} + run: TESTRUN_DIR=. cmd/install + timeout-minutes: 30 + - name: Run tests + shell: bash {0} + run: testing/tests/test_tests + testrun_baseline: permissions: {} name: Baseline @@ -71,7 +90,7 @@ jobs: run: cmd/install -l - name: Run tests for conn module shell: bash {0} - run: bash testing/unit/run_test_module.sh conn captures ethtool output + run: bash testing/unit/run_test_module.sh conn captures ethtool ifconfig output - name: Run tests for dns module shell: bash {0} run: bash testing/unit/run_test_module.sh dns captures reports output @@ -98,7 +117,7 @@ jobs: if: ${{ always() }} with: if-no-files-found: error - name: reports_${{ github.run_id }} + name: unit_reports_${{ github.run_id }} path: testing/unit/report/output pylint: diff --git a/README.md b/README.md index 8f1739031..e6cfc8943 100644 --- a/README.md +++ b/README.md @@ -34,7 +34,7 @@ Testrun provides the network and assistive tools for engineers when manual testi ## Hardware -- PC running Ubuntu LTS 20.04, 22.04, or 24.04 (laptop or desktop) +- PC running Ubuntu LTS 22.04 or 24.04 (laptop or desktop) - 2x ethernet ports (USB ethernet adapters work too) - Internet connection diff --git a/cmd/build b/cmd/build index 8ecccb5ef..5d04e049b 100755 --- a/cmd/build +++ b/cmd/build @@ -45,7 +45,7 @@ fi if docker build -t testrun/ui -f modules/ui/ui.Dockerfile . ; then echo Successully built the user interface else - echo An error occured whilst building the user interface + echo An error occurred whilst building the user interface exit 1 fi @@ -54,7 +54,7 @@ echo Building websockets server if docker build -t testrun/ws -f modules/ws/ws.Dockerfile . ; then echo Successully built the web sockets server else - echo An error occured whilst building the websockets server + echo An error occurred whilst building the websockets server exit 1 fi @@ -66,7 +66,7 @@ for dir in modules/network/* ; do if docker build -f modules/network/$module/$module.Dockerfile -t testrun/$module . ; then echo Successfully built container for network $module else - echo An error occured whilst building container for network module $module + echo An error occurred whilst building container for network module $module exit 1 fi done @@ -79,7 +79,7 @@ for dir in modules/devices/* ; do if docker build -f modules/devices/$module/$module.Dockerfile -t testrun/$module . ; then echo Successfully built container for device module $module else - echo An error occured whilst building container for device module $module + echo An error occurred whilst building container for device module $module exit 1 fi done @@ -92,7 +92,7 @@ for dir in modules/test/* ; do if docker build -f modules/test/$module/$module.Dockerfile -t testrun/$module-test . ; then echo Successfully built container for test module $module else - echo An error occured whilst building container for test module $module + echo An error occurred whilst building container for test module $module exit 1 fi done diff --git a/cmd/build_ui b/cmd/build_ui index 17ccf0c3c..bbfc53764 100755 --- a/cmd/build_ui +++ b/cmd/build_ui @@ -21,7 +21,7 @@ echo Building the ui builder if docker build -t testrun/build-ui -f modules/ui/build.Dockerfile . ; then echo Successully built the ui builder else - echo An error occured whilst building the ui builder + echo An error occurred whilst building the ui builder exit 1 fi diff --git a/docs/additional_config.md b/docs/additional_config.md new file mode 100644 index 000000000..ea9aaaf51 --- /dev/null +++ b/docs/additional_config.md @@ -0,0 +1,121 @@ +# Additional Configuration Options + +Some configuration options are available but not exposed through the user interface and requires direct access. +Modification of various configuration files is necessary to access these options. + +## Override test module timeout at the system level + +Testrun attempts to set reasonable timeouts for test modules to prevent overly long test times but sometimes +a device or series of device may require longer than these default values. These can be overridden at +the test module configuration level but is not preferred since these changes will be undone during every +version upgrade. To modify these values: + +1. Navigate to the testrun installation directory. By default, this will be at: + `/usr/local/testrun` + +2. Open the system.json file and add the following section: + `"test_modules":{}` + +3. Add the module name(s) and timeout property into this test_modules section you wish to +set the timeout property for: + ``` + "test_modules":{ + "connection":{ + "timeout": 500 + } + } + ``` + +Before timeout options: +``` +{ + "network": { + "device_intf": "ens0", + "internet_intf": "ens1" + }, + "log_level": "DEBUG", + "startup_timeout": 60, + "monitor_period": 60, + "max_device_reports": 5, + "org_name": "", + "single_intf": false + } +``` + +After timeout options: +``` +{ + "network": { + "device_intf": "ens0", + "internet_intf": "ens1" + }, + "log_level": "DEBUG", + "startup_timeout": 60, + "monitor_period": 60, + "max_device_reports": 5, + "org_name": "", + "single_intf": false, + "test_modules":{ + "connection":{ + "timeout": 500 + } + } +} +``` + +## Override test module log level at the system level + +Test modules default to the log level info to prevent unecessary logging. These can be overridden at the test module configuration level but is not preferred since these changes will be undone during every version upgrade. To modify these values: + +1. Navigate to the testrun installation directory. By default, this will be at: + `/usr/local/testrun` + +2. Open the system.json file and add the following section: + `"test_modules":{}` + +3. Add the module name(s) and log_level property into this test_modules section you wish to +set the log_level property for: + ``` + "test_modules":{ + "connection":{ + "log_level": "DEGUG" + } + } + ``` +Valid options for modifying the log level are: INFO, DEBUG, WARNING, ERROR. + +Before log_level options: +``` +{ + "network": { + "device_intf": "ens0", + "internet_intf": "ens1" + }, + "log_level": "DEBUG", + "startup_timeout": 60, + "monitor_period": 60, + "max_device_reports": 5, + "org_name": "", + "single_intf": false + } +``` + +After log_level options: +``` +{ + "network": { + "device_intf": "ens0", + "internet_intf": "ens1" + }, + "log_level": "DEBUG", + "startup_timeout": 60, + "monitor_period": 60, + "max_device_reports": 5, + "org_name": "", + "single_intf": false, + "test_modules":{ + "connection":{ + "log_level": "DEBUG" + } + } +``` \ No newline at end of file diff --git a/docs/dev/mockoon.json b/docs/dev/mockoon.json index 394800402..8e9590e76 100644 --- a/docs/dev/mockoon.json +++ b/docs/dev/mockoon.json @@ -604,8 +604,8 @@ "endpoint": "reports", "responses": [ { - "uuid": "9536ff4c-f97f-4880-b9fc-f477686ad6b8", - "body": "[\n {\n \"mac_addr\": \"00:1e:42:35:73:c6\",\n \"device\": {\n \"mac_addr\": \"00:1e:42:35:73:c4\",\n \"manufacturer\": \"Teltonika\",\n \"model\": \"TRB140\",\n \"firmware\": \"1.2.3\",\n \"test_modules\": {\n \"connection\": {\n \"enabled\": false\n },\n \"ntp\": {\n \"enabled\": true\n },\n \"dns\": {\n \"enabled\": true\n },\n \"services\": {\n \"enabled\": true\n },\n \"tls\": {\n \"enabled\": true\n },\n \"protocol\": {\n \"enabled\": true\n }\n }\n },\n \"status\": \"Non-Compliant\",\n \"started\": \"2024-05-03 12:09:59\",\n \"finished\": \"2024-05-03 12:15:51\",\n \"tests\": {\n \"total\": 20,\n \"results\": [\n {\n \"name\": \"protocol.valid_bacnet\",\n \"description\": \"BACnet discovery could not resolve any devices\",\n \"expected_behavior\": \"BACnet traffic can be seen on the network and packets are valid and not malformed\",\n \"required_result\": \"Recommended\",\n \"result\": \"Skipped\"\n },\n {\n \"name\": \"protocol.bacnet.version\",\n \"description\": \"No BACnet devices discovered.\",\n \"expected_behavior\": \"The BACnet client implements an up to date version of BACnet\",\n \"required_result\": \"Recommended\",\n \"result\": \"Skipped\"\n },\n {\n \"name\": \"protocol.valid_modbus\",\n \"description\": \"Failed to establish Modbus connection to device\",\n \"expected_behavior\": \"Any Modbus functionality works as expected and valid Modbus traffic can be observed\",\n \"required_result\": \"Recommended\",\n \"result\": \"Non-Compliant\"\n },\n {\n \"name\": \"ntp.network.ntp_support\",\n \"description\": \"Device sent NTPv3 packets. NTPv3 is not allowed.\",\n \"expected_behavior\": \"The device sends an NTPv4 request to the configured NTP server.\",\n \"required_result\": \"Required\",\n \"result\": \"Non-Compliant\"\n },\n {\n \"name\": \"ntp.network.ntp_dhcp\",\n \"description\": \"Device sent NTP request to non-DHCP provided server\",\n \"expected_behavior\": \"Device can accept NTP server address, provided by the DHCP server (DHCP OFFER PACKET)\",\n \"required_result\": \"Roadmap\",\n \"result\": \"Non-Compliant\"\n },\n {\n \"name\": \"security.services.ftp\",\n \"description\": \"No FTP server found\",\n \"expected_behavior\": \"There is no FTP service running on any port\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"security.ssh.version\",\n \"description\": \"SSH server found running protocol 2.0\",\n \"expected_behavior\": \"SSH server is not running or server is SSHv2\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"security.services.telnet\",\n \"description\": \"No telnet server found\",\n \"expected_behavior\": \"There is no Telnet service running on any port\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"security.services.smtp\",\n \"description\": \"No SMTP server found\",\n \"expected_behavior\": \"There is no SMTP service running on any port\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"security.services.http\",\n \"description\": \"Found HTTP server running on port 80/tcp\",\n \"expected_behavior\": \"Device is unreachable on port 80 (or any other port) and only responds to HTTPS requests on port 443 (or any other port if HTTP is used at all)\",\n \"required_result\": \"Required\",\n \"result\": \"Non-Compliant\"\n },\n {\n \"name\": \"security.services.pop\",\n \"description\": \"No POP server found\",\n \"expected_behavior\": \"There is no POP service running on any port\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"security.services.imap\",\n \"description\": \"No IMAP server found\",\n \"expected_behavior\": \"There is no IMAP service running on any port\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"security.services.snmpv3\",\n \"description\": \"No SNMP server found\",\n \"expected_behavior\": \"Device is unreachable on port 161 (or any other port) and device is unreachable on port 162 (or any other port) unless SNMP is essential in which case it is SNMPv3 is used.\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"security.services.vnc\",\n \"description\": \"No VNC server found\",\n \"expected_behavior\": \"Device cannot be accessed / connected to via VNC on any port\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"security.services.tftp\",\n \"description\": \"No TFTP server found\",\n \"expected_behavior\": \"There is no TFTP service running on any port\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"ntp.network.ntp_server\",\n \"description\": \"No NTP server found\",\n \"expected_behavior\": \"The device does not respond to NTP requests when it's IP is set as the NTP server on another device\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"dns.network.hostname_resolution\",\n \"description\": \"DNS traffic detected from device\",\n \"expected_behavior\": \"The device sends DNS requests.\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"dns.network.from_dhcp\",\n \"description\": \"DNS traffic detected only to DHCP provided server\",\n \"expected_behavior\": \"The device sends DNS requests to the DNS server provided by the DHCP server\",\n \"required_result\": \"Roadmap\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"security.tls.v1_2_server\",\n \"description\": \"TLS 1.2 not validated: Certificate has expired\\nEC key length passed: 256 >= 224\\nDevice certificate has not been signed\\nTLS 1.3 not validated: Certificate has expired\\nEC key length passed: 256 >= 224\\nDevice certificate has not been signed\",\n \"expected_behavior\": \"TLS 1.2 certificate is issued to the web browser client when accessed\",\n \"required_result\": \"Required\",\n \"result\": \"Non-Compliant\"\n },\n {\n \"name\": \"security.tls.v1_2_client\",\n \"description\": \"No outbound TLS connections were found.\",\n \"expected_behavior\": \"The packet indicates a TLS connection with at least TLS 1.2 and support for ECDH and ECDSA ciphers\",\n \"required_result\": \"Required\",\n \"result\": \"Skipped\"\n }\n ]\n },\n \"report\": \"http://localhost:8000/report/123 123/2024-05-03T12:09:59\"\n }\n]", + "uuid": "6adc954a-55c9-40ed-8f49-cf38f659d883", + "body": "[\n {\n \"mac_addr\": \"00:1e:42:35:73:c6\",\n \"device\": {\n \"mac_addr\": \"00:1e:42:35:73:c4\",\n \"manufacturer\": \"Teltonika\",\n \"model\": \"TRB140\",\n \"firmware\": \"1.2.3\",\n \"test_modules\": {\n \"connection\": {\n \"enabled\": false\n },\n \"ntp\": {\n \"enabled\": true\n },\n \"dns\": {\n \"enabled\": true\n },\n \"services\": {\n \"enabled\": true\n },\n \"tls\": {\n \"enabled\": true\n },\n \"protocol\": {\n \"enabled\": true\n }\n }\n },\n \"status\": \"Non-Compliant\",\n \"started\": \"2024-05-03 12:09:59\",\n \"finished\": \"2024-05-03 12:15:51\",\n \"tests\": {\n \"total\": 20,\n \"results\": [\n {\n \"name\": \"protocol.valid_bacnet\",\n \"description\": \"BACnet discovery could not resolve any devices\",\n \"expected_behavior\": \"BACnet traffic can be seen on the network and packets are valid and not malformed\",\n \"required_result\": \"Recommended\",\n \"result\": \"Skipped\"\n },\n {\n \"name\": \"protocol.bacnet.version\",\n \"description\": \"No BACnet devices discovered.\",\n \"expected_behavior\": \"The BACnet client implements an up to date version of BACnet\",\n \"required_result\": \"Recommended\",\n \"result\": \"Skipped\"\n },\n {\n \"name\": \"protocol.valid_modbus\",\n \"description\": \"Failed to establish Modbus connection to device\",\n \"expected_behavior\": \"Any Modbus functionality works as expected and valid Modbus traffic can be observed\",\n \"required_result\": \"Recommended\",\n \"result\": \"Non-Compliant\"\n },\n {\n \"name\": \"ntp.network.ntp_support\",\n \"description\": \"Device sent NTPv3 packets. NTPv3 is not allowed.\",\n \"expected_behavior\": \"The device sends an NTPv4 request to the configured NTP server.\",\n \"required_result\": \"Required\",\n \"result\": \"Non-Compliant\"\n },\n {\n \"name\": \"ntp.network.ntp_dhcp\",\n \"description\": \"Device sent NTP request to non-DHCP provided server\",\n \"expected_behavior\": \"Device can accept NTP server address, provided by the DHCP server (DHCP OFFER PACKET)\",\n \"required_result\": \"Roadmap\",\n \"result\": \"Non-Compliant\"\n },\n {\n \"name\": \"security.services.ftp\",\n \"description\": \"No FTP server found\",\n \"expected_behavior\": \"There is no FTP service running on any port\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"security.ssh.version\",\n \"description\": \"SSH server found running protocol 2.0\",\n \"expected_behavior\": \"SSH server is not running or server is SSHv2\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"security.services.telnet\",\n \"description\": \"No telnet server found\",\n \"expected_behavior\": \"There is no Telnet service running on any port\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"security.services.smtp\",\n \"description\": \"No SMTP server found\",\n \"expected_behavior\": \"There is no SMTP service running on any port\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"security.services.http\",\n \"description\": \"Found HTTP server running on port 80/tcp\",\n \"expected_behavior\": \"Device is unreachable on port 80 (or any other port) and only responds to HTTPS requests on port 443 (or any other port if HTTP is used at all)\",\n \"required_result\": \"Required\",\n \"result\": \"Non-Compliant\"\n },\n {\n \"name\": \"security.services.pop\",\n \"description\": \"No POP server found\",\n \"expected_behavior\": \"There is no POP service running on any port\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"security.services.imap\",\n \"description\": \"No IMAP server found\",\n \"expected_behavior\": \"There is no IMAP service running on any port\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"security.services.snmpv3\",\n \"description\": \"No SNMP server found\",\n \"expected_behavior\": \"Device is unreachable on port 161 (or any other port) and device is unreachable on port 162 (or any other port) unless SNMP is essential in which case it is SNMPv3 is used.\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"security.services.vnc\",\n \"description\": \"No VNC server found\",\n \"expected_behavior\": \"Device cannot be accessed / connected to via VNC on any port\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"security.services.tftp\",\n \"description\": \"No TFTP server found\",\n \"expected_behavior\": \"There is no TFTP service running on any port\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"ntp.network.ntp_server\",\n \"description\": \"No NTP server found\",\n \"expected_behavior\": \"The device does not respond to NTP requests when it's IP is set as the NTP server on another device\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"dns.network.hostname_resolution\",\n \"description\": \"DNS traffic detected from device\",\n \"expected_behavior\": \"The device sends DNS requests.\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"dns.network.from_dhcp\",\n \"description\": \"DNS traffic detected only to DHCP provided server\",\n \"expected_behavior\": \"The device sends DNS requests to the DNS server provided by the DHCP server\",\n \"required_result\": \"Roadmap\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"security.tls.v1_2_server\",\n \"description\": \"TLS 1.2 not validated: Certificate has expired\\nEC key length passed: 256 >= 224\\nDevice certificate has not been signed\\nTLS 1.3 not validated: Certificate has expired\\nEC key length passed: 256 >= 224\\nDevice certificate has not been signed\",\n \"expected_behavior\": \"TLS 1.2 certificate is issued to the web browser client when accessed\",\n \"required_result\": \"Required\",\n \"result\": \"Non-Compliant\"\n },\n {\n \"name\": \"security.tls.v1_2_client\",\n \"description\": \"No outbound TLS connections were found.\",\n \"expected_behavior\": \"The packet indicates a TLS connection with at least TLS 1.2 and support for ECDH and ECDSA ciphers\",\n \"required_result\": \"Required\",\n \"result\": \"Skipped\"\n }\n ]\n },\n \"report\": \"http://localhost:8000/report/123 123/2024-05-03T12:09:59\",\n \"export\": \"http://localhost:8000/export/123 123/2024-05-03T12:09:59\"\n \n }\n]", "latency": 0, "statusCode": 200, "label": "", @@ -885,10 +885,10 @@ }, { "uuid": "220e4ba9-6463-4dc3-b714-f77643706b7d", - "body": "{\n \"error\": \"An error occured whilst deleting the report\"\n}", + "body": "{\n \"error\": \"An error occurred whilst deleting the report\"\n}", "latency": 0, "statusCode": 500, - "label": "Error occured", + "label": "Error occurred", "headers": [], "bodyType": "INLINE", "filePath": "", @@ -971,10 +971,10 @@ }, { "uuid": "a7fbb2c8-81dc-4a40-80d6-482119314086", - "body": "{\n \"error\": \"An error occured whilst getting the report\"\n}", + "body": "{\n \"error\": \"An error occurred whilst getting the report\"\n}", "latency": 0, "statusCode": 500, - "label": "Error occured", + "label": "Error occurred", "headers": [], "bodyType": "INLINE", "filePath": "", @@ -1151,10 +1151,10 @@ }, { "uuid": "3bcb2d6d-3290-43bb-8392-7bfffda4feae", - "body": "{\n \"error\": \"An error occured whilst getting the test attempt\"\n}", + "body": "{\n \"error\": \"An error occurred whilst getting the test attempt\"\n}", "latency": 0, "statusCode": 500, - "label": "Error occured", + "label": "Error occurred", "headers": [], "bodyType": "INLINE", "filePath": "", @@ -1691,7 +1691,7 @@ "body": "{\n \"error\": \"Error retrieving the profile PDF\"\n}", "latency": 0, "statusCode": 500, - "label": "Error occured", + "label": "Error occurred", "headers": [], "bodyType": "INLINE", "filePath": "", diff --git a/docs/dev/postman.json b/docs/dev/postman.json index 1e8a9cafb..08369ac55 100644 --- a/docs/dev/postman.json +++ b/docs/dev/postman.json @@ -1068,7 +1068,7 @@ } ], "cookie": [], - "body": "[\n {\n \"testrun\": {\n \"version\": \"2.0\"\n },\n \"mac_addr\": \"00:1e:42:28:9e:4a\",\n \"device\": {\n \"mac_addr\": \"00:1e:42:28:9e:4a\",\n \"manufacturer\": \"Teltonika\",\n \"model\": \"TRB140\",\n \"firmware\": \"test\",\n \"test_modules\": {\n \"protocol\": {\n \"enabled\": true\n },\n \"services\": {\n \"enabled\": true\n },\n \"ntp\": {\n \"enabled\": true\n },\n \"tls\": {\n \"enabled\": true\n },\n \"connection\": {\n \"enabled\": true\n },\n \"dns\": {\n \"enabled\": true\n }\n }\n },\n \"status\": \"Non-Compliant\",\n \"started\": \"2000-01-01 00:00:00\",\n \"finished\": \"2000-01-01 00:30:00\",\n \"tests\": {\n \"total\": 40,\n \"results\": [\n {\n \"name\": \"protocol.valid_bacnet\",\n \"description\": \"BACnet device could not be discovered\",\n \"expected_behavior\": \"BACnet traffic can be seen on the network and packets are valid and not malformed\",\n \"required_result\": \"Recommended\",\n \"result\": \"Feature Not Detected\"\n },\n {\n \"name\": \"protocol.bacnet.version\",\n \"description\": \"Device did not respond to BACnet discovery\",\n \"expected_behavior\": \"The BACnet client implements an up to date version of BACnet\",\n \"required_result\": \"Recommended\",\n \"result\": \"Feature Not Detected\"\n },\n {\n \"name\": \"protocol.valid_modbus\",\n \"description\": \"Device did not respond to Modbus connection\",\n \"expected_behavior\": \"Any Modbus functionality works as expected and valid Modbus traffic can be observed\",\n \"required_result\": \"Recommended\",\n \"result\": \"Feature Not Detected\"\n },\n {\n \"name\": \"security.services.ftp\",\n \"description\": \"No FTP server found\",\n \"expected_behavior\": \"There is no FTP service running on any port\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"security.ssh.version\",\n \"description\": \"SSH server found running protocol 2.0\",\n \"expected_behavior\": \"SSH server is not running or server is SSHv2\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"security.services.telnet\",\n \"description\": \"No telnet server found\",\n \"expected_behavior\": \"There is no Telnet service running on any port\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"security.services.smtp\",\n \"description\": \"No SMTP server found\",\n \"expected_behavior\": \"There is no SMTP service running on any port\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"security.services.http\",\n \"description\": \"Found HTTP server running on port 80/tcp\",\n \"expected_behavior\": \"Device is unreachable on port 80 (or any other port) and only responds to HTTPS requests on port 443 (or any other port if HTTP is used at all)\",\n \"required_result\": \"Required\",\n \"result\": \"Non-Compliant\",\n \"recommendations\": [\n \"Disable all unsecure HTTP servers\",\n \"Setup TLS on the web server\"\n ]\n },\n {\n \"name\": \"security.services.pop\",\n \"description\": \"No POP server found\",\n \"expected_behavior\": \"There is no POP service running on any port\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"security.services.imap\",\n \"description\": \"No IMAP server found\",\n \"expected_behavior\": \"There is no IMAP service running on any port\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"security.services.snmpv3\",\n \"description\": \"No SNMP server found\",\n \"expected_behavior\": \"Device is unreachable on port 161 (or any other port) and device is unreachable on port 162 (or any other port) unless SNMP is essential in which case it is SNMPv3 is used.\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"security.services.vnc\",\n \"description\": \"No VNC server found\",\n \"expected_behavior\": \"Device cannot be accessed / connected to via VNC on any port\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"security.services.tftp\",\n \"description\": \"No TFTP server found\",\n \"expected_behavior\": \"There is no TFTP service running on any port\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"ntp.network.ntp_server\",\n \"description\": \"No NTP server found\",\n \"expected_behavior\": \"The device does not respond to NTP requests when it's IP is set as the NTP server on another device\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"connection.port_link\",\n \"description\": \"No port errors detected\",\n \"expected_behavior\": \"When the etherent cable is connected to the port, the device triggers the port to its enabled \\\"Link UP\\\" (LEDs illuminate on device and switch ports if present) state, and the switch shows no errors with the LEDs and when interrogated with a \\\"show interface\\\" command on most network switches.\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"connection.port_speed\",\n \"description\": \"Succesfully auto-negotiated speeds above 10 Mbps\",\n \"expected_behavior\": \"When the ethernet cable is connected to the port, the device autonegotiates a speed that can be checked with the \\\"show interface\\\" command on most network switches. The output of this command must also show that the \\\"configured speed\\\" is set to \\\"auto\\\".\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"connection.port_duplex\",\n \"description\": \"Succesfully auto-negotiated full duplex\",\n \"expected_behavior\": \"When the ethernet cable is connected to the port, the device autonegotiates a full-duplex connection.\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"connection.switch.arp_inspection\",\n \"description\": \"Device uses ARP\",\n \"expected_behavior\": \"Device continues to operate correctly when ARP inspection is enabled on the switch. No functionality is lost with ARP inspection enabled.\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"connection.switch.dhcp_snooping\",\n \"description\": \"Device does not act as a DHCP server\",\n \"expected_behavior\": \"Device continues to operate correctly when DHCP snooping is enabled on the switch.\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"connection.dhcp_address\",\n \"description\": \"Device responded to leased ip address\",\n \"expected_behavior\": \"The device is not setup with a static IP address. The device accepts an IP address from a DHCP server (RFC 2131) and responds succesfully to an ICMP echo (ping) request.\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"connection.mac_address\",\n \"description\": \"MAC address found: 00:1e:42:28:9e:4a\",\n \"expected_behavior\": \"N/A\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"connection.mac_oui\",\n \"description\": \"OUI Manufacturer found: Teltonika\",\n \"expected_behavior\": \"The MAC address prefix is registered in the IEEE Organizationally Unique Identifier database.\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"connection.private_address\",\n \"description\": \"All subnets are supported\",\n \"expected_behavior\": \"The device under test accepts IP addresses within all ranges specified in RFC 1918 and communicates using these addresses. The Internet Assigned Numbers Authority (IANA) has reserved the following three blocks of the IP address space for private internets. 10.0.0.0 - 10.255.255.255.255 (10/8 prefix). 172.16.0.0 - 172.31.255.255 (172.16/12 prefix). 192.168.0.0 - 192.168.255.255 (192.168/16 prefix)\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"connection.shared_address\",\n \"description\": \"All subnets are supported\",\n \"expected_behavior\": \"The device under test accepts IP addresses within the ranges specified in RFC 6598 and communicates using these addresses\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"connection.dhcp_disconnect\",\n \"description\": \"An error occured whilst running this test\",\n \"expected_behavior\": \"A client SHOULD use DHCP to reacquire or verify its IP address and network parameters whenever the local network parameters may have changed; e.g., at system boot time or after a disconnection from the local network, as the local network configuration may change without the client's or user's knowledge. If a client has knowledge ofa previous network address and is unable to contact a local DHCP server, the client may continue to use the previous network addres until the lease for that address expires. If the lease expires before the client can contact a DHCP server, the client must immediately discontinue use of the previous network address and may inform local users of the problem.\",\n \"required_result\": \"Required\",\n \"result\": \"Error\"\n },\n {\n \"name\": \"connection.single_ip\",\n \"description\": \"Device is using multiple IP addresses\",\n \"expected_behavior\": \"The device under test does not behave as a network switch and only requets one IP address. This test is to avoid that devices implement network switches that allow connecting strings of daisy chained devices to one single network port, as this would not make 802.1x port based authentication possible.\",\n \"required_result\": \"Required\",\n \"result\": \"Non-Compliant\",\n \"recommendations\": [\n \"Ensure that all ports on the device are isolated\",\n \"Ensure only one DHCP client is running\"\n ]\n },\n {\n \"name\": \"connection.target_ping\",\n \"description\": \"Device responds to ping\",\n \"expected_behavior\": \"The device under test responds to an ICMP echo (ping) request.\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"connection.ipaddr.ip_change\",\n \"description\": \"Device has accepted an IP address change\",\n \"expected_behavior\": \"If the lease expires before the client receiveds a DHCPACK, the client moves to INIT state, MUST immediately stop any other network processing and requires network initialization parameters as if the client were uninitialized. If the client then receives a DHCPACK allocating the client its previous network addres, the client SHOULD continue network processing. If the client is given a new network address, it MUST NOT continue using the previous network address and SHOULD notify the local users of the problem.\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"connection.ipaddr.dhcp_failover\",\n \"description\": \"Secondary DHCP server lease confirmed active in device\",\n \"expected_behavior\": \"\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"connection.ipv6_slaac\",\n \"description\": \"Device does not support IPv6 SLAAC\",\n \"expected_behavior\": \"The device under test complies with RFC4862 and forms a valid IPv6 SLAAC address\",\n \"required_result\": \"Required\",\n \"result\": \"Non-Compliant\",\n \"recommendations\": [\n \"Install a network manager that supports IPv6\",\n \"Disable DHCPv6\"\n ]\n },\n {\n \"name\": \"connection.ipv6_ping\",\n \"description\": \"No IPv6 SLAAC address found. Cannot ping\",\n \"expected_behavior\": \"The device responds to the ping as per RFC4443\",\n \"required_result\": \"Required\",\n \"result\": \"Non-Compliant\",\n \"recommendations\": [\n \"Enable ping response to IPv6 ICMP requests in network manager settings\",\n \"Create a firewall exception to allow ICMPv6 via LAN\"\n ]\n },\n {\n \"name\": \"security.tls.v1_2_server\",\n \"description\": \"TLS 1.2 certificate is invalid\",\n \"expected_behavior\": \"TLS 1.2 certificate is issued to the web browser client when accessed\",\n \"required_result\": \"Required if Applicable\",\n \"result\": \"Non-Compliant\",\n \"recommendations\": [\n \"Enable TLS 1.2 support in the web server configuration\",\n \"Disable TLS 1.0 and 1.1\",\n \"Sign the certificate used by the web server\"\n ]\n },\n {\n \"name\": \"security.tls.v1_2_client\",\n \"description\": \"No outbound TLS connections were found\",\n \"expected_behavior\": \"The packet indicates a TLS connection with at least TLS 1.2 and support for ECDH and ECDSA ciphers\",\n \"required_result\": \"Required if Applicable\",\n \"result\": \"Feature Not Detected\"\n },\n {\n \"name\": \"security.tls.v1_3_server\",\n \"description\": \"TLS 1.3 certificate is invalid\",\n \"expected_behavior\": \"TLS 1.3 certificate is issued to the web browser client when accessed\",\n \"required_result\": \"Informational\",\n \"result\": \"Informational\"\n },\n {\n \"name\": \"security.tls.v1_3_client\",\n \"description\": \"No outbound TLS connections were found\",\n \"expected_behavior\": \"The packet indicates a TLS connection with at least TLS 1.3\",\n \"required_result\": \"Informational\",\n \"result\": \"Informational\"\n },\n {\n \"name\": \"ntp.network.ntp_support\",\n \"description\": \"Device has not sent any NTP requests\",\n \"expected_behavior\": \"The device sends an NTPv4 request to the configured NTP server.\",\n \"required_result\": \"Required\",\n \"result\": \"Non-Compliant\",\n \"recommendations\": [\n \"Set the NTP version to v4 in the NTP client\",\n \"Install an NTP client that supports NTPv4\"\n ]\n },\n {\n \"name\": \"ntp.network.ntp_dhcp\",\n \"description\": \"Device has not sent any NTP requests\",\n \"expected_behavior\": \"Device can accept NTP server address, provided by the DHCP server (DHCP OFFER PACKET)\",\n \"required_result\": \"Roadmap\",\n \"result\": \"Feature Not Detected\"\n },\n {\n \"name\": \"dns.network.hostname_resolution\",\n \"description\": \"DNS traffic detected from device\",\n \"expected_behavior\": \"The device sends DNS requests.\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"dns.network.from_dhcp\",\n \"description\": \"DNS traffic detected only to DHCP provided server\",\n \"expected_behavior\": \"The device sends DNS requests to the DNS server provided by the DHCP server\",\n \"required_result\": \"Informational\",\n \"result\": \"Informational\"\n },\n {\n \"name\": \"dns.mdns\",\n \"description\": \"No MDNS traffic detected from the device\",\n \"expected_behavior\": \"Device may send MDNS requests\",\n \"required_result\": \"Informational\",\n \"result\": \"Informational\"\n }\n ]\n },\n \"report\": \"http://localhost:8000/report/Teltonika TRB140/2024-09-10T13:19:24\"\n }\n]" + "body": "[\n {\n \"testrun\": {\n \"version\": \"2.0\"\n },\n \"mac_addr\": \"00:1e:42:28:9e:4a\",\n \"device\": {\n \"mac_addr\": \"00:1e:42:28:9e:4a\",\n \"manufacturer\": \"Teltonika\",\n \"model\": \"TRB140\",\n \"firmware\": \"test\",\n \"test_modules\": {\n \"protocol\": {\n \"enabled\": true\n },\n \"services\": {\n \"enabled\": true\n },\n \"ntp\": {\n \"enabled\": true\n },\n \"tls\": {\n \"enabled\": true\n },\n \"connection\": {\n \"enabled\": true\n },\n \"dns\": {\n \"enabled\": true\n }\n }\n },\n \"status\": \"Non-Compliant\",\n \"started\": \"2000-01-01 00:00:00\",\n \"finished\": \"2000-01-01 00:30:00\",\n \"tests\": {\n \"total\": 40,\n \"results\": [\n {\n \"name\": \"protocol.valid_bacnet\",\n \"description\": \"BACnet device could not be discovered\",\n \"expected_behavior\": \"BACnet traffic can be seen on the network and packets are valid and not malformed\",\n \"required_result\": \"Recommended\",\n \"result\": \"Feature Not Detected\"\n },\n {\n \"name\": \"protocol.bacnet.version\",\n \"description\": \"Device did not respond to BACnet discovery\",\n \"expected_behavior\": \"The BACnet client implements an up to date version of BACnet\",\n \"required_result\": \"Recommended\",\n \"result\": \"Feature Not Detected\"\n },\n {\n \"name\": \"protocol.valid_modbus\",\n \"description\": \"Device did not respond to Modbus connection\",\n \"expected_behavior\": \"Any Modbus functionality works as expected and valid Modbus traffic can be observed\",\n \"required_result\": \"Recommended\",\n \"result\": \"Feature Not Detected\"\n },\n {\n \"name\": \"security.services.ftp\",\n \"description\": \"No FTP server found\",\n \"expected_behavior\": \"There is no FTP service running on any port\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"security.ssh.version\",\n \"description\": \"SSH server found running protocol 2.0\",\n \"expected_behavior\": \"SSH server is not running or server is SSHv2\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"security.services.telnet\",\n \"description\": \"No telnet server found\",\n \"expected_behavior\": \"There is no Telnet service running on any port\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"security.services.smtp\",\n \"description\": \"No SMTP server found\",\n \"expected_behavior\": \"There is no SMTP service running on any port\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"security.services.http\",\n \"description\": \"Found HTTP server running on port 80/tcp\",\n \"expected_behavior\": \"Device is unreachable on port 80 (or any other port) and only responds to HTTPS requests on port 443 (or any other port if HTTP is used at all)\",\n \"required_result\": \"Required\",\n \"result\": \"Non-Compliant\",\n \"recommendations\": [\n \"Disable all unsecure HTTP servers\",\n \"Setup TLS on the web server\"\n ]\n },\n {\n \"name\": \"security.services.pop\",\n \"description\": \"No POP server found\",\n \"expected_behavior\": \"There is no POP service running on any port\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"security.services.imap\",\n \"description\": \"No IMAP server found\",\n \"expected_behavior\": \"There is no IMAP service running on any port\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"security.services.snmpv3\",\n \"description\": \"No SNMP server found\",\n \"expected_behavior\": \"Device is unreachable on port 161 (or any other port) and device is unreachable on port 162 (or any other port) unless SNMP is essential in which case it is SNMPv3 is used.\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"security.services.vnc\",\n \"description\": \"No VNC server found\",\n \"expected_behavior\": \"Device cannot be accessed / connected to via VNC on any port\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"security.services.tftp\",\n \"description\": \"No TFTP server found\",\n \"expected_behavior\": \"There is no TFTP service running on any port\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"ntp.network.ntp_server\",\n \"description\": \"No NTP server found\",\n \"expected_behavior\": \"The device does not respond to NTP requests when it's IP is set as the NTP server on another device\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"connection.port_link\",\n \"description\": \"No port errors detected\",\n \"expected_behavior\": \"When the etherent cable is connected to the port, the device triggers the port to its enabled \\\"Link UP\\\" (LEDs illuminate on device and switch ports if present) state, and the switch shows no errors with the LEDs and when interrogated with a \\\"show interface\\\" command on most network switches.\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"connection.port_speed\",\n \"description\": \"Succesfully auto-negotiated speeds above 10 Mbps\",\n \"expected_behavior\": \"When the ethernet cable is connected to the port, the device autonegotiates a speed that can be checked with the \\\"show interface\\\" command on most network switches. The output of this command must also show that the \\\"configured speed\\\" is set to \\\"auto\\\".\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"connection.port_duplex\",\n \"description\": \"Succesfully auto-negotiated full duplex\",\n \"expected_behavior\": \"When the ethernet cable is connected to the port, the device autonegotiates a full-duplex connection.\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"connection.switch.arp_inspection\",\n \"description\": \"Device uses ARP\",\n \"expected_behavior\": \"Device continues to operate correctly when ARP inspection is enabled on the switch. No functionality is lost with ARP inspection enabled.\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"connection.switch.dhcp_snooping\",\n \"description\": \"Device does not act as a DHCP server\",\n \"expected_behavior\": \"Device continues to operate correctly when DHCP snooping is enabled on the switch.\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"connection.dhcp_address\",\n \"description\": \"Device responded to leased ip address\",\n \"expected_behavior\": \"The device is not setup with a static IP address. The device accepts an IP address from a DHCP server (RFC 2131) and responds succesfully to an ICMP echo (ping) request.\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"connection.mac_address\",\n \"description\": \"MAC address found: 00:1e:42:28:9e:4a\",\n \"expected_behavior\": \"N/A\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"connection.mac_oui\",\n \"description\": \"OUI Manufacturer found: Teltonika\",\n \"expected_behavior\": \"The MAC address prefix is registered in the IEEE Organizationally Unique Identifier database.\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"connection.private_address\",\n \"description\": \"All subnets are supported\",\n \"expected_behavior\": \"The device under test accepts IP addresses within all ranges specified in RFC 1918 and communicates using these addresses. The Internet Assigned Numbers Authority (IANA) has reserved the following three blocks of the IP address space for private internets. 10.0.0.0 - 10.255.255.255.255 (10/8 prefix). 172.16.0.0 - 172.31.255.255 (172.16/12 prefix). 192.168.0.0 - 192.168.255.255 (192.168/16 prefix)\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"connection.shared_address\",\n \"description\": \"All subnets are supported\",\n \"expected_behavior\": \"The device under test accepts IP addresses within the ranges specified in RFC 6598 and communicates using these addresses\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"connection.dhcp_disconnect\",\n \"description\": \"An error occurred whilst running this test\",\n \"expected_behavior\": \"A client SHOULD use DHCP to reacquire or verify its IP address and network parameters whenever the local network parameters may have changed; e.g., at system boot time or after a disconnection from the local network, as the local network configuration may change without the client's or user's knowledge. If a client has knowledge ofa previous network address and is unable to contact a local DHCP server, the client may continue to use the previous network addres until the lease for that address expires. If the lease expires before the client can contact a DHCP server, the client must immediately discontinue use of the previous network address and may inform local users of the problem.\",\n \"required_result\": \"Required\",\n \"result\": \"Error\"\n },\n {\n \"name\": \"connection.single_ip\",\n \"description\": \"Device is using multiple IP addresses\",\n \"expected_behavior\": \"The device under test does not behave as a network switch and only requets one IP address. This test is to avoid that devices implement network switches that allow connecting strings of daisy chained devices to one single network port, as this would not make 802.1x port based authentication possible.\",\n \"required_result\": \"Required\",\n \"result\": \"Non-Compliant\",\n \"recommendations\": [\n \"Ensure that all ports on the device are isolated\",\n \"Ensure only one DHCP client is running\"\n ]\n },\n {\n \"name\": \"connection.target_ping\",\n \"description\": \"Device responds to ping\",\n \"expected_behavior\": \"The device under test responds to an ICMP echo (ping) request.\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"connection.ipaddr.ip_change\",\n \"description\": \"Device has accepted an IP address change\",\n \"expected_behavior\": \"If the lease expires before the client receiveds a DHCPACK, the client moves to INIT state, MUST immediately stop any other network processing and requires network initialization parameters as if the client were uninitialized. If the client then receives a DHCPACK allocating the client its previous network addres, the client SHOULD continue network processing. If the client is given a new network address, it MUST NOT continue using the previous network address and SHOULD notify the local users of the problem.\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"connection.ipaddr.dhcp_failover\",\n \"description\": \"Secondary DHCP server lease confirmed active in device\",\n \"expected_behavior\": \"\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"connection.ipv6_slaac\",\n \"description\": \"Device does not support IPv6 SLAAC\",\n \"expected_behavior\": \"The device under test complies with RFC4862 and forms a valid IPv6 SLAAC address\",\n \"required_result\": \"Required\",\n \"result\": \"Non-Compliant\",\n \"recommendations\": [\n \"Install a network manager that supports IPv6\",\n \"Disable DHCPv6\"\n ]\n },\n {\n \"name\": \"connection.ipv6_ping\",\n \"description\": \"No IPv6 SLAAC address found. Cannot ping\",\n \"expected_behavior\": \"The device responds to the ping as per RFC4443\",\n \"required_result\": \"Required\",\n \"result\": \"Non-Compliant\",\n \"recommendations\": [\n \"Enable ping response to IPv6 ICMP requests in network manager settings\",\n \"Create a firewall exception to allow ICMPv6 via LAN\"\n ]\n },\n {\n \"name\": \"security.tls.v1_2_server\",\n \"description\": \"TLS 1.2 certificate is invalid\",\n \"expected_behavior\": \"TLS 1.2 certificate is issued to the web browser client when accessed\",\n \"required_result\": \"Required if Applicable\",\n \"result\": \"Non-Compliant\",\n \"recommendations\": [\n \"Enable TLS 1.2 support in the web server configuration\",\n \"Disable TLS 1.0 and 1.1\",\n \"Sign the certificate used by the web server\"\n ]\n },\n {\n \"name\": \"security.tls.v1_2_client\",\n \"description\": \"No outbound TLS connections were found\",\n \"expected_behavior\": \"The packet indicates a TLS connection with at least TLS 1.2 and support for ECDH and ECDSA ciphers\",\n \"required_result\": \"Required if Applicable\",\n \"result\": \"Feature Not Detected\"\n },\n {\n \"name\": \"security.tls.v1_3_server\",\n \"description\": \"TLS 1.3 certificate is invalid\",\n \"expected_behavior\": \"TLS 1.3 certificate is issued to the web browser client when accessed\",\n \"required_result\": \"Informational\",\n \"result\": \"Informational\"\n },\n {\n \"name\": \"security.tls.v1_3_client\",\n \"description\": \"No outbound TLS connections were found\",\n \"expected_behavior\": \"The packet indicates a TLS connection with at least TLS 1.3\",\n \"required_result\": \"Informational\",\n \"result\": \"Informational\"\n },\n {\n \"name\": \"ntp.network.ntp_support\",\n \"description\": \"Device has not sent any NTP requests\",\n \"expected_behavior\": \"The device sends an NTPv4 request to the configured NTP server.\",\n \"required_result\": \"Required\",\n \"result\": \"Non-Compliant\",\n \"recommendations\": [\n \"Set the NTP version to v4 in the NTP client\",\n \"Install an NTP client that supports NTPv4\"\n ]\n },\n {\n \"name\": \"ntp.network.ntp_dhcp\",\n \"description\": \"Device has not sent any NTP requests\",\n \"expected_behavior\": \"Device can accept NTP server address, provided by the DHCP server (DHCP OFFER PACKET)\",\n \"required_result\": \"Roadmap\",\n \"result\": \"Feature Not Detected\"\n },\n {\n \"name\": \"dns.network.hostname_resolution\",\n \"description\": \"DNS traffic detected from device\",\n \"expected_behavior\": \"The device sends DNS requests.\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"dns.network.from_dhcp\",\n \"description\": \"DNS traffic detected only to DHCP provided server\",\n \"expected_behavior\": \"The device sends DNS requests to the DNS server provided by the DHCP server\",\n \"required_result\": \"Informational\",\n \"result\": \"Informational\"\n },\n {\n \"name\": \"dns.mdns\",\n \"description\": \"No MDNS traffic detected from the device\",\n \"expected_behavior\": \"Device may send MDNS requests\",\n \"required_result\": \"Informational\",\n \"result\": \"Informational\"\n }\n ]\n },\n \"report\": \"http://localhost:8000/report/Teltonika TRB140/2024-09-10T13:19:24\",\n \"export\": \"http://localhost:8000/export/Teltonika TRB140/2024-09-10T13:19:24\"\n }\n]" }, { "name": "No Test Reports (200)", diff --git a/docs/get_started.md b/docs/get_started.md index f0c39c284..41a1d13f8 100644 --- a/docs/get_started.md +++ b/docs/get_started.md @@ -4,23 +4,29 @@ This page covers the following topics: -- [Prerequisites](#prerequisites) -- [Installation](#installation) -- [Testing](#testing) -- [Additional Configuration Options](#additional-configuration-options) -- [Troubleshooting](#troubleshooting) -- [Review the report](#review-the-report) -- [Uninstall](#uninstall) +- [Get started](#get-started) +- [Prerequisites](#prerequisites) + - [Hardware](#hardware) + - [Software](#software) + - [Device](#device) +- [Installation](#installation) +- [Testing](#testing) + - [Start Testrun](#start-testrun) + - [Test your device](#test-your-device) +- [Additional Configuration Options](/docs/additional_config.md) +- [Troubleshooting](#troubleshooting) +- [Review the report](#review-the-report) +- [Uninstall](#uninstall) # Prerequisites -We recommend that you run Testrun on a stand-alone machine that has a fresh install of Ubuntu 20.04, 22.04, or 24.04 LTS (laptop or desktop). +We recommend that you run Testrun on a stand-alone machine that has a fresh install of Ubuntu 22.04 or 24.04 LTS (laptop or desktop). ## Hardware Before you start, ensure you have the following hardware: -- PC running Ubuntu LTS (laptop or desktop) +- PC running Ubuntu 22.04 or 24.04 LTS (laptop or desktop) - 2x ethernet ports (USB ethernet adapters work too) - Internet connection @@ -68,7 +74,7 @@ Follow these steps to start Testrun: Notes: - Disable both adapters in the host system (IPv4, IPv6, and general) by opening **Settings**, then **Network**. - - Keep the DUT powered off until prompted. + - Keep the DUT powered off until prompted. Otherwise, Testrun will not be able to fully capture the device behavior during startup - resulting in inaccurate test results. 1. Start Testrun with the command `sudo testrun` - To run Testrun in network-only mode (without running any tests), use the `--net-only` option. @@ -81,89 +87,42 @@ Follow these steps to start Testrun: Follow these steps to test your IoT device: 1. Open Testrun by navigating to [http://localhost:8080](http://localhost:8080/) in your browser. -2. Select the **Settings** menu in the top-right corner, then select your network interfaces. You can change the settings at any time. - ![Settings menu button](/docs/ui/getstarted--7cfvdpdnc5o.png) -3. Select the **Certificates** menu in the top-right corner, then upload your local CA certificates for TLS server testing. - ![Certificates menu button](/docs/ui/getstarted--j21skepmx1.png) -5. Select the **Device Repository** icon on the left panel to add a new device for testing. - ![Device repository button](/docs/ui/getstarted--q5uw26tfod.png) -6. Select the **Add Device** button. -7. Enter the MAC address, manufacturer name, and model number. +2. Select the **Settings** menu in the top-right corner. It will open the **General** tab by default. Then select your network interfaces. You can change the settings at any time. + ![Settings menu button](/docs/ui/getstarted-settings-menu.png) +3. Select the **Certificates** tab (Settings menu), then upload your local CA certificates for TLS server testing. + ![Certificates menu button](/docs/ui/getstarted-certificates-menu.png) +4. Select the **Device Repository** icon on the left panel to add a new device for testing. + ![Device repository button](/docs/ui/getstarted-device-repository.png) + + Or + + Click the **Actions** button to add a new device for testing. + ![Actions button](/docs/ui/getstarted-actions-device.png) +5. Select the **Add Device** button on the Device repository. + ![Add device](/docs/ui/getstarted-add-device.png) +6. Enter the MAC address, manufacturer name, and model number. +7. Select Qualification or Pilot program. 8. Select the test modules you want to enable for this device. Note: For qualification purposes, you must select all. -9. Select **Save**. -10. Select the Testrun progress icon, then select the **Testing** button.![Testing button](/docs/ui/getstarted--w09wecsry3.png) - -11. Select the device you want to test. -12. Enter the version number of the firmware running on the device. -13. Select **Start Testrun**. -- If you need to stop Testrun during testing, select **Stop** next to the test name. -12. Once the Waiting for Device notification appears, power on the device under test. A report appears under the Reports icon once the test sequence is complete. - ![Reports button](/docs/ui/getstarted--m4si1otdu5d.png) - -# Additional Configuration Options - -Some configuration options are available but not exposed through the user interface and requires direct access. -Modification of various configuration files is necessary to access these options. - -## Override test module timeout at the system level - -Testrun attempts to set reasonable timeouts for test modules to prevent overly long test times but sometimes -a device or series of device may require longer than these default values. These can be overridden at -the test module configuration level but is not preferred since these changes will be undone during every -version upgrade. To modify these values: - -1. Navigate to the testrun installation directory. By default, this will be at: - `/usr/local/testrun` - -2. Open the system.json file and add the following section: - `"test_modules":{}` - -3. Add the module name(s) and timeout property into this test_modules section you wish to -set the timeout property for: - ``` - "test_modules":{ - "connection":{ - "timeout": 500 - } - } - ``` - -Before timeout options: -``` -{ - "network": { - "device_intf": "ens0", - "internet_intf": "ens1" - }, - "log_level": "DEBUG", - "startup_timeout": 60, - "monitor_period": 60, - "max_device_reports": 5, - "org_name": "", - "single_intf": false - } -``` - -After timeout options: -``` -{ - "network": { - "device_intf": "ens0", - "internet_intf": "ens1" - }, - "log_level": "DEBUG", - "startup_timeout": 60, - "monitor_period": 60, - "max_device_reports": 5, - "org_name": "", - "single_intf": false, - "test_modules":{ - "connection":{ - "timeout": 500 - } - } -``` +9. Answer a few questions about your device. +10. Select **Save**. +11. Select the Testrun progress icon, then select the **Testing** button.![Testing button](/docs/ui/getstarted-testing.png) + + Or + + Click the **Actions** button, then select the **Start Testing** button. + ![Actions button](/docs/ui/getstarted-actions-testing.png) + +12. Select the device you want to test. +13. Enter the version number of the firmware running on the device. +14. Select **Start Testrun**. + - If you need to stop Testrun during testing, select **Stop** next to the test name. +15. Once the Waiting for Device notification appears, power on the device under test. + ![Waiting for device](/docs/ui/getstarted-waiting-for-device.png) +16. While testing is in progress, you could complete a Risk Assessment. To do so, go to the **Risk assessment** tab. + ![Risk assessment](/docs/ui/getstarted-risk-assessment.png) +17. A report appears under the Reports icon once the test sequence is complete. + ![Reports button](/docs/ui/getstarted-reports.png) # Troubleshooting diff --git a/docs/roadmap.pdf b/docs/roadmap.pdf new file mode 100644 index 000000000..7c377f590 Binary files /dev/null and b/docs/roadmap.pdf differ diff --git a/docs/rooadmap.pdf b/docs/rooadmap.pdf deleted file mode 100644 index 6107370e6..000000000 Binary files a/docs/rooadmap.pdf and /dev/null differ diff --git a/docs/ui/getstarted--7cfvdpdnc5o.png b/docs/ui/getstarted--7cfvdpdnc5o.png deleted file mode 100644 index 288e5f876..000000000 Binary files a/docs/ui/getstarted--7cfvdpdnc5o.png and /dev/null differ diff --git a/docs/ui/getstarted--j21skepmx1.png b/docs/ui/getstarted--j21skepmx1.png deleted file mode 100644 index 7a4de661a..000000000 Binary files a/docs/ui/getstarted--j21skepmx1.png and /dev/null differ diff --git a/docs/ui/getstarted--m4si1otdu5d.png b/docs/ui/getstarted--m4si1otdu5d.png deleted file mode 100644 index f6b7eddc9..000000000 Binary files a/docs/ui/getstarted--m4si1otdu5d.png and /dev/null differ diff --git a/docs/ui/getstarted--q5uw26tfod.png b/docs/ui/getstarted--q5uw26tfod.png deleted file mode 100644 index 4b8b2e847..000000000 Binary files a/docs/ui/getstarted--q5uw26tfod.png and /dev/null differ diff --git a/docs/ui/getstarted--w09wecsry3.png b/docs/ui/getstarted--w09wecsry3.png deleted file mode 100644 index e379040b5..000000000 Binary files a/docs/ui/getstarted--w09wecsry3.png and /dev/null differ diff --git a/docs/ui/getstarted-actions-device.png b/docs/ui/getstarted-actions-device.png new file mode 100644 index 000000000..4363a194f Binary files /dev/null and b/docs/ui/getstarted-actions-device.png differ diff --git a/docs/ui/getstarted-actions-testing.png b/docs/ui/getstarted-actions-testing.png new file mode 100644 index 000000000..dbffebb14 Binary files /dev/null and b/docs/ui/getstarted-actions-testing.png differ diff --git a/docs/ui/getstarted-add-device.png b/docs/ui/getstarted-add-device.png new file mode 100644 index 000000000..5b93b46b4 Binary files /dev/null and b/docs/ui/getstarted-add-device.png differ diff --git a/docs/ui/getstarted-certificates-menu.png b/docs/ui/getstarted-certificates-menu.png new file mode 100644 index 000000000..60eceae7a Binary files /dev/null and b/docs/ui/getstarted-certificates-menu.png differ diff --git a/docs/ui/getstarted-device-repository.png b/docs/ui/getstarted-device-repository.png new file mode 100644 index 000000000..236842ce4 Binary files /dev/null and b/docs/ui/getstarted-device-repository.png differ diff --git a/docs/ui/getstarted-reports.png b/docs/ui/getstarted-reports.png new file mode 100644 index 000000000..17577b735 Binary files /dev/null and b/docs/ui/getstarted-reports.png differ diff --git a/docs/ui/getstarted-risk-assessment.png b/docs/ui/getstarted-risk-assessment.png new file mode 100644 index 000000000..a14b7da10 Binary files /dev/null and b/docs/ui/getstarted-risk-assessment.png differ diff --git a/docs/ui/getstarted-settings-menu.png b/docs/ui/getstarted-settings-menu.png new file mode 100644 index 000000000..6c53ed4ec Binary files /dev/null and b/docs/ui/getstarted-settings-menu.png differ diff --git a/docs/ui/getstarted-testing.png b/docs/ui/getstarted-testing.png new file mode 100644 index 000000000..634269e5c Binary files /dev/null and b/docs/ui/getstarted-testing.png differ diff --git a/docs/ui/getstarted-waiting-for-device.png b/docs/ui/getstarted-waiting-for-device.png new file mode 100644 index 000000000..36a307316 Binary files /dev/null and b/docs/ui/getstarted-waiting-for-device.png differ diff --git a/docs/virtual_machine.md b/docs/virtual_machine.md index 579a40c93..827ee36b3 100644 --- a/docs/virtual_machine.md +++ b/docs/virtual_machine.md @@ -18,7 +18,7 @@ Before you start with Testrun, ensure you have the following hardware: Ensure you have VirtualBox installed on the host PC. Then, install the following software on your virtual machine: -- Ubuntu LTS (22.04 or 20.04) +- Ubuntu LTS (22.04 or 24.04) - Docker - Refer to the [installation guide](https://docs.docker.com/engine/install/ubuntu/#install-using-the-repository) as needed. diff --git a/framework/python/src/api/api.py b/framework/python/src/api/api.py index 8febe03bb..a11b35160 100644 --- a/framework/python/src/api/api.py +++ b/framework/python/src/api/api.py @@ -24,7 +24,6 @@ import signal import threading import uvicorn -from urllib.parse import urlparse from core import tasks from common import logger @@ -437,15 +436,13 @@ async def get_reports(self, request: Request): LOGGER.debug("Received reports list request") # Resolve the server IP from the request so we # can fix the report URL - client_origin = request.headers.get("Origin") - parsed_url = urlparse(client_origin) - server_ip = parsed_url.hostname # This will give you the IP address - reports = self._session.get_all_reports() for report in reports: # report URL is currently hard coded as localhost so we can # replace that to fix the IP dynamically from the requester - report["report"] = report["report"].replace("localhost", server_ip) + report["report"] = report["report"].replace( + "localhost", request.client.host) + report["export"] = report["report"].replace("report", "export") return reports async def delete_report(self, request: Request, response: Response): @@ -498,7 +495,7 @@ async def delete_report(self, request: Request, response: Response): return self._generate_msg(True, "Deleted report") response.status_code = 500 - return self._generate_msg(False, "Error occured whilst deleting report") + return self._generate_msg(False, "Error occurred whilst deleting report") async def delete_device(self, request: Request, response: Response): @@ -549,7 +546,7 @@ async def delete_device(self, request: Request, response: Response): LOGGER.error(e) response.status_code = 500 return self._generate_msg( - False, "An error occured whilst deleting the device") + False, "An error occurred whilst deleting the device") async def save_device(self, request: Request, response: Response): LOGGER.debug("Received device post request") diff --git a/framework/python/src/common/risk_profile.py b/framework/python/src/common/risk_profile.py index fe613f69a..f2e684710 100644 --- a/framework/python/src/common/risk_profile.py +++ b/framework/python/src/common/risk_profile.py @@ -69,8 +69,7 @@ def __init__(self, profile_json=None, profile_format=None): 'r', encoding='utf-8') as device_format_file: device_format_json = json.load(device_format_file) - for step in device_format_json: - self._device_format.extend(step['questions']) + self._device_format = device_format_json except (IOError, ValueError) as e: LOGGER.error( 'An error occurred whilst loading the device profile format') diff --git a/framework/python/src/common/testreport.py b/framework/python/src/common/testreport.py index 04d58a45c..461f10be3 100644 --- a/framework/python/src/common/testreport.py +++ b/framework/python/src/common/testreport.py @@ -71,6 +71,7 @@ def __init__(self, self._module_reports = [] self._module_templates = [] self._report_url = '' + self._export_url = '' self._cur_page = 0 def update_device_profile(self, additional_info): @@ -110,6 +111,9 @@ def set_report_url(self, url): def get_report_url(self): return self._report_url + def get_export_url(self): + return self._export_url + def set_mac_addr(self, mac_addr): self._mac_addr = mac_addr @@ -186,6 +190,9 @@ def from_json(self, json_file): if 'report' in json_file: self._report_url = json_file['report'] + if 'export' in json_file: + self._export_url = json_file['export'] + self._total_tests = json_file['tests']['total'] # Loop through test results diff --git a/framework/python/src/core/docker/docker_module.py b/framework/python/src/core/docker/docker_module.py index 21dabdc16..d91331aee 100644 --- a/framework/python/src/core/docker/docker_module.py +++ b/framework/python/src/core/docker/docker_module.py @@ -21,7 +21,7 @@ IMAGE_PREFIX = 'testrun/' CONTAINER_PREFIX = 'tr-ct' DEFAULT_NETWORK = 'bridge' - +DEFAULT_LOG_LEVEL = 'INFO' class Module: """Represents the base module.""" @@ -33,6 +33,7 @@ def __init__(self, extra_hosts=None): self._session = session self.extra_hosts = extra_hosts + self.log_level=DEFAULT_LOG_LEVEL # Read the config file into a json object with open(module_config_file, encoding='UTF-8') as config_file: @@ -66,7 +67,15 @@ def __init__(self, 'enable_container', True) self.container: Container = None + # Configure the module logger self._add_logger(log_name=self.name, module_name=self.name) + try: + self.log_level = self._get_module_log_level(module_json) + self.logger.setLevel(self.log_level) + except Exception as error: + self.logger.error('Could not set defined log level') + self.logger.error(error) + self.setup_module(module_json) def _add_logger(self, log_name, module_name, log_dir=None): @@ -114,6 +123,22 @@ def get_mounts(self): def get_environment(self, device=None): # pylint: disable=W0613 return {} + def _get_module_log_level(self, module_json): + log_level = DEFAULT_LOG_LEVEL + try: + test_modules = self.get_session().get_config().get('test_modules', {}) + test_config = test_modules.get(self.name, {}) + sys_log_level = test_config.get('log_level', None) + + if sys_log_level is not None: + log_level = sys_log_level + elif 'log_level' in module_json['config']: + log_level = module_json['config']['log_level'] + except Exception: # pylint: disable=W0718 + # Ignore errors, just use default + log_level = DEFAULT_LOG_LEVEL + return log_level # pylint: disable=W0150 + def setup_module(self, module_json): pass diff --git a/framework/python/src/core/docker/network_docker_module.py b/framework/python/src/core/docker/network_docker_module.py index 6c892092a..83824dd68 100644 --- a/framework/python/src/core/docker/network_docker_module.py +++ b/framework/python/src/core/docker/network_docker_module.py @@ -68,7 +68,8 @@ def _setup_runtime(self, device): def get_environment(self, device=None): # pylint: disable=W0613 environment = { 'TZ': self.get_session().get_timezone(), - 'HOST_USER': self.get_session().get_host_user() + 'HOST_USER': self.get_session().get_host_user(), + 'LOG_LEVEL': self.log_level } return environment diff --git a/framework/python/src/core/docker/test_docker_module.py b/framework/python/src/core/docker/test_docker_module.py index 3198ef1ba..1235cf25d 100644 --- a/framework/python/src/core/docker/test_docker_module.py +++ b/framework/python/src/core/docker/test_docker_module.py @@ -107,7 +107,8 @@ def get_environment(self, device): 'IPV4_SUBNET': self.get_session().get_ipv4_subnet(), 'IPV6_SUBNET': self.get_session().get_ipv6_subnet(), 'DEV_IFACE': self.get_session().get_device_interface(), - 'DEV_IFACE_MAC': self.get_session().get_device_interface_mac_addr() + 'DEV_IFACE_MAC': self.get_session().get_device_interface_mac_addr(), + 'LOG_LEVEL': self.log_level } return environment @@ -155,3 +156,4 @@ def _get_module_timeout(self, module_json): # Ignore errors, just use default timeout = DEFAULT_TIMEOUT return timeout # pylint: disable=W0150 + \ No newline at end of file diff --git a/framework/python/src/core/session.py b/framework/python/src/core/session.py index baa7db056..16c8f056b 100644 --- a/framework/python/src/core/session.py +++ b/framework/python/src/core/session.py @@ -39,6 +39,7 @@ MAX_DEVICE_REPORTS_KEY = 'max_device_reports' ORG_NAME_KEY = 'org_name' TEST_CONFIG_KEY = 'test_modules' +ALLOW_DISCONNECT_KEY='allow_disconnect' CERTS_PATH = 'local/root_certs' CONFIG_FILE_PATH = 'local/system.json' STATUS_TOPIC = 'status' @@ -50,6 +51,12 @@ LOGGER = logger.get_logger('session') +STATUSES_COMPLETE = (TestrunStatus.CANCELLED, + TestrunStatus.COMPLETE, + TestrunStatus.DO_NOT_PROCEED, + TestrunStatus.PROCEED, + TestrunStatus.IDLE + ) def session_tracker(method): """Session changes tracker.""" @@ -57,11 +64,13 @@ def wrapper(self, *args, **kwargs): result = method(self, *args, **kwargs) - if self.get_status() != TestrunStatus.IDLE: + if self.get_status() != TestrunStatus.IDLE and not self.pause_message: self.get_mqtt_client().send_message( STATUS_TOPIC, jsonable_encoder(self.to_json()) ) + if self.get_status() in STATUSES_COMPLETE: + self.pause_message = True return result return wrapper @@ -84,6 +93,7 @@ class TestrunSession(): def __init__(self, root_dir): self._root_dir = root_dir + self.pause_message = False self._status = TestrunStatus.IDLE self._result = None self._description = None @@ -116,6 +126,9 @@ def __init__(self, root_dir): # Direct url for PDF report self._report_url = None + # Export URL + self._export_url = None + # Version self._load_version() @@ -161,6 +174,7 @@ def __init__(self, root_dir): def start(self): self.reset() self._status = TestrunStatus.STARTING + self.pause_message = False self._started = datetime.datetime.now() def get_started(self): @@ -190,6 +204,7 @@ def _get_default_config(self): 'log_level': 'INFO', 'startup_timeout': 60, 'monitor_period': 30, + 'allow_disconnect': False, 'max_device_reports': 0, 'api_url': 'http://localhost', 'api_port': 8000, @@ -228,6 +243,10 @@ def _load_config(self): self._config[MONITOR_PERIOD_KEY] = config_file_json.get( MONITOR_PERIOD_KEY) + if ALLOW_DISCONNECT_KEY in config_file_json: + self._config[ALLOW_DISCONNECT_KEY] = config_file_json.get( + ALLOW_DISCONNECT_KEY) + if LOG_LEVEL_KEY in config_file_json: self._config[LOG_LEVEL_KEY] = config_file_json.get(LOG_LEVEL_KEY) @@ -496,6 +515,10 @@ def get_all_reports(self): def add_total_tests(self, no_tests): self._total_tests += no_tests + + def get_allow_disconnect(self): + return self._config.get(ALLOW_DISCONNECT_KEY) + def get_total_tests(self): return self._total_tests @@ -505,6 +528,12 @@ def get_report_url(self): def set_report_url(self, url): self._report_url = url + def get_export_url(self): + return self._export_url + + def set_export_url(self, url): + self._export_url = url + def set_subnets(self, ipv4_subnet, ipv6_subnet): self._ipv4_subnet = ipv4_subnet self._ipv6_subnet = ipv6_subnet @@ -814,6 +843,7 @@ def delete_profile(self, profile): def reset(self): self.set_status(TestrunStatus.IDLE) + self.pause_message = False self.set_result(None) self.set_description(None) self.set_target_device(None) @@ -851,9 +881,10 @@ def to_json(self): if self._report_url is not None: session_json['report'] = self.get_report_url() + if self._export_url is not None: + session_json['export'] = self.get_export_url() - if self._description is not None: - session_json['description'] = self._description + session_json['description'] = self._description return session_json diff --git a/framework/python/src/core/testrun.py b/framework/python/src/core/testrun.py index f3fe33c80..4bc8b20c5 100644 --- a/framework/python/src/core/testrun.py +++ b/framework/python/src/core/testrun.py @@ -279,7 +279,7 @@ def _load_test_reports(self, device): # Check if the report.json file exists if not os.path.isfile(report_json_file_path): - # Some error may have occured during this test run + # Some error may have occurred during this test run continue with open(report_json_file_path, encoding='utf-8') as report_json_file: @@ -514,7 +514,7 @@ def start_ui(self): detach=True, ports={'80': 8080}) except docker.errors.ImageNotFound as ie: - LOGGER.error('An error occured whilst starting the UI. ' + + LOGGER.error('An error occurred whilst starting the UI. ' + 'Please investigate and try again.') LOGGER.error(ie) sys.exit(1) @@ -555,7 +555,7 @@ def start_ws(self): '1883': 1883 }) except docker.errors.ImageNotFound as ie: - LOGGER.error('An error occured whilst starting the websockets server. ' + + LOGGER.error('An error occurred whilst starting the websockets server. ' + 'Please investigate and try again.') LOGGER.error(ie) sys.exit(1) diff --git a/framework/python/src/net_orc/ip_control.py b/framework/python/src/net_orc/ip_control.py index 73a6aceeb..c6df5d91e 100644 --- a/framework/python/src/net_orc/ip_control.py +++ b/framework/python/src/net_orc/ip_control.py @@ -99,7 +99,7 @@ def get_iface_mac_address(iface): return addr_info.address return None - def get_iface_port_stats(self, iface): + def get_iface_ethtool_port_stats(self, iface): """Extract information about packets connection""" response = util.run_command(f'ethtool -S {iface}') if len(response[1]) == 0: @@ -107,6 +107,14 @@ def get_iface_port_stats(self, iface): else: return None + def get_iface_ifconfig_port_stats(self, iface): + """Extract information about packets connection""" + response = util.run_command(f'ifconfig -a {iface}') + if len(response[1]) == 0: + return response[0] + else: + return None + def get_ip_address(self, iface): addrs = psutil.net_if_addrs() if iface in addrs: diff --git a/framework/python/src/net_orc/network_orchestrator.py b/framework/python/src/net_orc/network_orchestrator.py index 25e036ef7..bb9046e11 100644 --- a/framework/python/src/net_orc/network_orchestrator.py +++ b/framework/python/src/net_orc/network_orchestrator.py @@ -142,7 +142,7 @@ def start_network(self): self._session.set_status(TestrunStatus.VALIDATING) self.validator.start() self.validator.stop() - except Exception as e: + except Exception as e: # pylint: disable=W0703 LOGGER.error(f'Validation failed {e}') self._session.set_status('Waiting for Device') @@ -257,14 +257,21 @@ def _get_conn_stats(self): def _get_port_stats(self, pre_monitor=True): """ Extract information about the port statistics and store it to a file for the conn test module to access""" + suffix = 'pre_monitor' if pre_monitor else 'post_monitor' dev_int = self._session.get_device_interface() - port_stats = self._ip_ctrl.get_iface_port_stats(dev_int) - if port_stats is not None: - suffix = 'pre_monitor' if pre_monitor else 'post_monitor' - eth_out_file = os.path.join(NET_DIR, f'ethtool_port_stats_{suffix}.txt') - with open(eth_out_file, 'w', encoding='utf-8') as f: - f.write(port_stats) - else: + ethtool_port_stats = self._ip_ctrl.get_iface_ethtool_port_stats(dev_int) + ifconfig_port_stats = self._ip_ctrl.get_iface_ifconfig_port_stats(dev_int) + if ethtool_port_stats is not None: + ethtool_out_file = os.path.join(NET_DIR, + f'ethtool_port_stats_{suffix}.txt') + with open(ethtool_out_file, 'w', encoding='utf-8') as f: + f.write(ethtool_port_stats) + if ifconfig_port_stats is not None: + ifconfig_out_file = os.path.join(NET_DIR, + f'ifconfig_port_stats_{suffix}.txt') + with open(ifconfig_out_file, 'w', encoding='utf-8') as f: + f.write(ifconfig_port_stats) + if ethtool_port_stats is None and ifconfig_port_stats is None: LOGGER.error('Failed to generate port stats') def monitor_in_progress(self): @@ -307,22 +314,20 @@ def _start_device_monitor(self, device): time.sleep(1) # Check Testrun hasn't been cancelled - if self._session.get_status() in ( - TestrunStatus.STOPPING, - TestrunStatus.CANCELLED - ): + if self._session.get_status() in (TestrunStatus.STOPPING, + TestrunStatus.CANCELLED): sniffer.stop() return - - if not self._ip_ctrl.check_interface_status( - self._session.get_device_interface()): - try: - sniffer.stop() - except Scapy_Exception: - LOGGER.error('Device adapter disconnected whilst monitoring.') - finally: - self._session.set_status(TestrunStatus.CANCELLED) - LOGGER.error('Device interface disconnected, cancelling Testrun') + if not self._session.get_allow_disconnect(): + if not self._ip_ctrl.check_interface_status( + self._session.get_device_interface()): + try: + sniffer.stop() + except Scapy_Exception: + LOGGER.error('Device adapter disconnected whilst monitoring.') + finally: + self._session.set_status(TestrunStatus.CANCELLED) + LOGGER.error('Device interface disconnected, cancelling Testrun') LOGGER.debug('Writing packets to monitor.pcap') wrpcap(os.path.join(device_runtime_dir, 'monitor.pcap'), @@ -438,8 +443,8 @@ def load_network_modules(self): for module_dir in os.listdir(net_modules_dir): - if (self._get_network_module(module_dir) is None and - module_dir != 'template'): + if (self._get_network_module(module_dir) is None + and module_dir != 'template'): loaded_module = self._load_network_module(module_dir) loaded_modules += loaded_module.dir_name + ' ' @@ -700,8 +705,7 @@ def network_adapters_checker(self, mqtt_client: mqtt.MQTT, topic: str): def is_device_connected(self): """Check if device connected""" return self._ip_ctrl.check_interface_status( - self._session.get_device_interface() - ) + self._session.get_device_interface()) def internet_conn_checker(self, mqtt_client: mqtt.MQTT, topic: str): """Checks internet connection and sends a status to frontend""" @@ -711,11 +715,9 @@ def internet_conn_checker(self, mqtt_client: mqtt.MQTT, topic: str): # Only check if Testrun is running if self.get_session().get_status() not in [ - TestrunStatus.WAITING_FOR_DEVICE, - TestrunStatus.MONITORING, - TestrunStatus.IN_PROGRESS, - TestrunStatus.STARTING - ]: + TestrunStatus.WAITING_FOR_DEVICE, TestrunStatus.MONITORING, + TestrunStatus.IN_PROGRESS, TestrunStatus.STARTING + ]: message['connection'] = None # Only run if single intf mode not used @@ -734,6 +736,7 @@ def internet_conn_checker(self, mqtt_client: mqtt.MQTT, topic: str): # Broadcast via MQTT client mqtt_client.send_message(topic, message) + class NetworkConfig: """Define all the properties of the network configuration""" diff --git a/framework/python/src/test_orc/test_orchestrator.py b/framework/python/src/test_orc/test_orchestrator.py index 4334349c6..a34f70554 100644 --- a/framework/python/src/test_orc/test_orchestrator.py +++ b/framework/python/src/test_orc/test_orchestrator.py @@ -185,6 +185,7 @@ def run_test_modules(self): self._write_reports(report) self._test_in_progress = False self.get_session().set_report_url(report.get_report_url()) + self.get_session().set_export_url(report.get_export_url()) # Set testing description test_pack: TestPack = self.get_test_pack(device.test_pack) @@ -266,6 +267,7 @@ def _generate_report(self): "{device_folder}", device.device_folder) + self.get_session().get_started().strftime("%Y-%m-%dT%H:%M:%S")) + report["export"] = report["report"].replace("report", "export") return report @@ -363,6 +365,9 @@ def zip_results(self, device, timestamp: str, profile): LOCAL_DEVICE_REPORTS.replace("{device_folder}", device.device_folder), timestamp, "test", device.mac_addr.replace(":", "")) + # report.json path + report_json_path = os.path.join(report_path, "report.json") + # Parse string timestamp date_timestamp: datetime.datetime = datetime.strptime( timestamp, "%Y-%m-%dT%H:%M:%S") @@ -377,28 +382,53 @@ def zip_results(self, device, timestamp: str, profile): if test_report is None: return None + # Load the report.json into TestReport + if os.path.exists(report_json_path): + with open(report_json_path, "r", encoding="utf-8") as report_json_file: + report_json = json.load(report_json_file) + test_report = TestReport() + test_report.from_json(report_json) + # Copy the original report for comparison - original_report = copy.deepcopy(test_report) + test_report_copy = copy.deepcopy(test_report) # Update the report with 'additional_info' field test_report.update_device_profile(device.additional_info) # Overwrite report only if additional_info has been updated - if original_report.to_json() != test_report.to_json(): - - # Write the json report + if test_report.to_json() != test_report_copy.to_json(): + + # Store the jinja templates + reload_templates = [] + + # Load the jinja templates + if os.path.isdir(report_path): + for dir_path, _, filenames in os.walk(report_path): + for filename in filenames: + try: + if filename.endswith(".j2.html"): + with open(os.path.join(dir_path, filename), "r", + encoding="utf-8") as f: + reload_templates.append(f.read()) + except Exception as e: + LOGGER.debug(f"Could not read the file: {e}") + + # Add the jinja templates to the report + test_report.add_module_templates(reload_templates) + + # Rewrite the json report with open(os.path.join(report_path, "report.json"), "w", encoding="utf-8") as f: json.dump(test_report.to_json(), f, indent=2) - # Write the html report + # Rewrite the html report with open(os.path.join(report_path, "report.html"), "w", encoding="utf-8") as f: f.write(test_report.to_html()) - # Write the pdf report + # Rewrite the pdf report with open(os.path.join(report_path, "report.pdf"), "wb") as f: f.write(test_report.to_pdf().getvalue()) diff --git a/framework/requirements.txt b/framework/requirements.txt index b5726aca2..543b8ff5e 100644 --- a/framework/requirements.txt +++ b/framework/requirements.txt @@ -41,5 +41,5 @@ paho-mqtt==2.1.0 APScheduler==3.10.4 # Requirements for reports generation -Jinja2==3.1.5 +Jinja2==3.1.6 beautifulsoup4==4.12.3 diff --git a/local/system.json.example b/local/system.json.example index df89b502f..43527240f 100644 --- a/local/system.json.example +++ b/local/system.json.example @@ -6,6 +6,7 @@ "log_level": "INFO", "startup_timeout": 60, "monitor_period": 300, + "allow_disconnect": false, "max_device_reports": 0, "org_name": "" } diff --git a/make/DEBIAN/control b/make/DEBIAN/control index 2a0235082..c45dd27c9 100644 --- a/make/DEBIAN/control +++ b/make/DEBIAN/control @@ -1,5 +1,5 @@ Package: Testrun -Version: 2.1.1 +Version: 2.2 Architecture: amd64 Maintainer: Google Homepage: https://github.com/google/testrun diff --git a/modules/test/base/python/requirements.txt b/modules/test/base/python/requirements.txt index 5faa12fc8..0a40a660d 100644 --- a/modules/test/base/python/requirements.txt +++ b/modules/test/base/python/requirements.txt @@ -9,4 +9,4 @@ grpcio-tools==1.67.1 netifaces==0.11.0 # Requirements for reports generation -Jinja2==3.1.5 +Jinja2==3.1.6 diff --git a/modules/test/base/python/src/test_module.py b/modules/test/base/python/src/test_module.py index 42ee3ff85..8030d7757 100644 --- a/modules/test/base/python/src/test_module.py +++ b/modules/test/base/python/src/test_module.py @@ -44,6 +44,7 @@ def __init__(self, self._device_test_pack = json.loads(os.environ.get('DEVICE_TEST_PACK', '')) self._report_template_folder = os.environ.get('REPORT_TEMPLATE_PATH') self._base_template_file=os.environ.get('BASE_TEMPLATE_FILE') + self._log_level = os.environ.get('LOG_LEVEL', None) self._add_logger(log_name=log_name) self._config = self._read_config( conf_file=conf_file if conf_file is not None else CONF_FILE) @@ -53,6 +54,8 @@ def __init__(self, def _add_logger(self, log_name): global LOGGER LOGGER = logger.get_logger(name=log_name) + if self._log_level is not None: + LOGGER.setLevel(self._log_level) def generate_module_report(self): pass @@ -165,7 +168,7 @@ def run_tests(self): if len(result) > 1: test['description'] = result[1] else: - test['description'] = 'An error occured whilst running this test' + test['description'] = 'An error occurred whilst running this test' # Compliant / Non-Compliant result elif isinstance(result[0], bool): @@ -194,7 +197,7 @@ def run_tests(self): else: LOGGER.debug('No result was returned from the test module') test['result'] = TestResult.ERROR - test['description'] = 'An error occured whilst running this test' + test['description'] = 'An error occurred whilst running this test' # Remove the steps to resolve if compliant already if (test['result'] == TestResult.COMPLIANT and 'recommendations' in test): diff --git a/modules/test/conn/python/src/port_stats_util.py b/modules/test/conn/python/src/port_stats_util.py index a1f68cb03..340cabd02 100644 --- a/modules/test/conn/python/src/port_stats_util.py +++ b/modules/test/conn/python/src/port_stats_util.py @@ -14,12 +14,17 @@ """Module that contains various methods for validating the Port statistics """ import os +import re ETHTOOL_CONN_STATS_FILE = 'runtime/network/ethtool_conn_stats.txt' ETHTOOL_PORT_STATS_PRE_FILE = ( 'runtime/network/ethtool_port_stats_pre_monitor.txt') ETHTOOL_PORT_STATS_POST_FILE = ( 'runtime/network/ethtool_port_stats_post_monitor.txt') +IFCONFIG_PORT_STATS_PRE_FILE = ( + 'runtime/network/ifconfig_port_stats_pre_monitor.txt') +IFCONFIG_PORT_STATS_POST_FILE = ( + 'runtime/network/ifconfig_port_stats_post_monitor.txt') LOG_NAME = 'port_stats_util' LOGGER = None @@ -32,10 +37,14 @@ def __init__(self, logger, ethtool_conn_stats_file=ETHTOOL_CONN_STATS_FILE, ethtool_port_stats_pre_file=ETHTOOL_PORT_STATS_PRE_FILE, - ethtool_port_stats_post_file=ETHTOOL_PORT_STATS_POST_FILE): + ethtool_port_stats_post_file=ETHTOOL_PORT_STATS_POST_FILE, + ifconfig_port_stats_pre_file=IFCONFIG_PORT_STATS_PRE_FILE, + ifconfig_port_stats_post_file=IFCONFIG_PORT_STATS_POST_FILE): self.ethtool_conn_stats_file = ethtool_conn_stats_file self.ethtool_port_stats_pre_file = ethtool_port_stats_pre_file self.ethtool_port_stats_post_file = ethtool_port_stats_post_file + self.ifconfig_port_stats_pre_file = ifconfig_port_stats_pre_file + self.ifconfig_port_stats_post_file = ifconfig_port_stats_post_file global LOGGER LOGGER = logger self.conn_stats = self._read_stats_file(self.ethtool_conn_stats_file) @@ -48,16 +57,13 @@ def is_autonegotiate(self): auto_negotiation = 'on' in auto_negotiation_status return auto_negotiation - def connection_port_link_test(self): + def ethtool_port_link_test(self): stats_pre = self._read_stats_file(self.ethtool_port_stats_pre_file) stats_post = self._read_stats_file(self.ethtool_port_stats_post_file) result = None description = '' details = '' - if stats_pre is None or stats_pre is None: - result = 'Error' - description = 'Port stats not available' - else: + if stats_pre is not None and stats_pre is not None: tx_errors_pre = self._get_stat_option(stats=stats_pre, option='tx_errors:') tx_errors_post = self._get_stat_option(stats=stats_post, @@ -68,11 +74,33 @@ def connection_port_link_test(self): option='rx_errors:') # Check that the above have been resolved correctly - if (tx_errors_pre is None or tx_errors_post is None or - rx_errors_pre is None or rx_errors_post is None): - result = 'Error' - description = 'Port stats not available' - else: + if (tx_errors_pre is not None and tx_errors_post is not None + and rx_errors_pre is not None and rx_errors_post is not None): + tx_errors = int(tx_errors_post) - int(tx_errors_pre) + rx_errors = int(rx_errors_post) - int(rx_errors_pre) + if tx_errors > 0 or rx_errors > 0: + result = False + description = 'Port errors detected' + details = f'TX errors: {tx_errors}, RX errors: {rx_errors}' + else: + result = True + description = 'No port errors detected' + return result, description, details + + def ifconfig_port_link_test(self): + stats_pre = self._read_stats_file(self.ifconfig_port_stats_pre_file) + stats_post = self._read_stats_file(self.ifconfig_port_stats_post_file) + result = None + description = '' + details = '' + if stats_pre is not None and stats_pre is not None: + rx_errors_pre, tx_errors_pre = self.extract_rx_tx_error_counts(stats_pre) + rx_errors_post, tx_errors_post = self.extract_rx_tx_error_counts( + stats_post) + + # Check that the above have been resolved correctly + if (tx_errors_pre is not None and tx_errors_post is not None + and rx_errors_pre is not None and rx_errors_post is not None): tx_errors = int(tx_errors_post) - int(tx_errors_pre) rx_errors = int(rx_errors_post) - int(rx_errors_pre) if tx_errors > 0 or rx_errors > 0: @@ -84,6 +112,17 @@ def connection_port_link_test(self): description = 'No port errors detected' return result, description, details + def connection_port_link_test(self): + port_results = self.ethtool_port_link_test() + if port_results[0] is None: + port_results = self.ifconfig_port_link_test() + if port_results[0] is None: + result = 'Error' + description = 'Port stats not available' + details = '' + port_results = result, description, details + return port_results + def connection_port_duplex_test(self): auto_negotiation = self.is_autonegotiate() # Calculate final results @@ -132,11 +171,19 @@ def connection_port_speed_test(self): details = f'Speed negotiated: {speed}' return result, description, details + def extract_rx_tx_error_counts(self, ifconfig): + rx_match = re.search(r'^\s*RX errors (\d+)', ifconfig, re.MULTILINE) + tx_match = re.search(r'^\s*TX errors (\d+)', ifconfig, re.MULTILINE) + + if rx_match and tx_match: + return int(rx_match.group(1)), int(tx_match.group(1)) + else: + return None, None + def _get_stat_option(self, stats, option): """Extract the requested parameter from the ethtool result""" value = None for line in stats.split('\n'): - #LOGGER.info(f'Checking option: {line}') if line.startswith(f'{option}'): value = line.split(':')[1].strip() break diff --git a/modules/test/dns/python/src/dns_module.py b/modules/test/dns/python/src/dns_module.py index c1db567ae..fc735d002 100644 --- a/modules/test/dns/python/src/dns_module.py +++ b/modules/test/dns/python/src/dns_module.py @@ -13,7 +13,8 @@ # limitations under the License. """DNS test module""" import subprocess -from scapy.all import rdpcap, DNS, IP, Ether +from scapy.all import rdpcap, DNS, IP, Ether, DNSRR +from scapy.error import Scapy_Exception from test_module import TestModule import os from collections import Counter @@ -156,16 +157,22 @@ def generate_module_report(self): def extract_dns_data(self): dns_data = [] - # Read the pcap file - packets = rdpcap(self.dns_server_capture_file) + rdpcap( - self.startup_capture_file) + rdpcap(self.monitor_capture_file) + # Read the startup and monitor pcap files + packets = (rdpcap(self.startup_capture_file) + + rdpcap(self.monitor_capture_file)) + + # Read the dns.pcap file + try: + packets += rdpcap(self.dns_server_capture_file) + except (FileNotFoundError, Scapy_Exception): + LOGGER.error('dns.pcap not found or empty, ignoring') # Iterate through DNS packets for packet in packets: if DNS in packet and packet.haslayer(IP): # Check if either source or destination MAC matches the device - if self._device_mac in (packet[Ether].src, packet[Ether].dst): + if self._device_mac in [packet[Ether].src, packet[Ether].dst]: source_ip = packet[IP].src destination_ip = packet[IP].dst dns_layer = packet[DNS] @@ -184,15 +191,17 @@ def extract_dns_data(self): if dns_layer.qr == 1 and hasattr(dns_layer, 'an') and dns_layer.ancount > 0: # Loop through all answers in the DNS response - for i in range(dns_layer.ancount): + for i in range(min(dns_layer.ancount, len(dns_layer.an))): answer = dns_layer.an[i] - # Check for IPv4 (A record) or IPv6 (AAAA record) - if answer.type == 1: # Indicates an A record (IPv4 address) - resolved_ip = answer.rdata # Extract IPv4 address - break # Stop after finding the first valid resolved IP - elif answer.type == 28: # Indicates an AAAA record (IPv6 address) - resolved_ip = answer.rdata # Extract IPv6 address - break # Stop after finding the first valid resolved IP + # Check if the answer is of type DNSRR + if isinstance(answer, DNSRR): + # Check for IPv4 (A record) or IPv6 (AAAA record) + if answer.type == 1: # Indicates an A record (IPv4 address) + resolved_ip = answer.rdata # Extract IPv4 address + break # Stop after finding the first valid resolved IP + elif answer.type == 28: # Indicates AAAA record (IPv6 address) + resolved_ip = answer.rdata # Extract IPv6 address + break # Stop after finding the first valid resolved IP dns_data.append({ 'Timestamp': float(packet.time), # Timestamp of the DNS packet @@ -217,15 +226,15 @@ def extract_dns_data(self): def _has_dns_traffic(self, tcpdump_filter): dns_server_queries = self._exec_tcpdump(tcpdump_filter, - DNS_SERVER_CAPTURE_FILE) + self.dns_server_capture_file) LOGGER.info('DNS Server queries found: ' + str(len(dns_server_queries))) dns_startup_queries = self._exec_tcpdump(tcpdump_filter, - STARTUP_CAPTURE_FILE) + self.startup_capture_file) LOGGER.info('Startup DNS queries found: ' + str(len(dns_startup_queries))) dns_monitor_queries = self._exec_tcpdump(tcpdump_filter, - MONITOR_CAPTURE_FILE) + self.monitor_capture_file) LOGGER.info('Monitor DNS queries found: ' + str(len(dns_monitor_queries))) num_query_dns = len(dns_server_queries) + len(dns_startup_queries) + len( @@ -234,6 +243,10 @@ def _has_dns_traffic(self, tcpdump_filter): return num_query_dns > 0 + # Added to access the method for dns unittests + def dns_network_from_dhcp(self): + return self._dns_network_from_dhcp() + def _dns_network_from_dhcp(self): LOGGER.info('Running dns.network.from_dhcp') LOGGER.info('Checking DNS traffic for configured DHCP DNS server: ' + @@ -246,10 +259,9 @@ def _dns_network_from_dhcp(self): dns_packets_local = self._has_dns_traffic(tcpdump_filter=tcpdump_filter) # Check if the device sends any DNS traffic to non-DHCP provided server - tcpdump_filter = (f'dst port 53 and dst not host {self._dns_server} ' + - 'ether src {self._device_mac}') + tcpdump_filter = (f'dst port 53 and not dst host {self._dns_server} ' + + f'and ether src {self._device_mac}') dns_packets_not_local = self._has_dns_traffic(tcpdump_filter=tcpdump_filter) - if dns_packets_local or dns_packets_not_local: if dns_packets_not_local: description = 'DNS traffic detected to non-DHCP provided server' @@ -257,8 +269,10 @@ def _dns_network_from_dhcp(self): LOGGER.info('DNS traffic detected only to configured DHCP DNS server') description = 'DNS traffic detected only to DHCP provided server' else: - LOGGER.info('No DNS traffic detected from the device') - description = 'No DNS traffic detected from the device' + LOGGER.info( + 'No DNS traffic detected from the device to the DHCP DNS server') + description = '' \ + 'No DNS traffic detected from the device to the DHCP DNS server' return 'Informational', description def _dns_network_hostname_resolution(self): diff --git a/modules/test/protocol/bin/get_bacnet_packets.sh b/modules/test/protocol/bin/get_bacnet_packets.sh old mode 100644 new mode 100755 diff --git a/modules/test/protocol/python/src/protocol_bacnet.py b/modules/test/protocol/python/src/protocol_bacnet.py index 9d4399b2b..3b6d8f0ce 100644 --- a/modules/test/protocol/python/src/protocol_bacnet.py +++ b/modules/test/protocol/python/src/protocol_bacnet.py @@ -29,7 +29,6 @@ DEFAULT_CAPTURE_FILE = 'protocol.pcap' DEFAULT_BIN_DIR = '/testrun/bin' - class BACnet(): """BACnet Test module""" @@ -93,17 +92,17 @@ def validate_device(self): description = 'BACnet device could not be discovered' LOGGER.info(description) except Exception: # pylint: disable=W0718 - LOGGER.error('Error occured when validating device', exc_info=True) + LOGGER.error('Error occurred when validating device', exc_info=True) return result, description - def validate_protocol_version(self, device_ip, device_id): + def validate_protocol_version(self, device_addr, device_id): LOGGER.info(f'Resolving protocol version for BACnet device: {device_id}') try: version = self.bacnet.read( - f'{device_ip} device {device_id} protocolVersion') + f'{device_addr} device {device_id} protocolVersion') revision = self.bacnet.read( - f'{device_ip} device {device_id} protocolRevision') + f'{device_addr} device {device_id} protocolRevision') protocol_version = f'{version}.{revision}' result = True result_description = f'Device uses BACnet version {protocol_version}' @@ -122,6 +121,9 @@ def validate_bacnet_source(self, object_id, device_hw_addr): capture_file = os.path.join(self._captures_dir, self._capture_file) packets = self.get_bacnet_packets(capture_file, object_id) valid = None + # If no packets are found in protocol.pcap + if not packets: + LOGGER.debug(f'No BACnet packets found for object id {object_id}') for packet in packets: if object_id in packet['_source']['layers']['bacapp.instance_number']: if device_hw_addr.lower() in packet['_source']['layers']['eth.src']: @@ -138,7 +140,7 @@ def validate_bacnet_source(self, object_id, device_hw_addr): valid = False return valid except Exception: # pylint: disable=W0718 - LOGGER.error('Error occured when validating source', exc_info=True) + LOGGER.error('Error occurred when validating source', exc_info=True) return False def get_bacnet_packets(self, capture_file, object_id): diff --git a/modules/test/protocol/python/src/protocol_module.py b/modules/test/protocol/python/src/protocol_module.py index 9d99c91bd..92891465d 100644 --- a/modules/test/protocol/python/src/protocol_module.py +++ b/modules/test/protocol/python/src/protocol_module.py @@ -29,7 +29,7 @@ def __init__(self, module): super().__init__(module_name=module, log_name=LOG_NAME) global LOGGER LOGGER = self._get_logger() - self._bacnet = BACnet(log=LOGGER,device_hw_addr=self._device_mac) + self._bacnet = BACnet(log=LOGGER, device_hw_addr=self._device_mac) def _protocol_valid_bacnet(self): LOGGER.info('Running protocol.valid_bacnet') @@ -64,19 +64,20 @@ def _protocol_bacnet_version(self): result_status = 'Feature Not Detected' result_description = 'Device did not respond to BACnet discovery' + LOGGER.debug(f'BACnet supported: {self._supports_bacnet}') + # Do not run test if device does not support BACnet if not self._supports_bacnet: return result_status, result_description if len(self._bacnet.devices) > 0: for device in self._bacnet.devices: - if self._device_ipv4_addr in device[2]: - LOGGER.debug(f'Checking BACnet version for device: {device}') - result_status, result_description = \ - self._bacnet.validate_protocol_version(device[2], device[3]) - break - else: - LOGGER.debug('Device does not match expected IP address, skipping') + LOGGER.debug(f'Checking BACnet version for device: {device}') + device_addr = device[2] + device_id = device[3] + result_status, result_description = \ + self._bacnet.validate_protocol_version(device_addr,device_id) + break LOGGER.info(result_description) return result_status, result_description diff --git a/modules/test/services/python/src/services_module.py b/modules/test/services/python/src/services_module.py index 3ec713962..06ceed67d 100644 --- a/modules/test/services/python/src/services_module.py +++ b/modules/test/services/python/src/services_module.py @@ -187,10 +187,10 @@ def _process_port_results(self): self._scan_results.update(self._scan_udp_results) def _scan_tcp_ports(self): - LOGGER.info('Running nmap TCP port scan') + LOGGER.info(f'Running nmap TCP port scan for {self._device_ipv4_addr}') nmap_results = util.run_command( # pylint: disable=E1120 f'''nmap --open -sT -sV -Pn -v -p 1-65535 - --version-intensity 7 -T4 -oX - {self._ipv4_addr}''')[0] + --version-intensity 7 -T4 -oX - {self._device_ipv4_addr}''')[0] LOGGER.info('TCP port scan complete') LOGGER.debug(f'TCP Scan results raw: {nmap_results}') @@ -216,10 +216,10 @@ def _scan_udp_ports(self): if len(ports) > 0: port_list = ','.join(ports) - LOGGER.info('Running nmap UDP port scan') + LOGGER.info(f'Running nmap UDP port scan for {self._device_ipv4_addr}') LOGGER.info('UDP ports: ' + str(port_list)) nmap_results = util.run_command( # pylint: disable=E1120 - f'nmap -sU -sV -p {port_list} -oX - {self._ipv4_addr}')[0] + f'nmap -sU -sV -p {port_list} -oX - {self._device_ipv4_addr}')[0] LOGGER.info('UDP port scan complete') LOGGER.debug(f'UDP Scan results raw: {nmap_results}') nmap_results_json = self._nmap_results_to_json(nmap_results) @@ -257,12 +257,14 @@ def _json_port_to_dict(self, port_json): port['number'] = port_json['@portid'] port['tcp_udp'] = port_json['@protocol'] port['state'] = port_json['state']['@state'] - port['service'] = port_json['service']['@name'] + port['service'] = 'unknown' port['version'] = '' - if '@version' in port_json['service']: - port['version'] += port_json['service']['@version'] - if '@extrainfo' in port_json['service']: - port['version'] += ' ' + port_json['service']['@extrainfo'] + if 'service' in port_json: + port['service'] = port_json['service']['@name'] + if '@version' in port_json['service']: + port['version'] += port_json['service']['@version'] + if '@extrainfo' in port_json['service']: + port['version'] += ' ' + port_json['service']['@extrainfo'] port_result = {port_json['@portid'] + port['tcp_udp']: port} return port_result diff --git a/modules/test/tls/python/src/tls_util.py b/modules/test/tls/python/src/tls_util.py index d92379aab..a60332e2d 100644 --- a/modules/test/tls/python/src/tls_util.py +++ b/modules/test/tls/python/src/tls_util.py @@ -158,6 +158,9 @@ def get_public_certificate(self, except socket.timeout: LOGGER.info('Socket timeout error') return None + except OSError as e: + LOGGER.error(e) + return None return cert_pem diff --git a/modules/ui/angular.json b/modules/ui/angular.json index d56b3b8ee..327fc2908 100644 --- a/modules/ui/angular.json +++ b/modules/ui/angular.json @@ -24,9 +24,27 @@ "tsConfig": "tsconfig.app.json", "inlineStyleLanguage": "scss", "assets": ["src/favicon.ico", "src/assets"], - "styles": ["src/styles.scss"], + "styles": [ + "src/styles.scss", + "src/theming/m3-theme.scss", + "src/theming/colors.scss" + ], "scripts": [], - "allowedCommonJsDependencies": ["mqtt-browser"] + "allowedCommonJsDependencies": ["mqtt-browser"], + "stylePreprocessorOptions": { + "includePaths": ["src/theming/"] + }, + "optimization": { + "scripts": true, + "styles": true, + "fonts": true + }, + "sourceMap": { + "scripts": true, + "styles": false, + "hidden": true, + "vendor": true + } }, "configurations": { "production": { @@ -34,12 +52,12 @@ { "type": "initial", "maximumWarning": "1500kb", - "maximumError": "3000kb" + "maximumError": "4000kb" }, { "type": "anyComponentStyle", - "maximumWarning": "6kb", - "maximumError": "7kb" + "maximumWarning": "60kb", + "maximumError": "65kb" } ], "outputHashing": "all" @@ -57,12 +75,15 @@ }, "serve": { "builder": "@angular-devkit/build-angular:dev-server", + "options": { + "buildTarget": "test-run-ui:build" + }, "configurations": { "production": { - "browserTarget": "test-run-ui:build:production" + "buildTarget": "test-run-ui:build:production" }, "development": { - "browserTarget": "test-run-ui:build:development" + "buildTarget": "test-run-ui:build:development" } }, "defaultConfiguration": "development" @@ -70,7 +91,7 @@ "extract-i18n": { "builder": "@angular-devkit/build-angular:extract-i18n", "options": { - "browserTarget": "test-run-ui:build" + "buildTarget": "test-run-ui:build" } }, "test": { @@ -82,7 +103,10 @@ "assets": ["src/favicon.ico", "src/assets"], "styles": ["src/styles.scss"], "scripts": [], - "karmaConfig": "karma.conf.js" + "karmaConfig": "karma.conf.js", + "stylePreprocessorOptions": { + "includePaths": ["src/theming/"] + } } }, "lint": { diff --git a/modules/ui/package-lock.json b/modules/ui/package-lock.json index 1e4edf90f..97a198cac 100644 --- a/modules/ui/package-lock.json +++ b/modules/ui/package-lock.json @@ -8,34 +8,36 @@ "name": "test-run-ui", "version": "0.0.0", "dependencies": { - "@angular/animations": "^18.2.4", - "@angular/cdk": "^18.2.0", - "@angular/common": "^18.2.4", - "@angular/compiler": "^18.2.4", - "@angular/core": "^18.2.4", - "@angular/forms": "^18.2.4", - "@angular/material": "^18.2.0", - "@angular/platform-browser": "^18.2.4", - "@angular/platform-browser-dynamic": "^18.2.4", - "@angular/router": "^18.2.4", - "@ngrx/component-store": "^18.0.2", - "@ngrx/effects": "^18.0.2", - "@ngrx/store": "^18.0.2", + "@angular/animations": "^19.0.1", + "@angular/cdk": "^19.0.1", + "@angular/common": "^19.0.1", + "@angular/compiler": "^19.0.1", + "@angular/core": "^19.0.1", + "@angular/forms": "^19.0.1", + "@angular/material": "^19.0.1", + "@angular/platform-browser": "^19.0.1", + "@angular/platform-browser-dynamic": "^19.0.1", + "@angular/router": "^19.0.1", + "@ngrx/component-store": "19.0.0-beta.0", + "@ngrx/effects": "19.0.0-beta.0", + "@ngrx/operators": "^19.0.0-beta.0", + "@ngrx/signals": "^19.0.0-beta.0", + "@ngrx/store": "19.0.0-beta.0", "ngx-mask": "^16.4.2", "ngx-mqtt": "^17.0.0", "rxjs": "~7.8.0", "tslib": "^2.6.2", - "zone.js": "^0.14.10" + "zone.js": "^0.15.0" }, "devDependencies": { - "@angular-devkit/build-angular": "^18.1.4", - "@angular-eslint/builder": "18.3.0", - "@angular-eslint/eslint-plugin": "^18.3.0", - "@angular-eslint/eslint-plugin-template": "^18.3.0", - "@angular-eslint/schematics": "^18.3.0", - "@angular-eslint/template-parser": "18.3.0", - "@angular/cli": "~18.2.4", - "@angular/compiler-cli": "^18.2.4", + "@angular-devkit/build-angular": "^19.0.2", + "@angular-eslint/builder": "19.0.0-alpha.4", + "@angular-eslint/eslint-plugin": "19.0.0-alpha.4", + "@angular-eslint/eslint-plugin-template": "19.0.0-alpha.4", + "@angular-eslint/schematics": "19.0.0-alpha.4", + "@angular-eslint/template-parser": "19.0.0-alpha.4", + "@angular/cli": "~19.0.2", + "@angular/compiler-cli": "^19.0.1", "@types/jasmine": "~4.3.6", "@typescript-eslint/eslint-plugin": "^8.2.0", "@typescript-eslint/parser": "^8.2.0", @@ -58,6 +60,7 @@ "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", "dev": true, + "license": "Apache-2.0", "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" @@ -67,12 +70,13 @@ } }, "node_modules/@angular-devkit/architect": { - "version": "0.1802.14", - "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1802.14.tgz", - "integrity": "sha512-eplaGCXSlPwf1f4XwyzsYTd8/lJ0/Adm6XsODsBxvkZlIpLcps80/h2lH5MVJpoDREzIFu1BweDpYCoNK5yYZg==", + "version": "0.1902.9", + "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1902.9.tgz", + "integrity": "sha512-SLUc7EaFMjhCnimqxTcv32wESJBLQ3E6c/1sAndPojyCoGiX24ASu2pxrTXrYNS9DqiJT8tReAnqmh7dmf3xwQ==", "dev": true, + "license": "MIT", "dependencies": { - "@angular-devkit/core": "18.2.14", + "@angular-devkit/core": "19.2.9", "rxjs": "7.8.1" }, "engines": { @@ -81,71 +85,76 @@ "yarn": ">= 1.13.0" } }, + "node_modules/@angular-devkit/architect/node_modules/rxjs": { + "version": "7.8.1", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", + "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.1.0" + } + }, "node_modules/@angular-devkit/build-angular": { - "version": "18.2.14", - "resolved": "https://registry.npmjs.org/@angular-devkit/build-angular/-/build-angular-18.2.14.tgz", - "integrity": "sha512-ycie4OhvNv8eNVqvq46pCIf6kB50xbMOdnAVqmlj+BaQjWbGjUQPjAmp4VGqeDZZ/lW82xkfTmJZxc6pYp7YdQ==", + "version": "19.2.9", + "resolved": "https://registry.npmjs.org/@angular-devkit/build-angular/-/build-angular-19.2.9.tgz", + "integrity": "sha512-v6x3h+LYyEew3EjoI1+2IiFDz6f96lJB1JvbbZj3Li9FMhO4M/xo4BaWHbeg9Lot/vUy6IAlR+BJywawNIzv0Q==", "dev": true, + "license": "MIT", "dependencies": { "@ampproject/remapping": "2.3.0", - "@angular-devkit/architect": "0.1802.14", - "@angular-devkit/build-webpack": "0.1802.14", - "@angular-devkit/core": "18.2.14", - "@angular/build": "18.2.14", - "@babel/core": "7.25.2", - "@babel/generator": "7.25.0", - "@babel/helper-annotate-as-pure": "7.24.7", + "@angular-devkit/architect": "0.1902.9", + "@angular-devkit/build-webpack": "0.1902.9", + "@angular-devkit/core": "19.2.9", + "@angular/build": "19.2.9", + "@babel/core": "7.26.10", + "@babel/generator": "7.26.10", + "@babel/helper-annotate-as-pure": "7.25.9", "@babel/helper-split-export-declaration": "7.24.7", - "@babel/plugin-transform-async-generator-functions": "7.25.0", - "@babel/plugin-transform-async-to-generator": "7.24.7", - "@babel/plugin-transform-runtime": "7.24.7", - "@babel/preset-env": "7.25.3", - "@babel/runtime": "7.25.0", - "@discoveryjs/json-ext": "0.6.1", - "@ngtools/webpack": "18.2.14", - "@vitejs/plugin-basic-ssl": "1.1.0", + "@babel/plugin-transform-async-generator-functions": "7.26.8", + "@babel/plugin-transform-async-to-generator": "7.25.9", + "@babel/plugin-transform-runtime": "7.26.10", + "@babel/preset-env": "7.26.9", + "@babel/runtime": "7.26.10", + "@discoveryjs/json-ext": "0.6.3", + "@ngtools/webpack": "19.2.9", + "@vitejs/plugin-basic-ssl": "1.2.0", "ansi-colors": "4.1.3", "autoprefixer": "10.4.20", - "babel-loader": "9.1.3", + "babel-loader": "9.2.1", "browserslist": "^4.21.5", "copy-webpack-plugin": "12.0.2", - "critters": "0.0.24", "css-loader": "7.1.2", - "esbuild-wasm": "0.23.0", - "fast-glob": "3.3.2", - "http-proxy-middleware": "3.0.3", - "https-proxy-agent": "7.0.5", + "esbuild-wasm": "0.25.1", + "fast-glob": "3.3.3", + "http-proxy-middleware": "3.0.5", "istanbul-lib-instrument": "6.0.3", "jsonc-parser": "3.3.1", "karma-source-map-support": "1.4.0", - "less": "4.2.0", + "less": "4.2.2", "less-loader": "12.2.0", "license-webpack-plugin": "4.0.2", "loader-utils": "3.3.1", - "magic-string": "0.30.11", - "mini-css-extract-plugin": "2.9.0", - "mrmime": "2.0.0", + "mini-css-extract-plugin": "2.9.2", "open": "10.1.0", "ora": "5.4.1", - "parse5-html-rewriting-stream": "7.0.0", "picomatch": "4.0.2", - "piscina": "4.6.1", - "postcss": "8.4.41", + "piscina": "4.8.0", + "postcss": "8.5.2", "postcss-loader": "8.1.1", "resolve-url-loader": "5.0.0", "rxjs": "7.8.1", - "sass": "1.77.6", - "sass-loader": "16.0.0", - "semver": "7.6.3", + "sass": "1.85.0", + "sass-loader": "16.0.5", + "semver": "7.7.1", "source-map-loader": "5.0.0", "source-map-support": "0.5.21", - "terser": "5.31.6", + "terser": "5.39.0", "tree-kill": "1.2.2", - "tslib": "2.6.3", - "watchpack": "2.4.1", - "webpack": "5.94.0", + "tslib": "2.8.1", + "webpack": "5.98.0", "webpack-dev-middleware": "7.4.2", - "webpack-dev-server": "5.0.4", + "webpack-dev-server": "5.2.0", "webpack-merge": "6.0.1", "webpack-subresource-integrity": "5.1.0" }, @@ -155,22 +164,23 @@ "yarn": ">= 1.13.0" }, "optionalDependencies": { - "esbuild": "0.23.0" + "esbuild": "0.25.1" }, "peerDependencies": { - "@angular/compiler-cli": "^18.0.0", - "@angular/localize": "^18.0.0", - "@angular/platform-server": "^18.0.0", - "@angular/service-worker": "^18.0.0", - "@web/test-runner": "^0.18.0", + "@angular/compiler-cli": "^19.0.0 || ^19.2.0-next.0", + "@angular/localize": "^19.0.0 || ^19.2.0-next.0", + "@angular/platform-server": "^19.0.0 || ^19.2.0-next.0", + "@angular/service-worker": "^19.0.0 || ^19.2.0-next.0", + "@angular/ssr": "^19.2.9", + "@web/test-runner": "^0.20.0", "browser-sync": "^3.0.2", "jest": "^29.5.0", "jest-environment-jsdom": "^29.5.0", "karma": "^6.3.0", - "ng-packagr": "^18.0.0", + "ng-packagr": "^19.0.0 || ^19.2.0-next.0", "protractor": "^7.0.0", - "tailwindcss": "^2.0.0 || ^3.0.0", - "typescript": ">=5.4 <5.6" + "tailwindcss": "^2.0.0 || ^3.0.0 || ^4.0.0", + "typescript": ">=5.5 <5.9" }, "peerDependenciesMeta": { "@angular/localize": { @@ -182,6 +192,9 @@ "@angular/service-worker": { "optional": true }, + "@angular/ssr": { + "optional": true + }, "@web/test-runner": { "optional": true }, @@ -208,19 +221,24 @@ } } }, - "node_modules/@angular-devkit/build-angular/node_modules/tslib": { - "version": "2.6.3", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz", - "integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==", - "dev": true + "node_modules/@angular-devkit/build-angular/node_modules/rxjs": { + "version": "7.8.1", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", + "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.1.0" + } }, "node_modules/@angular-devkit/build-webpack": { - "version": "0.1802.14", - "resolved": "https://registry.npmjs.org/@angular-devkit/build-webpack/-/build-webpack-0.1802.14.tgz", - "integrity": "sha512-cccne0SG4BaQHsKRRZCi/wMLJ7yFXrwvE8w+Kug3HdpJJoyH3FeG386EQuca/azslQlK+c5g4ywSZdXeNkGazA==", + "version": "0.1902.9", + "resolved": "https://registry.npmjs.org/@angular-devkit/build-webpack/-/build-webpack-0.1902.9.tgz", + "integrity": "sha512-iklNoxKgwd54KT5GE0o5SB+0hr6Iu3YSpj9fi23DlLKcWWwFYaKqoRaYcfuL7KdUzunFg7dzB7n6TgYpVHWWJw==", "dev": true, + "license": "MIT", "dependencies": { - "@angular-devkit/architect": "0.1802.14", + "@angular-devkit/architect": "0.1902.9", "rxjs": "7.8.1" }, "engines": { @@ -233,11 +251,22 @@ "webpack-dev-server": "^5.0.2" } }, + "node_modules/@angular-devkit/build-webpack/node_modules/rxjs": { + "version": "7.8.1", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", + "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.1.0" + } + }, "node_modules/@angular-devkit/core": { - "version": "18.2.14", - "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-18.2.14.tgz", - "integrity": "sha512-UGIGOjXuOyCW+5S4tINu7e6LOu738CmTw3h7Ui1I8OzdTIYJcYJrei8sgrwDwOYADRal+p0MeMlnykH3TM5XBA==", + "version": "19.2.9", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-19.2.9.tgz", + "integrity": "sha512-vbTomKnN7H4jaif0hWAECFU2WvRbhfkYWHdlk/JtJM53iIJVL3mKWBRZ0QXITjmgfdIo3c9RcX+wFI7gGqGd6g==", "dev": true, + "license": "MIT", "dependencies": { "ajv": "8.17.1", "ajv-formats": "3.0.1", @@ -252,7 +281,7 @@ "yarn": ">= 1.13.0" }, "peerDependencies": { - "chokidar": "^3.5.2" + "chokidar": "^4.0.0" }, "peerDependenciesMeta": { "chokidar": { @@ -260,15 +289,27 @@ } } }, + "node_modules/@angular-devkit/core/node_modules/rxjs": { + "version": "7.8.1", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", + "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.1.0" + } + }, "node_modules/@angular-devkit/schematics": { - "version": "18.2.14", - "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-18.2.14.tgz", - "integrity": "sha512-mukjZIHHB7gWratq8fZwUq5WZ+1bF4feG/idXr1wgQ+/FqWjs2PP7HDesHVcPymmRulpTyCpB7TNB1O1fgnCpA==", + "version": "19.2.9", + "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-19.2.9.tgz", + "integrity": "sha512-B8FQ4hFsP4Ffh895F9GVvyhgDoZztWnAyYKiM1pyvLSQikzaUZqi9NZnD12HgMALmwm2z36zTzoSNsYFBTHgaw==", "dev": true, + "license": "MIT", + "peer": true, "dependencies": { - "@angular-devkit/core": "18.2.14", + "@angular-devkit/core": "19.2.9", "jsonc-parser": "3.3.1", - "magic-string": "0.30.11", + "magic-string": "0.30.17", "ora": "5.4.1", "rxjs": "7.8.1" }, @@ -278,30 +319,44 @@ "yarn": ">= 1.13.0" } }, + "node_modules/@angular-devkit/schematics/node_modules/rxjs": { + "version": "7.8.1", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", + "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "tslib": "^2.1.0" + } + }, "node_modules/@angular-eslint/builder": { - "version": "18.3.0", - "resolved": "https://registry.npmjs.org/@angular-eslint/builder/-/builder-18.3.0.tgz", - "integrity": "sha512-httEQyqyBw3+0CRtAa7muFxHrauRfkEfk/jmrh5fn2Eiu+I53hAqFPgrwVi1V6AP/kj2zbAiWhd5xM3pMJdoRQ==", + "version": "19.0.0-alpha.4", + "resolved": "https://registry.npmjs.org/@angular-eslint/builder/-/builder-19.0.0-alpha.4.tgz", + "integrity": "sha512-iSDl0Hs2fkJJH0aR/RQ80nmickY7o1xv+mucSw/Gy4YwFDJFU0FiV++1OxhjturuEXt3k+TJ115xe4DJa86BMw==", "dev": true, + "license": "MIT", "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": "*" } }, "node_modules/@angular-eslint/bundled-angular-compiler": { - "version": "18.4.3", - "resolved": "https://registry.npmjs.org/@angular-eslint/bundled-angular-compiler/-/bundled-angular-compiler-18.4.3.tgz", - "integrity": "sha512-zdrA8mR98X+U4YgHzUKmivRU+PxzwOL/j8G7eTOvBuq8GPzsP+hvak+tyxlgeGm9HsvpFj9ERHLtJ0xDUPs8fg==", - "dev": true + "version": "19.0.0-alpha.4", + "resolved": "https://registry.npmjs.org/@angular-eslint/bundled-angular-compiler/-/bundled-angular-compiler-19.0.0-alpha.4.tgz", + "integrity": "sha512-SS2FHqRaGslJzI+cTBNDC7xg/Zx5c0iIXZnpwGa8VjJ/8L82+PlRS+d9CTBhb8tMsR06ifUTK9ym2JQ3VmE2Cg==", + "dev": true, + "license": "MIT" }, "node_modules/@angular-eslint/eslint-plugin": { - "version": "18.4.3", - "resolved": "https://registry.npmjs.org/@angular-eslint/eslint-plugin/-/eslint-plugin-18.4.3.tgz", - "integrity": "sha512-AyJbupiwTBR81P6T59v+aULEnPpZBCBxL2S5QFWfAhNCwWhcof4GihvdK2Z87yhvzDGeAzUFSWl/beJfeFa+PA==", + "version": "19.0.0-alpha.4", + "resolved": "https://registry.npmjs.org/@angular-eslint/eslint-plugin/-/eslint-plugin-19.0.0-alpha.4.tgz", + "integrity": "sha512-IhBeiUohYLsnUrSJ92riSrhfIkGefuXIrGTgBnagn887WFR45/Go5dIIivVfMGdvTg849dtLpBDXZrycHY3QFA==", "dev": true, + "license": "MIT", "dependencies": { - "@angular-eslint/bundled-angular-compiler": "18.4.3", - "@angular-eslint/utils": "18.4.3" + "@angular-eslint/bundled-angular-compiler": "19.0.0-alpha.4", + "@angular-eslint/utils": "19.0.0-alpha.4" }, "peerDependencies": { "@typescript-eslint/utils": "^7.11.0 || ^8.0.0", @@ -310,13 +365,14 @@ } }, "node_modules/@angular-eslint/eslint-plugin-template": { - "version": "18.4.3", - "resolved": "https://registry.npmjs.org/@angular-eslint/eslint-plugin-template/-/eslint-plugin-template-18.4.3.tgz", - "integrity": "sha512-ijGlX2N01ayMXTpeQivOA31AszO8OEbu9ZQUCxnu9AyMMhxyi2q50bujRChAvN9YXQfdQtbxuajxV6+aiWb5BQ==", + "version": "19.0.0-alpha.4", + "resolved": "https://registry.npmjs.org/@angular-eslint/eslint-plugin-template/-/eslint-plugin-template-19.0.0-alpha.4.tgz", + "integrity": "sha512-YiFB+tyTZ/mj/w/5DLJHl9J1ABGaHNhGdXJzagI0ufqyrePR0wTYMIyJpIGWOMHr9E5nYPsVuHn+d08dG1R0aQ==", "dev": true, + "license": "MIT", "dependencies": { - "@angular-eslint/bundled-angular-compiler": "18.4.3", - "@angular-eslint/utils": "18.4.3", + "@angular-eslint/bundled-angular-compiler": "19.0.0-alpha.4", + "@angular-eslint/utils": "19.0.0-alpha.4", "aria-query": "5.3.2", "axobject-query": "4.1.0" }, @@ -328,27 +384,44 @@ } }, "node_modules/@angular-eslint/schematics": { - "version": "18.4.3", - "resolved": "https://registry.npmjs.org/@angular-eslint/schematics/-/schematics-18.4.3.tgz", - "integrity": "sha512-D5maKn5e6n58+8n7jLFLD4g+RGPOPeDSsvPc1sqial5tEKLxAJQJS9WZ28oef3bhkob6C60D+1H0mMmEEVvyVA==", + "version": "19.0.0-alpha.4", + "resolved": "https://registry.npmjs.org/@angular-eslint/schematics/-/schematics-19.0.0-alpha.4.tgz", + "integrity": "sha512-Xv8g2PbNqhm7igTnY3uuY511Fr+FVRBHG+ZHbcUPZT7YDBaFZRJezAzbbPLZ6GdqqhoQJPHdGuEvo22yKhKXag==", "dev": true, + "license": "MIT", "dependencies": { - "@angular-devkit/core": ">= 18.0.0 < 19.0.0", - "@angular-devkit/schematics": ">= 18.0.0 < 19.0.0", - "@angular-eslint/eslint-plugin": "18.4.3", - "@angular-eslint/eslint-plugin-template": "18.4.3", + "@angular-eslint/eslint-plugin": "19.0.0-alpha.4", + "@angular-eslint/eslint-plugin-template": "19.0.0-alpha.4", "ignore": "6.0.2", "semver": "7.6.3", "strip-json-comments": "3.1.1" + }, + "peerDependencies": { + "@angular-devkit/core": ">= 19.0.0 < 20.0.0", + "@angular-devkit/schematics": ">= 19.0.0 < 20.0.0" + } + }, + "node_modules/@angular-eslint/schematics/node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" } }, "node_modules/@angular-eslint/template-parser": { - "version": "18.3.0", - "resolved": "https://registry.npmjs.org/@angular-eslint/template-parser/-/template-parser-18.3.0.tgz", - "integrity": "sha512-1mUquqcnugI4qsoxcYZKZ6WMi6RPelDcJZg2YqGyuaIuhWmi3ZqJZLErSSpjP60+TbYZu7wM8Kchqa1bwJtEaQ==", + "version": "19.0.0-alpha.4", + "resolved": "https://registry.npmjs.org/@angular-eslint/template-parser/-/template-parser-19.0.0-alpha.4.tgz", + "integrity": "sha512-Mvy1kbnqoYBQFFpQtmBB/TkhmmoN97ruSv9xa3mpKzv8JlDdVCkIn7IdqLtzcLwGr+MGcPC7GFPl8o7q12N3BQ==", "dev": true, + "license": "MIT", "dependencies": { - "@angular-eslint/bundled-angular-compiler": "18.3.0", + "@angular-eslint/bundled-angular-compiler": "19.0.0-alpha.4", "eslint-scope": "^8.0.2" }, "peerDependencies": { @@ -356,19 +429,14 @@ "typescript": "*" } }, - "node_modules/@angular-eslint/template-parser/node_modules/@angular-eslint/bundled-angular-compiler": { - "version": "18.3.0", - "resolved": "https://registry.npmjs.org/@angular-eslint/bundled-angular-compiler/-/bundled-angular-compiler-18.3.0.tgz", - "integrity": "sha512-v/59FxUKnMzymVce99gV43huxoqXWMb85aKvzlNvLN+ScDu6ZE4YMiTQNpfapVL2lkxhs0uwB3jH17EYd5TcsA==", - "dev": true - }, "node_modules/@angular-eslint/utils": { - "version": "18.4.3", - "resolved": "https://registry.npmjs.org/@angular-eslint/utils/-/utils-18.4.3.tgz", - "integrity": "sha512-w0bJ9+ELAEiPBSTPPm9bvDngfu1d8JbzUhvs2vU+z7sIz/HMwUZT5S4naypj2kNN0gZYGYrW0lt+HIbW87zTAQ==", + "version": "19.0.0-alpha.4", + "resolved": "https://registry.npmjs.org/@angular-eslint/utils/-/utils-19.0.0-alpha.4.tgz", + "integrity": "sha512-6Pxqs3QqSPBcAkP8I/GYijoPoAmqOYqyQvJGvBWd1oKlA3EqmXSi7uaSUa6nUI6BiA8JEJZTBaSOP6O2oyK25Q==", "dev": true, + "license": "MIT", "dependencies": { - "@angular-eslint/bundled-angular-compiler": "18.4.3" + "@angular-eslint/bundled-angular-compiler": "19.0.0-alpha.4" }, "peerDependencies": { "@typescript-eslint/utils": "^7.11.0 || ^8.0.0", @@ -377,9 +445,10 @@ } }, "node_modules/@angular/animations": { - "version": "18.2.13", - "resolved": "https://registry.npmjs.org/@angular/animations/-/animations-18.2.13.tgz", - "integrity": "sha512-rG5J5Ek5Hg+Tz2NjkNOaG6PupiNK/lPfophXpsR1t/nWujqnMWX2krahD/i6kgD+jNWNKCJCYSOVvCx/BHOtKA==", + "version": "19.2.8", + "resolved": "https://registry.npmjs.org/@angular/animations/-/animations-19.2.8.tgz", + "integrity": "sha512-gKWBusQvjb946uuTXaXWzkEfLdTiy9GUNZ9okF3yolv+aoW0D8AM9mVvTX1xdqAV3xuIxRXRbkWG7BR+p8xVzQ==", + "license": "MIT", "dependencies": { "tslib": "^2.3.0" }, @@ -387,55 +456,65 @@ "node": "^18.19.1 || ^20.11.1 || >=22.0.0" }, "peerDependencies": { - "@angular/core": "18.2.13" + "@angular/common": "19.2.8", + "@angular/core": "19.2.8" } }, "node_modules/@angular/build": { - "version": "18.2.14", - "resolved": "https://registry.npmjs.org/@angular/build/-/build-18.2.14.tgz", - "integrity": "sha512-9g24Oe/ZLULacW3hEpRCjSZIJPJTzN5BeFbA27epSV5NsrQOoeUGsEpRs90Zmt6eReO0fW1BGshWRoZtpSedcw==", + "version": "19.2.9", + "resolved": "https://registry.npmjs.org/@angular/build/-/build-19.2.9.tgz", + "integrity": "sha512-hrRhSdY98wGQ/jrpT3K73/Ii5FadQEJFcHy+ockqP2Xh7pXOwhGFc+D0ks4AdHea+pHtNbIb/qPd+UvR5izY3Q==", "dev": true, + "license": "MIT", "dependencies": { "@ampproject/remapping": "2.3.0", - "@angular-devkit/architect": "0.1802.14", - "@babel/core": "7.25.2", - "@babel/helper-annotate-as-pure": "7.24.7", + "@angular-devkit/architect": "0.1902.9", + "@babel/core": "7.26.10", + "@babel/helper-annotate-as-pure": "7.25.9", "@babel/helper-split-export-declaration": "7.24.7", - "@babel/plugin-syntax-import-attributes": "7.24.7", - "@inquirer/confirm": "3.1.22", - "@vitejs/plugin-basic-ssl": "1.1.0", + "@babel/plugin-syntax-import-attributes": "7.26.0", + "@inquirer/confirm": "5.1.6", + "@vitejs/plugin-basic-ssl": "1.2.0", + "beasties": "0.3.2", "browserslist": "^4.23.0", - "critters": "0.0.24", - "esbuild": "0.23.0", - "fast-glob": "3.3.2", - "https-proxy-agent": "7.0.5", - "listr2": "8.2.4", - "lmdb": "3.0.13", - "magic-string": "0.30.11", - "mrmime": "2.0.0", + "esbuild": "0.25.1", + "fast-glob": "3.3.3", + "https-proxy-agent": "7.0.6", + "istanbul-lib-instrument": "6.0.3", + "listr2": "8.2.5", + "magic-string": "0.30.17", + "mrmime": "2.0.1", "parse5-html-rewriting-stream": "7.0.0", "picomatch": "4.0.2", - "piscina": "4.6.1", - "rollup": "4.22.4", - "sass": "1.77.6", - "semver": "7.6.3", - "vite": "5.4.14", - "watchpack": "2.4.1" + "piscina": "4.8.0", + "rollup": "4.34.8", + "sass": "1.85.0", + "semver": "7.7.1", + "source-map-support": "0.5.21", + "vite": "6.2.6", + "watchpack": "2.4.2" }, "engines": { "node": "^18.19.1 || ^20.11.1 || >=22.0.0", "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", "yarn": ">= 1.13.0" }, + "optionalDependencies": { + "lmdb": "3.2.6" + }, "peerDependencies": { - "@angular/compiler-cli": "^18.0.0", - "@angular/localize": "^18.0.0", - "@angular/platform-server": "^18.0.0", - "@angular/service-worker": "^18.0.0", + "@angular/compiler": "^19.0.0 || ^19.2.0-next.0", + "@angular/compiler-cli": "^19.0.0 || ^19.2.0-next.0", + "@angular/localize": "^19.0.0 || ^19.2.0-next.0", + "@angular/platform-server": "^19.0.0 || ^19.2.0-next.0", + "@angular/service-worker": "^19.0.0 || ^19.2.0-next.0", + "@angular/ssr": "^19.2.9", + "karma": "^6.4.0", "less": "^4.2.0", + "ng-packagr": "^19.0.0 || ^19.2.0-next.0", "postcss": "^8.4.0", - "tailwindcss": "^2.0.0 || ^3.0.0", - "typescript": ">=5.4 <5.6" + "tailwindcss": "^2.0.0 || ^3.0.0 || ^4.0.0", + "typescript": ">=5.5 <5.9" }, "peerDependenciesMeta": { "@angular/localize": { @@ -447,9 +526,18 @@ "@angular/service-worker": { "optional": true }, + "@angular/ssr": { + "optional": true + }, + "karma": { + "optional": true + }, "less": { "optional": true }, + "ng-packagr": { + "optional": true + }, "postcss": { "optional": true }, @@ -458,41 +546,142 @@ } } }, - "node_modules/@angular/cdk": { - "version": "18.2.14", - "resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-18.2.14.tgz", - "integrity": "sha512-vDyOh1lwjfVk9OqoroZAP8pf3xxKUvyl+TVR8nJxL4c5fOfUFkD7l94HaanqKSRwJcI2xiztuu92IVoHn8T33Q==", + "node_modules/@angular/build/node_modules/vite": { + "version": "6.2.6", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.2.6.tgz", + "integrity": "sha512-9xpjNl3kR4rVDZgPNdTL0/c6ao4km69a/2ihNQbcANz8RuCOK3hQBmLSJf3bRKVQjVMda+YvizNE8AwvogcPbw==", + "dev": true, + "license": "MIT", "dependencies": { - "tslib": "^2.3.0" + "esbuild": "^0.25.0", + "postcss": "^8.5.3", + "rollup": "^4.30.1" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" }, "optionalDependencies": { - "parse5": "^7.1.2" + "fsevents": "~2.3.3" }, "peerDependencies": { - "@angular/common": "^18.0.0 || ^19.0.0", - "@angular/core": "^18.0.0 || ^19.0.0", - "rxjs": "^6.5.3 || ^7.4.0" + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "jiti": ">=1.21.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } } }, - "node_modules/@angular/cli": { - "version": "18.2.14", - "resolved": "https://registry.npmjs.org/@angular/cli/-/cli-18.2.14.tgz", - "integrity": "sha512-kWgRRQtJPkr8iwN7DMbTi3sXOnv7H5QhbU/GgD3nNX3D8YCSPmnby4PAE/P3wn7FsIK9JsSchsCt7MZ37Urh9A==", + "node_modules/@angular/build/node_modules/vite/node_modules/postcss": { + "version": "8.5.3", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.3.tgz", + "integrity": "sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==", "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.8", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/@angular/cdk": { + "version": "19.2.11", + "resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-19.2.11.tgz", + "integrity": "sha512-G568yWIJlnsuS563WxvCofmxc1405+wRQvDGQ32+qWOblJScFkHgr4jeDkZGcyt/r8OudaW0H0/rNeg1dzdnIQ==", + "license": "MIT", "dependencies": { - "@angular-devkit/architect": "0.1802.14", - "@angular-devkit/core": "18.2.14", - "@angular-devkit/schematics": "18.2.14", - "@inquirer/prompts": "5.3.8", - "@listr2/prompt-adapter-inquirer": "2.0.15", - "@schematics/angular": "18.2.14", + "parse5": "^7.1.2", + "tslib": "^2.3.0" + }, + "peerDependencies": { + "@angular/common": "^19.0.0 || ^20.0.0", + "@angular/core": "^19.0.0 || ^20.0.0", + "rxjs": "^6.5.3 || ^7.4.0" + } + }, + "node_modules/@angular/cli": { + "version": "19.0.7", + "resolved": "https://registry.npmjs.org/@angular/cli/-/cli-19.0.7.tgz", + "integrity": "sha512-y6C4B4XdiZwe2+OADLWXyKqUVvW/XDzTuJ2mZ5PhTnSiiXDN4zRWId1F5wA8ve8vlbUKApPHXRQuaqiQJmA24g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@angular-devkit/architect": "0.1900.7", + "@angular-devkit/core": "19.0.7", + "@angular-devkit/schematics": "19.0.7", + "@inquirer/prompts": "7.1.0", + "@listr2/prompt-adapter-inquirer": "2.0.18", + "@schematics/angular": "19.0.7", "@yarnpkg/lockfile": "1.1.0", - "ini": "4.1.3", + "ini": "5.0.0", "jsonc-parser": "3.3.1", - "listr2": "8.2.4", - "npm-package-arg": "11.0.3", - "npm-pick-manifest": "9.1.0", - "pacote": "18.0.6", + "listr2": "8.2.5", + "npm-package-arg": "12.0.0", + "npm-pick-manifest": "10.0.0", + "pacote": "20.0.0", "resolve": "1.22.8", "semver": "7.6.3", "symbol-observable": "4.0.0", @@ -507,87 +696,107 @@ "yarn": ">= 1.13.0" } }, - "node_modules/@angular/common": { - "version": "18.2.13", - "resolved": "https://registry.npmjs.org/@angular/common/-/common-18.2.13.tgz", - "integrity": "sha512-4ZqrNp1PoZo7VNvW+sbSc2CB2axP1sCH2wXl8B0wdjsj8JY1hF1OhuugwhpAHtGxqewed2kCXayE+ZJqSTV4jw==", + "node_modules/@angular/cli/node_modules/@angular-devkit/architect": { + "version": "0.1900.7", + "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1900.7.tgz", + "integrity": "sha512-3dRV0IB+MbNYbAGbYEFMcABkMphqcTvn5MG79dQkwcf2a9QZxCq2slwf/rIleWoDUcFm9r1NnVPYrTYNYJaqQg==", + "dev": true, + "license": "MIT", "dependencies": { - "tslib": "^2.3.0" + "@angular-devkit/core": "19.0.7", + "rxjs": "7.8.1" }, "engines": { - "node": "^18.19.1 || ^20.11.1 || >=22.0.0" - }, - "peerDependencies": { - "@angular/core": "18.2.13", - "rxjs": "^6.5.3 || ^7.4.0" + "node": "^18.19.1 || ^20.11.1 || >=22.0.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" } }, - "node_modules/@angular/compiler": { - "version": "18.2.13", - "resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-18.2.13.tgz", - "integrity": "sha512-TzWcrkopyjFF+WeDr2cRe8CcHjU72KfYV3Sm2TkBkcXrkYX5sDjGWrBGrG3hRB4e4okqchrOCvm1MiTdy2vKMA==", + "node_modules/@angular/cli/node_modules/@angular-devkit/core": { + "version": "19.0.7", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-19.0.7.tgz", + "integrity": "sha512-VyuORSitT6LIaGUEF0KEnv2TwNaeWl6L3/4L4stok0BJ23B4joVca2DYVcrLC1hSzz8V4dwVgSlbNIgjgGdVpg==", + "dev": true, + "license": "MIT", "dependencies": { - "tslib": "^2.3.0" + "ajv": "8.17.1", + "ajv-formats": "3.0.1", + "jsonc-parser": "3.3.1", + "picomatch": "4.0.2", + "rxjs": "7.8.1", + "source-map": "0.7.4" }, "engines": { - "node": "^18.19.1 || ^20.11.1 || >=22.0.0" + "node": "^18.19.1 || ^20.11.1 || >=22.0.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" }, "peerDependencies": { - "@angular/core": "18.2.13" + "chokidar": "^4.0.0" }, "peerDependenciesMeta": { - "@angular/core": { + "chokidar": { "optional": true } } }, - "node_modules/@angular/compiler-cli": { - "version": "18.2.13", - "resolved": "https://registry.npmjs.org/@angular/compiler-cli/-/compiler-cli-18.2.13.tgz", - "integrity": "sha512-DBSh4AQwkiJDSiVvJATRmjxf6wyUs9pwQLgaFdSlfuTRO+sdb0J2z1r3BYm8t0IqdoyXzdZq2YCH43EmyvD71g==", + "node_modules/@angular/cli/node_modules/@angular-devkit/schematics": { + "version": "19.0.7", + "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-19.0.7.tgz", + "integrity": "sha512-BHXQv6kMc9xo4TH9lhwMv8nrZXHkLioQvLun2qYjwvOsyzt3qd+sUM9wpHwbG6t+01+FIQ05iNN9ox+Cvpndgg==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/core": "7.25.2", - "@jridgewell/sourcemap-codec": "^1.4.14", - "chokidar": "^4.0.0", - "convert-source-map": "^1.5.1", - "reflect-metadata": "^0.2.0", - "semver": "^7.0.0", - "tslib": "^2.3.0", - "yargs": "^17.2.1" - }, - "bin": { - "ng-xi18n": "bundles/src/bin/ng_xi18n.js", - "ngc": "bundles/src/bin/ngc.js", - "ngcc": "bundles/ngcc/index.js" + "@angular-devkit/core": "19.0.7", + "jsonc-parser": "3.3.1", + "magic-string": "0.30.12", + "ora": "5.4.1", + "rxjs": "7.8.1" }, "engines": { - "node": "^18.19.1 || ^20.11.1 || >=22.0.0" - }, - "peerDependencies": { - "@angular/compiler": "18.2.13", - "typescript": ">=5.4 <5.6" + "node": "^18.19.1 || ^20.11.1 || >=22.0.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" } }, - "node_modules/@angular/core": { - "version": "18.2.13", - "resolved": "https://registry.npmjs.org/@angular/core/-/core-18.2.13.tgz", - "integrity": "sha512-8mbWHMgO95OuFV1Ejy4oKmbe9NOJ3WazQf/f7wks8Bck7pcihd0IKhlPBNjFllbF5o+04EYSwFhEtvEgjMDClA==", + "node_modules/@angular/cli/node_modules/magic-string": { + "version": "0.30.12", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.12.tgz", + "integrity": "sha512-Ea8I3sQMVXr8JhN4z+H/d8zwo+tYDgHE9+5G4Wnrwhs0gaK9fXTKx0Tw5Xwsd/bCPTTZNRAdpyzvoeORe9LYpw==", + "dev": true, + "license": "MIT", "dependencies": { - "tslib": "^2.3.0" + "@jridgewell/sourcemap-codec": "^1.5.0" + } + }, + "node_modules/@angular/cli/node_modules/rxjs": { + "version": "7.8.1", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", + "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/@angular/cli/node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" }, "engines": { - "node": "^18.19.1 || ^20.11.1 || >=22.0.0" - }, - "peerDependencies": { - "rxjs": "^6.5.3 || ^7.4.0", - "zone.js": "~0.14.10" + "node": ">=10" } }, - "node_modules/@angular/forms": { - "version": "18.2.13", - "resolved": "https://registry.npmjs.org/@angular/forms/-/forms-18.2.13.tgz", - "integrity": "sha512-A67D867fu3DSBhdLWWZl/F5pr7v2+dRM2u3U7ZJ0ewh4a+sv+0yqWdJW+a8xIoiHxS+btGEJL2qAKJiH+MCFfg==", + "node_modules/@angular/common": { + "version": "19.2.8", + "resolved": "https://registry.npmjs.org/@angular/common/-/common-19.2.8.tgz", + "integrity": "sha512-SnW+/amz1Mtni9125xlzPZ5MU+wSzUepc9G5jRnL0q9vrFglRWa3BEW3GxVurfbdnf6FleroZ7fZCZFAfREw7Q==", + "license": "MIT", "dependencies": { "tslib": "^2.3.0" }, @@ -595,54 +804,177 @@ "node": "^18.19.1 || ^20.11.1 || >=22.0.0" }, "peerDependencies": { - "@angular/common": "18.2.13", - "@angular/core": "18.2.13", - "@angular/platform-browser": "18.2.13", + "@angular/core": "19.2.8", "rxjs": "^6.5.3 || ^7.4.0" } }, - "node_modules/@angular/material": { - "version": "18.2.14", - "resolved": "https://registry.npmjs.org/@angular/material/-/material-18.2.14.tgz", - "integrity": "sha512-28pxzJP49Mymt664WnCtPkKeg7kXUsQKTKGf/Kl95rNTEdTJLbnlcc8wV0rT0yQNR7kXgpfBnG7h0ETLv/iu5Q==", + "node_modules/@angular/compiler": { + "version": "19.2.8", + "resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-19.2.8.tgz", + "integrity": "sha512-HBtt96X09XFatHAnkquFYbcD3aQSvuYoqqhCV5OLkhAwHmvr3BGyHx/EBZ5JGOfCNOzCupoQmOBF+nh5LKwkeQ==", + "license": "MIT", "dependencies": { "tslib": "^2.3.0" }, - "peerDependencies": { - "@angular/animations": "^18.0.0 || ^19.0.0", - "@angular/cdk": "18.2.14", - "@angular/common": "^18.0.0 || ^19.0.0", - "@angular/core": "^18.0.0 || ^19.0.0", - "@angular/forms": "^18.0.0 || ^19.0.0", - "@angular/platform-browser": "^18.0.0 || ^19.0.0", - "rxjs": "^6.5.3 || ^7.4.0" + "engines": { + "node": "^18.19.1 || ^20.11.1 || >=22.0.0" } }, - "node_modules/@angular/platform-browser": { - "version": "18.2.13", - "resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-18.2.13.tgz", - "integrity": "sha512-tu7ZzY6qD3ATdWFzcTcsAKe7M6cJeWbT/4/bF9unyGO3XBPcNYDKoiz10+7ap2PUd0fmPwvuvTvSNJiFEBnB8Q==", + "node_modules/@angular/compiler-cli": { + "version": "19.2.8", + "resolved": "https://registry.npmjs.org/@angular/compiler-cli/-/compiler-cli-19.2.8.tgz", + "integrity": "sha512-gq/sc3D3m6aKmhdSTTzzD59wfQcVjIZ8dgJoPW7pOcmPVQL1N8syjv+quHySfSJlBkbs5dQ0P4Kk0yvxRw9S7g==", + "dev": true, + "license": "MIT", "dependencies": { - "tslib": "^2.3.0" + "@babel/core": "7.26.9", + "@jridgewell/sourcemap-codec": "^1.4.14", + "chokidar": "^4.0.0", + "convert-source-map": "^1.5.1", + "reflect-metadata": "^0.2.0", + "semver": "^7.0.0", + "tslib": "^2.3.0", + "yargs": "^17.2.1" + }, + "bin": { + "ng-xi18n": "bundles/src/bin/ng_xi18n.js", + "ngc": "bundles/src/bin/ngc.js", + "ngcc": "bundles/ngcc/index.js" }, "engines": { "node": "^18.19.1 || ^20.11.1 || >=22.0.0" }, "peerDependencies": { - "@angular/animations": "18.2.13", - "@angular/common": "18.2.13", - "@angular/core": "18.2.13" - }, - "peerDependenciesMeta": { - "@angular/animations": { - "optional": true - } + "@angular/compiler": "19.2.8", + "typescript": ">=5.5 <5.9" } }, - "node_modules/@angular/platform-browser-dynamic": { - "version": "18.2.13", - "resolved": "https://registry.npmjs.org/@angular/platform-browser-dynamic/-/platform-browser-dynamic-18.2.13.tgz", - "integrity": "sha512-kbQCf9+8EpuJC7buBxhSiwBtXvjAwAKh6MznD6zd2pyCYqfY6gfRCZQRtK59IfgVtKmEONWI9grEyNIRoTmqJg==", + "node_modules/@angular/compiler-cli/node_modules/@babel/core": { + "version": "7.26.9", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.26.9.tgz", + "integrity": "sha512-lWBYIrF7qK5+GjY5Uy+/hEgp8OJWOD/rpy74GplYRhEauvbHDeFB8t5hPOZxCZ0Oxf4Cc36tK51/l3ymJysrKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.26.2", + "@babel/generator": "^7.26.9", + "@babel/helper-compilation-targets": "^7.26.5", + "@babel/helper-module-transforms": "^7.26.0", + "@babel/helpers": "^7.26.9", + "@babel/parser": "^7.26.9", + "@babel/template": "^7.26.9", + "@babel/traverse": "^7.26.9", + "@babel/types": "^7.26.9", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@angular/compiler-cli/node_modules/@babel/core/node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@angular/compiler-cli/node_modules/@babel/core/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@angular/core": { + "version": "19.2.8", + "resolved": "https://registry.npmjs.org/@angular/core/-/core-19.2.8.tgz", + "integrity": "sha512-iNISGgLr+nBzEaGbfzRCOVfV3T66gbEu+Ee4VCnEqifU7Er6fnvn+oFfHo3gNKHrCdicrbyb2oKAmeOJynKbsA==", + "license": "MIT", + "dependencies": { + "tslib": "^2.3.0" + }, + "engines": { + "node": "^18.19.1 || ^20.11.1 || >=22.0.0" + }, + "peerDependencies": { + "rxjs": "^6.5.3 || ^7.4.0", + "zone.js": "~0.15.0" + } + }, + "node_modules/@angular/forms": { + "version": "19.2.8", + "resolved": "https://registry.npmjs.org/@angular/forms/-/forms-19.2.8.tgz", + "integrity": "sha512-4q/6ad8YZPixxLhDwOxm4pQO3ekwGriOTVB0pMb9FdpvjOUSdDTM08o8ToHvu6MBbZjHzLs8+xkMw9QCd55x/w==", + "license": "MIT", + "dependencies": { + "tslib": "^2.3.0" + }, + "engines": { + "node": "^18.19.1 || ^20.11.1 || >=22.0.0" + }, + "peerDependencies": { + "@angular/common": "19.2.8", + "@angular/core": "19.2.8", + "@angular/platform-browser": "19.2.8", + "rxjs": "^6.5.3 || ^7.4.0" + } + }, + "node_modules/@angular/material": { + "version": "19.2.11", + "resolved": "https://registry.npmjs.org/@angular/material/-/material-19.2.11.tgz", + "integrity": "sha512-0OWwv55Il25mit7oGTloMeKVi0v/q1tr13wUJj0KJOcvICA6JCEW6VEc9zqYmkMPstDCx96cSJgPKxkHjKYyqg==", + "license": "MIT", + "dependencies": { + "tslib": "^2.3.0" + }, + "peerDependencies": { + "@angular/cdk": "19.2.11", + "@angular/common": "^19.0.0 || ^20.0.0", + "@angular/core": "^19.0.0 || ^20.0.0", + "@angular/forms": "^19.0.0 || ^20.0.0", + "@angular/platform-browser": "^19.0.0 || ^20.0.0", + "rxjs": "^6.5.3 || ^7.4.0" + } + }, + "node_modules/@angular/platform-browser": { + "version": "19.2.8", + "resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-19.2.8.tgz", + "integrity": "sha512-3O69vMAq/ki13YX8hWBUs1R6iwS1GmkcHWu5fIUU7rjSuhGfD60nASqRBYZiJb68eUom//T544KavOvfAl1PzQ==", + "license": "MIT", + "dependencies": { + "tslib": "^2.3.0" + }, + "engines": { + "node": "^18.19.1 || ^20.11.1 || >=22.0.0" + }, + "peerDependencies": { + "@angular/animations": "19.2.8", + "@angular/common": "19.2.8", + "@angular/core": "19.2.8" + }, + "peerDependenciesMeta": { + "@angular/animations": { + "optional": true + } + } + }, + "node_modules/@angular/platform-browser-dynamic": { + "version": "19.2.8", + "resolved": "https://registry.npmjs.org/@angular/platform-browser-dynamic/-/platform-browser-dynamic-19.2.8.tgz", + "integrity": "sha512-Vwh53CGCC/I3DQ/nqWxNTKk052CRHv46H6KjfWBsD8vOVTJoQf2HXwEbDKntpmJ0K4MtMdIdbpwXieUMLyfmXA==", + "license": "MIT", "dependencies": { "tslib": "^2.3.0" }, @@ -650,16 +982,17 @@ "node": "^18.19.1 || ^20.11.1 || >=22.0.0" }, "peerDependencies": { - "@angular/common": "18.2.13", - "@angular/compiler": "18.2.13", - "@angular/core": "18.2.13", - "@angular/platform-browser": "18.2.13" + "@angular/common": "19.2.8", + "@angular/compiler": "19.2.8", + "@angular/core": "19.2.8", + "@angular/platform-browser": "19.2.8" } }, "node_modules/@angular/router": { - "version": "18.2.13", - "resolved": "https://registry.npmjs.org/@angular/router/-/router-18.2.13.tgz", - "integrity": "sha512-VKmfgi/r/CkyBq9nChQ/ptmfu0JT/8ONnLVJ5H+SkFLRYJcIRyHLKjRihMCyVm6xM5yktOdCaW73NTQrFz7+bg==", + "version": "19.2.8", + "resolved": "https://registry.npmjs.org/@angular/router/-/router-19.2.8.tgz", + "integrity": "sha512-aZenxUzrz8idGmw0jsVaPFY8EAPOYcOHmv9mDljzAhJZHaSX/r0iVasnjf5qUkTb7ElpRXppS4wXPNNGKTrXZA==", + "license": "MIT", "dependencies": { "tslib": "^2.3.0" }, @@ -667,9 +1000,9 @@ "node": "^18.19.1 || ^20.11.1 || >=22.0.0" }, "peerDependencies": { - "@angular/common": "18.2.13", - "@angular/core": "18.2.13", - "@angular/platform-browser": "18.2.13", + "@angular/common": "19.2.8", + "@angular/core": "19.2.8", + "@angular/platform-browser": "19.2.8", "rxjs": "^6.5.3 || ^7.4.0" } }, @@ -678,6 +1011,7 @@ "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.26.2.tgz", "integrity": "sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==", "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-validator-identifier": "^7.25.9", "js-tokens": "^4.0.0", @@ -688,30 +1022,32 @@ } }, "node_modules/@babel/compat-data": { - "version": "7.26.5", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.26.5.tgz", - "integrity": "sha512-XvcZi1KWf88RVbF9wn8MN6tYFloU5qX8KjuF3E1PVBmJ9eypXfs4GRiJwLuTZL0iSnJUKn1BFPa5BPZZJyFzPg==", + "version": "7.26.8", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.26.8.tgz", + "integrity": "sha512-oH5UPLMWR3L2wEFLnFJ1TZXqHufiTKAiLfqw5zkhS4dKXLJ10yVztfil/twG8EDTA4F/tvVNw9nOl4ZMslB8rQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/core": { - "version": "7.25.2", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.25.2.tgz", - "integrity": "sha512-BBt3opiCOxUr9euZ5/ro/Xv8/V7yJ5bjYMqG/C1YAo8MIKAnumZalCN+msbci3Pigy4lIQfPUpfMM27HMGaYEA==", + "version": "7.26.10", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.26.10.tgz", + "integrity": "sha512-vMqyb7XCDMPvJFFOaT9kxtiRh42GwlZEg1/uIgtZshS5a/8OaduUfCi7kynKgc3Tw/6Uo2D+db9qBttghhmxwQ==", "dev": true, + "license": "MIT", "dependencies": { "@ampproject/remapping": "^2.2.0", - "@babel/code-frame": "^7.24.7", - "@babel/generator": "^7.25.0", - "@babel/helper-compilation-targets": "^7.25.2", - "@babel/helper-module-transforms": "^7.25.2", - "@babel/helpers": "^7.25.0", - "@babel/parser": "^7.25.0", - "@babel/template": "^7.25.0", - "@babel/traverse": "^7.25.2", - "@babel/types": "^7.25.2", + "@babel/code-frame": "^7.26.2", + "@babel/generator": "^7.26.10", + "@babel/helper-compilation-targets": "^7.26.5", + "@babel/helper-module-transforms": "^7.26.0", + "@babel/helpers": "^7.26.10", + "@babel/parser": "^7.26.10", + "@babel/template": "^7.26.9", + "@babel/traverse": "^7.26.10", + "@babel/types": "^7.26.10", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", @@ -730,51 +1066,57 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/@babel/core/node_modules/semver": { "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true, + "license": "ISC", "bin": { "semver": "bin/semver.js" } }, "node_modules/@babel/generator": { - "version": "7.25.0", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.25.0.tgz", - "integrity": "sha512-3LEEcj3PVW8pW2R1SR1M89g/qrYk/m/mB/tLqn7dn4sbBUQyTqnlod+II2U4dqiGtUmkcnAmkMDralTFZttRiw==", + "version": "7.26.10", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.26.10.tgz", + "integrity": "sha512-rRHT8siFIXQrAYOYqZQVsAr8vJ+cBNqcVAY6m5V8/4QqzaPl+zDBe6cLEPRDuNOUf3ww8RfJVlOyQMoSI+5Ang==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/types": "^7.25.0", + "@babel/parser": "^7.26.10", + "@babel/types": "^7.26.10", "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.25", - "jsesc": "^2.5.1" + "jsesc": "^3.0.2" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-annotate-as-pure": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.24.7.tgz", - "integrity": "sha512-BaDeOonYvhdKw+JoMVkAixAAJzG2jVPIwWoKBPdYuY9b452e2rPuI9QPYh3KpofZ3pW2akOmwZLOiOsHMiqRAg==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.25.9.tgz", + "integrity": "sha512-gv7320KBUFJz1RnylIg5WWYPRXKZ884AGkYpgpWW02TH66Dl+HaC1t1CKd0z3R4b6hdYEcmrNZHUmfCP+1u3/g==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/types": "^7.24.7" + "@babel/types": "^7.25.9" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-compilation-targets": { - "version": "7.26.5", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.26.5.tgz", - "integrity": "sha512-IXuyn5EkouFJscIDuFF5EsiSolseme1s0CZB+QxVugqJLYmKdxI1VfIBOst0SUu4rnk2Z7kqTwmoO1lp3HIfnA==", + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.0.tgz", + "integrity": "sha512-LVk7fbXml0H2xH34dFzKQ7TDZ2G4/rVTOrq9V+icbbadjbVxxeFeDsNHv2SrZeWoA+6ZiTyWYWtScEIW07EAcA==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/compat-data": "^7.26.5", + "@babel/compat-data": "^7.26.8", "@babel/helper-validator-option": "^7.25.9", "browserslist": "^4.24.0", "lru-cache": "^5.1.1", @@ -789,22 +1131,24 @@ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true, + "license": "ISC", "bin": { "semver": "bin/semver.js" } }, "node_modules/@babel/helper-create-class-features-plugin": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.25.9.tgz", - "integrity": "sha512-UTZQMvt0d/rSz6KI+qdu7GQze5TIajwTS++GUozlw8VBJDEOAqSXwm1WvmYEZwqdqSGQshRocPDqrt4HBZB3fQ==", + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.27.0.tgz", + "integrity": "sha512-vSGCvMecvFCd/BdpGlhpXYNhhC4ccxyvQWpbGL4CWbvfEoLFWUZuSuf7s9Aw70flgQF+6vptvgK2IfOnKlRmBg==", "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-annotate-as-pure": "^7.25.9", "@babel/helper-member-expression-to-functions": "^7.25.9", "@babel/helper-optimise-call-expression": "^7.25.9", - "@babel/helper-replace-supers": "^7.25.9", + "@babel/helper-replace-supers": "^7.26.5", "@babel/helper-skip-transparent-expression-wrappers": "^7.25.9", - "@babel/traverse": "^7.25.9", + "@babel/traverse": "^7.27.0", "semver": "^6.3.1" }, "engines": { @@ -814,32 +1158,22 @@ "@babel/core": "^7.0.0" } }, - "node_modules/@babel/helper-create-class-features-plugin/node_modules/@babel/helper-annotate-as-pure": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.25.9.tgz", - "integrity": "sha512-gv7320KBUFJz1RnylIg5WWYPRXKZ884AGkYpgpWW02TH66Dl+HaC1t1CKd0z3R4b6hdYEcmrNZHUmfCP+1u3/g==", - "dev": true, - "dependencies": { - "@babel/types": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" - } - }, "node_modules/@babel/helper-create-class-features-plugin/node_modules/semver": { "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true, + "license": "ISC", "bin": { "semver": "bin/semver.js" } }, "node_modules/@babel/helper-create-regexp-features-plugin": { - "version": "7.26.3", - "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.26.3.tgz", - "integrity": "sha512-G7ZRb40uUgdKOQqPLjfD12ZmGA54PzqDFUv2BKImnC9QIfGhIHKvVML0oN8IUiDq4iRqpq74ABpvOaerfWdong==", + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.27.0.tgz", + "integrity": "sha512-fO8l08T76v48BhpNRW/nQ0MxfnSdoSKUJBMjubOAYffsVuGG5qOfMq7N6Es7UJvi7Y8goXXo07EfcHZXDPuELQ==", "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-annotate-as-pure": "^7.25.9", "regexpu-core": "^6.2.0", @@ -852,32 +1186,22 @@ "@babel/core": "^7.0.0" } }, - "node_modules/@babel/helper-create-regexp-features-plugin/node_modules/@babel/helper-annotate-as-pure": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.25.9.tgz", - "integrity": "sha512-gv7320KBUFJz1RnylIg5WWYPRXKZ884AGkYpgpWW02TH66Dl+HaC1t1CKd0z3R4b6hdYEcmrNZHUmfCP+1u3/g==", - "dev": true, - "dependencies": { - "@babel/types": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" - } - }, "node_modules/@babel/helper-create-regexp-features-plugin/node_modules/semver": { "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true, + "license": "ISC", "bin": { "semver": "bin/semver.js" } }, "node_modules/@babel/helper-define-polyfill-provider": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.3.tgz", - "integrity": "sha512-HK7Bi+Hj6H+VTHA3ZvBis7V/6hu9QuTrnMXNybfUf2iiuU/N97I8VjB+KbhFF8Rld/Lx5MzoCwPCpPjfK+n8Cg==", + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.4.tgz", + "integrity": "sha512-jljfR1rGnXXNWnmQg2K3+bvhkxB51Rl32QRaOTuwwjviGrHzIbSc8+x9CpraDtbT7mfyjXObULP4w/adunNwAw==", "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-compilation-targets": "^7.22.6", "@babel/helper-plugin-utils": "^7.22.5", @@ -894,6 +1218,7 @@ "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.25.9.tgz", "integrity": "sha512-wbfdZ9w5vk0C0oyHqAJbc62+vet5prjj01jjJ8sKn3j9h3MQQlflEdXYvuqRWjHnM12coDEqiC1IRCi0U/EKwQ==", "dev": true, + "license": "MIT", "dependencies": { "@babel/traverse": "^7.25.9", "@babel/types": "^7.25.9" @@ -907,6 +1232,7 @@ "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.25.9.tgz", "integrity": "sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw==", "dev": true, + "license": "MIT", "dependencies": { "@babel/traverse": "^7.25.9", "@babel/types": "^7.25.9" @@ -920,6 +1246,7 @@ "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.26.0.tgz", "integrity": "sha512-xO+xu6B5K2czEnQye6BHA7DolFFmS3LB7stHZFaOLb1pAwO1HWLS8fXA+eh0A2yIvltPVmx3eNNDBJA2SLHXFw==", "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-module-imports": "^7.25.9", "@babel/helper-validator-identifier": "^7.25.9", @@ -937,6 +1264,7 @@ "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.25.9.tgz", "integrity": "sha512-FIpuNaz5ow8VyrYcnXQTDRGvV6tTjkNtCK/RYNDXGSLlUD6cBuQTSw43CShGxjvfBTfcUA/r6UhUCbtYqkhcuQ==", "dev": true, + "license": "MIT", "dependencies": { "@babel/types": "^7.25.9" }, @@ -949,6 +1277,7 @@ "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.26.5.tgz", "integrity": "sha512-RS+jZcRdZdRFzMyr+wcsaqOmld1/EqTghfaBGQQd/WnRdzdlvSZ//kF7U8VQTxf1ynZ4cjUcYgjVGx13ewNPMg==", "dev": true, + "license": "MIT", "engines": { "node": ">=6.9.0" } @@ -958,6 +1287,7 @@ "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.25.9.tgz", "integrity": "sha512-IZtukuUeBbhgOcaW2s06OXTzVNJR0ybm4W5xC1opWFFJMZbwRj5LCk+ByYH7WdZPZTt8KnFwA8pvjN2yqcPlgw==", "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-annotate-as-pure": "^7.25.9", "@babel/helper-wrap-function": "^7.25.9", @@ -970,23 +1300,12 @@ "@babel/core": "^7.0.0" } }, - "node_modules/@babel/helper-remap-async-to-generator/node_modules/@babel/helper-annotate-as-pure": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.25.9.tgz", - "integrity": "sha512-gv7320KBUFJz1RnylIg5WWYPRXKZ884AGkYpgpWW02TH66Dl+HaC1t1CKd0z3R4b6hdYEcmrNZHUmfCP+1u3/g==", - "dev": true, - "dependencies": { - "@babel/types": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" - } - }, "node_modules/@babel/helper-replace-supers": { "version": "7.26.5", "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.26.5.tgz", "integrity": "sha512-bJ6iIVdYX1YooY2X7w1q6VITt+LnUILtNk7zT78ykuwStx8BauCzxvFqFaHjOpW1bVnSUM1PN1f0p5P21wHxvg==", "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-member-expression-to-functions": "^7.25.9", "@babel/helper-optimise-call-expression": "^7.25.9", @@ -1004,6 +1323,7 @@ "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.25.9.tgz", "integrity": "sha512-K4Du3BFa3gvyhzgPcntrkDgZzQaq6uozzcpGbOO1OEJaI+EJdqWIMTLgFgQf6lrfiDFo5FU+BxKepI9RmZqahA==", "dev": true, + "license": "MIT", "dependencies": { "@babel/traverse": "^7.25.9", "@babel/types": "^7.25.9" @@ -1017,6 +1337,7 @@ "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.24.7.tgz", "integrity": "sha512-oy5V7pD+UvfkEATUKvIjvIAH/xCzfsFVw7ygW2SI6NClZzquT+mwdTfgfdbUiceh6iQO0CHtCPsyze/MZ2YbAA==", "dev": true, + "license": "MIT", "dependencies": { "@babel/types": "^7.24.7" }, @@ -1029,6 +1350,7 @@ "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz", "integrity": "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==", "dev": true, + "license": "MIT", "engines": { "node": ">=6.9.0" } @@ -1038,6 +1360,7 @@ "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz", "integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=6.9.0" } @@ -1047,6 +1370,7 @@ "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.25.9.tgz", "integrity": "sha512-e/zv1co8pp55dNdEcCynfj9X7nyUKUXoUEwfXqaZt0omVOmDe9oOTdKStH4GmAw6zxMFs50ZayuMfHDKlO7Tfw==", "dev": true, + "license": "MIT", "engines": { "node": ">=6.9.0" } @@ -1056,6 +1380,7 @@ "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.25.9.tgz", "integrity": "sha512-ETzz9UTjQSTmw39GboatdymDq4XIQbR8ySgVrylRhPOFpsd+JrKHIuF0de7GCWmem+T4uC5z7EZguod7Wj4A4g==", "dev": true, + "license": "MIT", "dependencies": { "@babel/template": "^7.25.9", "@babel/traverse": "^7.25.9", @@ -1066,13 +1391,14 @@ } }, "node_modules/@babel/helpers": { - "version": "7.26.7", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.26.7.tgz", - "integrity": "sha512-8NHiL98vsi0mbPQmYAGWwfcFaOy4j2HY49fXJCfuDcdE7fMIsH9a7GdaeXpIBsbT7307WU8KCMp5pUVDNL4f9A==", + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.27.0.tgz", + "integrity": "sha512-U5eyP/CTFPuNE3qk+WZMxFkp/4zUzdceQlfzf7DdGdhp+Fezd7HD+i8Y24ZuTMKX3wQBld449jijbGq6OdGNQg==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/template": "^7.25.9", - "@babel/types": "^7.26.7" + "@babel/template": "^7.27.0", + "@babel/types": "^7.27.0" }, "engines": { "node": ">=6.9.0" @@ -1083,6 +1409,7 @@ "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.25.9.tgz", "integrity": "sha512-llL88JShoCsth8fF8R4SJnIn+WLvR6ccFxu1H3FlMhDontdcmZWf2HgIZ7AIqV3Xcck1idlohrN4EUBQz6klbw==", "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-validator-identifier": "^7.25.9", "chalk": "^2.4.2", @@ -1098,6 +1425,7 @@ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", "dev": true, + "license": "MIT", "dependencies": { "color-convert": "^1.9.0" }, @@ -1110,6 +1438,7 @@ "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", "dev": true, + "license": "MIT", "dependencies": { "ansi-styles": "^3.2.1", "escape-string-regexp": "^1.0.5", @@ -1124,6 +1453,7 @@ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", "dev": true, + "license": "MIT", "dependencies": { "color-name": "1.1.3" } @@ -1132,13 +1462,15 @@ "version": "1.1.3", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/@babel/highlight/node_modules/escape-string-regexp": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.8.0" } @@ -1148,6 +1480,7 @@ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", "dev": true, + "license": "MIT", "engines": { "node": ">=4" } @@ -1157,6 +1490,7 @@ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", "dev": true, + "license": "MIT", "dependencies": { "has-flag": "^3.0.0" }, @@ -1165,12 +1499,13 @@ } }, "node_modules/@babel/parser": { - "version": "7.26.7", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.26.7.tgz", - "integrity": "sha512-kEvgGGgEjRUutvdVvZhbn/BxVt+5VSpwXz1j3WYXQbXDo8KzFOPNG2GQbdAiNq8g6wn1yKk7C/qrke03a84V+w==", + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.0.tgz", + "integrity": "sha512-iaepho73/2Pz7w2eMS0Q5f83+0RKI7i4xmiYeBmDzfRVbQtTOG7Ts0S4HzJVsTMGI9keU8rNfuZr8DKfSt7Yyg==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/types": "^7.26.7" + "@babel/types": "^7.27.0" }, "bin": { "parser": "bin/babel-parser.js" @@ -1184,6 +1519,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.25.9.tgz", "integrity": "sha512-ZkRyVkThtxQ/J6nv3JFYv1RYY+JT5BvU0y3k5bWrmuG4woXypRa4PXmm9RhOwodRkYFWqC0C0cqcJ4OqR7kW+g==", "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.25.9", "@babel/traverse": "^7.25.9" @@ -1200,6 +1536,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-class-field-initializer-scope/-/plugin-bugfix-safari-class-field-initializer-scope-7.25.9.tgz", "integrity": "sha512-MrGRLZxLD/Zjj0gdU15dfs+HH/OXvnw/U4jJD8vpcP2CJQapPEv1IWwjc/qMg7ItBlPwSv1hRBbb7LeuANdcnw==", "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.25.9" }, @@ -1215,6 +1552,7 @@ "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.25.9.tgz", "integrity": "sha512-2qUwwfAFpJLZqxd02YW9btUCZHl+RFvdDkNfZwaIJrvB8Tesjsk8pEQkTvGwZXLqXUx/2oyY3ySRhm6HOXuCug==", "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.25.9" }, @@ -1230,6 +1568,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.25.9.tgz", "integrity": "sha512-6xWgLZTJXwilVjlnV7ospI3xi+sl8lN8rXXbBD6vYn3UYDlGsag8wrZkKcSI8G6KgqKP7vNFaDgeDnfAABq61g==", "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.25.9", "@babel/helper-skip-transparent-expression-wrappers": "^7.25.9", @@ -1247,6 +1586,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.25.9.tgz", "integrity": "sha512-aLnMXYPnzwwqhYSCyXfKkIkYgJ8zv9RK+roo9DkTXz38ynIhd9XCbN08s3MGvqL2MYGVUGdRQLL/JqBIeJhJBg==", "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.25.9", "@babel/traverse": "^7.25.9" @@ -1263,6 +1603,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz", "integrity": "sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==", "dev": true, + "license": "MIT", "engines": { "node": ">=6.9.0" }, @@ -1270,74 +1611,12 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-syntax-async-generators": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", - "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-class-properties": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", - "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.12.13" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-class-static-block": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz", - "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-dynamic-import": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz", - "integrity": "sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-export-namespace-from": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-export-namespace-from/-/plugin-syntax-export-namespace-from-7.8.3.tgz", - "integrity": "sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.3" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, "node_modules/@babel/plugin-syntax-import-assertions": { "version": "7.26.0", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.26.0.tgz", "integrity": "sha512-QCWT5Hh830hK5EQa7XzuqIkQU9tT/whqbDz7kuaZMHFl1inRRg7JnuAEOQ0Ur0QUl0NufCk1msK2BeY79Aj/eg==", "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.25.9" }, @@ -1349,138 +1628,13 @@ } }, "node_modules/@babel/plugin-syntax-import-attributes": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.24.7.tgz", - "integrity": "sha512-hbX+lKKeUMGihnK8nvKqmXBInriT3GVjzXKFriV3YC6APGxMbP8RZNFwy91+hocLXq90Mta+HshoB31802bb8A==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-import-meta": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", - "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-json-strings": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", - "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-logical-assignment-operators": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", - "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", - "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-numeric-separator": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", - "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-object-rest-spread": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", - "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-optional-catch-binding": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", - "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-optional-chaining": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", - "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-private-property-in-object": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", - "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-top-level-await": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", - "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.26.0.tgz", + "integrity": "sha512-e2dttdsJ1ZTpi3B9UYGLw41hifAubg19AtCu/2I/F1QNVclOBr1dYpTdmdyZ84Xiz43BS/tCUkMAZNLv12Pi+A==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1494,6 +1648,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-unicode-sets-regex/-/plugin-syntax-unicode-sets-regex-7.18.6.tgz", "integrity": "sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg==", "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.18.6", "@babel/helper-plugin-utils": "^7.18.6" @@ -1510,6 +1665,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.25.9.tgz", "integrity": "sha512-6jmooXYIwn9ca5/RylZADJ+EnSxVUS5sjeJ9UPk6RWRzXCmOJCy6dqItPJFpw2cuCangPK4OYr5uhGKcmrm5Qg==", "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.25.9" }, @@ -1521,15 +1677,15 @@ } }, "node_modules/@babel/plugin-transform-async-generator-functions": { - "version": "7.25.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.25.0.tgz", - "integrity": "sha512-uaIi2FdqzjpAMvVqvB51S42oC2JEVgh0LDsGfZVDysWE8LrJtQC2jvKmOqEYThKyB7bDEb7BP1GYWDm7tABA0Q==", + "version": "7.26.8", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.26.8.tgz", + "integrity": "sha512-He9Ej2X7tNf2zdKMAGOsmg2MrFc+hfoAhd3po4cWfo/NWjzEAKa0oQruj1ROVUdl0e6fb6/kE/G3SSxE0lRJOg==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.8", - "@babel/helper-remap-async-to-generator": "^7.25.0", - "@babel/plugin-syntax-async-generators": "^7.8.4", - "@babel/traverse": "^7.25.0" + "@babel/helper-plugin-utils": "^7.26.5", + "@babel/helper-remap-async-to-generator": "^7.25.9", + "@babel/traverse": "^7.26.8" }, "engines": { "node": ">=6.9.0" @@ -1539,14 +1695,15 @@ } }, "node_modules/@babel/plugin-transform-async-to-generator": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.24.7.tgz", - "integrity": "sha512-SQY01PcJfmQ+4Ash7NE+rpbLFbmqA2GPIgqzxfFTL4t1FKRq4zTms/7htKpoCUI9OcFYgzqfmCdH53s6/jn5fA==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.25.9.tgz", + "integrity": "sha512-NT7Ejn7Z/LjUH0Gv5KsBCxh7BH3fbLTV0ptHvpeMvrt3cPThHfJfst9Wrb7S8EvJ7vRTFI7z+VAvFVEQn/m5zQ==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-module-imports": "^7.24.7", - "@babel/helper-plugin-utils": "^7.24.7", - "@babel/helper-remap-async-to-generator": "^7.24.7" + "@babel/helper-module-imports": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/helper-remap-async-to-generator": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1560,6 +1717,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.26.5.tgz", "integrity": "sha512-chuTSY+hq09+/f5lMj8ZSYgCFpppV2CbYrhNFJ1BFoXpiWPnnAb7R0MqrafCpN8E1+YRrtM1MXZHJdIx8B6rMQ==", "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.26.5" }, @@ -1571,12 +1729,13 @@ } }, "node_modules/@babel/plugin-transform-block-scoping": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.25.9.tgz", - "integrity": "sha512-1F05O7AYjymAtqbsFETboN1NvBdcnzMerO+zlMyJBEz6WkMdejvGWw9p05iTSjC85RLlBseHHQpYaM4gzJkBGg==", + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.27.0.tgz", + "integrity": "sha512-u1jGphZ8uDI2Pj/HJj6YQ6XQLZCNjOlprjxB5SVz6rq2T6SwAR+CdrWK0CP7F+9rDVMXdB0+r6Am5G5aobOjAQ==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-plugin-utils": "^7.26.5" }, "engines": { "node": ">=6.9.0" @@ -1590,6 +1749,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.25.9.tgz", "integrity": "sha512-bbMAII8GRSkcd0h0b4X+36GksxuheLFjP65ul9w6C3KgAamI3JqErNgSrosX6ZPj+Mpim5VvEbawXxJCyEUV3Q==", "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-create-class-features-plugin": "^7.25.9", "@babel/helper-plugin-utils": "^7.25.9" @@ -1606,6 +1766,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.26.0.tgz", "integrity": "sha512-6J2APTs7BDDm+UMqP1useWqhcRAXo0WIoVj26N7kPFB6S73Lgvyka4KTZYIxtgYXiN5HTyRObA72N2iu628iTQ==", "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-create-class-features-plugin": "^7.25.9", "@babel/helper-plugin-utils": "^7.25.9" @@ -1622,6 +1783,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.25.9.tgz", "integrity": "sha512-mD8APIXmseE7oZvZgGABDyM34GUmK45Um2TXiBUt7PnuAxrgoSVf123qUzPxEr/+/BHrRn5NMZCdE2m/1F8DGg==", "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-annotate-as-pure": "^7.25.9", "@babel/helper-compilation-targets": "^7.25.9", @@ -1637,23 +1799,12 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-classes/node_modules/@babel/helper-annotate-as-pure": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.25.9.tgz", - "integrity": "sha512-gv7320KBUFJz1RnylIg5WWYPRXKZ884AGkYpgpWW02TH66Dl+HaC1t1CKd0z3R4b6hdYEcmrNZHUmfCP+1u3/g==", - "dev": true, - "dependencies": { - "@babel/types": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/plugin-transform-computed-properties": { + "node_modules/@babel/plugin-transform-computed-properties": { "version": "7.25.9", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.25.9.tgz", "integrity": "sha512-HnBegGqXZR12xbcTHlJ9HGxw1OniltT26J5YpfruGqtUHlz/xKf/G2ak9e+t0rVqrjXa9WOhvYPz1ERfMj23AA==", "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.25.9", "@babel/template": "^7.25.9" @@ -1670,6 +1821,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.25.9.tgz", "integrity": "sha512-WkCGb/3ZxXepmMiX101nnGiU+1CAdut8oHyEOHxkKuS1qKpU2SMXE2uSvfz8PBuLd49V6LEsbtyPhWC7fnkgvQ==", "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.25.9" }, @@ -1685,6 +1837,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.25.9.tgz", "integrity": "sha512-t7ZQ7g5trIgSRYhI9pIJtRl64KHotutUJsh4Eze5l7olJv+mRSg4/MmbZ0tv1eeqRbdvo/+trvJD/Oc5DmW2cA==", "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.25.9", "@babel/helper-plugin-utils": "^7.25.9" @@ -1701,6 +1854,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.25.9.tgz", "integrity": "sha512-LZxhJ6dvBb/f3x8xwWIuyiAHy56nrRG3PeYTpBkkzkYRRQ6tJLu68lEF5VIqMUZiAV7a8+Tb78nEoMCMcqjXBw==", "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.25.9" }, @@ -1716,6 +1870,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-named-capturing-groups-regex/-/plugin-transform-duplicate-named-capturing-groups-regex-7.25.9.tgz", "integrity": "sha512-0UfuJS0EsXbRvKnwcLjFtJy/Sxc5J5jhLHnFhy7u4zih97Hz6tJkLU+O+FMMrNZrosUPxDi6sYxJ/EA8jDiAog==", "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.25.9", "@babel/helper-plugin-utils": "^7.25.9" @@ -1732,6 +1887,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.25.9.tgz", "integrity": "sha512-GCggjexbmSLaFhqsojeugBpeaRIgWNTcgKVq/0qIteFEqY2A+b9QidYadrWlnbWQUrW5fn+mCvf3tr7OeBFTyg==", "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.25.9" }, @@ -1747,6 +1903,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.26.3.tgz", "integrity": "sha512-7CAHcQ58z2chuXPWblnn1K6rLDnDWieghSOEmqQsrBenH0P9InCUtOJYD89pvngljmZlJcz3fcmgYsXFNGa1ZQ==", "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.25.9" }, @@ -1762,6 +1919,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.25.9.tgz", "integrity": "sha512-2NsEz+CxzJIVOPx2o9UsW1rXLqtChtLoVnwYHHiB04wS5sgn7mrV45fWMBX0Kk+ub9uXytVYfNP2HjbVbCB3Ww==", "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.25.9" }, @@ -1773,12 +1931,13 @@ } }, "node_modules/@babel/plugin-transform-for-of": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.25.9.tgz", - "integrity": "sha512-LqHxduHoaGELJl2uhImHwRQudhCM50pT46rIBNvtT/Oql3nqiS3wOwP+5ten7NpYSXrrVLgtZU3DZmPtWZo16A==", + "version": "7.26.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.26.9.tgz", + "integrity": "sha512-Hry8AusVm8LW5BVFgiyUReuoGzPUpdHQQqJY5bZnbbf+ngOHWuCuYFKw/BqaaWlvEUrF91HMhDtEaI1hZzNbLg==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9", + "@babel/helper-plugin-utils": "^7.26.5", "@babel/helper-skip-transparent-expression-wrappers": "^7.25.9" }, "engines": { @@ -1793,6 +1952,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.25.9.tgz", "integrity": "sha512-8lP+Yxjv14Vc5MuWBpJsoUCd3hD6V9DgBon2FVYL4jJgbnVQ9fTgYmonchzZJOVNgzEgbxp4OwAf6xz6M/14XA==", "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-compilation-targets": "^7.25.9", "@babel/helper-plugin-utils": "^7.25.9", @@ -1810,6 +1970,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.25.9.tgz", "integrity": "sha512-xoTMk0WXceiiIvsaquQQUaLLXSW1KJ159KP87VilruQm0LNNGxWzahxSS6T6i4Zg3ezp4vA4zuwiNUR53qmQAw==", "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.25.9" }, @@ -1825,6 +1986,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.25.9.tgz", "integrity": "sha512-9N7+2lFziW8W9pBl2TzaNht3+pgMIRP74zizeCSrtnSKVdUl8mAjjOP2OOVQAfZ881P2cNjDj1uAMEdeD50nuQ==", "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.25.9" }, @@ -1840,6 +2002,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.25.9.tgz", "integrity": "sha512-wI4wRAzGko551Y8eVf6iOY9EouIDTtPb0ByZx+ktDGHwv6bHFimrgJM/2T021txPZ2s4c7bqvHbd+vXG6K948Q==", "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.25.9" }, @@ -1855,6 +2018,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.25.9.tgz", "integrity": "sha512-PYazBVfofCQkkMzh2P6IdIUaCEWni3iYEerAsRWuVd8+jlM1S9S9cz1dF9hIzyoZ8IA3+OwVYIp9v9e+GbgZhA==", "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.25.9" }, @@ -1870,6 +2034,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.25.9.tgz", "integrity": "sha512-g5T11tnI36jVClQlMlt4qKDLlWnG5pP9CSM4GhdRciTNMRgkfpo5cR6b4rGIOYPgRRuFAvwjPQ/Yk+ql4dyhbw==", "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-module-transforms": "^7.25.9", "@babel/helper-plugin-utils": "^7.25.9" @@ -1886,6 +2051,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.26.3.tgz", "integrity": "sha512-MgR55l4q9KddUDITEzEFYn5ZsGDXMSsU9E+kh7fjRXTIC3RHqfCo8RPRbyReYJh44HQ/yomFkqbOFohXvDCiIQ==", "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-module-transforms": "^7.26.0", "@babel/helper-plugin-utils": "^7.25.9" @@ -1902,6 +2068,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.25.9.tgz", "integrity": "sha512-hyss7iIlH/zLHaehT+xwiymtPOpsiwIIRlCAOwBB04ta5Tt+lNItADdlXw3jAWZ96VJ2jlhl/c+PNIQPKNfvcA==", "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-module-transforms": "^7.25.9", "@babel/helper-plugin-utils": "^7.25.9", @@ -1920,6 +2087,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.25.9.tgz", "integrity": "sha512-bS9MVObUgE7ww36HEfwe6g9WakQ0KF07mQF74uuXdkoziUPfKyu/nIm663kz//e5O1nPInPFx36z7WJmJ4yNEw==", "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-module-transforms": "^7.25.9", "@babel/helper-plugin-utils": "^7.25.9" @@ -1936,6 +2104,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.25.9.tgz", "integrity": "sha512-oqB6WHdKTGl3q/ItQhpLSnWWOpjUJLsOCLVyeFgeTktkBSCiurvPOsyt93gibI9CmuKvTUEtWmG5VhZD+5T/KA==", "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.25.9", "@babel/helper-plugin-utils": "^7.25.9" @@ -1952,6 +2121,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.25.9.tgz", "integrity": "sha512-U/3p8X1yCSoKyUj2eOBIx3FOn6pElFOKvAAGf8HTtItuPyB+ZeOqfn+mvTtg9ZlOAjsPdK3ayQEjqHjU/yLeVQ==", "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.25.9" }, @@ -1967,6 +2137,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.26.6.tgz", "integrity": "sha512-CKW8Vu+uUZneQCPtXmSBUC6NCAUdya26hWCElAWh5mVSlSRsmiCPUUDKb3Z0szng1hiAJa098Hkhg9o4SE35Qw==", "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.26.5" }, @@ -1982,6 +2153,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.25.9.tgz", "integrity": "sha512-TlprrJ1GBZ3r6s96Yq8gEQv82s8/5HnCVHtEJScUj90thHQbwe+E5MLhi2bbNHBEJuzrvltXSru+BUxHDoog7Q==", "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.25.9" }, @@ -1997,6 +2169,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.25.9.tgz", "integrity": "sha512-fSaXafEE9CVHPweLYw4J0emp1t8zYTXyzN3UuG+lylqkvYd7RMrsOQ8TYx5RF231be0vqtFC6jnx3UmpJmKBYg==", "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-compilation-targets": "^7.25.9", "@babel/helper-plugin-utils": "^7.25.9", @@ -2014,6 +2187,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.25.9.tgz", "integrity": "sha512-Kj/Gh+Rw2RNLbCK1VAWj2U48yxxqL2x0k10nPtSdRa0O2xnHXalD0s+o1A6a0W43gJ00ANo38jxkQreckOzv5A==", "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.25.9", "@babel/helper-replace-supers": "^7.25.9" @@ -2030,6 +2204,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.25.9.tgz", "integrity": "sha512-qM/6m6hQZzDcZF3onzIhZeDHDO43bkNNlOX0i8n3lR6zLbu0GN2d8qfM/IERJZYauhAHSLHy39NF0Ctdvcid7g==", "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.25.9" }, @@ -2045,6 +2220,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.25.9.tgz", "integrity": "sha512-6AvV0FsLULbpnXeBjrY4dmWF8F7gf8QnvTEoO/wX/5xm/xE1Xo8oPuD3MPS+KS9f9XBEAWN7X1aWr4z9HdOr7A==", "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.25.9", "@babel/helper-skip-transparent-expression-wrappers": "^7.25.9" @@ -2061,6 +2237,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.25.9.tgz", "integrity": "sha512-wzz6MKwpnshBAiRmn4jR8LYz/g8Ksg0o80XmwZDlordjwEk9SxBzTWC7F5ef1jhbrbOW2DJ5J6ayRukrJmnr0g==", "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.25.9" }, @@ -2076,6 +2253,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.25.9.tgz", "integrity": "sha512-D/JUozNpQLAPUVusvqMxyvjzllRaF8/nSrP1s2YGQT/W4LHK4xxsMcHjhOGTS01mp9Hda8nswb+FblLdJornQw==", "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-create-class-features-plugin": "^7.25.9", "@babel/helper-plugin-utils": "^7.25.9" @@ -2092,6 +2270,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.25.9.tgz", "integrity": "sha512-Evf3kcMqzXA3xfYJmZ9Pg1OvKdtqsDMSWBDzZOPLvHiTt36E75jLDQo5w1gtRU95Q4E5PDttrTf25Fw8d/uWLw==", "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-annotate-as-pure": "^7.25.9", "@babel/helper-create-class-features-plugin": "^7.25.9", @@ -2104,23 +2283,12 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-private-property-in-object/node_modules/@babel/helper-annotate-as-pure": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.25.9.tgz", - "integrity": "sha512-gv7320KBUFJz1RnylIg5WWYPRXKZ884AGkYpgpWW02TH66Dl+HaC1t1CKd0z3R4b6hdYEcmrNZHUmfCP+1u3/g==", - "dev": true, - "dependencies": { - "@babel/types": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" - } - }, "node_modules/@babel/plugin-transform-property-literals": { "version": "7.25.9", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.25.9.tgz", "integrity": "sha512-IvIUeV5KrS/VPavfSM/Iu+RE6llrHrYIKY1yfCzyO/lMXHQ+p7uGhonmGVisv6tSBSVgWzMBohTcvkC9vQcQFA==", "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.25.9" }, @@ -2132,12 +2300,13 @@ } }, "node_modules/@babel/plugin-transform-regenerator": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.25.9.tgz", - "integrity": "sha512-vwDcDNsgMPDGP0nMqzahDWE5/MLcX8sv96+wfX7as7LoF/kr97Bo/7fI00lXY4wUXYfVmwIIyG80fGZ1uvt2qg==", + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.27.0.tgz", + "integrity": "sha512-LX/vCajUJQDqE7Aum/ELUMZAY19+cDpghxrnyt5I1tV6X5PyC86AOoWXWFYFeIvauyeSA6/ktn4tQVn/3ZifsA==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9", + "@babel/helper-plugin-utils": "^7.26.5", "regenerator-transform": "^0.15.2" }, "engines": { @@ -2147,11 +2316,29 @@ "@babel/core": "^7.0.0-0" } }, + "node_modules/@babel/plugin-transform-regexp-modifiers": { + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regexp-modifiers/-/plugin-transform-regexp-modifiers-7.26.0.tgz", + "integrity": "sha512-vN6saax7lrA2yA/Pak3sCxuD6F5InBjn9IcrIKQPjpsLvuHYLVroTxjdlVRHjjBWxKOqIwpTXDkOssYT4BFdRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, "node_modules/@babel/plugin-transform-reserved-words": { "version": "7.25.9", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.25.9.tgz", "integrity": "sha512-7DL7DKYjn5Su++4RXu8puKZm2XBPHyjWLUidaPEkCUBbE7IPcsrkRHggAOOKydH1dASWdcUBxrkOGNxUv5P3Jg==", "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.25.9" }, @@ -2163,15 +2350,16 @@ } }, "node_modules/@babel/plugin-transform-runtime": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.24.7.tgz", - "integrity": "sha512-YqXjrk4C+a1kZjewqt+Mmu2UuV1s07y8kqcUf4qYLnoqemhR4gRQikhdAhSVJioMjVTu6Mo6pAbaypEA3jY6fw==", + "version": "7.26.10", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.26.10.tgz", + "integrity": "sha512-NWaL2qG6HRpONTnj4JvDU6th4jYeZOJgu3QhmFTCihib0ermtOJqktA5BduGm3suhhVe9EMP9c9+mfJ/I9slqw==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-module-imports": "^7.24.7", - "@babel/helper-plugin-utils": "^7.24.7", + "@babel/helper-module-imports": "^7.25.9", + "@babel/helper-plugin-utils": "^7.26.5", "babel-plugin-polyfill-corejs2": "^0.4.10", - "babel-plugin-polyfill-corejs3": "^0.10.1", + "babel-plugin-polyfill-corejs3": "^0.11.0", "babel-plugin-polyfill-regenerator": "^0.6.1", "semver": "^6.3.1" }, @@ -2187,6 +2375,7 @@ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true, + "license": "ISC", "bin": { "semver": "bin/semver.js" } @@ -2196,6 +2385,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.25.9.tgz", "integrity": "sha512-MUv6t0FhO5qHnS/W8XCbHmiRWOphNufpE1IVxhK5kuN3Td9FT1x4rx4K42s3RYdMXCXpfWkGSbCSd0Z64xA7Ng==", "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.25.9" }, @@ -2211,6 +2401,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.25.9.tgz", "integrity": "sha512-oNknIB0TbURU5pqJFVbOOFspVlrpVwo2H1+HUIsVDvp5VauGGDP1ZEvO8Nn5xyMEs3dakajOxlmkNW7kNgSm6A==", "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.25.9", "@babel/helper-skip-transparent-expression-wrappers": "^7.25.9" @@ -2227,6 +2418,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.25.9.tgz", "integrity": "sha512-WqBUSgeVwucYDP9U/xNRQam7xV8W5Zf+6Eo7T2SRVUFlhRiMNFdFz58u0KZmCVVqs2i7SHgpRnAhzRNmKfi2uA==", "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.25.9" }, @@ -2238,12 +2430,13 @@ } }, "node_modules/@babel/plugin-transform-template-literals": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.25.9.tgz", - "integrity": "sha512-o97AE4syN71M/lxrCtQByzphAdlYluKPDBzDVzMmfCobUjjhAryZV0AIpRPrxN0eAkxXO6ZLEScmt+PNhj2OTw==", + "version": "7.26.8", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.26.8.tgz", + "integrity": "sha512-OmGDL5/J0CJPJZTHZbi2XpO0tyT2Ia7fzpW5GURwdtp2X3fMmN8au/ej6peC/T33/+CRiIpA8Krse8hFGVmT5Q==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-plugin-utils": "^7.26.5" }, "engines": { "node": ">=6.9.0" @@ -2253,10 +2446,11 @@ } }, "node_modules/@babel/plugin-transform-typeof-symbol": { - "version": "7.26.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.26.7.tgz", - "integrity": "sha512-jfoTXXZTgGg36BmhqT3cAYK5qkmqvJpvNrPhaK/52Vgjhw4Rq29s9UqpWWV0D6yuRmgiFH/BUVlkl96zJWqnaw==", + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.27.0.tgz", + "integrity": "sha512-+LLkxA9rKJpNoGsbLnAgOCdESl73vwYn+V6b+5wHbrE7OGKVDPHIQvbFSzqE6rwqaCw2RE+zdJrlLkcf8YOA0w==", "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.26.5" }, @@ -2272,6 +2466,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.25.9.tgz", "integrity": "sha512-s5EDrE6bW97LtxOcGj1Khcx5AaXwiMmi4toFWRDP9/y0Woo6pXC+iyPu/KuhKtfSrNFd7jJB+/fkOtZy6aIC6Q==", "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.25.9" }, @@ -2287,6 +2482,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.25.9.tgz", "integrity": "sha512-Jt2d8Ga+QwRluxRQ307Vlxa6dMrYEMZCgGxoPR8V52rxPyldHu3hdlHspxaqYmE7oID5+kB+UKUB/eWS+DkkWg==", "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.25.9", "@babel/helper-plugin-utils": "^7.25.9" @@ -2303,6 +2499,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.25.9.tgz", "integrity": "sha512-yoxstj7Rg9dlNn9UQxzk4fcNivwv4nUYz7fYXBaKxvw/lnmPuOm/ikoELygbYq68Bls3D/D+NBPHiLwZdZZ4HA==", "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.25.9", "@babel/helper-plugin-utils": "^7.25.9" @@ -2319,6 +2516,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.25.9.tgz", "integrity": "sha512-8BYqO3GeVNHtx69fdPshN3fnzUNLrWdHhk/icSwigksJGczKSizZ+Z6SBCxTs723Fr5VSNorTIK7a+R2tISvwQ==", "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.25.9", "@babel/helper-plugin-utils": "^7.25.9" @@ -2331,93 +2529,80 @@ } }, "node_modules/@babel/preset-env": { - "version": "7.25.3", - "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.25.3.tgz", - "integrity": "sha512-QsYW7UeAaXvLPX9tdVliMJE7MD7M6MLYVTovRTIwhoYQVFHR1rM4wO8wqAezYi3/BpSD+NzVCZ69R6smWiIi8g==", - "dev": true, - "dependencies": { - "@babel/compat-data": "^7.25.2", - "@babel/helper-compilation-targets": "^7.25.2", - "@babel/helper-plugin-utils": "^7.24.8", - "@babel/helper-validator-option": "^7.24.8", - "@babel/plugin-bugfix-firefox-class-in-computed-class-key": "^7.25.3", - "@babel/plugin-bugfix-safari-class-field-initializer-scope": "^7.25.0", - "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.25.0", - "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.24.7", - "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.25.0", + "version": "7.26.9", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.26.9.tgz", + "integrity": "sha512-vX3qPGE8sEKEAZCWk05k3cpTAE3/nOYca++JA+Rd0z2NCNzabmYvEiSShKzm10zdquOIAVXsy2Ei/DTW34KlKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.26.8", + "@babel/helper-compilation-targets": "^7.26.5", + "@babel/helper-plugin-utils": "^7.26.5", + "@babel/helper-validator-option": "^7.25.9", + "@babel/plugin-bugfix-firefox-class-in-computed-class-key": "^7.25.9", + "@babel/plugin-bugfix-safari-class-field-initializer-scope": "^7.25.9", + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.25.9", + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.25.9", + "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.25.9", "@babel/plugin-proposal-private-property-in-object": "7.21.0-placeholder-for-preset-env.2", - "@babel/plugin-syntax-async-generators": "^7.8.4", - "@babel/plugin-syntax-class-properties": "^7.12.13", - "@babel/plugin-syntax-class-static-block": "^7.14.5", - "@babel/plugin-syntax-dynamic-import": "^7.8.3", - "@babel/plugin-syntax-export-namespace-from": "^7.8.3", - "@babel/plugin-syntax-import-assertions": "^7.24.7", - "@babel/plugin-syntax-import-attributes": "^7.24.7", - "@babel/plugin-syntax-import-meta": "^7.10.4", - "@babel/plugin-syntax-json-strings": "^7.8.3", - "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", - "@babel/plugin-syntax-numeric-separator": "^7.10.4", - "@babel/plugin-syntax-object-rest-spread": "^7.8.3", - "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", - "@babel/plugin-syntax-optional-chaining": "^7.8.3", - "@babel/plugin-syntax-private-property-in-object": "^7.14.5", - "@babel/plugin-syntax-top-level-await": "^7.14.5", + "@babel/plugin-syntax-import-assertions": "^7.26.0", + "@babel/plugin-syntax-import-attributes": "^7.26.0", "@babel/plugin-syntax-unicode-sets-regex": "^7.18.6", - "@babel/plugin-transform-arrow-functions": "^7.24.7", - "@babel/plugin-transform-async-generator-functions": "^7.25.0", - "@babel/plugin-transform-async-to-generator": "^7.24.7", - "@babel/plugin-transform-block-scoped-functions": "^7.24.7", - "@babel/plugin-transform-block-scoping": "^7.25.0", - "@babel/plugin-transform-class-properties": "^7.24.7", - "@babel/plugin-transform-class-static-block": "^7.24.7", - "@babel/plugin-transform-classes": "^7.25.0", - "@babel/plugin-transform-computed-properties": "^7.24.7", - "@babel/plugin-transform-destructuring": "^7.24.8", - "@babel/plugin-transform-dotall-regex": "^7.24.7", - "@babel/plugin-transform-duplicate-keys": "^7.24.7", - "@babel/plugin-transform-duplicate-named-capturing-groups-regex": "^7.25.0", - "@babel/plugin-transform-dynamic-import": "^7.24.7", - "@babel/plugin-transform-exponentiation-operator": "^7.24.7", - "@babel/plugin-transform-export-namespace-from": "^7.24.7", - "@babel/plugin-transform-for-of": "^7.24.7", - "@babel/plugin-transform-function-name": "^7.25.1", - "@babel/plugin-transform-json-strings": "^7.24.7", - "@babel/plugin-transform-literals": "^7.25.2", - "@babel/plugin-transform-logical-assignment-operators": "^7.24.7", - "@babel/plugin-transform-member-expression-literals": "^7.24.7", - "@babel/plugin-transform-modules-amd": "^7.24.7", - "@babel/plugin-transform-modules-commonjs": "^7.24.8", - "@babel/plugin-transform-modules-systemjs": "^7.25.0", - "@babel/plugin-transform-modules-umd": "^7.24.7", - "@babel/plugin-transform-named-capturing-groups-regex": "^7.24.7", - "@babel/plugin-transform-new-target": "^7.24.7", - "@babel/plugin-transform-nullish-coalescing-operator": "^7.24.7", - "@babel/plugin-transform-numeric-separator": "^7.24.7", - "@babel/plugin-transform-object-rest-spread": "^7.24.7", - "@babel/plugin-transform-object-super": "^7.24.7", - "@babel/plugin-transform-optional-catch-binding": "^7.24.7", - "@babel/plugin-transform-optional-chaining": "^7.24.8", - "@babel/plugin-transform-parameters": "^7.24.7", - "@babel/plugin-transform-private-methods": "^7.24.7", - "@babel/plugin-transform-private-property-in-object": "^7.24.7", - "@babel/plugin-transform-property-literals": "^7.24.7", - "@babel/plugin-transform-regenerator": "^7.24.7", - "@babel/plugin-transform-reserved-words": "^7.24.7", - "@babel/plugin-transform-shorthand-properties": "^7.24.7", - "@babel/plugin-transform-spread": "^7.24.7", - "@babel/plugin-transform-sticky-regex": "^7.24.7", - "@babel/plugin-transform-template-literals": "^7.24.7", - "@babel/plugin-transform-typeof-symbol": "^7.24.8", - "@babel/plugin-transform-unicode-escapes": "^7.24.7", - "@babel/plugin-transform-unicode-property-regex": "^7.24.7", - "@babel/plugin-transform-unicode-regex": "^7.24.7", - "@babel/plugin-transform-unicode-sets-regex": "^7.24.7", + "@babel/plugin-transform-arrow-functions": "^7.25.9", + "@babel/plugin-transform-async-generator-functions": "^7.26.8", + "@babel/plugin-transform-async-to-generator": "^7.25.9", + "@babel/plugin-transform-block-scoped-functions": "^7.26.5", + "@babel/plugin-transform-block-scoping": "^7.25.9", + "@babel/plugin-transform-class-properties": "^7.25.9", + "@babel/plugin-transform-class-static-block": "^7.26.0", + "@babel/plugin-transform-classes": "^7.25.9", + "@babel/plugin-transform-computed-properties": "^7.25.9", + "@babel/plugin-transform-destructuring": "^7.25.9", + "@babel/plugin-transform-dotall-regex": "^7.25.9", + "@babel/plugin-transform-duplicate-keys": "^7.25.9", + "@babel/plugin-transform-duplicate-named-capturing-groups-regex": "^7.25.9", + "@babel/plugin-transform-dynamic-import": "^7.25.9", + "@babel/plugin-transform-exponentiation-operator": "^7.26.3", + "@babel/plugin-transform-export-namespace-from": "^7.25.9", + "@babel/plugin-transform-for-of": "^7.26.9", + "@babel/plugin-transform-function-name": "^7.25.9", + "@babel/plugin-transform-json-strings": "^7.25.9", + "@babel/plugin-transform-literals": "^7.25.9", + "@babel/plugin-transform-logical-assignment-operators": "^7.25.9", + "@babel/plugin-transform-member-expression-literals": "^7.25.9", + "@babel/plugin-transform-modules-amd": "^7.25.9", + "@babel/plugin-transform-modules-commonjs": "^7.26.3", + "@babel/plugin-transform-modules-systemjs": "^7.25.9", + "@babel/plugin-transform-modules-umd": "^7.25.9", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.25.9", + "@babel/plugin-transform-new-target": "^7.25.9", + "@babel/plugin-transform-nullish-coalescing-operator": "^7.26.6", + "@babel/plugin-transform-numeric-separator": "^7.25.9", + "@babel/plugin-transform-object-rest-spread": "^7.25.9", + "@babel/plugin-transform-object-super": "^7.25.9", + "@babel/plugin-transform-optional-catch-binding": "^7.25.9", + "@babel/plugin-transform-optional-chaining": "^7.25.9", + "@babel/plugin-transform-parameters": "^7.25.9", + "@babel/plugin-transform-private-methods": "^7.25.9", + "@babel/plugin-transform-private-property-in-object": "^7.25.9", + "@babel/plugin-transform-property-literals": "^7.25.9", + "@babel/plugin-transform-regenerator": "^7.25.9", + "@babel/plugin-transform-regexp-modifiers": "^7.26.0", + "@babel/plugin-transform-reserved-words": "^7.25.9", + "@babel/plugin-transform-shorthand-properties": "^7.25.9", + "@babel/plugin-transform-spread": "^7.25.9", + "@babel/plugin-transform-sticky-regex": "^7.25.9", + "@babel/plugin-transform-template-literals": "^7.26.8", + "@babel/plugin-transform-typeof-symbol": "^7.26.7", + "@babel/plugin-transform-unicode-escapes": "^7.25.9", + "@babel/plugin-transform-unicode-property-regex": "^7.25.9", + "@babel/plugin-transform-unicode-regex": "^7.25.9", + "@babel/plugin-transform-unicode-sets-regex": "^7.25.9", "@babel/preset-modules": "0.1.6-no-external-plugins", "babel-plugin-polyfill-corejs2": "^0.4.10", - "babel-plugin-polyfill-corejs3": "^0.10.4", + "babel-plugin-polyfill-corejs3": "^0.11.0", "babel-plugin-polyfill-regenerator": "^0.6.1", - "core-js-compat": "^3.37.1", + "core-js-compat": "^3.40.0", "semver": "^6.3.1" }, "engines": { @@ -2432,6 +2617,7 @@ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true, + "license": "ISC", "bin": { "semver": "bin/semver.js" } @@ -2441,6 +2627,7 @@ "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.6-no-external-plugins.tgz", "integrity": "sha512-HrcgcIESLm9aIR842yhJ5RWan/gebQUJ6E/E5+rf0y9o6oj7w0Br+sWuL6kEQ/o/AdfvR1Je9jG18/gnpwjEyA==", "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.0.0", "@babel/types": "^7.4.4", @@ -2451,10 +2638,11 @@ } }, "node_modules/@babel/runtime": { - "version": "7.25.0", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.25.0.tgz", - "integrity": "sha512-7dRy4DwXwtzBrPbZflqxnvfxLF8kdZXPkhymtDeFoFqE6ldzjQFgYTtYIFARcLEYDrqfBfYcZt1WqFxRoyC9Rw==", + "version": "7.26.10", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.26.10.tgz", + "integrity": "sha512-2WJMeRQPHKSPemqk/awGrAiuFfzBmOIPXKizAsVhWH9YJqLZ0H+HS4c8loHGgW6utJ3E/ejXQUsiGaQy2NZ9Fw==", "dev": true, + "license": "MIT", "dependencies": { "regenerator-runtime": "^0.14.0" }, @@ -2463,30 +2651,32 @@ } }, "node_modules/@babel/template": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.25.9.tgz", - "integrity": "sha512-9DGttpmPvIxBb/2uwpVo3dqJ+O6RooAFOS+lB+xDqoE2PVCE8nfoHMdZLpfCQRLwvohzXISPZcgxt80xLfsuwg==", + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.0.tgz", + "integrity": "sha512-2ncevenBqXI6qRMukPlXwHKHchC7RyMuu4xv5JBXRfOGVcTy1mXCD12qrp7Jsoxll1EV3+9sE4GugBVRjT2jFA==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.25.9", - "@babel/parser": "^7.25.9", - "@babel/types": "^7.25.9" + "@babel/code-frame": "^7.26.2", + "@babel/parser": "^7.27.0", + "@babel/types": "^7.27.0" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/traverse": { - "version": "7.26.7", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.26.7.tgz", - "integrity": "sha512-1x1sgeyRLC3r5fQOM0/xtQKsYjyxmFjaOrLJNtZ81inNjyJHGIolTULPiSc/2qe1/qfpFLisLQYFnnZl7QoedA==", + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.27.0.tgz", + "integrity": "sha512-19lYZFzYVQkkHkl4Cy4WrAVcqBkgvV2YM2TU3xG6DIwO7O3ecbDPfW3yM3bjAGcqcQHi+CCtjMR3dIEHxsd6bA==", "dev": true, + "license": "MIT", "dependencies": { "@babel/code-frame": "^7.26.2", - "@babel/generator": "^7.26.5", - "@babel/parser": "^7.26.7", - "@babel/template": "^7.25.9", - "@babel/types": "^7.26.7", + "@babel/generator": "^7.27.0", + "@babel/parser": "^7.27.0", + "@babel/template": "^7.27.0", + "@babel/types": "^7.27.0", "debug": "^4.3.1", "globals": "^11.1.0" }, @@ -2495,13 +2685,14 @@ } }, "node_modules/@babel/traverse/node_modules/@babel/generator": { - "version": "7.26.5", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.26.5.tgz", - "integrity": "sha512-2caSP6fN9I7HOe6nqhtft7V4g7/V/gfDsC3Ag4W7kEzzvRGKqiv0pu0HogPiZ3KaVSoNDhUws6IJjDjpfmYIXw==", + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.27.0.tgz", + "integrity": "sha512-VybsKvpiN1gU1sdMZIp7FcqphVVKEwcuj02x73uvcHE0PTihx1nlBcowYWhDwjpoAXRv43+gDzyggGnn1XZhVw==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/parser": "^7.26.5", - "@babel/types": "^7.26.5", + "@babel/parser": "^7.27.0", + "@babel/types": "^7.27.0", "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.25", "jsesc": "^3.0.2" @@ -2510,23 +2701,12 @@ "node": ">=6.9.0" } }, - "node_modules/@babel/traverse/node_modules/jsesc": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", - "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", - "dev": true, - "bin": { - "jsesc": "bin/jsesc" - }, - "engines": { - "node": ">=6" - } - }, "node_modules/@babel/types": { - "version": "7.26.7", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.26.7.tgz", - "integrity": "sha512-t8kDRGrKXyp6+tjUh7hw2RLyclsW4TRoRvRHtSyAX9Bb5ldlFh+90YAYY6awRXrlB4G5G2izNeGySpATlFzmOg==", + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.0.tgz", + "integrity": "sha512-H45s8fVLYjbhFH62dIJ3WtmJ6RSPt/3DRO0ZcT2SUiYiQyz3BLVb9ADEnLl91m74aQPS3AzzeajZHYOalWe3bg==", "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-string-parser": "^7.25.9", "@babel/helper-validator-identifier": "^7.25.9" @@ -2540,27 +2720,30 @@ "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", "integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.1.90" } }, "node_modules/@discoveryjs/json-ext": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.6.1.tgz", - "integrity": "sha512-boghen8F0Q8D+0/Q1/1r6DUEieUJ8w2a1gIknExMSHBsJFOr2+0KUfHiVYBvucPwl3+RU5PFBK833FjFCh3BhA==", + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.6.3.tgz", + "integrity": "sha512-4B4OijXeVNOPZlYA2oEwWOTkzyltLao+xbotHQeqN++Rv27Y6s818+n2Qkp8q+Fxhn0t/5lA5X1Mxktud8eayQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=14.17.0" } }, "node_modules/@esbuild/aix-ppc64": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.23.0.tgz", - "integrity": "sha512-3sG8Zwa5fMcA9bgqB8AfWPQ+HFke6uD3h1s3RIwUNK8EG7a4buxvuFTs3j1IMs2NXAk9F30C/FF4vxRgQCcmoQ==", + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.1.tgz", + "integrity": "sha512-kfYGy8IdzTGy+z0vFGvExZtxkFlA4zAxgKEahG9KE1ScBjpQnFsNOX8KTU5ojNru5ed5CVoJYXFtoxaq5nFbjQ==", "cpu": [ "ppc64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "aix" @@ -2570,13 +2753,14 @@ } }, "node_modules/@esbuild/android-arm": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.23.0.tgz", - "integrity": "sha512-+KuOHTKKyIKgEEqKbGTK8W7mPp+hKinbMBeEnNzjJGyFcWsfrXjSTNluJHCY1RqhxFurdD8uNXQDei7qDlR6+g==", + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.1.tgz", + "integrity": "sha512-dp+MshLYux6j/JjdqVLnMglQlFu+MuVeNrmT5nk6q07wNhCdSnB7QZj+7G8VMUGh1q+vj2Bq8kRsuyA00I/k+Q==", "cpu": [ "arm" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "android" @@ -2586,13 +2770,14 @@ } }, "node_modules/@esbuild/android-arm64": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.23.0.tgz", - "integrity": "sha512-EuHFUYkAVfU4qBdyivULuu03FhJO4IJN9PGuABGrFy4vUuzk91P2d+npxHcFdpUnfYKy0PuV+n6bKIpHOB3prQ==", + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.1.tgz", + "integrity": "sha512-50tM0zCJW5kGqgG7fQ7IHvQOcAn9TKiVRuQ/lN0xR+T2lzEFvAi1ZcS8DiksFcEpf1t/GYOeOfCAgDHFpkiSmA==", "cpu": [ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "android" @@ -2602,13 +2787,14 @@ } }, "node_modules/@esbuild/android-x64": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.23.0.tgz", - "integrity": "sha512-WRrmKidLoKDl56LsbBMhzTTBxrsVwTKdNbKDalbEZr0tcsBgCLbEtoNthOW6PX942YiYq8HzEnb4yWQMLQuipQ==", + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.1.tgz", + "integrity": "sha512-GCj6WfUtNldqUzYkN/ITtlhwQqGWu9S45vUXs7EIYf+7rCiiqH9bCloatO9VhxsL0Pji+PF4Lz2XXCES+Q8hDw==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "android" @@ -2618,13 +2804,14 @@ } }, "node_modules/@esbuild/darwin-arm64": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.23.0.tgz", - "integrity": "sha512-YLntie/IdS31H54Ogdn+v50NuoWF5BDkEUFpiOChVa9UnKpftgwzZRrI4J132ETIi+D8n6xh9IviFV3eXdxfow==", + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.1.tgz", + "integrity": "sha512-5hEZKPf+nQjYoSr/elb62U19/l1mZDdqidGfmFutVUjjUZrOazAtwK+Kr+3y0C/oeJfLlxo9fXb1w7L+P7E4FQ==", "cpu": [ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "darwin" @@ -2634,13 +2821,14 @@ } }, "node_modules/@esbuild/darwin-x64": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.23.0.tgz", - "integrity": "sha512-IMQ6eme4AfznElesHUPDZ+teuGwoRmVuuixu7sv92ZkdQcPbsNHzutd+rAfaBKo8YK3IrBEi9SLLKWJdEvJniQ==", + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.1.tgz", + "integrity": "sha512-hxVnwL2Dqs3fM1IWq8Iezh0cX7ZGdVhbTfnOy5uURtao5OIVCEyj9xIzemDi7sRvKsuSdtCAhMKarxqtlyVyfA==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "darwin" @@ -2650,13 +2838,14 @@ } }, "node_modules/@esbuild/freebsd-arm64": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.23.0.tgz", - "integrity": "sha512-0muYWCng5vqaxobq6LB3YNtevDFSAZGlgtLoAc81PjUfiFz36n4KMpwhtAd4he8ToSI3TGyuhyx5xmiWNYZFyw==", + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.1.tgz", + "integrity": "sha512-1MrCZs0fZa2g8E+FUo2ipw6jw5qqQiH+tERoS5fAfKnRx6NXH31tXBKI3VpmLijLH6yriMZsxJtaXUyFt/8Y4A==", "cpu": [ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "freebsd" @@ -2666,13 +2855,14 @@ } }, "node_modules/@esbuild/freebsd-x64": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.23.0.tgz", - "integrity": "sha512-XKDVu8IsD0/q3foBzsXGt/KjD/yTKBCIwOHE1XwiXmrRwrX6Hbnd5Eqn/WvDekddK21tfszBSrE/WMaZh+1buQ==", + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.1.tgz", + "integrity": "sha512-0IZWLiTyz7nm0xuIs0q1Y3QWJC52R8aSXxe40VUxm6BB1RNmkODtW6LHvWRrGiICulcX7ZvyH6h5fqdLu4gkww==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "freebsd" @@ -2682,13 +2872,14 @@ } }, "node_modules/@esbuild/linux-arm": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.23.0.tgz", - "integrity": "sha512-SEELSTEtOFu5LPykzA395Mc+54RMg1EUgXP+iw2SJ72+ooMwVsgfuwXo5Fn0wXNgWZsTVHwY2cg4Vi/bOD88qw==", + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.1.tgz", + "integrity": "sha512-NdKOhS4u7JhDKw9G3cY6sWqFcnLITn6SqivVArbzIaf3cemShqfLGHYMx8Xlm/lBit3/5d7kXvriTUGa5YViuQ==", "cpu": [ "arm" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" @@ -2698,13 +2889,14 @@ } }, "node_modules/@esbuild/linux-arm64": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.23.0.tgz", - "integrity": "sha512-j1t5iG8jE7BhonbsEg5d9qOYcVZv/Rv6tghaXM/Ug9xahM0nX/H2gfu6X6z11QRTMT6+aywOMA8TDkhPo8aCGw==", + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.1.tgz", + "integrity": "sha512-jaN3dHi0/DDPelk0nLcXRm1q7DNJpjXy7yWaWvbfkPvI+7XNSc/lDOnCLN7gzsyzgu6qSAmgSvP9oXAhP973uQ==", "cpu": [ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" @@ -2714,13 +2906,14 @@ } }, "node_modules/@esbuild/linux-ia32": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.23.0.tgz", - "integrity": "sha512-P7O5Tkh2NbgIm2R6x1zGJJsnacDzTFcRWZyTTMgFdVit6E98LTxO+v8LCCLWRvPrjdzXHx9FEOA8oAZPyApWUA==", + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.1.tgz", + "integrity": "sha512-OJykPaF4v8JidKNGz8c/q1lBO44sQNUQtq1KktJXdBLn1hPod5rE/Hko5ugKKZd+D2+o1a9MFGUEIUwO2YfgkQ==", "cpu": [ "ia32" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" @@ -2730,13 +2923,14 @@ } }, "node_modules/@esbuild/linux-loong64": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.23.0.tgz", - "integrity": "sha512-InQwepswq6urikQiIC/kkx412fqUZudBO4SYKu0N+tGhXRWUqAx+Q+341tFV6QdBifpjYgUndV1hhMq3WeJi7A==", + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.1.tgz", + "integrity": "sha512-nGfornQj4dzcq5Vp835oM/o21UMlXzn79KobKlcs3Wz9smwiifknLy4xDCLUU0BWp7b/houtdrgUz7nOGnfIYg==", "cpu": [ "loong64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" @@ -2746,13 +2940,14 @@ } }, "node_modules/@esbuild/linux-mips64el": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.23.0.tgz", - "integrity": "sha512-J9rflLtqdYrxHv2FqXE2i1ELgNjT+JFURt/uDMoPQLcjWQA5wDKgQA4t/dTqGa88ZVECKaD0TctwsUfHbVoi4w==", + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.1.tgz", + "integrity": "sha512-1osBbPEFYwIE5IVB/0g2X6i1qInZa1aIoj1TdL4AaAb55xIIgbg8Doq6a5BzYWgr+tEcDzYH67XVnTmUzL+nXg==", "cpu": [ "mips64el" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" @@ -2762,13 +2957,14 @@ } }, "node_modules/@esbuild/linux-ppc64": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.23.0.tgz", - "integrity": "sha512-cShCXtEOVc5GxU0fM+dsFD10qZ5UpcQ8AM22bYj0u/yaAykWnqXJDpd77ublcX6vdDsWLuweeuSNZk4yUxZwtw==", + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.1.tgz", + "integrity": "sha512-/6VBJOwUf3TdTvJZ82qF3tbLuWsscd7/1w+D9LH0W/SqUgM5/JJD0lrJ1fVIfZsqB6RFmLCe0Xz3fmZc3WtyVg==", "cpu": [ "ppc64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" @@ -2778,13 +2974,14 @@ } }, "node_modules/@esbuild/linux-riscv64": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.23.0.tgz", - "integrity": "sha512-HEtaN7Y5UB4tZPeQmgz/UhzoEyYftbMXrBCUjINGjh3uil+rB/QzzpMshz3cNUxqXN7Vr93zzVtpIDL99t9aRw==", + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.1.tgz", + "integrity": "sha512-nSut/Mx5gnilhcq2yIMLMe3Wl4FK5wx/o0QuuCLMtmJn+WeWYoEGDN1ipcN72g1WHsnIbxGXd4i/MF0gTcuAjQ==", "cpu": [ "riscv64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" @@ -2794,13 +2991,14 @@ } }, "node_modules/@esbuild/linux-s390x": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.23.0.tgz", - "integrity": "sha512-WDi3+NVAuyjg/Wxi+o5KPqRbZY0QhI9TjrEEm+8dmpY9Xir8+HE/HNx2JoLckhKbFopW0RdO2D72w8trZOV+Wg==", + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.1.tgz", + "integrity": "sha512-cEECeLlJNfT8kZHqLarDBQso9a27o2Zd2AQ8USAEoGtejOrCYHNtKP8XQhMDJMtthdF4GBmjR2au3x1udADQQQ==", "cpu": [ "s390x" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" @@ -2810,13 +3008,14 @@ } }, "node_modules/@esbuild/linux-x64": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.23.0.tgz", - "integrity": "sha512-a3pMQhUEJkITgAw6e0bWA+F+vFtCciMjW/LPtoj99MhVt+Mfb6bbL9hu2wmTZgNd994qTAEw+U/r6k3qHWWaOQ==", + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.1.tgz", + "integrity": "sha512-xbfUhu/gnvSEg+EGovRc+kjBAkrvtk38RlerAzQxvMzlB4fXpCFCeUAYzJvrnhFtdeyVCDANSjJvOvGYoeKzFA==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" @@ -2825,14 +3024,32 @@ "node": ">=18" } }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.1.tgz", + "integrity": "sha512-O96poM2XGhLtpTh+s4+nP7YCCAfb4tJNRVZHfIE7dgmax+yMP2WgMd2OecBuaATHKTHsLWHQeuaxMRnCsH8+5g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, "node_modules/@esbuild/netbsd-x64": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.23.0.tgz", - "integrity": "sha512-cRK+YDem7lFTs2Q5nEv/HHc4LnrfBCbH5+JHu6wm2eP+d8OZNoSMYgPZJq78vqQ9g+9+nMuIsAO7skzphRXHyw==", + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.1.tgz", + "integrity": "sha512-X53z6uXip6KFXBQ+Krbx25XHV/NCbzryM6ehOAeAil7X7oa4XIq+394PWGnwaSQ2WRA0KI6PUO6hTO5zeF5ijA==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "netbsd" @@ -2842,13 +3059,14 @@ } }, "node_modules/@esbuild/openbsd-arm64": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.23.0.tgz", - "integrity": "sha512-suXjq53gERueVWu0OKxzWqk7NxiUWSUlrxoZK7usiF50C6ipColGR5qie2496iKGYNLhDZkPxBI3erbnYkU0rQ==", + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.1.tgz", + "integrity": "sha512-Na9T3szbXezdzM/Kfs3GcRQNjHzM6GzFBeU1/6IV/npKP5ORtp9zbQjvkDJ47s6BCgaAZnnnu/cY1x342+MvZg==", "cpu": [ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "openbsd" @@ -2858,13 +3076,14 @@ } }, "node_modules/@esbuild/openbsd-x64": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.23.0.tgz", - "integrity": "sha512-6p3nHpby0DM/v15IFKMjAaayFhqnXV52aEmv1whZHX56pdkK+MEaLoQWj+H42ssFarP1PcomVhbsR4pkz09qBg==", + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.1.tgz", + "integrity": "sha512-T3H78X2h1tszfRSf+txbt5aOp/e7TAz3ptVKu9Oyir3IAOFPGV6O9c2naym5TOriy1l0nNf6a4X5UXRZSGX/dw==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "openbsd" @@ -2874,13 +3093,14 @@ } }, "node_modules/@esbuild/sunos-x64": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.23.0.tgz", - "integrity": "sha512-BFelBGfrBwk6LVrmFzCq1u1dZbG4zy/Kp93w2+y83Q5UGYF1d8sCzeLI9NXjKyujjBBniQa8R8PzLFAUrSM9OA==", + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.1.tgz", + "integrity": "sha512-2H3RUvcmULO7dIE5EWJH8eubZAI4xw54H1ilJnRNZdeo8dTADEZ21w6J22XBkXqGJbe0+wnNJtw3UXRoLJnFEg==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "sunos" @@ -2890,13 +3110,14 @@ } }, "node_modules/@esbuild/win32-arm64": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.23.0.tgz", - "integrity": "sha512-lY6AC8p4Cnb7xYHuIxQ6iYPe6MfO2CC43XXKo9nBXDb35krYt7KGhQnOkRGar5psxYkircpCqfbNDB4uJbS2jQ==", + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.1.tgz", + "integrity": "sha512-GE7XvrdOzrb+yVKB9KsRMq+7a2U/K5Cf/8grVFRAGJmfADr/e/ODQ134RK2/eeHqYV5eQRFxb1hY7Nr15fv1NQ==", "cpu": [ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "win32" @@ -2906,13 +3127,14 @@ } }, "node_modules/@esbuild/win32-ia32": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.23.0.tgz", - "integrity": "sha512-7L1bHlOTcO4ByvI7OXVI5pNN6HSu6pUQq9yodga8izeuB1KcT2UkHaH6118QJwopExPn0rMHIseCTx1CRo/uNA==", + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.1.tgz", + "integrity": "sha512-uOxSJCIcavSiT6UnBhBzE8wy3n0hOkJsBOzy7HDAuTDE++1DJMRRVCPGisULScHL+a/ZwdXPpXD3IyFKjA7K8A==", "cpu": [ "ia32" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "win32" @@ -2922,13 +3144,14 @@ } }, "node_modules/@esbuild/win32-x64": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.23.0.tgz", - "integrity": "sha512-Arm+WgUFLUATuoxCJcahGuk6Yj9Pzxd6l11Zb/2aAuv5kWWvvfhLFo2fni4uSK5vzlUdCGZ/BdV5tH8klj8p8g==", + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.1.tgz", + "integrity": "sha512-Y1EQdcfwMSeQN/ujR5VayLOJ1BHaK+ssyk0AEzPjC+t1lITgsnccPqFjb6V+LsTp/9Iov4ysfjxLaGJ9RPtkVg==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "win32" @@ -2938,10 +3161,11 @@ } }, "node_modules/@eslint-community/eslint-utils": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.1.tgz", - "integrity": "sha512-s3O3waFUrMV8P/XaF/+ZTp1X9XBZW1a4B97ZnjQF2KYWaFD2A8KyFBsrsfSjEmjn3RGWAIuvlneuZm3CUK3jbA==", + "version": "4.6.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.6.1.tgz", + "integrity": "sha512-KTsJMmobmbrFLe3LDh0PC2FXpcSYJt/MLjlkh/9LEnmKYLSYmT/0EW9JWANjeoemiuZrmogti0tW5Ch+qNUYDw==", "dev": true, + "license": "MIT", "dependencies": { "eslint-visitor-keys": "^3.4.3" }, @@ -2960,6 +3184,7 @@ "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz", "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==", "dev": true, + "license": "MIT", "engines": { "node": "^12.0.0 || ^14.0.0 || >=16.0.0" } @@ -2969,6 +3194,7 @@ "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", "dev": true, + "license": "MIT", "dependencies": { "ajv": "^6.12.4", "debug": "^4.3.2", @@ -2992,6 +3218,7 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "dev": true, + "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -3008,6 +3235,7 @@ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "dev": true, + "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -3018,6 +3246,7 @@ "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", "dev": true, + "license": "MIT", "dependencies": { "type-fest": "^0.20.2" }, @@ -3033,6 +3262,7 @@ "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", "dev": true, + "license": "MIT", "engines": { "node": ">= 4" } @@ -3041,13 +3271,15 @@ "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 + "dev": true, + "license": "MIT" }, "node_modules/@eslint/eslintrc/node_modules/minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "dev": true, + "license": "ISC", "dependencies": { "brace-expansion": "^1.1.7" }, @@ -3060,6 +3292,7 @@ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", "dev": true, + "license": "(MIT OR CC0-1.0)", "engines": { "node": ">=10" }, @@ -3072,6 +3305,7 @@ "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.1.tgz", "integrity": "sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==", "dev": true, + "license": "MIT", "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } @@ -3082,6 +3316,7 @@ "integrity": "sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==", "deprecated": "Use @eslint/config-array instead", "dev": true, + "license": "Apache-2.0", "dependencies": { "@humanwhocodes/object-schema": "^2.0.3", "debug": "^4.3.1", @@ -3096,6 +3331,7 @@ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "dev": true, + "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -3106,6 +3342,7 @@ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "dev": true, + "license": "ISC", "dependencies": { "brace-expansion": "^1.1.7" }, @@ -3118,6 +3355,7 @@ "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", "dev": true, + "license": "Apache-2.0", "engines": { "node": ">=12.22" }, @@ -3131,225 +3369,320 @@ "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", "deprecated": "Use @eslint/object-schema instead", - "dev": true + "dev": true, + "license": "BSD-3-Clause" }, "node_modules/@inquirer/checkbox": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/@inquirer/checkbox/-/checkbox-2.5.0.tgz", - "integrity": "sha512-sMgdETOfi2dUHT8r7TT1BTKOwNvdDGFDXYWtQ2J69SvlYNntk9I/gJe7r5yvMwwsuKnYbuRs3pNhx4tgNck5aA==", + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@inquirer/checkbox/-/checkbox-4.1.5.tgz", + "integrity": "sha512-swPczVU+at65xa5uPfNP9u3qx/alNwiaykiI/ExpsmMSQW55trmZcwhYWzw/7fj+n6Q8z1eENvR7vFfq9oPSAQ==", "dev": true, + "license": "MIT", "dependencies": { - "@inquirer/core": "^9.1.0", - "@inquirer/figures": "^1.0.5", - "@inquirer/type": "^1.5.3", + "@inquirer/core": "^10.1.10", + "@inquirer/figures": "^1.0.11", + "@inquirer/type": "^3.0.6", "ansi-escapes": "^4.3.2", "yoctocolors-cjs": "^2.1.2" }, "engines": { "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } } }, "node_modules/@inquirer/confirm": { - "version": "3.1.22", - "resolved": "https://registry.npmjs.org/@inquirer/confirm/-/confirm-3.1.22.tgz", - "integrity": "sha512-gsAKIOWBm2Q87CDfs9fEo7wJT3fwWIJfnDGMn9Qy74gBnNFOACDNfhUzovubbJjWnKLGBln7/NcSmZwj5DuEXg==", + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/@inquirer/confirm/-/confirm-5.1.6.tgz", + "integrity": "sha512-6ZXYK3M1XmaVBZX6FCfChgtponnL0R6I7k8Nu+kaoNkT828FVZTcca1MqmWQipaW2oNREQl5AaPCUOOCVNdRMw==", "dev": true, + "license": "MIT", "dependencies": { - "@inquirer/core": "^9.0.10", - "@inquirer/type": "^1.5.2" + "@inquirer/core": "^10.1.7", + "@inquirer/type": "^3.0.4" }, "engines": { "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } } }, "node_modules/@inquirer/core": { - "version": "9.2.1", - "resolved": "https://registry.npmjs.org/@inquirer/core/-/core-9.2.1.tgz", - "integrity": "sha512-F2VBt7W/mwqEU4bL0RnHNZmC/OxzNx9cOYxHqnXX3MP6ruYvZUZAW9imgN9+h/uBT/oP8Gh888J2OZSbjSeWcg==", + "version": "10.1.10", + "resolved": "https://registry.npmjs.org/@inquirer/core/-/core-10.1.10.tgz", + "integrity": "sha512-roDaKeY1PYY0aCqhRmXihrHjoSW2A00pV3Ke5fTpMCkzcGF64R8e0lw3dK+eLEHwS4vB5RnW1wuQmvzoRul8Mw==", "dev": true, + "license": "MIT", "dependencies": { - "@inquirer/figures": "^1.0.6", - "@inquirer/type": "^2.0.0", - "@types/mute-stream": "^0.0.4", - "@types/node": "^22.5.5", - "@types/wrap-ansi": "^3.0.0", + "@inquirer/figures": "^1.0.11", + "@inquirer/type": "^3.0.6", "ansi-escapes": "^4.3.2", "cli-width": "^4.1.0", - "mute-stream": "^1.0.0", + "mute-stream": "^2.0.0", "signal-exit": "^4.1.0", - "strip-ansi": "^6.0.1", "wrap-ansi": "^6.2.0", "yoctocolors-cjs": "^2.1.2" }, "engines": { "node": ">=18" - } - }, - "node_modules/@inquirer/core/node_modules/@inquirer/type": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@inquirer/type/-/type-2.0.0.tgz", - "integrity": "sha512-XvJRx+2KR3YXyYtPUUy+qd9i7p+GO9Ko6VIIpWlBrpWwXDv8WLFeHTxz35CfQFUiBMLXlGHhGzys7lqit9gWag==", - "dev": true, - "dependencies": { - "mute-stream": "^1.0.0" }, - "engines": { - "node": ">=18" + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } } }, "node_modules/@inquirer/editor": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@inquirer/editor/-/editor-2.2.0.tgz", - "integrity": "sha512-9KHOpJ+dIL5SZli8lJ6xdaYLPPzB8xB9GZItg39MBybzhxA16vxmszmQFrRwbOA918WA2rvu8xhDEg/p6LXKbw==", + "version": "4.2.10", + "resolved": "https://registry.npmjs.org/@inquirer/editor/-/editor-4.2.10.tgz", + "integrity": "sha512-5GVWJ+qeI6BzR6TIInLP9SXhWCEcvgFQYmcRG6d6RIlhFjM5TyG18paTGBgRYyEouvCmzeco47x9zX9tQEofkw==", "dev": true, + "license": "MIT", "dependencies": { - "@inquirer/core": "^9.1.0", - "@inquirer/type": "^1.5.3", + "@inquirer/core": "^10.1.10", + "@inquirer/type": "^3.0.6", "external-editor": "^3.1.0" }, "engines": { "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } } }, "node_modules/@inquirer/expand": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@inquirer/expand/-/expand-2.3.0.tgz", - "integrity": "sha512-qnJsUcOGCSG1e5DTOErmv2BPQqrtT6uzqn1vI/aYGiPKq+FgslGZmtdnXbhuI7IlT7OByDoEEqdnhUnVR2hhLw==", + "version": "4.0.12", + "resolved": "https://registry.npmjs.org/@inquirer/expand/-/expand-4.0.12.tgz", + "integrity": "sha512-jV8QoZE1fC0vPe6TnsOfig+qwu7Iza1pkXoUJ3SroRagrt2hxiL+RbM432YAihNR7m7XnU0HWl/WQ35RIGmXHw==", "dev": true, + "license": "MIT", "dependencies": { - "@inquirer/core": "^9.1.0", - "@inquirer/type": "^1.5.3", + "@inquirer/core": "^10.1.10", + "@inquirer/type": "^3.0.6", "yoctocolors-cjs": "^2.1.2" }, "engines": { "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } } }, "node_modules/@inquirer/figures": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/@inquirer/figures/-/figures-1.0.10.tgz", - "integrity": "sha512-Ey6176gZmeqZuY/W/nZiUyvmb1/qInjcpiZjXWi6nON+nxJpD1bxtSoBxNliGISae32n6OwbY+TSXPZ1CfS4bw==", + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@inquirer/figures/-/figures-1.0.11.tgz", + "integrity": "sha512-eOg92lvrn/aRUqbxRyvpEWnrvRuTYRifixHkYVpJiygTgVSBIHDqLh0SrMQXkafvULg3ck11V7xvR+zcgvpHFw==", "dev": true, + "license": "MIT", "engines": { "node": ">=18" } }, "node_modules/@inquirer/input": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@inquirer/input/-/input-2.3.0.tgz", - "integrity": "sha512-XfnpCStx2xgh1LIRqPXrTNEEByqQWoxsWYzNRSEUxJ5c6EQlhMogJ3vHKu8aXuTacebtaZzMAHwEL0kAflKOBw==", + "version": "4.1.9", + "resolved": "https://registry.npmjs.org/@inquirer/input/-/input-4.1.9.tgz", + "integrity": "sha512-mshNG24Ij5KqsQtOZMgj5TwEjIf+F2HOESk6bjMwGWgcH5UBe8UoljwzNFHqdMbGYbgAf6v2wU/X9CAdKJzgOA==", "dev": true, + "license": "MIT", "dependencies": { - "@inquirer/core": "^9.1.0", - "@inquirer/type": "^1.5.3" + "@inquirer/core": "^10.1.10", + "@inquirer/type": "^3.0.6" }, "engines": { "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } } }, "node_modules/@inquirer/number": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@inquirer/number/-/number-1.1.0.tgz", - "integrity": "sha512-ilUnia/GZUtfSZy3YEErXLJ2Sljo/mf9fiKc08n18DdwdmDbOzRcTv65H1jjDvlsAuvdFXf4Sa/aL7iw/NanVA==", + "version": "3.0.12", + "resolved": "https://registry.npmjs.org/@inquirer/number/-/number-3.0.12.tgz", + "integrity": "sha512-7HRFHxbPCA4e4jMxTQglHJwP+v/kpFsCf2szzfBHy98Wlc3L08HL76UDiA87TOdX5fwj2HMOLWqRWv9Pnn+Z5Q==", "dev": true, + "license": "MIT", "dependencies": { - "@inquirer/core": "^9.1.0", - "@inquirer/type": "^1.5.3" + "@inquirer/core": "^10.1.10", + "@inquirer/type": "^3.0.6" }, "engines": { "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } } }, "node_modules/@inquirer/password": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@inquirer/password/-/password-2.2.0.tgz", - "integrity": "sha512-5otqIpgsPYIshqhgtEwSspBQE40etouR8VIxzpJkv9i0dVHIpyhiivbkH9/dGiMLdyamT54YRdGJLfl8TFnLHg==", + "version": "4.0.12", + "resolved": "https://registry.npmjs.org/@inquirer/password/-/password-4.0.12.tgz", + "integrity": "sha512-FlOB0zvuELPEbnBYiPaOdJIaDzb2PmJ7ghi/SVwIHDDSQ2K4opGBkF+5kXOg6ucrtSUQdLhVVY5tycH0j0l+0g==", "dev": true, + "license": "MIT", "dependencies": { - "@inquirer/core": "^9.1.0", - "@inquirer/type": "^1.5.3", + "@inquirer/core": "^10.1.10", + "@inquirer/type": "^3.0.6", "ansi-escapes": "^4.3.2" }, "engines": { "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } } }, "node_modules/@inquirer/prompts": { - "version": "5.3.8", - "resolved": "https://registry.npmjs.org/@inquirer/prompts/-/prompts-5.3.8.tgz", - "integrity": "sha512-b2BudQY/Si4Y2a0PdZZL6BeJtl8llgeZa7U2j47aaJSCeAl1e4UI7y8a9bSkO3o/ZbZrgT5muy/34JbsjfIWxA==", + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@inquirer/prompts/-/prompts-7.1.0.tgz", + "integrity": "sha512-5U/XiVRH2pp1X6gpNAjWOglMf38/Ys522ncEHIKT1voRUvSj/DQnR22OVxHnwu5S+rCFaUiPQ57JOtMFQayqYA==", "dev": true, + "license": "MIT", "dependencies": { - "@inquirer/checkbox": "^2.4.7", - "@inquirer/confirm": "^3.1.22", - "@inquirer/editor": "^2.1.22", - "@inquirer/expand": "^2.1.22", - "@inquirer/input": "^2.2.9", - "@inquirer/number": "^1.0.10", - "@inquirer/password": "^2.1.22", - "@inquirer/rawlist": "^2.2.4", - "@inquirer/search": "^1.0.7", - "@inquirer/select": "^2.4.7" + "@inquirer/checkbox": "^4.0.2", + "@inquirer/confirm": "^5.0.2", + "@inquirer/editor": "^4.1.0", + "@inquirer/expand": "^4.0.2", + "@inquirer/input": "^4.0.2", + "@inquirer/number": "^3.0.2", + "@inquirer/password": "^4.0.2", + "@inquirer/rawlist": "^4.0.2", + "@inquirer/search": "^3.0.2", + "@inquirer/select": "^4.0.2" }, "engines": { "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" } }, "node_modules/@inquirer/rawlist": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@inquirer/rawlist/-/rawlist-2.3.0.tgz", - "integrity": "sha512-zzfNuINhFF7OLAtGHfhwOW2TlYJyli7lOUoJUXw/uyklcwalV6WRXBXtFIicN8rTRK1XTiPWB4UY+YuW8dsnLQ==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@inquirer/rawlist/-/rawlist-4.1.0.tgz", + "integrity": "sha512-6ob45Oh9pXmfprKqUiEeMz/tjtVTFQTgDDz1xAMKMrIvyrYjAmRbQZjMJfsictlL4phgjLhdLu27IkHNnNjB7g==", "dev": true, + "license": "MIT", "dependencies": { - "@inquirer/core": "^9.1.0", - "@inquirer/type": "^1.5.3", + "@inquirer/core": "^10.1.10", + "@inquirer/type": "^3.0.6", "yoctocolors-cjs": "^2.1.2" }, "engines": { "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } } }, "node_modules/@inquirer/search": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@inquirer/search/-/search-1.1.0.tgz", - "integrity": "sha512-h+/5LSj51dx7hp5xOn4QFnUaKeARwUCLs6mIhtkJ0JYPBLmEYjdHSYh7I6GrLg9LwpJ3xeX0FZgAG1q0QdCpVQ==", + "version": "3.0.12", + "resolved": "https://registry.npmjs.org/@inquirer/search/-/search-3.0.12.tgz", + "integrity": "sha512-H/kDJA3kNlnNIjB8YsaXoQI0Qccgf0Na14K1h8ExWhNmUg2E941dyFPrZeugihEa9AZNW5NdsD/NcvUME83OPQ==", "dev": true, + "license": "MIT", "dependencies": { - "@inquirer/core": "^9.1.0", - "@inquirer/figures": "^1.0.5", - "@inquirer/type": "^1.5.3", + "@inquirer/core": "^10.1.10", + "@inquirer/figures": "^1.0.11", + "@inquirer/type": "^3.0.6", "yoctocolors-cjs": "^2.1.2" }, "engines": { "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } } }, "node_modules/@inquirer/select": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/@inquirer/select/-/select-2.5.0.tgz", - "integrity": "sha512-YmDobTItPP3WcEI86GvPo+T2sRHkxxOq/kXmsBjHS5BVXUgvgZ5AfJjkvQvZr03T81NnI3KrrRuMzeuYUQRFOA==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@inquirer/select/-/select-4.2.0.tgz", + "integrity": "sha512-KkXQ4aSySWimpV4V/TUJWdB3tdfENZUU765GjOIZ0uPwdbGIG6jrxD4dDf1w68uP+DVtfNhr1A92B+0mbTZ8FA==", "dev": true, + "license": "MIT", "dependencies": { - "@inquirer/core": "^9.1.0", - "@inquirer/figures": "^1.0.5", - "@inquirer/type": "^1.5.3", + "@inquirer/core": "^10.1.10", + "@inquirer/figures": "^1.0.11", + "@inquirer/type": "^3.0.6", "ansi-escapes": "^4.3.2", "yoctocolors-cjs": "^2.1.2" }, "engines": { "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } } }, "node_modules/@inquirer/type": { - "version": "1.5.5", - "resolved": "https://registry.npmjs.org/@inquirer/type/-/type-1.5.5.tgz", - "integrity": "sha512-MzICLu4yS7V8AA61sANROZ9vT1H3ooca5dSmI1FjZkzq7o/koMsRfQSzRtFo+F3Ao4Sf1C0bpLKejpKB/+j6MA==", + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@inquirer/type/-/type-3.0.6.tgz", + "integrity": "sha512-/mKVCtVpyBu3IDarv0G+59KC4stsD5mDsGpYh+GKs1NZT88Jh52+cuoA1AtLk2Q0r/quNl+1cSUyLRHBFeD0XA==", "dev": true, - "dependencies": { - "mute-stream": "^1.0.0" - }, + "license": "MIT", "engines": { "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } } }, "node_modules/@isaacs/cliui": { @@ -3357,6 +3690,7 @@ "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", "dev": true, + "license": "ISC", "dependencies": { "string-width": "^5.1.2", "string-width-cjs": "npm:string-width@^4.2.0", @@ -3374,6 +3708,7 @@ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", "dev": true, + "license": "MIT", "engines": { "node": ">=12" }, @@ -3386,6 +3721,7 @@ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", "dev": true, + "license": "MIT", "engines": { "node": ">=12" }, @@ -3397,13 +3733,15 @@ "version": "9.2.2", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/@isaacs/cliui/node_modules/string-width": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", "dev": true, + "license": "MIT", "dependencies": { "eastasianwidth": "^0.2.0", "emoji-regex": "^9.2.2", @@ -3421,6 +3759,7 @@ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", "dev": true, + "license": "MIT", "dependencies": { "ansi-regex": "^6.0.1" }, @@ -3436,6 +3775,7 @@ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", "dev": true, + "license": "MIT", "dependencies": { "ansi-styles": "^6.1.0", "string-width": "^5.0.1", @@ -3448,11 +3788,25 @@ "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, + "node_modules/@isaacs/fs-minipass": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz", + "integrity": "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "minipass": "^7.0.4" + }, + "engines": { + "node": ">=18.0.0" + } + }, "node_modules/@istanbuljs/schema": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } @@ -3462,6 +3816,7 @@ "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz", "integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==", "dev": true, + "license": "MIT", "dependencies": { "@jridgewell/set-array": "^1.2.1", "@jridgewell/sourcemap-codec": "^1.4.10", @@ -3476,6 +3831,7 @@ "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", "dev": true, + "license": "MIT", "engines": { "node": ">=6.0.0" } @@ -3485,6 +3841,7 @@ "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", "dev": true, + "license": "MIT", "engines": { "node": ">=6.0.0" } @@ -3494,6 +3851,7 @@ "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.6.tgz", "integrity": "sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==", "dev": true, + "license": "MIT", "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.25" @@ -3503,13 +3861,15 @@ "version": "1.5.0", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/@jridgewell/trace-mapping": { "version": "0.3.25", "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", "dev": true, + "license": "MIT", "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" @@ -3520,6 +3880,7 @@ "resolved": "https://registry.npmjs.org/@jsonjoy.com/base64/-/base64-1.1.2.tgz", "integrity": "sha512-q6XAnWQDIMA3+FTiOYajoYqySkO+JSat0ytXGSuRdq9uXE7o92gzuQwQM14xaCRlBLGq3v5miDGC4vkVTn54xA==", "dev": true, + "license": "Apache-2.0", "engines": { "node": ">=10.0" }, @@ -3532,10 +3893,11 @@ } }, "node_modules/@jsonjoy.com/json-pack": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@jsonjoy.com/json-pack/-/json-pack-1.1.1.tgz", - "integrity": "sha512-osjeBqMJ2lb/j/M8NCPjs1ylqWIcTRTycIhVB5pt6LgzgeRSb0YRZ7j9RfA8wIUrsr/medIuhVyonXRZWLyfdw==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/json-pack/-/json-pack-1.2.0.tgz", + "integrity": "sha512-io1zEbbYcElht3tdlqEOFxZ0dMTYrHz9iMf0gqn1pPjZFTCgM5R4R5IMA20Chb2UPYYsxjzs8CgZ7Nb5n2K2rA==", "dev": true, + "license": "Apache-2.0", "dependencies": { "@jsonjoy.com/base64": "^1.1.1", "@jsonjoy.com/util": "^1.1.2", @@ -3558,6 +3920,7 @@ "resolved": "https://registry.npmjs.org/@jsonjoy.com/util/-/util-1.5.0.tgz", "integrity": "sha512-ojoNsrIuPI9g6o8UxhraZQSyF2ByJanAY4cTFbc8Mf2AXEF4aQRGY1dJxyJpuyav8r9FGflEt/Ff3u5Nt6YMPA==", "dev": true, + "license": "Apache-2.0", "engines": { "node": ">=10.0" }, @@ -3573,96 +3936,127 @@ "version": "2.0.5", "resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.5.tgz", "integrity": "sha512-Vo+PSpZG2/fmgmiNzYK9qWRh8h/CHrwD0mo1h1DzL4yzHNSfWYujGTYsWGreD000gcgmZ7K4Ys6Tx9TxtsKdDw==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/@listr2/prompt-adapter-inquirer": { - "version": "2.0.15", - "resolved": "https://registry.npmjs.org/@listr2/prompt-adapter-inquirer/-/prompt-adapter-inquirer-2.0.15.tgz", - "integrity": "sha512-MZrGem/Ujjd4cPTLYDfCZK2iKKeiO/8OX13S6jqxldLs0Prf2aGqVlJ77nMBqMv7fzqgXEgjrNHLXcKR8l9lOg==", + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/@listr2/prompt-adapter-inquirer/-/prompt-adapter-inquirer-2.0.18.tgz", + "integrity": "sha512-0hz44rAcrphyXcA8IS7EJ2SCoaBZD2u5goE8S/e+q/DL+dOGpqpcLidVOFeLG3VgML62SXmfRLAhWt0zL1oW4Q==", "dev": true, + "license": "MIT", "dependencies": { - "@inquirer/type": "^1.5.1" + "@inquirer/type": "^1.5.5" }, "engines": { "node": ">=18.0.0" }, "peerDependencies": { - "@inquirer/prompts": ">= 3 < 6" + "@inquirer/prompts": ">= 3 < 8" + } + }, + "node_modules/@listr2/prompt-adapter-inquirer/node_modules/@inquirer/type": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@inquirer/type/-/type-1.5.5.tgz", + "integrity": "sha512-MzICLu4yS7V8AA61sANROZ9vT1H3ooca5dSmI1FjZkzq7o/koMsRfQSzRtFo+F3Ao4Sf1C0bpLKejpKB/+j6MA==", + "dev": true, + "license": "MIT", + "dependencies": { + "mute-stream": "^1.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@listr2/prompt-adapter-inquirer/node_modules/mute-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-1.0.0.tgz", + "integrity": "sha512-avsJQhyd+680gKXyG/sQc0nXaC6rBkPOfyHYcFb9+hdkqQkR9bdnkJ0AMZhke0oesPqIO+mFFJ+IdBc7mst4IA==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, "node_modules/@lmdb/lmdb-darwin-arm64": { - "version": "3.0.13", - "resolved": "https://registry.npmjs.org/@lmdb/lmdb-darwin-arm64/-/lmdb-darwin-arm64-3.0.13.tgz", - "integrity": "sha512-uiKPB0Fv6WEEOZjruu9a6wnW/8jrjzlZbxXscMB8kuCJ1k6kHpcBnuvaAWcqhbI7rqX5GKziwWEdD+wi2gNLfA==", + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/@lmdb/lmdb-darwin-arm64/-/lmdb-darwin-arm64-3.2.6.tgz", + "integrity": "sha512-yF/ih9EJJZc72psFQbwnn8mExIWfTnzWJg+N02hnpXtDPETYLmQswIMBn7+V88lfCaFrMozJsUvcEQIkEPU0Gg==", "cpu": [ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "darwin" ] }, "node_modules/@lmdb/lmdb-darwin-x64": { - "version": "3.0.13", - "resolved": "https://registry.npmjs.org/@lmdb/lmdb-darwin-x64/-/lmdb-darwin-x64-3.0.13.tgz", - "integrity": "sha512-bEVIIfK5mSQoG1R19qA+fJOvCB+0wVGGnXHT3smchBVahYBdlPn2OsZZKzlHWfb1E+PhLBmYfqB5zQXFP7hJig==", + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/@lmdb/lmdb-darwin-x64/-/lmdb-darwin-x64-3.2.6.tgz", + "integrity": "sha512-5BbCumsFLbCi586Bb1lTWQFkekdQUw8/t8cy++Uq251cl3hbDIGEwD9HAwh8H6IS2F6QA9KdKmO136LmipRNkg==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "darwin" ] }, "node_modules/@lmdb/lmdb-linux-arm": { - "version": "3.0.13", - "resolved": "https://registry.npmjs.org/@lmdb/lmdb-linux-arm/-/lmdb-linux-arm-3.0.13.tgz", - "integrity": "sha512-Yml1KlMzOnXj/tnW7yX8U78iAzTk39aILYvCPbqeewAq1kSzl+w59k/fiVkTBfvDi/oW/5YRxL+Fq+Y1Fr1r2Q==", + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/@lmdb/lmdb-linux-arm/-/lmdb-linux-arm-3.2.6.tgz", + "integrity": "sha512-+6XgLpMb7HBoWxXj+bLbiiB4s0mRRcDPElnRS3LpWRzdYSe+gFk5MT/4RrVNqd2MESUDmb53NUXw1+BP69bjiQ==", "cpu": [ "arm" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ] }, "node_modules/@lmdb/lmdb-linux-arm64": { - "version": "3.0.13", - "resolved": "https://registry.npmjs.org/@lmdb/lmdb-linux-arm64/-/lmdb-linux-arm64-3.0.13.tgz", - "integrity": "sha512-afbVrsMgZ9dUTNUchFpj5VkmJRxvht/u335jUJ7o23YTbNbnpmXif3VKQGCtnjSh+CZaqm6N3CPG8KO3zwyZ1Q==", + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/@lmdb/lmdb-linux-arm64/-/lmdb-linux-arm64-3.2.6.tgz", + "integrity": "sha512-l5VmJamJ3nyMmeD1ANBQCQqy7do1ESaJQfKPSm2IG9/ADZryptTyCj8N6QaYgIWewqNUrcbdMkJajRQAt5Qjfg==", "cpu": [ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ] }, "node_modules/@lmdb/lmdb-linux-x64": { - "version": "3.0.13", - "resolved": "https://registry.npmjs.org/@lmdb/lmdb-linux-x64/-/lmdb-linux-x64-3.0.13.tgz", - "integrity": "sha512-vOtxu0xC0SLdQ2WRXg8Qgd8T32ak4SPqk5zjItRszrJk2BdeXqfGxBJbP7o4aOvSPSmSSv46Lr1EP4HXU8v7Kg==", + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/@lmdb/lmdb-linux-x64/-/lmdb-linux-x64-3.2.6.tgz", + "integrity": "sha512-nDYT8qN9si5+onHYYaI4DiauDMx24OAiuZAUsEqrDy+ja/3EbpXPX/VAkMV8AEaQhy3xc4dRC+KcYIvOFefJ4Q==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ] }, "node_modules/@lmdb/lmdb-win32-x64": { - "version": "3.0.13", - "resolved": "https://registry.npmjs.org/@lmdb/lmdb-win32-x64/-/lmdb-win32-x64-3.0.13.tgz", - "integrity": "sha512-UCrMJQY/gJnOl3XgbWRZZUvGGBuKy6i0YNSptgMzHBjs+QYDYR1Mt/RLTOPy4fzzves65O1EDmlL//OzEqoLlA==", + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/@lmdb/lmdb-win32-x64/-/lmdb-win32-x64-3.2.6.tgz", + "integrity": "sha512-XlqVtILonQnG+9fH2N3Aytria7P/1fwDgDhl29rde96uH2sLB8CHORIf2PfuLVzFQJ7Uqp8py9AYwr3ZUCFfWg==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "win32" @@ -3676,6 +4070,7 @@ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "darwin" @@ -3689,6 +4084,7 @@ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "darwin" @@ -3702,6 +4098,7 @@ "arm" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" @@ -3715,6 +4112,7 @@ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" @@ -3728,6 +4126,7 @@ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" @@ -3741,61 +4140,401 @@ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "win32" ] }, - "node_modules/@ngrx/component-store": { - "version": "18.1.1", - "resolved": "https://registry.npmjs.org/@ngrx/component-store/-/component-store-18.1.1.tgz", - "integrity": "sha512-+FDd44D+unx/eE/7qCyK1IDsTu8503JOnj3lEhL9f9TDmE4arDNqeNx/8Sh3V3HDkH7mVC9iV0c538ACGlvWIA==", - "dependencies": { - "tslib": "^2.0.0" + "node_modules/@napi-rs/nice": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice/-/nice-1.0.1.tgz", + "integrity": "sha512-zM0mVWSXE0a0h9aKACLwKmD6nHcRiKrPpCfvaKqG1CqDEyjEawId0ocXxVzPMCAm6kkWr2P025msfxXEnt8UGQ==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 10" }, - "peerDependencies": { - "@angular/core": "^18.0.0", - "rxjs": "^6.5.3 || ^7.5.0" + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + }, + "optionalDependencies": { + "@napi-rs/nice-android-arm-eabi": "1.0.1", + "@napi-rs/nice-android-arm64": "1.0.1", + "@napi-rs/nice-darwin-arm64": "1.0.1", + "@napi-rs/nice-darwin-x64": "1.0.1", + "@napi-rs/nice-freebsd-x64": "1.0.1", + "@napi-rs/nice-linux-arm-gnueabihf": "1.0.1", + "@napi-rs/nice-linux-arm64-gnu": "1.0.1", + "@napi-rs/nice-linux-arm64-musl": "1.0.1", + "@napi-rs/nice-linux-ppc64-gnu": "1.0.1", + "@napi-rs/nice-linux-riscv64-gnu": "1.0.1", + "@napi-rs/nice-linux-s390x-gnu": "1.0.1", + "@napi-rs/nice-linux-x64-gnu": "1.0.1", + "@napi-rs/nice-linux-x64-musl": "1.0.1", + "@napi-rs/nice-win32-arm64-msvc": "1.0.1", + "@napi-rs/nice-win32-ia32-msvc": "1.0.1", + "@napi-rs/nice-win32-x64-msvc": "1.0.1" + } + }, + "node_modules/@napi-rs/nice-android-arm-eabi": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-android-arm-eabi/-/nice-android-arm-eabi-1.0.1.tgz", + "integrity": "sha512-5qpvOu5IGwDo7MEKVqqyAxF90I6aLj4n07OzpARdgDRfz8UbBztTByBp0RC59r3J1Ij8uzYi6jI7r5Lws7nn6w==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10" } }, - "node_modules/@ngrx/effects": { - "version": "18.1.1", - "resolved": "https://registry.npmjs.org/@ngrx/effects/-/effects-18.1.1.tgz", - "integrity": "sha512-XXob8kYEvYMaZwgHtrrTW0XZargbu5PloEpNHLnzB8jPk0yWEw6keryxaF09Ylws1779MWvMmF/YP2rPl04nHQ==", + "node_modules/@napi-rs/nice-android-arm64": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-android-arm64/-/nice-android-arm64-1.0.1.tgz", + "integrity": "sha512-GqvXL0P8fZ+mQqG1g0o4AO9hJjQaeYG84FRfZaYjyJtZZZcMjXW5TwkL8Y8UApheJgyE13TQ4YNUssQaTgTyvA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-darwin-arm64": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-darwin-arm64/-/nice-darwin-arm64-1.0.1.tgz", + "integrity": "sha512-91k3HEqUl2fsrz/sKkuEkscj6EAj3/eZNCLqzD2AA0TtVbkQi8nqxZCZDMkfklULmxLkMxuUdKe7RvG/T6s2AA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-darwin-x64": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-darwin-x64/-/nice-darwin-x64-1.0.1.tgz", + "integrity": "sha512-jXnMleYSIR/+TAN/p5u+NkCA7yidgswx5ftqzXdD5wgy/hNR92oerTXHc0jrlBisbd7DpzoaGY4cFD7Sm5GlgQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-freebsd-x64": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-freebsd-x64/-/nice-freebsd-x64-1.0.1.tgz", + "integrity": "sha512-j+iJ/ezONXRQsVIB/FJfwjeQXX7A2tf3gEXs4WUGFrJjpe/z2KB7sOv6zpkm08PofF36C9S7wTNuzHZ/Iiccfw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-linux-arm-gnueabihf": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-linux-arm-gnueabihf/-/nice-linux-arm-gnueabihf-1.0.1.tgz", + "integrity": "sha512-G8RgJ8FYXYkkSGQwywAUh84m946UTn6l03/vmEXBYNJxQJcD+I3B3k5jmjFG/OPiU8DfvxutOP8bi+F89MCV7Q==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-linux-arm64-gnu": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-linux-arm64-gnu/-/nice-linux-arm64-gnu-1.0.1.tgz", + "integrity": "sha512-IMDak59/W5JSab1oZvmNbrms3mHqcreaCeClUjwlwDr0m3BoR09ZiN8cKFBzuSlXgRdZ4PNqCYNeGQv7YMTjuA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-linux-arm64-musl": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-linux-arm64-musl/-/nice-linux-arm64-musl-1.0.1.tgz", + "integrity": "sha512-wG8fa2VKuWM4CfjOjjRX9YLIbysSVV1S3Kgm2Fnc67ap/soHBeYZa6AGMeR5BJAylYRjnoVOzV19Cmkco3QEPw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-linux-ppc64-gnu": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-linux-ppc64-gnu/-/nice-linux-ppc64-gnu-1.0.1.tgz", + "integrity": "sha512-lxQ9WrBf0IlNTCA9oS2jg/iAjQyTI6JHzABV664LLrLA/SIdD+I1i3Mjf7TsnoUbgopBcCuDztVLfJ0q9ubf6Q==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-linux-riscv64-gnu": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-linux-riscv64-gnu/-/nice-linux-riscv64-gnu-1.0.1.tgz", + "integrity": "sha512-3xs69dO8WSWBb13KBVex+yvxmUeEsdWexxibqskzoKaWx9AIqkMbWmE2npkazJoopPKX2ULKd8Fm9veEn0g4Ig==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-linux-s390x-gnu": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-linux-s390x-gnu/-/nice-linux-s390x-gnu-1.0.1.tgz", + "integrity": "sha512-lMFI3i9rlW7hgToyAzTaEybQYGbQHDrpRkg+1gJWEpH0PLAQoZ8jiY0IzakLfNWnVda1eTYYlxxFYzW8Rqczkg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-linux-x64-gnu": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-linux-x64-gnu/-/nice-linux-x64-gnu-1.0.1.tgz", + "integrity": "sha512-XQAJs7DRN2GpLN6Fb+ZdGFeYZDdGl2Fn3TmFlqEL5JorgWKrQGRUrpGKbgZ25UeZPILuTKJ+OowG2avN8mThBA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-linux-x64-musl": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-linux-x64-musl/-/nice-linux-x64-musl-1.0.1.tgz", + "integrity": "sha512-/rodHpRSgiI9o1faq9SZOp/o2QkKQg7T+DK0R5AkbnI/YxvAIEHf2cngjYzLMQSQgUhxym+LFr+UGZx4vK4QdQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-win32-arm64-msvc": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-win32-arm64-msvc/-/nice-win32-arm64-msvc-1.0.1.tgz", + "integrity": "sha512-rEcz9vZymaCB3OqEXoHnp9YViLct8ugF+6uO5McifTedjq4QMQs3DHz35xBEGhH3gJWEsXMUbzazkz5KNM5YUg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-win32-ia32-msvc": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-win32-ia32-msvc/-/nice-win32-ia32-msvc-1.0.1.tgz", + "integrity": "sha512-t7eBAyPUrWL8su3gDxw9xxxqNwZzAqKo0Szv3IjVQd1GpXXVkb6vBBQUuxfIYaXMzZLwlxRQ7uzM2vdUE9ULGw==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-win32-x64-msvc": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-win32-x64-msvc/-/nice-win32-x64-msvc-1.0.1.tgz", + "integrity": "sha512-JlF+uDcatt3St2ntBG8H02F1mM45i5SF9W+bIKiReVE6wiy3o16oBP/yxt+RZ+N6LbCImJXJ6bXNO2kn9AXicg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@ngrx/component-store": { + "version": "19.0.0-beta.0", + "resolved": "https://registry.npmjs.org/@ngrx/component-store/-/component-store-19.0.0-beta.0.tgz", + "integrity": "sha512-yJYxRR28SazEj8xBfKWznvnblWsWt9gY5yqEgTatFc1BiA3dptMKSPrHf6WuXtsqRDg/UnM/oEoywM0LWz05Wg==", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.0" + }, + "peerDependencies": { + "@angular/core": "^19.0.0", + "rxjs": "^6.5.3 || ^7.5.0" + } + }, + "node_modules/@ngrx/effects": { + "version": "19.0.0-beta.0", + "resolved": "https://registry.npmjs.org/@ngrx/effects/-/effects-19.0.0-beta.0.tgz", + "integrity": "sha512-No+qZtPxOGCp07Ob06qJKwzLFd/edlKnyX0eQlP8eP9OCNkFJYKgXRbCTBzHWHwq1heGxV/prMEm3YTs3bJWAQ==", + "license": "MIT", "dependencies": { "tslib": "^2.0.0" }, "peerDependencies": { - "@angular/core": "^18.0.0", - "@ngrx/store": "18.1.1", + "@angular/core": "^19.0.0", + "@ngrx/store": "19.0.0-beta.0", "rxjs": "^6.5.3 || ^7.5.0" } }, + "node_modules/@ngrx/operators": { + "version": "19.1.0", + "resolved": "https://registry.npmjs.org/@ngrx/operators/-/operators-19.1.0.tgz", + "integrity": "sha512-gpo69FnoAF69X68pk9eWFHB630xqerBYkF68wOFMciLOV2im3b/fAf+0sRvnQJmVtG/8jO0IPVdvLqa1TbWmPA==", + "license": "MIT", + "dependencies": { + "tslib": "^2.3.0" + }, + "peerDependencies": { + "rxjs": "^6.5.3 || ^7.4.0" + } + }, + "node_modules/@ngrx/signals": { + "version": "19.1.0", + "resolved": "https://registry.npmjs.org/@ngrx/signals/-/signals-19.1.0.tgz", + "integrity": "sha512-v8sbb+Iox9kdIaKbFgt4Z1W+NxzIU4+g+6qQU6/c27UmtQXv0s1zUKKofPRK0qwkaZzNWkxNToxyoE285ukqcQ==", + "license": "MIT", + "dependencies": { + "tslib": "^2.3.0" + }, + "peerDependencies": { + "@angular/core": "^19.0.0", + "rxjs": "^6.5.3 || ^7.4.0" + }, + "peerDependenciesMeta": { + "rxjs": { + "optional": true + } + } + }, "node_modules/@ngrx/store": { - "version": "18.1.1", - "resolved": "https://registry.npmjs.org/@ngrx/store/-/store-18.1.1.tgz", - "integrity": "sha512-K0v1akJ2sEnIeb1AUA064+ksgRgbMgVG9HbSsLBxENbFjK2ZvKRxo1bpOw6WHW9+hyDTlhZGl7+gUtjmo3497g==", + "version": "19.0.0-beta.0", + "resolved": "https://registry.npmjs.org/@ngrx/store/-/store-19.0.0-beta.0.tgz", + "integrity": "sha512-vc7WgocKsQAcz7w5JFIwSSQauIl5OmDm92Wq00Vc2PjMWM2gcns20IHK+qAlpK/scJ6oiiBztWK3kNTu24QaEg==", + "license": "MIT", "dependencies": { "tslib": "^2.0.0" }, "peerDependencies": { - "@angular/core": "^18.0.0", + "@angular/core": "^19.0.0", "rxjs": "^6.5.3 || ^7.5.0" } }, "node_modules/@ngtools/webpack": { - "version": "18.2.14", - "resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-18.2.14.tgz", - "integrity": "sha512-rT+Y4WR8QTVsijtb+YRqHcPTpd1ZiwRbklQXRTxU0YGFHpxpi+bhjmY8FjpPoAtdPO1Lg3l3KIZPZa0thG0FNg==", + "version": "19.2.9", + "resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-19.2.9.tgz", + "integrity": "sha512-CLfUauqi2Xp/jKGxp5wUwjqfVQWcBE09GMd51ovcCRLkgB2Kh26+CiVnGw5/lkBpISUCNdgN6nGiS+nfqMfFeQ==", "dev": true, + "license": "MIT", "engines": { "node": "^18.19.1 || ^20.11.1 || >=22.0.0", "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", "yarn": ">= 1.13.0" }, "peerDependencies": { - "@angular/compiler-cli": "^18.0.0", - "typescript": ">=5.4 <5.6", + "@angular/compiler-cli": "^19.0.0 || ^19.2.0-next.0", + "typescript": ">=5.5 <5.9", "webpack": "^5.54.0" } }, @@ -3804,6 +4543,7 @@ "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", "dev": true, + "license": "MIT", "dependencies": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" @@ -3817,6 +4557,7 @@ "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", "dev": true, + "license": "MIT", "engines": { "node": ">= 8" } @@ -3826,6 +4567,7 @@ "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", "dev": true, + "license": "MIT", "dependencies": { "@nodelib/fs.scandir": "2.1.5", "fastq": "^1.6.0" @@ -3835,10 +4577,11 @@ } }, "node_modules/@npmcli/agent": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/@npmcli/agent/-/agent-2.2.2.tgz", - "integrity": "sha512-OrcNPXdpSl9UX7qPVRWbmWMCSXrcDa2M9DvrbOTj7ao1S4PlqVFYv9/yLKMkrJKZ/V5A/kDBC690or307i26Og==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@npmcli/agent/-/agent-3.0.0.tgz", + "integrity": "sha512-S79NdEgDQd/NGCay6TCoVzXSj74skRZIKJcpJjC5lOq34SZzyI6MqtiiWoiVWoVrTcGjNeC4ipbh1VIHlpfF5Q==", "dev": true, + "license": "ISC", "dependencies": { "agent-base": "^7.1.0", "http-proxy-agent": "^7.0.0", @@ -3847,45 +4590,47 @@ "socks-proxy-agent": "^8.0.3" }, "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": "^18.17.0 || >=20.5.0" } }, "node_modules/@npmcli/agent/node_modules/lru-cache": { "version": "10.4.3", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", - "dev": true + "dev": true, + "license": "ISC" }, "node_modules/@npmcli/fs": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-3.1.1.tgz", - "integrity": "sha512-q9CRWjpHCMIh5sVyefoD1cA7PkvILqCZsnSOEUUivORLjxCO/Irmue2DprETiNgEqktDBZaM1Bi+jrarx1XdCg==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-4.0.0.tgz", + "integrity": "sha512-/xGlezI6xfGO9NwuJlnwz/K14qD1kCSAGtacBHnGzeAIuJGazcp45KP5NuyARXoKb7cwulAGWVsbeSxdG/cb0Q==", "dev": true, + "license": "ISC", "dependencies": { "semver": "^7.3.5" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^18.17.0 || >=20.5.0" } }, "node_modules/@npmcli/git": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/@npmcli/git/-/git-5.0.8.tgz", - "integrity": "sha512-liASfw5cqhjNW9UFd+ruwwdEf/lbOAQjLL2XY2dFW/bkJheXDYZgOyul/4gVvEV4BWkTXjYGmDqMw9uegdbJNQ==", + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/@npmcli/git/-/git-6.0.3.tgz", + "integrity": "sha512-GUYESQlxZRAdhs3UhbB6pVRNUELQOHXwK9ruDkwmCv2aZ5y0SApQzUJCg02p3A7Ue2J5hxvlk1YI53c00NmRyQ==", "dev": true, + "license": "ISC", "dependencies": { - "@npmcli/promise-spawn": "^7.0.0", - "ini": "^4.1.3", + "@npmcli/promise-spawn": "^8.0.0", + "ini": "^5.0.0", "lru-cache": "^10.0.1", - "npm-pick-manifest": "^9.0.0", - "proc-log": "^4.0.0", - "promise-inflight": "^1.0.1", + "npm-pick-manifest": "^10.0.0", + "proc-log": "^5.0.0", "promise-retry": "^2.0.1", "semver": "^7.3.5", - "which": "^4.0.0" + "which": "^5.0.0" }, "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": "^18.17.0 || >=20.5.0" } }, "node_modules/@npmcli/git/node_modules/isexe": { @@ -3893,6 +4638,7 @@ "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz", "integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==", "dev": true, + "license": "ISC", "engines": { "node": ">=16" } @@ -3901,13 +4647,15 @@ "version": "10.4.3", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", - "dev": true + "dev": true, + "license": "ISC" }, "node_modules/@npmcli/git/node_modules/which": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/which/-/which-4.0.0.tgz", - "integrity": "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/which/-/which-5.0.0.tgz", + "integrity": "sha512-JEdGzHwwkrbWoGOlIHqQ5gtprKGOenpDHpxE9zVR1bWbOtYRyPPHMe9FaP6x61CmNaTThSkb0DAJte5jD+DmzQ==", "dev": true, + "license": "ISC", "dependencies": { "isexe": "^3.1.1" }, @@ -3915,50 +4663,53 @@ "node-which": "bin/which.js" }, "engines": { - "node": "^16.13.0 || >=18.0.0" + "node": "^18.17.0 || >=20.5.0" } }, "node_modules/@npmcli/installed-package-contents": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@npmcli/installed-package-contents/-/installed-package-contents-2.1.0.tgz", - "integrity": "sha512-c8UuGLeZpm69BryRykLuKRyKFZYJsZSCT4aVY5ds4omyZqJ172ApzgfKJ5eV/r3HgLdUYgFVe54KSFVjKoe27w==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@npmcli/installed-package-contents/-/installed-package-contents-3.0.0.tgz", + "integrity": "sha512-fkxoPuFGvxyrH+OQzyTkX2LUEamrF4jZSmxjAtPPHHGO0dqsQ8tTKjnIS8SAnPHdk2I03BDtSMR5K/4loKg79Q==", "dev": true, + "license": "ISC", "dependencies": { - "npm-bundled": "^3.0.0", - "npm-normalize-package-bin": "^3.0.0" + "npm-bundled": "^4.0.0", + "npm-normalize-package-bin": "^4.0.0" }, "bin": { "installed-package-contents": "bin/index.js" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^18.17.0 || >=20.5.0" } }, "node_modules/@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==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@npmcli/node-gyp/-/node-gyp-4.0.0.tgz", + "integrity": "sha512-+t5DZ6mO/QFh78PByMq1fGSAub/agLJZDRfJRMeOSNCt8s9YVlTjmGpIPwPhvXTGUIJk+WszlT0rQa1W33yzNA==", "dev": true, + "license": "ISC", "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^18.17.0 || >=20.5.0" } }, "node_modules/@npmcli/package-json": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/@npmcli/package-json/-/package-json-5.2.1.tgz", - "integrity": "sha512-f7zYC6kQautXHvNbLEWgD/uGu1+xCn9izgqBfgItWSx22U0ZDekxN08A1vM8cTxj/cRVe0Q94Ode+tdoYmIOOQ==", + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/@npmcli/package-json/-/package-json-6.1.1.tgz", + "integrity": "sha512-d5qimadRAUCO4A/Txw71VM7UrRZzV+NPclxz/dc+M6B2oYwjWTjqh8HA/sGQgs9VZuJ6I/P7XIAlJvgrl27ZOw==", "dev": true, + "license": "ISC", "dependencies": { - "@npmcli/git": "^5.0.0", + "@npmcli/git": "^6.0.0", "glob": "^10.2.2", - "hosted-git-info": "^7.0.0", - "json-parse-even-better-errors": "^3.0.0", - "normalize-package-data": "^6.0.0", - "proc-log": "^4.0.0", - "semver": "^7.5.3" + "hosted-git-info": "^8.0.0", + "json-parse-even-better-errors": "^4.0.0", + "proc-log": "^5.0.0", + "semver": "^7.5.3", + "validate-npm-package-license": "^3.0.4" }, "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": "^18.17.0 || >=20.5.0" } }, "node_modules/@npmcli/package-json/node_modules/glob": { @@ -3966,6 +4717,7 @@ "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", "dev": true, + "license": "ISC", "dependencies": { "foreground-child": "^3.1.0", "jackspeak": "^3.1.2", @@ -3982,15 +4734,16 @@ } }, "node_modules/@npmcli/promise-spawn": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/@npmcli/promise-spawn/-/promise-spawn-7.0.2.tgz", - "integrity": "sha512-xhfYPXoV5Dy4UkY0D+v2KkwvnDfiA/8Mt3sWCGI/hM03NsYIH8ZaG6QzS9x7pje5vHZBZJ2v6VRFVTWACnqcmQ==", + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@npmcli/promise-spawn/-/promise-spawn-8.0.2.tgz", + "integrity": "sha512-/bNJhjc+o6qL+Dwz/bqfTQClkEO5nTQ1ZEcdCkAQjhkZMHIh22LPG7fNh1enJP1NKWDqYiiABnjFCY7E0zHYtQ==", "dev": true, + "license": "ISC", "dependencies": { - "which": "^4.0.0" + "which": "^5.0.0" }, "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": "^18.17.0 || >=20.5.0" } }, "node_modules/@npmcli/promise-spawn/node_modules/isexe": { @@ -3998,15 +4751,17 @@ "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz", "integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==", "dev": true, + "license": "ISC", "engines": { "node": ">=16" } }, "node_modules/@npmcli/promise-spawn/node_modules/which": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/which/-/which-4.0.0.tgz", - "integrity": "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/which/-/which-5.0.0.tgz", + "integrity": "sha512-JEdGzHwwkrbWoGOlIHqQ5gtprKGOenpDHpxE9zVR1bWbOtYRyPPHMe9FaP6x61CmNaTThSkb0DAJte5jD+DmzQ==", "dev": true, + "license": "ISC", "dependencies": { "isexe": "^3.1.1" }, @@ -4014,297 +4769,709 @@ "node-which": "bin/which.js" }, "engines": { - "node": "^16.13.0 || >=18.0.0" + "node": "^18.17.0 || >=20.5.0" } }, "node_modules/@npmcli/redact": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@npmcli/redact/-/redact-2.0.1.tgz", - "integrity": "sha512-YgsR5jCQZhVmTJvjduTOIHph0L73pK8xwMVaDY0PatySqVM9AZj93jpoXYSJqfHFxFkN9dmqTw6OiqExsS3LPw==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@npmcli/redact/-/redact-3.2.0.tgz", + "integrity": "sha512-NyJXHoZwJE0iUsCDTclXf1bWHJTsshtnp5xUN6F2vY+OLJv6d2cNc4Do6fKNkmPToB0GzoffxRh405ibTwG+Og==", "dev": true, + "license": "ISC", "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": "^18.17.0 || >=20.5.0" } }, "node_modules/@npmcli/run-script": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/@npmcli/run-script/-/run-script-8.1.0.tgz", - "integrity": "sha512-y7efHHwghQfk28G2z3tlZ67pLG0XdfYbcVG26r7YIXALRsrVQcTq4/tdenSmdOrEsNahIYA/eh8aEVROWGFUDg==", + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/@npmcli/run-script/-/run-script-9.1.0.tgz", + "integrity": "sha512-aoNSbxtkePXUlbZB+anS1LqsJdctG5n3UVhfU47+CDdwMi6uNTBMF9gPcQRnqghQd2FGzcwwIFBruFMxjhBewg==", + "dev": true, + "license": "ISC", + "dependencies": { + "@npmcli/node-gyp": "^4.0.0", + "@npmcli/package-json": "^6.0.0", + "@npmcli/promise-spawn": "^8.0.0", + "node-gyp": "^11.0.0", + "proc-log": "^5.0.0", + "which": "^5.0.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/@npmcli/run-script/node_modules/isexe": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz", + "integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=16" + } + }, + "node_modules/@npmcli/run-script/node_modules/which": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/which/-/which-5.0.0.tgz", + "integrity": "sha512-JEdGzHwwkrbWoGOlIHqQ5gtprKGOenpDHpxE9zVR1bWbOtYRyPPHMe9FaP6x61CmNaTThSkb0DAJte5jD+DmzQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^3.1.1" + }, + "bin": { + "node-which": "bin/which.js" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/@parcel/watcher": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.5.1.tgz", + "integrity": "sha512-dfUnCxiN9H4ap84DvD2ubjw+3vUNpstxa0TneY/Paat8a3R4uQZDLSvWjmznAY/DoahqTHl9V46HF/Zs3F29pg==", "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, "dependencies": { - "@npmcli/node-gyp": "^3.0.0", - "@npmcli/package-json": "^5.0.0", - "@npmcli/promise-spawn": "^7.0.0", - "node-gyp": "^10.0.0", - "proc-log": "^4.0.0", - "which": "^4.0.0" + "detect-libc": "^1.0.3", + "is-glob": "^4.0.3", + "micromatch": "^4.0.5", + "node-addon-api": "^7.0.0" + }, + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "@parcel/watcher-android-arm64": "2.5.1", + "@parcel/watcher-darwin-arm64": "2.5.1", + "@parcel/watcher-darwin-x64": "2.5.1", + "@parcel/watcher-freebsd-x64": "2.5.1", + "@parcel/watcher-linux-arm-glibc": "2.5.1", + "@parcel/watcher-linux-arm-musl": "2.5.1", + "@parcel/watcher-linux-arm64-glibc": "2.5.1", + "@parcel/watcher-linux-arm64-musl": "2.5.1", + "@parcel/watcher-linux-x64-glibc": "2.5.1", + "@parcel/watcher-linux-x64-musl": "2.5.1", + "@parcel/watcher-win32-arm64": "2.5.1", + "@parcel/watcher-win32-ia32": "2.5.1", + "@parcel/watcher-win32-x64": "2.5.1" + } + }, + "node_modules/@parcel/watcher-android-arm64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.5.1.tgz", + "integrity": "sha512-KF8+j9nNbUN8vzOFDpRMsaKBHZ/mcjEjMToVMJOhTozkDonQFFrRcfdLWn6yWKCmJKmdVxSgHiYvTCef4/qcBA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-darwin-arm64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.5.1.tgz", + "integrity": "sha512-eAzPv5osDmZyBhou8PoF4i6RQXAfeKL9tjb3QzYuccXFMQU0ruIc/POh30ePnaOyD1UXdlKguHBmsTs53tVoPw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10.0.0" }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-darwin-x64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.5.1.tgz", + "integrity": "sha512-1ZXDthrnNmwv10A0/3AJNZ9JGlzrF82i3gNQcWOzd7nJ8aj+ILyW1MTxVk35Db0u91oD5Nlk9MBiujMlwmeXZg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-freebsd-x64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.5.1.tgz", + "integrity": "sha512-SI4eljM7Flp9yPuKi8W0ird8TI/JK6CSxju3NojVI6BjHsTyK7zxA9urjVjEKJ5MBYC+bLmMcbAWlZ+rFkLpJQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm-glibc": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.5.1.tgz", + "integrity": "sha512-RCdZlEyTs8geyBkkcnPWvtXLY44BCeZKmGYRtSgtwwnHR4dxfHRG3gR99XdMEdQ7KeiDdasJwwvNSF5jKtDwdA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm-musl": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-musl/-/watcher-linux-arm-musl-2.5.1.tgz", + "integrity": "sha512-6E+m/Mm1t1yhB8X412stiKFG3XykmgdIOqhjWj+VL8oHkKABfu/gjFj8DvLrYVHSBNC+/u5PeNrujiSQ1zwd1Q==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm64-glibc": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.5.1.tgz", + "integrity": "sha512-LrGp+f02yU3BN9A+DGuY3v3bmnFUggAITBGriZHUREfNEzZh/GO06FF5u2kx8x+GBEUYfyTGamol4j3m9ANe8w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm64-musl": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.5.1.tgz", + "integrity": "sha512-cFOjABi92pMYRXS7AcQv9/M1YuKRw8SZniCDw0ssQb/noPkRzA+HBDkwmyOJYp5wXcsTrhxO0zq1U11cK9jsFg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-x64-glibc": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.5.1.tgz", + "integrity": "sha512-GcESn8NZySmfwlTsIur+49yDqSny2IhPeZfXunQi48DMugKeZ7uy1FX83pO0X22sHntJ4Ub+9k34XQCX+oHt2A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-x64-musl": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.5.1.tgz", + "integrity": "sha512-n0E2EQbatQ3bXhcH2D1XIAANAcTZkQICBPVaxMeaCVBtOpBZpWJuf7LwyWPSBDITb7In8mqQgJ7gH8CILCURXg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-arm64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.5.1.tgz", + "integrity": "sha512-RFzklRvmc3PkjKjry3hLF9wD7ppR4AKcWNzH7kXR7GUe0Igb3Nz8fyPwtZCSquGrhU5HhUNDr/mKBqj7tqA2Vw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-ia32": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-ia32/-/watcher-win32-ia32-2.5.1.tgz", + "integrity": "sha512-c2KkcVN+NJmuA7CGlaGD1qJh1cLfDnQsHjE89E60vUEMlqduHGCdCLJCID5geFVM0dOtA3ZiIO8BoEQmzQVfpQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/@npmcli/run-script/node_modules/isexe": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz", - "integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==", + "node_modules/@parcel/watcher-win32-x64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.5.1.tgz", + "integrity": "sha512-9lHBdJITeNR++EvSQVUcaZoWupyHfXe1jZvGZ06O/5MflPcuPLtEphScIBL+AiCWBO46tDSHzWyD0uDmmZqsgA==", + "cpu": [ + "x64" + ], "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], "engines": { - "node": ">=16" + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/@npmcli/run-script/node_modules/which": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/which/-/which-4.0.0.tgz", - "integrity": "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==", + "node_modules/@parcel/watcher/node_modules/detect-libc": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", + "integrity": "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==", "dev": true, - "dependencies": { - "isexe": "^3.1.1" - }, + "license": "Apache-2.0", + "optional": true, "bin": { - "node-which": "bin/which.js" + "detect-libc": "bin/detect-libc.js" }, "engines": { - "node": "^16.13.0 || >=18.0.0" + "node": ">=0.10" } }, + "node_modules/@parcel/watcher/node_modules/node-addon-api": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz", + "integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==", + "dev": true, + "license": "MIT", + "optional": true + }, "node_modules/@pkgjs/parseargs": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", "dev": true, + "license": "MIT", "optional": true, "engines": { "node": ">=14" } }, "node_modules/@pkgr/core": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.1.1.tgz", - "integrity": "sha512-cq8o4cWH0ibXh9VGi5P20Tu9XF/0fFXl9EUinr9QfTM7a7p0oTA4iJRCQWppXR1Pg8dSM0UCItCkPwsk9qWWYA==", + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.2.4.tgz", + "integrity": "sha512-ROFF39F6ZrnzSUEmQQZUar0Jt4xVoP9WnDRdWwF4NNcXs3xBTLgBUDoOwW141y1jP+S8nahIbdxbFC7IShw9Iw==", "dev": true, + "license": "MIT", "engines": { "node": "^12.20.0 || ^14.18.0 || >=16.0.0" }, "funding": { - "url": "https://opencollective.com/unts" + "url": "https://opencollective.com/pkgr" } }, "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.22.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.22.4.tgz", - "integrity": "sha512-Fxamp4aEZnfPOcGA8KSNEohV8hX7zVHOemC8jVBoBUHu5zpJK/Eu3uJwt6BMgy9fkvzxDaurgj96F/NiLukF2w==", + "version": "4.34.8", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.34.8.tgz", + "integrity": "sha512-q217OSE8DTp8AFHuNHXo0Y86e1wtlfVrXiAlwkIvGRQv9zbc6mE3sjIVfwI8sYUyNxwOg0j/Vm1RKM04JcWLJw==", "cpu": [ "arm" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "android" ] }, "node_modules/@rollup/rollup-android-arm64": { - "version": "4.22.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.22.4.tgz", - "integrity": "sha512-VXoK5UMrgECLYaMuGuVTOx5kcuap1Jm8g/M83RnCHBKOqvPPmROFJGQaZhGccnsFtfXQ3XYa4/jMCJvZnbJBdA==", + "version": "4.34.8", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.34.8.tgz", + "integrity": "sha512-Gigjz7mNWaOL9wCggvoK3jEIUUbGul656opstjaUSGC3eT0BM7PofdAJaBfPFWWkXNVAXbaQtC99OCg4sJv70Q==", "cpu": [ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "android" ] }, "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.22.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.22.4.tgz", - "integrity": "sha512-xMM9ORBqu81jyMKCDP+SZDhnX2QEVQzTcC6G18KlTQEzWK8r/oNZtKuZaCcHhnsa6fEeOBionoyl5JsAbE/36Q==", + "version": "4.34.8", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.34.8.tgz", + "integrity": "sha512-02rVdZ5tgdUNRxIUrFdcMBZQoaPMrxtwSb+/hOfBdqkatYHR3lZ2A2EGyHq2sGOd0Owk80oV3snlDASC24He3Q==", "cpu": [ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "darwin" ] }, "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.22.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.22.4.tgz", - "integrity": "sha512-aJJyYKQwbHuhTUrjWjxEvGnNNBCnmpHDvrb8JFDbeSH3m2XdHcxDd3jthAzvmoI8w/kSjd2y0udT+4okADsZIw==", + "version": "4.34.8", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.34.8.tgz", + "integrity": "sha512-qIP/elwR/tq/dYRx3lgwK31jkZvMiD6qUtOycLhTzCvrjbZ3LjQnEM9rNhSGpbLXVJYQ3rq39A6Re0h9tU2ynw==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "darwin" ] }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.34.8", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.34.8.tgz", + "integrity": "sha512-IQNVXL9iY6NniYbTaOKdrlVP3XIqazBgJOVkddzJlqnCpRi/yAeSOa8PLcECFSQochzqApIOE1GHNu3pCz+BDA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.34.8", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.34.8.tgz", + "integrity": "sha512-TYXcHghgnCqYFiE3FT5QwXtOZqDj5GmaFNTNt3jNC+vh22dc/ukG2cG+pi75QO4kACohZzidsq7yKTKwq/Jq7Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.22.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.22.4.tgz", - "integrity": "sha512-j63YtCIRAzbO+gC2L9dWXRh5BFetsv0j0va0Wi9epXDgU/XUi5dJKo4USTttVyK7fGw2nPWK0PbAvyliz50SCQ==", + "version": "4.34.8", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.34.8.tgz", + "integrity": "sha512-A4iphFGNkWRd+5m3VIGuqHnG3MVnqKe7Al57u9mwgbyZ2/xF9Jio72MaY7xxh+Y87VAHmGQr73qoKL9HPbXj1g==", "cpu": [ "arm" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ] }, "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.22.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.22.4.tgz", - "integrity": "sha512-dJnWUgwWBX1YBRsuKKMOlXCzh2Wu1mlHzv20TpqEsfdZLb3WoJW2kIEsGwLkroYf24IrPAvOT/ZQ2OYMV6vlrg==", + "version": "4.34.8", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.34.8.tgz", + "integrity": "sha512-S0lqKLfTm5u+QTxlFiAnb2J/2dgQqRy/XvziPtDd1rKZFXHTyYLoVL58M/XFwDI01AQCDIevGLbQrMAtdyanpA==", "cpu": [ "arm" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ] }, "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.22.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.22.4.tgz", - "integrity": "sha512-AdPRoNi3NKVLolCN/Sp4F4N1d98c4SBnHMKoLuiG6RXgoZ4sllseuGioszumnPGmPM2O7qaAX/IJdeDU8f26Aw==", + "version": "4.34.8", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.34.8.tgz", + "integrity": "sha512-jpz9YOuPiSkL4G4pqKrus0pn9aYwpImGkosRKwNi+sJSkz+WU3anZe6hi73StLOQdfXYXC7hUfsQlTnjMd3s1A==", "cpu": [ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ] }, "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.22.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.22.4.tgz", - "integrity": "sha512-Gl0AxBtDg8uoAn5CCqQDMqAx22Wx22pjDOjBdmG0VIWX3qUBHzYmOKh8KXHL4UpogfJ14G4wk16EQogF+v8hmA==", + "version": "4.34.8", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.34.8.tgz", + "integrity": "sha512-KdSfaROOUJXgTVxJNAZ3KwkRc5nggDk+06P6lgi1HLv1hskgvxHUKZ4xtwHkVYJ1Rep4GNo+uEfycCRRxht7+Q==", "cpu": [ "arm64" ], "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loongarch64-gnu": { + "version": "4.34.8", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.34.8.tgz", + "integrity": "sha512-NyF4gcxwkMFRjgXBM6g2lkT58OWztZvw5KkV2K0qqSnUEqCVcqdh2jN4gQrTn/YUpAcNKyFHfoOZEer9nwo6uQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ] }, "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { - "version": "4.22.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.22.4.tgz", - "integrity": "sha512-3aVCK9xfWW1oGQpTsYJJPF6bfpWfhbRnhdlyhak2ZiyFLDaayz0EP5j9V1RVLAAxlmWKTDfS9wyRyY3hvhPoOg==", + "version": "4.34.8", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.34.8.tgz", + "integrity": "sha512-LMJc999GkhGvktHU85zNTDImZVUCJ1z/MbAJTnviiWmmjyckP5aQsHtcujMjpNdMZPT2rQEDBlJfubhs3jsMfw==", "cpu": [ "ppc64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ] }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.22.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.22.4.tgz", - "integrity": "sha512-ePYIir6VYnhgv2C5Xe9u+ico4t8sZWXschR6fMgoPUK31yQu7hTEJb7bCqivHECwIClJfKgE7zYsh1qTP3WHUA==", + "version": "4.34.8", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.34.8.tgz", + "integrity": "sha512-xAQCAHPj8nJq1PI3z8CIZzXuXCstquz7cIOL73HHdXiRcKk8Ywwqtx2wrIy23EcTn4aZ2fLJNBB8d0tQENPCmw==", "cpu": [ "riscv64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ] }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.40.0.tgz", + "integrity": "sha512-rKmSj6EXQRnhSkE22+WvrqOqRtk733x3p5sWpZilhmjnkHkpeCgWsFFo0dGnUGeA+OZjRl3+VYq+HyCOEuwcxQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true + }, "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.22.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.22.4.tgz", - "integrity": "sha512-GqFJ9wLlbB9daxhVlrTe61vJtEY99/xB3C8e4ULVsVfflcpmR6c8UZXjtkMA6FhNONhj2eA5Tk9uAVw5orEs4Q==", + "version": "4.34.8", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.34.8.tgz", + "integrity": "sha512-DdePVk1NDEuc3fOe3dPPTb+rjMtuFw89gw6gVWxQFAuEqqSdDKnrwzZHrUYdac7A7dXl9Q2Vflxpme15gUWQFA==", "cpu": [ "s390x" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ] }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.22.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.22.4.tgz", - "integrity": "sha512-87v0ol2sH9GE3cLQLNEy0K/R0pz1nvg76o8M5nhMR0+Q+BBGLnb35P0fVz4CQxHYXaAOhE8HhlkaZfsdUOlHwg==", + "version": "4.34.8", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.34.8.tgz", + "integrity": "sha512-8y7ED8gjxITUltTUEJLQdgpbPh1sUQ0kMTmufRF/Ns5tI9TNMNlhWtmPKKHCU0SilX+3MJkZ0zERYYGIVBYHIA==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ] }, "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.22.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.22.4.tgz", - "integrity": "sha512-UV6FZMUgePDZrFjrNGIWzDo/vABebuXBhJEqrHxrGiU6HikPy0Z3LfdtciIttEUQfuDdCn8fqh7wiFJjCNwO+g==", + "version": "4.34.8", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.34.8.tgz", + "integrity": "sha512-SCXcP0ZpGFIe7Ge+McxY5zKxiEI5ra+GT3QRxL0pMMtxPfpyLAKleZODi1zdRHkz5/BhueUrYtYVgubqe9JBNQ==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ] }, "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.22.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.22.4.tgz", - "integrity": "sha512-BjI+NVVEGAXjGWYHz/vv0pBqfGoUH0IGZ0cICTn7kB9PyjrATSkX+8WkguNjWoj2qSr1im/+tTGRaY+4/PdcQw==", + "version": "4.34.8", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.34.8.tgz", + "integrity": "sha512-YHYsgzZgFJzTRbth4h7Or0m5O74Yda+hLin0irAIobkLQFRQd1qWmnoVfwmKm9TXIZVAD0nZ+GEb2ICicLyCnQ==", "cpu": [ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "win32" ] }, "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.22.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.22.4.tgz", - "integrity": "sha512-SiWG/1TuUdPvYmzmYnmd3IEifzR61Tragkbx9D3+R8mzQqDBz8v+BvZNDlkiTtI9T15KYZhP0ehn3Dld4n9J5g==", + "version": "4.34.8", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.34.8.tgz", + "integrity": "sha512-r3NRQrXkHr4uWy5TOjTpTYojR9XmF0j/RYgKCef+Ag46FWUTltm5ziticv8LdNsDMehjJ543x/+TJAek/xBA2w==", "cpu": [ "ia32" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "win32" ] }, "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.22.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.22.4.tgz", - "integrity": "sha512-j8pPKp53/lq9lMXN57S8cFz0MynJk8OWNuUnXct/9KCpKU7DgU3bYMJhwWmcqC0UU29p8Lr0/7KEVcaM6bf47Q==", + "version": "4.34.8", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.34.8.tgz", + "integrity": "sha512-U0FaE5O1BCpZSeE6gBl3c5ObhePQSfk9vDRToMmTkbhCOgW4jqvtS5LGyQ76L1fH8sM0keRp4uDTsbjiUyjk0g==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "win32" ] }, "node_modules/@schematics/angular": { - "version": "18.2.14", - "resolved": "https://registry.npmjs.org/@schematics/angular/-/angular-18.2.14.tgz", - "integrity": "sha512-CHh6ew2Az71UlvVcnYeuMEwjwkZqR7y/9ebLzFRvczC71ZL8qPVBpBTVGbCpGBd54VEbCZVWRxBQoZZ5LP/aBw==", + "version": "19.0.7", + "resolved": "https://registry.npmjs.org/@schematics/angular/-/angular-19.0.7.tgz", + "integrity": "sha512-1WtTqKFPuEaV99VIP+y/gf/XW3TVJh/NbJbbEF4qYpp7qQiJ4ntF4klVZmsJcQzFucZSzlg91QVMPQKev5WZGA==", "dev": true, + "license": "MIT", "dependencies": { - "@angular-devkit/core": "18.2.14", - "@angular-devkit/schematics": "18.2.14", + "@angular-devkit/core": "19.0.7", + "@angular-devkit/schematics": "19.0.7", "jsonc-parser": "3.3.1" }, "engines": { @@ -4313,78 +5480,151 @@ "yarn": ">= 1.13.0" } }, + "node_modules/@schematics/angular/node_modules/@angular-devkit/core": { + "version": "19.0.7", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-19.0.7.tgz", + "integrity": "sha512-VyuORSitT6LIaGUEF0KEnv2TwNaeWl6L3/4L4stok0BJ23B4joVca2DYVcrLC1hSzz8V4dwVgSlbNIgjgGdVpg==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "8.17.1", + "ajv-formats": "3.0.1", + "jsonc-parser": "3.3.1", + "picomatch": "4.0.2", + "rxjs": "7.8.1", + "source-map": "0.7.4" + }, + "engines": { + "node": "^18.19.1 || ^20.11.1 || >=22.0.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + }, + "peerDependencies": { + "chokidar": "^4.0.0" + }, + "peerDependenciesMeta": { + "chokidar": { + "optional": true + } + } + }, + "node_modules/@schematics/angular/node_modules/@angular-devkit/schematics": { + "version": "19.0.7", + "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-19.0.7.tgz", + "integrity": "sha512-BHXQv6kMc9xo4TH9lhwMv8nrZXHkLioQvLun2qYjwvOsyzt3qd+sUM9wpHwbG6t+01+FIQ05iNN9ox+Cvpndgg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@angular-devkit/core": "19.0.7", + "jsonc-parser": "3.3.1", + "magic-string": "0.30.12", + "ora": "5.4.1", + "rxjs": "7.8.1" + }, + "engines": { + "node": "^18.19.1 || ^20.11.1 || >=22.0.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + } + }, + "node_modules/@schematics/angular/node_modules/magic-string": { + "version": "0.30.12", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.12.tgz", + "integrity": "sha512-Ea8I3sQMVXr8JhN4z+H/d8zwo+tYDgHE9+5G4Wnrwhs0gaK9fXTKx0Tw5Xwsd/bCPTTZNRAdpyzvoeORe9LYpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0" + } + }, + "node_modules/@schematics/angular/node_modules/rxjs": { + "version": "7.8.1", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", + "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.1.0" + } + }, "node_modules/@sigstore/bundle": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/@sigstore/bundle/-/bundle-2.3.2.tgz", - "integrity": "sha512-wueKWDk70QixNLB363yHc2D2ItTgYiMTdPwK8D9dKQMR3ZQ0c35IxP5xnwQ8cNLoCgCRcHf14kE+CLIvNX1zmA==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@sigstore/bundle/-/bundle-3.1.0.tgz", + "integrity": "sha512-Mm1E3/CmDDCz3nDhFKTuYdB47EdRFRQMOE/EAbiG1MJW77/w1b3P7Qx7JSrVJs8PfwOLOVcKQCHErIwCTyPbag==", "dev": true, + "license": "Apache-2.0", "dependencies": { - "@sigstore/protobuf-specs": "^0.3.2" + "@sigstore/protobuf-specs": "^0.4.0" }, "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": "^18.17.0 || >=20.5.0" } }, "node_modules/@sigstore/core": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@sigstore/core/-/core-1.1.0.tgz", - "integrity": "sha512-JzBqdVIyqm2FRQCulY6nbQzMpJJpSiJ8XXWMhtOX9eKgaXXpfNOF53lzQEjIydlStnd/eFtuC1dW4VYdD93oRg==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@sigstore/core/-/core-2.0.0.tgz", + "integrity": "sha512-nYxaSb/MtlSI+JWcwTHQxyNmWeWrUXJJ/G4liLrGG7+tS4vAz6LF3xRXqLH6wPIVUoZQel2Fs4ddLx4NCpiIYg==", "dev": true, + "license": "Apache-2.0", "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": "^18.17.0 || >=20.5.0" } }, "node_modules/@sigstore/protobuf-specs": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/@sigstore/protobuf-specs/-/protobuf-specs-0.3.3.tgz", - "integrity": "sha512-RpacQhBlwpBWd7KEJsRKcBQalbV28fvkxwTOJIqhIuDysMMaJW47V4OqW30iJB9uRpqOSxxEAQFdr8tTattReQ==", + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@sigstore/protobuf-specs/-/protobuf-specs-0.4.1.tgz", + "integrity": "sha512-7MJXQhIm7dWF9zo7rRtMYh8d2gSnc3+JddeQOTIg6gUN7FjcuckZ9EwGq+ReeQtbbl3Tbf5YqRrWxA1DMfIn+w==", "dev": true, + "license": "Apache-2.0", "engines": { "node": "^18.17.0 || >=20.5.0" } }, "node_modules/@sigstore/sign": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/@sigstore/sign/-/sign-2.3.2.tgz", - "integrity": "sha512-5Vz5dPVuunIIvC5vBb0APwo7qKA4G9yM48kPWJT+OEERs40md5GoUR1yedwpekWZ4m0Hhw44m6zU+ObsON+iDA==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@sigstore/sign/-/sign-3.1.0.tgz", + "integrity": "sha512-knzjmaOHOov1Ur7N/z4B1oPqZ0QX5geUfhrVaqVlu+hl0EAoL4o+l0MSULINcD5GCWe3Z0+YJO8ues6vFlW0Yw==", "dev": true, + "license": "Apache-2.0", "dependencies": { - "@sigstore/bundle": "^2.3.2", - "@sigstore/core": "^1.0.0", - "@sigstore/protobuf-specs": "^0.3.2", - "make-fetch-happen": "^13.0.1", - "proc-log": "^4.2.0", + "@sigstore/bundle": "^3.1.0", + "@sigstore/core": "^2.0.0", + "@sigstore/protobuf-specs": "^0.4.0", + "make-fetch-happen": "^14.0.2", + "proc-log": "^5.0.0", "promise-retry": "^2.0.1" }, "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": "^18.17.0 || >=20.5.0" } }, "node_modules/@sigstore/tuf": { - "version": "2.3.4", - "resolved": "https://registry.npmjs.org/@sigstore/tuf/-/tuf-2.3.4.tgz", - "integrity": "sha512-44vtsveTPUpqhm9NCrbU8CWLe3Vck2HO1PNLw7RIajbB7xhtn5RBPm1VNSCMwqGYHhDsBJG8gDF0q4lgydsJvw==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@sigstore/tuf/-/tuf-3.1.0.tgz", + "integrity": "sha512-suVMQEA+sKdOz5hwP9qNcEjX6B45R+hFFr4LAWzbRc5O+U2IInwvay/bpG5a4s+qR35P/JK/PiKiRGjfuLy1IA==", "dev": true, + "license": "Apache-2.0", "dependencies": { - "@sigstore/protobuf-specs": "^0.3.2", - "tuf-js": "^2.2.1" + "@sigstore/protobuf-specs": "^0.4.0", + "tuf-js": "^3.0.1" }, "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": "^18.17.0 || >=20.5.0" } }, "node_modules/@sigstore/verify": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@sigstore/verify/-/verify-1.2.1.tgz", - "integrity": "sha512-8iKx79/F73DKbGfRf7+t4dqrc0bRr0thdPrxAtCKWRm/F0tG71i6O1rvlnScncJLLBZHn3h8M3c1BSUAb9yu8g==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@sigstore/verify/-/verify-2.1.0.tgz", + "integrity": "sha512-kAAM06ca4CzhvjIZdONAL9+MLppW3K48wOFy1TbuaWFW/OMfl8JuTgW0Bm02JB1WJGT/ET2eqav0KTEKmxqkIA==", "dev": true, + "license": "Apache-2.0", "dependencies": { - "@sigstore/bundle": "^2.3.2", - "@sigstore/core": "^1.1.0", - "@sigstore/protobuf-specs": "^0.3.2" + "@sigstore/bundle": "^3.1.0", + "@sigstore/core": "^2.0.0", + "@sigstore/protobuf-specs": "^0.4.0" }, "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": "^18.17.0 || >=20.5.0" } }, "node_modules/@sindresorhus/merge-streams": { @@ -4392,6 +5632,7 @@ "resolved": "https://registry.npmjs.org/@sindresorhus/merge-streams/-/merge-streams-2.3.0.tgz", "integrity": "sha512-LtoMMhxAlorcGhmFYI+LhPgbPZCkgP6ra1YL604EeF6U98pLlQ3iWIGMdWSC+vWmPBWBNgmDBAhnAobLROJmwg==", "dev": true, + "license": "MIT", "engines": { "node": ">=18" }, @@ -4403,28 +5644,31 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz", "integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/@tufjs/canonical-json": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/@tufjs/canonical-json/-/canonical-json-2.0.0.tgz", "integrity": "sha512-yVtV8zsdo8qFHe+/3kw81dSLyF7D576A5cCFCi4X7B39tWT7SekaEFUnvnWJHz+9qO7qJTah1JbrDjWKqFtdWA==", "dev": true, + "license": "MIT", "engines": { "node": "^16.14.0 || >=18.0.0" } }, "node_modules/@tufjs/models": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@tufjs/models/-/models-2.0.1.tgz", - "integrity": "sha512-92F7/SFyufn4DXsha9+QfKnN03JGqtMFMXgSHbZOo8JG59WkTni7UzAouNQDf7AuP9OAMxVOPQcqG3sB7w+kkg==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@tufjs/models/-/models-3.0.1.tgz", + "integrity": "sha512-UUYHISyhCU3ZgN8yaear3cGATHb3SMuKHsQ/nVbHXcmnBf+LzQ/cQfhNG+rfaSHgqGKNEm2cOCLVLELStUQ1JA==", "dev": true, + "license": "MIT", "dependencies": { "@tufjs/canonical-json": "2.0.0", - "minimatch": "^9.0.4" + "minimatch": "^9.0.5" }, "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": "^18.17.0 || >=20.5.0" } }, "node_modules/@types/body-parser": { @@ -4432,6 +5676,7 @@ "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.5.tgz", "integrity": "sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==", "dev": true, + "license": "MIT", "dependencies": { "@types/connect": "*", "@types/node": "*" @@ -4442,6 +5687,7 @@ "resolved": "https://registry.npmjs.org/@types/bonjour/-/bonjour-3.5.13.tgz", "integrity": "sha512-z9fJ5Im06zvUL548KvYNecEVlA7cVDkGUi6kZusb04mpyEFKCIZJvloCcmpmLaIahDpOQGHaHmG6imtPMmPXGQ==", "dev": true, + "license": "MIT", "dependencies": { "@types/node": "*" } @@ -4451,6 +5697,7 @@ "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", "dev": true, + "license": "MIT", "dependencies": { "@types/node": "*" } @@ -4460,6 +5707,7 @@ "resolved": "https://registry.npmjs.org/@types/connect-history-api-fallback/-/connect-history-api-fallback-1.5.4.tgz", "integrity": "sha512-n6Cr2xS1h4uAulPRdlw6Jl6s1oG8KrVilPN2yUITEs+K48EzMJJ3W1xy8K5eWuFvjp3R74AOIGSmp2UfBJ8HFw==", "dev": true, + "license": "MIT", "dependencies": { "@types/express-serve-static-core": "*", "@types/node": "*" @@ -4470,27 +5718,53 @@ "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.17.tgz", "integrity": "sha512-8CGDvrBj1zgo2qE+oS3pOCyYNqCPryMWY2bGfwA0dcfopWGgxs+78df0Rs3rc9THP4JkOhLsAa+15VdpAqkcUA==", "dev": true, + "license": "MIT", "dependencies": { "@types/node": "*" } }, + "node_modules/@types/eslint": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-9.6.1.tgz", + "integrity": "sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "*", + "@types/json-schema": "*" + } + }, + "node_modules/@types/eslint-scope": { + "version": "3.7.7", + "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.7.tgz", + "integrity": "sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/eslint": "*", + "@types/estree": "*" + } + }, "node_modules/@types/eslint-visitor-keys": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/@types/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz", "integrity": "sha512-OCutwjDZ4aFS6PB1UZ988C4YgwlBHJd6wCeQqaLdmadZ/7e+w79+hbMUFC1QXDNCmdyoRfAFdm0RypzwR+Qpag==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/@types/estree": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", - "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", - "dev": true + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", + "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==", + "dev": true, + "license": "MIT" }, "node_modules/@types/express": { "version": "4.17.21", "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.21.tgz", "integrity": "sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ==", "dev": true, + "license": "MIT", "dependencies": { "@types/body-parser": "*", "@types/express-serve-static-core": "^4.17.33", @@ -4503,6 +5777,7 @@ "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-5.0.6.tgz", "integrity": "sha512-3xhRnjJPkULekpSzgtoNYYcTWgEZkp4myc+Saevii5JPnHNvHMRlBSHDbs7Bh1iPPoVTERHEZXyhyLbMEsExsA==", "dev": true, + "license": "MIT", "dependencies": { "@types/node": "*", "@types/qs": "*", @@ -4515,6 +5790,7 @@ "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.19.6.tgz", "integrity": "sha512-N4LZ2xG7DatVqhCZzOGb1Yi5lMbXSZcmdLDe9EzSndPV2HpWYWzRbaerl2n27irrm94EPpprqa8KpskPT085+A==", "dev": true, + "license": "MIT", "dependencies": { "@types/node": "*", "@types/qs": "*", @@ -4526,13 +5802,15 @@ "version": "2.0.4", "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.4.tgz", "integrity": "sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/@types/http-proxy": { - "version": "1.17.15", - "resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.15.tgz", - "integrity": "sha512-25g5atgiVNTIv0LBDTg1H74Hvayx0ajtJPLLcYE3whFv75J0pWNtOBzaXJQgDTmrX1bx5U9YC2w/n65BN1HwRQ==", + "version": "1.17.16", + "resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.16.tgz", + "integrity": "sha512-sdWoUajOB1cd0A8cRRQ1cfyWNbmFKLAqBB89Y8x5iYyG/mkJHc0YUH8pdWBy2omi9qtCpiIgGjuwO0dQST2l5w==", "dev": true, + "license": "MIT", "dependencies": { "@types/node": "*" } @@ -4541,36 +5819,31 @@ "version": "4.3.6", "resolved": "https://registry.npmjs.org/@types/jasmine/-/jasmine-4.3.6.tgz", "integrity": "sha512-3N0FpQTeiWjm+Oo1WUYWguUS7E6JLceiGTriFrG8k5PU7zRLJCzLcWURU3wjMbZGS//a2/LgjsnO3QxIlwxt9g==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/@types/json-schema": { "version": "7.0.15", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/@types/mime": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==", - "dev": true - }, - "node_modules/@types/mute-stream": { - "version": "0.0.4", - "resolved": "https://registry.npmjs.org/@types/mute-stream/-/mute-stream-0.0.4.tgz", - "integrity": "sha512-CPM9nzrCPPJHQNA9keH9CVkVI+WR5kMa+7XEs5jcGQ0VoAGnLv242w8lIVgwAEfmE4oufJRaTc9PNLQl0ioAow==", "dev": true, - "dependencies": { - "@types/node": "*" - } + "license": "MIT" }, "node_modules/@types/node": { - "version": "22.12.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.12.0.tgz", - "integrity": "sha512-Fll2FZ1riMjNmlmJOdAyY5pUbkftXslB5DgEzlIuNaiWhXd00FhWxVC/r4yV/4wBb9JfImTu+jiSvXTkJ7F/gA==", + "version": "22.15.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.15.2.tgz", + "integrity": "sha512-uKXqKN9beGoMdBfcaTY1ecwz6ctxuJAcUlwE55938g0ZJ8lRxwAZqRz2AJ4pzpt5dHdTPMB863UZ0ESiFUcP7A==", "dev": true, + "license": "MIT", "dependencies": { - "undici-types": "~6.20.0" + "undici-types": "~6.21.0" } }, "node_modules/@types/node-forge": { @@ -4578,6 +5851,7 @@ "resolved": "https://registry.npmjs.org/@types/node-forge/-/node-forge-1.3.11.tgz", "integrity": "sha512-FQx220y22OKNTqaByeBGqHWYz4cl94tpcxeFdvBo3wjG6XPBuZ0BNgNZRV5J5TFmmcsJ4IzsLkmGRiQbnYsBEQ==", "dev": true, + "license": "MIT", "dependencies": { "@types/node": "*" } @@ -4586,25 +5860,29 @@ "version": "6.9.18", "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.18.tgz", "integrity": "sha512-kK7dgTYDyGqS+e2Q4aK9X3D7q234CIZ1Bv0q/7Z5IwRDoADNU81xXJK/YVyLbLTZCoIwUoDoffFeF+p/eIklAA==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/@types/range-parser": { "version": "1.2.7", "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/@types/retry": { "version": "0.12.2", "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.2.tgz", "integrity": "sha512-XISRgDJ2Tc5q4TRqvgJtzsRkFYNJzZrhTdtMoGVBttwzzQJkPnS3WWTFc7kuDRoPtPakl+T+OfdEUjYJj7Jbow==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/@types/send": { "version": "0.17.4", "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.4.tgz", "integrity": "sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA==", "dev": true, + "license": "MIT", "dependencies": { "@types/mime": "^1", "@types/node": "*" @@ -4615,6 +5893,7 @@ "resolved": "https://registry.npmjs.org/@types/serve-index/-/serve-index-1.9.4.tgz", "integrity": "sha512-qLpGZ/c2fhSs5gnYsQxtDEq3Oy8SXPClIXkW5ghvAvsNuVSA8k+gCONcUCS/UjLEYvYps+e8uBtfgXgvhwfNug==", "dev": true, + "license": "MIT", "dependencies": { "@types/express": "*" } @@ -4624,6 +5903,7 @@ "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.7.tgz", "integrity": "sha512-W8Ym+h8nhuRwaKPaDw34QUkwsGi6Rc4yYqvKFo5rm2FUEhCFbzVWrxXUxuKK8TASjWsysJY0nsmNCGhCOIsrOw==", "dev": true, + "license": "MIT", "dependencies": { "@types/http-errors": "*", "@types/node": "*", @@ -4635,40 +5915,37 @@ "resolved": "https://registry.npmjs.org/@types/sockjs/-/sockjs-0.3.36.tgz", "integrity": "sha512-MK9V6NzAS1+Ud7JV9lJLFqW85VbC9dq3LmwZCuBe4wBDgKC0Kj/jd8Xl+nSviU+Qc3+m7umHHyHg//2KSa0a0Q==", "dev": true, + "license": "MIT", "dependencies": { "@types/node": "*" } }, - "node_modules/@types/wrap-ansi": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@types/wrap-ansi/-/wrap-ansi-3.0.0.tgz", - "integrity": "sha512-ltIpx+kM7g/MLRZfkbL7EsCEjfzCcScLpkg37eXEtx5kmrAKBkTJwd1GIAjDSL8wTpM6Hzn5YO4pSb91BEwu1g==", - "dev": true - }, "node_modules/@types/ws": { - "version": "8.5.14", - "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.14.tgz", - "integrity": "sha512-bd/YFLW+URhBzMXurx7lWByOu+xzU9+kb3RboOteXYDfW+tr+JZa99OyNmPINEGB/ahzKrEuc8rcv4gnpJmxTw==", + "version": "8.18.1", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz", + "integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==", "dev": true, + "license": "MIT", "dependencies": { "@types/node": "*" } }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.22.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.22.0.tgz", - "integrity": "sha512-4Uta6REnz/xEJMvwf72wdUnC3rr4jAQf5jnTkeRQ9b6soxLxhDEbS/pfMPoJLDfFPNVRdryqWUIV/2GZzDJFZw==", + "version": "8.31.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.31.0.tgz", + "integrity": "sha512-evaQJZ/J/S4wisevDvC1KFZkPzRetH8kYZbkgcTRyql3mcKsf+ZFDV1BVWUGTCAW5pQHoqn5gK5b8kn7ou9aFQ==", "dev": true, + "license": "MIT", "dependencies": { "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "8.22.0", - "@typescript-eslint/type-utils": "8.22.0", - "@typescript-eslint/utils": "8.22.0", - "@typescript-eslint/visitor-keys": "8.22.0", + "@typescript-eslint/scope-manager": "8.31.0", + "@typescript-eslint/type-utils": "8.31.0", + "@typescript-eslint/utils": "8.31.0", + "@typescript-eslint/visitor-keys": "8.31.0", "graphemer": "^1.4.0", "ignore": "^5.3.1", "natural-compare": "^1.4.0", - "ts-api-utils": "^2.0.0" + "ts-api-utils": "^2.0.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -4680,7 +5957,7 @@ "peerDependencies": { "@typescript-eslint/parser": "^8.0.0 || ^8.0.0-alpha.0", "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <5.8.0" + "typescript": ">=4.8.4 <5.9.0" } }, "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { @@ -4688,6 +5965,7 @@ "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", "dev": true, + "license": "MIT", "engines": { "node": ">= 4" } @@ -4697,6 +5975,7 @@ "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-3.10.1.tgz", "integrity": "sha512-DewqIgscDzmAfd5nOGe4zm6Bl7PKtMG2Ad0KG8CUZAHlXfAKTF9Ol5PXhiMh39yRL2ChRH1cuuUGOcVyyrhQIw==", "dev": true, + "license": "MIT", "dependencies": { "@types/json-schema": "^7.0.3", "@typescript-eslint/types": "3.10.1", @@ -4720,6 +5999,7 @@ "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-3.10.1.tgz", "integrity": "sha512-+3+FCUJIahE9q0lDi1WleYzjCwJs5hIsbugIgnbB+dSCYUxl8L6PwmsyOPFZde2hc1DlTo/xnkOgiTLSyAbHiQ==", "dev": true, + "license": "MIT", "engines": { "node": "^8.10.0 || ^10.13.0 || >=11.10.1" }, @@ -4733,6 +6013,7 @@ "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-3.10.1.tgz", "integrity": "sha512-QbcXOuq6WYvnB3XPsZpIwztBoquEYLXh2MtwVU+kO8jgYCiv4G5xrSP/1wg4tkvrEE+esZVquIPX/dxPlePk1w==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { "@typescript-eslint/types": "3.10.1", "@typescript-eslint/visitor-keys": "3.10.1", @@ -4761,6 +6042,7 @@ "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-3.10.1.tgz", "integrity": "sha512-9JgC82AaQeglebjZMgYR5wgmfUdUc+EitGUUMW8u2nDckaeimzW+VsoLV6FoimPv2id3VQzfjwBxEMVz08ameQ==", "dev": true, + "license": "MIT", "dependencies": { "eslint-visitor-keys": "^1.1.0" }, @@ -4777,6 +6059,7 @@ "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^4.1.1" @@ -4790,6 +6073,7 @@ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", "dev": true, + "license": "Apache-2.0", "engines": { "node": ">=4" } @@ -4799,20 +6083,22 @@ "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", "dev": true, + "license": "BSD-2-Clause", "engines": { "node": ">=4.0" } }, "node_modules/@typescript-eslint/parser": { - "version": "8.22.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.22.0.tgz", - "integrity": "sha512-MqtmbdNEdoNxTPzpWiWnqNac54h8JDAmkWtJExBVVnSrSmi9z+sZUt0LfKqk9rjqmKOIeRhO4fHHJ1nQIjduIQ==", + "version": "8.31.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.31.0.tgz", + "integrity": "sha512-67kYYShjBR0jNI5vsf/c3WG4u+zDnCTHTPqVMQguffaWWFs7artgwKmfwdifl+r6XyM5LYLas/dInj2T0SgJyw==", "dev": true, + "license": "MIT", "dependencies": { - "@typescript-eslint/scope-manager": "8.22.0", - "@typescript-eslint/types": "8.22.0", - "@typescript-eslint/typescript-estree": "8.22.0", - "@typescript-eslint/visitor-keys": "8.22.0", + "@typescript-eslint/scope-manager": "8.31.0", + "@typescript-eslint/types": "8.31.0", + "@typescript-eslint/typescript-estree": "8.31.0", + "@typescript-eslint/visitor-keys": "8.31.0", "debug": "^4.3.4" }, "engines": { @@ -4824,17 +6110,18 @@ }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <5.8.0" + "typescript": ">=4.8.4 <5.9.0" } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.22.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.22.0.tgz", - "integrity": "sha512-/lwVV0UYgkj7wPSw0o8URy6YI64QmcOdwHuGuxWIYznO6d45ER0wXUbksr9pYdViAofpUCNJx/tAzNukgvaaiQ==", + "version": "8.31.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.31.0.tgz", + "integrity": "sha512-knO8UyF78Nt8O/B64i7TlGXod69ko7z6vJD9uhSlm0qkAbGeRUSudcm0+K/4CrRjrpiHfBCjMWlc08Vav1xwcw==", "dev": true, + "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.22.0", - "@typescript-eslint/visitor-keys": "8.22.0" + "@typescript-eslint/types": "8.31.0", + "@typescript-eslint/visitor-keys": "8.31.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -4845,15 +6132,16 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "8.22.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.22.0.tgz", - "integrity": "sha512-NzE3aB62fDEaGjaAYZE4LH7I1MUwHooQ98Byq0G0y3kkibPJQIXVUspzlFOmOfHhiDLwKzMlWxaNv+/qcZurJA==", + "version": "8.31.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.31.0.tgz", + "integrity": "sha512-DJ1N1GdjI7IS7uRlzJuEDCgDQix3ZVYVtgeWEyhyn4iaoitpMBX6Ndd488mXSx0xah/cONAkEaYyylDyAeHMHg==", "dev": true, + "license": "MIT", "dependencies": { - "@typescript-eslint/typescript-estree": "8.22.0", - "@typescript-eslint/utils": "8.22.0", + "@typescript-eslint/typescript-estree": "8.31.0", + "@typescript-eslint/utils": "8.31.0", "debug": "^4.3.4", - "ts-api-utils": "^2.0.0" + "ts-api-utils": "^2.0.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -4864,14 +6152,15 @@ }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <5.8.0" + "typescript": ">=4.8.4 <5.9.0" } }, "node_modules/@typescript-eslint/types": { - "version": "8.22.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.22.0.tgz", - "integrity": "sha512-0S4M4baNzp612zwpD4YOieP3VowOARgK2EkN/GBn95hpyF8E2fbMT55sRHWBq+Huaqk3b3XK+rxxlM8sPgGM6A==", + "version": "8.31.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.31.0.tgz", + "integrity": "sha512-Ch8oSjVyYyJxPQk8pMiP2FFGYatqXQfQIaMp+TpuuLlDachRWpUAeEu1u9B/v/8LToehUIWyiKcA/w5hUFRKuQ==", "dev": true, + "license": "MIT", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, @@ -4881,19 +6170,20 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.22.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.22.0.tgz", - "integrity": "sha512-SJX99NAS2ugGOzpyhMza/tX+zDwjvwAtQFLsBo3GQxiGcvaKlqGBkmZ+Y1IdiSi9h4Q0Lr5ey+Cp9CGWNY/F/w==", + "version": "8.31.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.31.0.tgz", + "integrity": "sha512-xLmgn4Yl46xi6aDSZ9KkyfhhtnYI15/CvHbpOy/eR5NWhK/BK8wc709KKwhAR0m4ZKRP7h07bm4BWUYOCuRpQQ==", "dev": true, + "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.22.0", - "@typescript-eslint/visitor-keys": "8.22.0", + "@typescript-eslint/types": "8.31.0", + "@typescript-eslint/visitor-keys": "8.31.0", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", "minimatch": "^9.0.4", "semver": "^7.6.0", - "ts-api-utils": "^2.0.0" + "ts-api-utils": "^2.0.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -4903,19 +6193,20 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "typescript": ">=4.8.4 <5.8.0" + "typescript": ">=4.8.4 <5.9.0" } }, "node_modules/@typescript-eslint/utils": { - "version": "8.22.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.22.0.tgz", - "integrity": "sha512-T8oc1MbF8L+Bk2msAvCUzjxVB2Z2f+vXYfcucE2wOmYs7ZUwco5Ep0fYZw8quNwOiw9K8GYVL+Kgc2pETNTLOg==", + "version": "8.31.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.31.0.tgz", + "integrity": "sha512-qi6uPLt9cjTFxAb1zGNgTob4x9ur7xC6mHQJ8GwEzGMGE9tYniublmJaowOJ9V2jUzxrltTPfdG2nKlWsq0+Ww==", "dev": true, + "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", - "@typescript-eslint/scope-manager": "8.22.0", - "@typescript-eslint/types": "8.22.0", - "@typescript-eslint/typescript-estree": "8.22.0" + "@typescript-eslint/scope-manager": "8.31.0", + "@typescript-eslint/types": "8.31.0", + "@typescript-eslint/typescript-estree": "8.31.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -4926,16 +6217,17 @@ }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <5.8.0" + "typescript": ">=4.8.4 <5.9.0" } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.22.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.22.0.tgz", - "integrity": "sha512-AWpYAXnUgvLNabGTy3uBylkgZoosva/miNd1I8Bz3SjotmQPbVqhO4Cczo8AsZ44XVErEBPr/CRSgaj8sG7g0w==", + "version": "8.31.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.31.0.tgz", + "integrity": "sha512-QcGHmlRHWOl93o64ZUMNewCdwKGU6WItOU52H0djgNmn1EOrhVudrDzXz4OycCRSCPwFCDrE2iIt5vmuUdHxuQ==", "dev": true, + "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.22.0", + "@typescript-eslint/types": "8.31.0", "eslint-visitor-keys": "^4.2.0" }, "engines": { @@ -4951,6 +6243,7 @@ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", "dev": true, + "license": "Apache-2.0", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, @@ -4962,18 +6255,20 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==", - "dev": true + "dev": true, + "license": "ISC" }, "node_modules/@vitejs/plugin-basic-ssl": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@vitejs/plugin-basic-ssl/-/plugin-basic-ssl-1.1.0.tgz", - "integrity": "sha512-wO4Dk/rm8u7RNhOf95ZzcEmC9rYOncYgvq4z3duaJrCgjN8BxAnDVyndanfcJZ0O6XZzHz6Q0hTimxTg8Y9g/A==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-basic-ssl/-/plugin-basic-ssl-1.2.0.tgz", + "integrity": "sha512-mkQnxTkcldAzIsomk1UuLfAu9n+kpQ3JbHcpCp7d2Oo6ITtji8pHS3QToOWjhPFvNQSnhlkAjmGbhv2QvwO/7Q==", "dev": true, + "license": "MIT", "engines": { - "node": ">=14.6.0" + "node": ">=14.21.3" }, "peerDependencies": { - "vite": "^3.0.0 || ^4.0.0 || ^5.0.0" + "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0" } }, "node_modules/@webassemblyjs/ast": { @@ -4981,6 +6276,7 @@ "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.14.1.tgz", "integrity": "sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==", "dev": true, + "license": "MIT", "dependencies": { "@webassemblyjs/helper-numbers": "1.13.2", "@webassemblyjs/helper-wasm-bytecode": "1.13.2" @@ -4990,25 +6286,29 @@ "version": "1.13.2", "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.13.2.tgz", "integrity": "sha512-6oXyTOzbKxGH4steLbLNOu71Oj+C8Lg34n6CqRvqfS2O71BxY6ByfMDRhBytzknj9yGUPVJ1qIKhRlAwO1AovA==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/@webassemblyjs/helper-api-error": { "version": "1.13.2", "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.13.2.tgz", "integrity": "sha512-U56GMYxy4ZQCbDZd6JuvvNV/WFildOjsaWD3Tzzvmw/mas3cXzRJPMjP83JqEsgSbyrmaGjBfDtV7KDXV9UzFQ==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/@webassemblyjs/helper-buffer": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.14.1.tgz", "integrity": "sha512-jyH7wtcHiKssDtFPRB+iQdxlDf96m0E39yb0k5uJVhFGleZFoNw1c4aeIcVUPPbXUVJ94wwnMOAqUHyzoEPVMA==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/@webassemblyjs/helper-numbers": { "version": "1.13.2", "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.13.2.tgz", "integrity": "sha512-FE8aCmS5Q6eQYcV3gI35O4J789wlQA+7JrqTTpJqn5emA4U2hvwJmvFRC0HODS+3Ye6WioDklgd6scJ3+PLnEA==", "dev": true, + "license": "MIT", "dependencies": { "@webassemblyjs/floating-point-hex-parser": "1.13.2", "@webassemblyjs/helper-api-error": "1.13.2", @@ -5019,13 +6319,15 @@ "version": "1.13.2", "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.13.2.tgz", "integrity": "sha512-3QbLKy93F0EAIXLh0ogEVR6rOubA9AoZ+WRYhNbFyuB70j3dRdwH9g+qXhLAO0kiYGlg3TxDV+I4rQTr/YNXkA==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/@webassemblyjs/helper-wasm-section": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.14.1.tgz", "integrity": "sha512-ds5mXEqTJ6oxRoqjhWDU83OgzAYjwsCV8Lo/N+oRsNDmx/ZDpqalmrtgOMkHwxsG0iI//3BwWAErYRHtgn0dZw==", "dev": true, + "license": "MIT", "dependencies": { "@webassemblyjs/ast": "1.14.1", "@webassemblyjs/helper-buffer": "1.14.1", @@ -5038,6 +6340,7 @@ "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.13.2.tgz", "integrity": "sha512-4LtOzh58S/5lX4ITKxnAK2USuNEvpdVV9AlgGQb8rJDHaLeHciwG4zlGr0j/SNWlr7x3vO1lDEsuePvtcDNCkw==", "dev": true, + "license": "MIT", "dependencies": { "@xtuc/ieee754": "^1.2.0" } @@ -5047,6 +6350,7 @@ "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.13.2.tgz", "integrity": "sha512-Lde1oNoIdzVzdkNEAWZ1dZ5orIbff80YPdHx20mrHwHrVNNTjNr8E3xz9BdpcGqRQbAEa+fkrCb+fRFTl/6sQw==", "dev": true, + "license": "Apache-2.0", "dependencies": { "@xtuc/long": "4.2.2" } @@ -5055,13 +6359,15 @@ "version": "1.13.2", "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.13.2.tgz", "integrity": "sha512-3NQWGjKTASY1xV5m7Hr0iPeXD9+RDobLll3T9d2AO+g3my8xy5peVyjSag4I50mR1bBSN/Ct12lo+R9tJk0NZQ==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/@webassemblyjs/wasm-edit": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.14.1.tgz", "integrity": "sha512-RNJUIQH/J8iA/1NzlE4N7KtyZNHi3w7at7hDjvRNm5rcUXa00z1vRz3glZoULfJ5mpvYhLybmVcwcjGrC1pRrQ==", "dev": true, + "license": "MIT", "dependencies": { "@webassemblyjs/ast": "1.14.1", "@webassemblyjs/helper-buffer": "1.14.1", @@ -5078,6 +6384,7 @@ "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.14.1.tgz", "integrity": "sha512-AmomSIjP8ZbfGQhumkNvgC33AY7qtMCXnN6bL2u2Js4gVCg8fp735aEiMSBbDR7UQIj90n4wKAFUSEd0QN2Ukg==", "dev": true, + "license": "MIT", "dependencies": { "@webassemblyjs/ast": "1.14.1", "@webassemblyjs/helper-wasm-bytecode": "1.13.2", @@ -5091,6 +6398,7 @@ "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.14.1.tgz", "integrity": "sha512-PTcKLUNvBqnY2U6E5bdOQcSM+oVP/PmrDY9NzowJjislEjwP/C4an2303MCVS2Mg9d3AJpIGdUFIQQWbPds0Sw==", "dev": true, + "license": "MIT", "dependencies": { "@webassemblyjs/ast": "1.14.1", "@webassemblyjs/helper-buffer": "1.14.1", @@ -5103,6 +6411,7 @@ "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.14.1.tgz", "integrity": "sha512-JLBl+KZ0R5qB7mCnud/yyX08jWFw5MsoalJ1pQ4EdFlgj9VdXKGuENGsiCIjegI1W7p91rUlcB/LB5yRJKNTcQ==", "dev": true, + "license": "MIT", "dependencies": { "@webassemblyjs/ast": "1.14.1", "@webassemblyjs/helper-api-error": "1.13.2", @@ -5117,6 +6426,7 @@ "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.14.1.tgz", "integrity": "sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw==", "dev": true, + "license": "MIT", "dependencies": { "@webassemblyjs/ast": "1.14.1", "@xtuc/long": "4.2.2" @@ -5126,27 +6436,31 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", - "dev": true + "dev": true, + "license": "BSD-3-Clause" }, "node_modules/@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 + "dev": true, + "license": "Apache-2.0" }, "node_modules/@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 + "dev": true, + "license": "BSD-2-Clause" }, "node_modules/abbrev": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-2.0.0.tgz", - "integrity": "sha512-6/mh1E2u2YgEsCHdY0Yx5oW+61gZU+1vXaoiHHrpKeuRNNgFvS+/jrwHiQhB5apAf5oB7UB7E19ol2R2LKH8hQ==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-3.0.1.tgz", + "integrity": "sha512-AO2ac6pjRB3SJmGJo+v5/aK6Omggp6fsLrs6wN9bd35ulu4cCwaAU9+7ZhXjeqHVkaHThLuzH0nZr0YpCDhygg==", "dev": true, + "license": "ISC", "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^18.17.0 || >=20.5.0" } }, "node_modules/accepts": { @@ -5154,6 +6468,7 @@ "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", "dev": true, + "license": "MIT", "dependencies": { "mime-types": "~2.1.34", "negotiator": "0.6.3" @@ -5167,15 +6482,17 @@ "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", "dev": true, + "license": "MIT", "engines": { "node": ">= 0.6" } }, "node_modules/acorn": { - "version": "8.14.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz", - "integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==", + "version": "8.14.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.1.tgz", + "integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==", "dev": true, + "license": "MIT", "bin": { "acorn": "bin/acorn" }, @@ -5183,20 +6500,12 @@ "node": ">=0.4.0" } }, - "node_modules/acorn-import-attributes": { - "version": "1.9.5", - "resolved": "https://registry.npmjs.org/acorn-import-attributes/-/acorn-import-attributes-1.9.5.tgz", - "integrity": "sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ==", - "dev": true, - "peerDependencies": { - "acorn": "^8" - } - }, "node_modules/acorn-jsx": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", "dev": true, + "license": "MIT", "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } @@ -5206,6 +6515,7 @@ "resolved": "https://registry.npmjs.org/adjust-sourcemap-loader/-/adjust-sourcemap-loader-4.0.0.tgz", "integrity": "sha512-OXwN5b9pCUXNQHJpwwD2qP40byEmSgzj8B4ydSN0uMNYWiFmJ6x6KwUllMmfk8Rwu/HJDFR7U8ubsWBoN0Xp0A==", "dev": true, + "license": "MIT", "dependencies": { "loader-utils": "^2.0.0", "regex-parser": "^2.2.11" @@ -5219,6 +6529,7 @@ "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.4.tgz", "integrity": "sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==", "dev": true, + "license": "MIT", "dependencies": { "big.js": "^5.2.2", "emojis-list": "^3.0.0", @@ -5233,28 +6544,17 @@ "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.3.tgz", "integrity": "sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==", "dev": true, + "license": "MIT", "engines": { "node": ">= 14" } }, - "node_modules/aggregate-error": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", - "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", - "dev": true, - "dependencies": { - "clean-stack": "^2.0.0", - "indent-string": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/ajv": { "version": "8.17.1", "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", "dev": true, + "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", @@ -5271,6 +6571,7 @@ "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz", "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==", "dev": true, + "license": "MIT", "dependencies": { "ajv": "^8.0.0" }, @@ -5288,6 +6589,7 @@ "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", "dev": true, + "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.3" }, @@ -5300,6 +6602,7 @@ "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==", "dev": true, + "license": "MIT", "engines": { "node": ">=6" } @@ -5309,6 +6612,7 @@ "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", "dev": true, + "license": "MIT", "dependencies": { "type-fest": "^0.21.3" }, @@ -5327,6 +6631,7 @@ "engines": [ "node >= 0.8.0" ], + "license": "Apache-2.0", "bin": { "ansi-html": "bin/ansi-html" } @@ -5336,6 +6641,7 @@ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.1.tgz", "integrity": "sha512-+O9Jct8wf++lXxxFc4hc8LsjaSq0HFzzL7cVsw8pRDIPdjKD2mT4ytDZlLuSBZ4cLKZFXIrMGO7DbQCtMJJMKw==", "dev": true, + "license": "MIT", "engines": { "node": ">=4" } @@ -5345,6 +6651,7 @@ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, + "license": "MIT", "dependencies": { "color-convert": "^2.0.1" }, @@ -5360,6 +6667,7 @@ "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", "dev": true, + "license": "ISC", "dependencies": { "normalize-path": "^3.0.0", "picomatch": "^2.0.4" @@ -5373,6 +6681,7 @@ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", "dev": true, + "license": "MIT", "engines": { "node": ">=8.6" }, @@ -5384,13 +6693,15 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true + "dev": true, + "license": "Python-2.0" }, "node_modules/aria-query": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.2.tgz", "integrity": "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==", "dev": true, + "license": "Apache-2.0", "engines": { "node": ">= 0.4" } @@ -5399,13 +6710,15 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/astral-regex": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } @@ -5429,6 +6742,7 @@ "url": "https://github.com/sponsors/ai" } ], + "license": "MIT", "dependencies": { "browserslist": "^4.23.3", "caniuse-lite": "^1.0.30001646", @@ -5452,15 +6766,17 @@ "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz", "integrity": "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==", "dev": true, + "license": "Apache-2.0", "engines": { "node": ">= 0.4" } }, "node_modules/babel-loader": { - "version": "9.1.3", - "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-9.1.3.tgz", - "integrity": "sha512-xG3ST4DglodGf8qSwv0MdeWLhrDsw/32QMdTO5T1ZIp9gQur0HkCyFs7Awskr10JKXFXwpAhiCuYX5oGXnRGbw==", + "version": "9.2.1", + "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-9.2.1.tgz", + "integrity": "sha512-fqe8naHt46e0yIdkjUZYqddSXfej3AHajX+CSO5X7oy0EmPc6o5Xh+RClNoHjnieWz9AW4kZxW9yyFMhVB1QLA==", "dev": true, + "license": "MIT", "dependencies": { "find-cache-dir": "^4.0.0", "schema-utils": "^4.0.0" @@ -5474,13 +6790,14 @@ } }, "node_modules/babel-plugin-polyfill-corejs2": { - "version": "0.4.12", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.12.tgz", - "integrity": "sha512-CPWT6BwvhrTO2d8QVorhTCQw9Y43zOu7G9HigcfxvepOU6b8o3tcWad6oVgZIsZCTt42FFv97aA7ZJsbM4+8og==", + "version": "0.4.13", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.13.tgz", + "integrity": "sha512-3sX/eOms8kd3q2KZ6DAhKPc0dgm525Gqq5NtWKZ7QYYZEv57OQ54KtblzJzH1lQF/eQxO8KjWGIK9IPUJNus5g==", "dev": true, + "license": "MIT", "dependencies": { "@babel/compat-data": "^7.22.6", - "@babel/helper-define-polyfill-provider": "^0.6.3", + "@babel/helper-define-polyfill-provider": "^0.6.4", "semver": "^6.3.1" }, "peerDependencies": { @@ -5492,30 +6809,33 @@ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true, + "license": "ISC", "bin": { "semver": "bin/semver.js" } }, "node_modules/babel-plugin-polyfill-corejs3": { - "version": "0.10.6", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.10.6.tgz", - "integrity": "sha512-b37+KR2i/khY5sKmWNVQAnitvquQbNdWy6lJdsr0kmquCKEEUgMKK4SboVM3HtfnZilfjr4MMQ7vY58FVWDtIA==", + "version": "0.11.1", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.11.1.tgz", + "integrity": "sha512-yGCqvBT4rwMczo28xkH/noxJ6MZ4nJfkVYdoDaC/utLtWrXxv27HVrzAeSbqR8SxDsp46n0YF47EbHoixy6rXQ==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-define-polyfill-provider": "^0.6.2", - "core-js-compat": "^3.38.0" + "@babel/helper-define-polyfill-provider": "^0.6.3", + "core-js-compat": "^3.40.0" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, "node_modules/babel-plugin-polyfill-regenerator": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.3.tgz", - "integrity": "sha512-LiWSbl4CRSIa5x/JAU6jZiG9eit9w6mz+yVMFwDE83LAWvt0AfGBoZ7HS/mkhrKuh2ZlzfVZYKoLjXdqw6Yt7Q==", + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.4.tgz", + "integrity": "sha512-7gD3pRadPrbjhjLyxebmx/WrFYcuSjZ0XbdUujQMZ/fcE9oeewk2U/7PCvez84UeuK3oSjmPZ0Ch0dlupQvGzw==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-define-polyfill-provider": "^0.6.3" + "@babel/helper-define-polyfill-provider": "^0.6.4" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" @@ -5524,7 +6844,8 @@ "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "license": "MIT" }, "node_modules/base64-js": { "version": "1.5.1", @@ -5543,13 +6864,15 @@ "type": "consulting", "url": "https://feross.org/support" } - ] + ], + "license": "MIT" }, "node_modules/base64id": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz", "integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==", "dev": true, + "license": "MIT", "engines": { "node": "^4.5.0 || >= 5.9" } @@ -5558,13 +6881,35 @@ "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 + "dev": true, + "license": "MIT" + }, + "node_modules/beasties": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/beasties/-/beasties-0.3.2.tgz", + "integrity": "sha512-p4AF8uYzm9Fwu8m/hSVTCPXrRBPmB34hQpHsec2KOaR9CZmgoU8IOv4Cvwq4hgz2p4hLMNbsdNl5XeA6XbAQwA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "css-select": "^5.1.0", + "css-what": "^6.1.0", + "dom-serializer": "^2.0.0", + "domhandler": "^5.0.3", + "htmlparser2": "^10.0.0", + "picocolors": "^1.1.1", + "postcss": "^8.4.49", + "postcss-media-query-parser": "^0.2.3" + }, + "engines": { + "node": ">=14.0.0" + } }, "node_modules/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, + "license": "MIT", "engines": { "node": "*" } @@ -5574,6 +6919,7 @@ "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" }, @@ -5585,6 +6931,7 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "license": "MIT", "dependencies": { "buffer": "^5.5.0", "inherits": "^2.0.4", @@ -5596,6 +6943,7 @@ "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", "dev": true, + "license": "MIT", "dependencies": { "bytes": "3.1.2", "content-type": "~1.0.5", @@ -5620,6 +6968,7 @@ "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", "dev": true, + "license": "MIT", "dependencies": { "ms": "2.0.0" } @@ -5628,13 +6977,15 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/bonjour-service": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/bonjour-service/-/bonjour-service-1.3.0.tgz", "integrity": "sha512-3YuAUiSkWykd+2Azjgyxei8OWf8thdn8AITIog2M4UICzoqfjlqr64WIjEXZllf/W6vK1goqleSR6brGomxQqA==", "dev": true, + "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.3", "multicast-dns": "^7.2.5" @@ -5644,13 +6995,15 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", - "dev": true + "dev": true, + "license": "ISC" }, "node_modules/brace-expansion": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", "dev": true, + "license": "MIT", "dependencies": { "balanced-match": "^1.0.0" } @@ -5660,6 +7013,7 @@ "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", "dev": true, + "license": "MIT", "dependencies": { "fill-range": "^7.1.1" }, @@ -5686,6 +7040,7 @@ "url": "https://github.com/sponsors/ai" } ], + "license": "MIT", "dependencies": { "caniuse-lite": "^1.0.30001688", "electron-to-chromium": "^1.5.73", @@ -5717,6 +7072,7 @@ "url": "https://feross.org/support" } ], + "license": "MIT", "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.1.13" @@ -5725,13 +7081,15 @@ "node_modules/buffer-from": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", - "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==" + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "license": "MIT" }, "node_modules/bundle-name": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/bundle-name/-/bundle-name-4.1.0.tgz", "integrity": "sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==", "dev": true, + "license": "MIT", "dependencies": { "run-applescript": "^7.0.0" }, @@ -5747,17 +7105,19 @@ "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", "dev": true, + "license": "MIT", "engines": { "node": ">= 0.8" } }, "node_modules/cacache": { - "version": "18.0.4", - "resolved": "https://registry.npmjs.org/cacache/-/cacache-18.0.4.tgz", - "integrity": "sha512-B+L5iIa9mgcjLbliir2th36yEwPftrzteHYujzsx3dFP/31GCHcIeS8f5MGd80odLOjaOvSpU3EEAmRQptkxLQ==", + "version": "19.0.1", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-19.0.1.tgz", + "integrity": "sha512-hdsUxulXCi5STId78vRVYEtDAjq99ICAUktLTeTYsLoTE6Z8dS0c8pWNCxwdrk9YfJeobDZc2Y186hD/5ZQgFQ==", "dev": true, + "license": "ISC", "dependencies": { - "@npmcli/fs": "^3.1.0", + "@npmcli/fs": "^4.0.0", "fs-minipass": "^3.0.0", "glob": "^10.2.2", "lru-cache": "^10.0.1", @@ -5765,13 +7125,23 @@ "minipass-collect": "^2.0.1", "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" + "p-map": "^7.0.2", + "ssri": "^12.0.0", + "tar": "^7.4.3", + "unique-filename": "^4.0.0" }, "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/cacache/node_modules/chownr": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz", + "integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" } }, "node_modules/cacache/node_modules/glob": { @@ -5779,6 +7149,7 @@ "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", "dev": true, + "license": "ISC", "dependencies": { "foreground-child": "^3.1.0", "jackspeak": "^3.1.2", @@ -5794,17 +7165,63 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/cacache/node_modules/lru-cache": { - "version": "10.4.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", - "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", - "dev": true - }, + "node_modules/cacache/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/cacache/node_modules/mkdirp": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-3.0.1.tgz", + "integrity": "sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==", + "dev": true, + "license": "MIT", + "bin": { + "mkdirp": "dist/cjs/src/bin.js" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/cacache/node_modules/tar": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/tar/-/tar-7.4.3.tgz", + "integrity": "sha512-5S7Va8hKfV7W5U6g3aYxXmlPoZVAwUMy9AOKyF2fVuZa2UD3qZjg578OrLRt8PcNN1PleVaL/5/yYATNL0ICUw==", + "dev": true, + "license": "ISC", + "dependencies": { + "@isaacs/fs-minipass": "^4.0.0", + "chownr": "^3.0.0", + "minipass": "^7.1.2", + "minizlib": "^3.0.1", + "mkdirp": "^3.0.1", + "yallist": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/cacache/node_modules/yallist": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz", + "integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, "node_modules/call-bind-apply-helpers": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.1.tgz", - "integrity": "sha512-BhYE+WDaywFg2TBWYNXAE+8B1ATnThNBqXHP5nQu0jWJdVvY2hvkpyB3qOmtmDePiS5/BDQ8wASEWGMWRG148g==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", "dev": true, + "license": "MIT", "dependencies": { "es-errors": "^1.3.0", "function-bind": "^1.1.2" @@ -5814,13 +7231,14 @@ } }, "node_modules/call-bound": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.3.tgz", - "integrity": "sha512-YTd+6wGlNlPxSuri7Y6X8tY2dmm12UMH66RpKMhiX6rsk5wXXnYgbUcOt8kiS31/AjfoTOvCsE+w8nZQLQnzHA==", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", "dev": true, + "license": "MIT", "dependencies": { - "call-bind-apply-helpers": "^1.0.1", - "get-intrinsic": "^1.2.6" + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" }, "engines": { "node": ">= 0.4" @@ -5834,14 +7252,15 @@ "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=6" } }, "node_modules/caniuse-lite": { - "version": "1.0.30001696", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001696.tgz", - "integrity": "sha512-pDCPkvzfa39ehJtJ+OwGT/2yvT2SbjfHhiIW2LWOAcMQ7BzwxT/XuyUp4OTOd0XFWA6BKw0JalnBHgSi5DGJBQ==", + "version": "1.0.30001715", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001715.tgz", + "integrity": "sha512-7ptkFGMm2OAOgvZpwgA4yjQ5SQbrNVGdRjzH0pBdy1Fasvcr+KAeECmbCAECzTuDuoX0FCY8KzUxjf9+9kfZEw==", "dev": true, "funding": [ { @@ -5856,13 +7275,15 @@ "type": "github", "url": "https://github.com/sponsors/ai" } - ] + ], + "license": "CC-BY-4.0" }, "node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, + "license": "MIT", "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -5878,13 +7299,15 @@ "version": "0.7.0", "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/chokidar": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", "dev": true, + "license": "MIT", "dependencies": { "readdirp": "^4.0.1" }, @@ -5900,6 +7323,7 @@ "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", "dev": true, + "license": "ISC", "engines": { "node": ">=10" } @@ -5909,24 +7333,17 @@ "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.4.tgz", "integrity": "sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=6.0" } }, - "node_modules/clean-stack": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", - "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", - "dev": true, - "engines": { - "node": ">=6" - } - }, "node_modules/cli-cursor": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-5.0.0.tgz", "integrity": "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==", "dev": true, + "license": "MIT", "dependencies": { "restore-cursor": "^5.0.0" }, @@ -5942,6 +7359,7 @@ "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz", "integrity": "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==", "dev": true, + "license": "MIT", "engines": { "node": ">=6" }, @@ -5954,6 +7372,7 @@ "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-4.0.0.tgz", "integrity": "sha512-nPdaFdQ0h/GEigbPClz11D0v/ZJEwxmeVZGeMo3Z5StPtUTkA9o1lD6QwoirYiSDzbcwn2XcjwmCp68W1IS4TA==", "dev": true, + "license": "MIT", "dependencies": { "slice-ansi": "^5.0.0", "string-width": "^7.0.0" @@ -5970,6 +7389,7 @@ "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-4.1.0.tgz", "integrity": "sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==", "dev": true, + "license": "ISC", "engines": { "node": ">= 12" } @@ -5979,6 +7399,7 @@ "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", "dev": true, + "license": "ISC", "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.1", @@ -5992,13 +7413,15 @@ "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 + "dev": true, + "license": "MIT" }, "node_modules/cliui/node_modules/is-fullwidth-code-point": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } @@ -6008,6 +7431,7 @@ "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "dev": true, + "license": "MIT", "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", @@ -6022,6 +7446,7 @@ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", "dev": true, + "license": "MIT", "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", @@ -6039,6 +7464,7 @@ "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", "integrity": "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.8" } @@ -6048,6 +7474,7 @@ "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz", "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==", "dev": true, + "license": "MIT", "dependencies": { "is-plain-object": "^2.0.4", "kind-of": "^6.0.2", @@ -6062,6 +7489,7 @@ "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", "dev": true, + "license": "MIT", "dependencies": { "isobject": "^3.0.1" }, @@ -6074,6 +7502,7 @@ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, + "license": "MIT", "dependencies": { "color-name": "~1.1.4" }, @@ -6085,24 +7514,28 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/colorette": { "version": "2.0.20", "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/commander": { "version": "2.20.3", "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/commist": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/commist/-/commist-1.1.0.tgz", "integrity": "sha512-rraC8NXWOEjhADbZe9QBNzLAN5Q3fsTPQtBV+fEVj6xKIgDgNiEVE6ZNfHpZOqfQ21YUzfVNUXLOEZquYvQPPg==", + "license": "MIT", "dependencies": { "leven": "^2.1.0", "minimist": "^1.1.0" @@ -6112,13 +7545,15 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/common-path-prefix/-/common-path-prefix-3.0.0.tgz", "integrity": "sha512-QE33hToZseCH3jS0qN96O/bSh3kaw/h+Tq7ngyY9eWDUnTlTNUyqfqvCXioLe5Na5jFsL78ra/wuBU4iuEgd4w==", - "dev": true + "dev": true, + "license": "ISC" }, "node_modules/common-tags": { "version": "1.8.2", "resolved": "https://registry.npmjs.org/common-tags/-/common-tags-1.8.2.tgz", "integrity": "sha512-gk/Z852D2Wtb//0I+kRFNKKE9dIIVirjoqPoA1wJU+XePVXZfGeBpk45+A1rKO4Q43prqWBNY/MiIeRLbPWUaA==", "dev": true, + "license": "MIT", "engines": { "node": ">=4.0.0" } @@ -6128,6 +7563,7 @@ "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", "dev": true, + "license": "MIT", "dependencies": { "mime-db": ">= 1.43.0 < 2" }, @@ -6136,10 +7572,11 @@ } }, "node_modules/compression": { - "version": "1.7.5", - "resolved": "https://registry.npmjs.org/compression/-/compression-1.7.5.tgz", - "integrity": "sha512-bQJ0YRck5ak3LgtnpKkiabX5pNF7tMUh1BSy2ZBOTh0Dim0BUu6aPPwByIns6/A5Prh8PufSPerMDUklpzes2Q==", + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/compression/-/compression-1.8.0.tgz", + "integrity": "sha512-k6WLKfunuqCYD3t6AsuPGvQWaKwuLLh2/xHNcX4qE+vIfDNXpSqnrhwA7O53R7WVQUnt8dVAIW+YHr7xTgOgGA==", "dev": true, + "license": "MIT", "dependencies": { "bytes": "3.1.2", "compressible": "~2.0.18", @@ -6158,6 +7595,7 @@ "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", "dev": true, + "license": "MIT", "dependencies": { "ms": "2.0.0" } @@ -6166,12 +7604,24 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "dev": true + "dev": true, + "license": "MIT" + }, + "node_modules/compression/node_modules/negotiator": { + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.4.tgz", + "integrity": "sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "license": "MIT" }, "node_modules/concat-stream": { "version": "2.0.0", @@ -6180,6 +7630,7 @@ "engines": [ "node >= 6.0" ], + "license": "MIT", "dependencies": { "buffer-from": "^1.0.0", "inherits": "^2.0.3", @@ -6192,6 +7643,7 @@ "resolved": "https://registry.npmjs.org/connect/-/connect-3.7.0.tgz", "integrity": "sha512-ZqRXc+tZukToSNmh5C2iWMSoV3X1YUcPbqEM4DkEG5tNQXrQUZCNVGGv3IuicnkMtPfGf3Xtp8WCXs295iQ1pQ==", "dev": true, + "license": "MIT", "dependencies": { "debug": "2.6.9", "finalhandler": "1.1.2", @@ -6207,6 +7659,7 @@ "resolved": "https://registry.npmjs.org/connect-history-api-fallback/-/connect-history-api-fallback-2.0.0.tgz", "integrity": "sha512-U73+6lQFmfiNPrYbXqr6kZ1i1wiRqXnp2nhMsINseWXO8lDau0LGEffJ8kQi4EjLZympVgRdvqjAgiZ1tgzDDA==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.8" } @@ -6216,6 +7669,7 @@ "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", "dev": true, + "license": "MIT", "dependencies": { "ms": "2.0.0" } @@ -6224,13 +7678,15 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/content-disposition": { "version": "0.5.4", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", "dev": true, + "license": "MIT", "dependencies": { "safe-buffer": "5.2.1" }, @@ -6243,6 +7699,7 @@ "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", "dev": true, + "license": "MIT", "engines": { "node": ">= 0.6" } @@ -6251,13 +7708,15 @@ "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 + "dev": true, + "license": "MIT" }, "node_modules/cookie": { "version": "0.7.2", "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", "dev": true, + "license": "MIT", "engines": { "node": ">= 0.6" } @@ -6266,13 +7725,15 @@ "version": "1.0.6", "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/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, + "license": "MIT", "dependencies": { "is-what": "^3.14.1" }, @@ -6285,6 +7746,7 @@ "resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-12.0.2.tgz", "integrity": "sha512-SNwdBeHyII+rWvee/bTnAYyO8vfVdcSTud4EIb6jcZ8inLeWucJE0DnxXQBjlQ5zlteuuvooGQy3LIyGxhvlOA==", "dev": true, + "license": "MIT", "dependencies": { "fast-glob": "^3.3.2", "glob-parent": "^6.0.1", @@ -6305,12 +7767,13 @@ } }, "node_modules/core-js-compat": { - "version": "3.40.0", - "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.40.0.tgz", - "integrity": "sha512-0XEDpr5y5mijvw8Lbc6E5AkjrHfp7eEoPlu36SWeAbcL8fn1G1ANe8DBlo2XoNN89oVpxWwOjYIPVzR4ZvsKCQ==", + "version": "3.41.0", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.41.0.tgz", + "integrity": "sha512-RFsU9LySVue9RTwdDVX/T0e2Y6jRYWXERKElIjpuEOEnxaXffI0X7RUwVzfYLfzuLXSNJDYoRYUAmRUcyln20A==", "dev": true, + "license": "MIT", "dependencies": { - "browserslist": "^4.24.3" + "browserslist": "^4.24.4" }, "funding": { "type": "opencollective", @@ -6321,13 +7784,15 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/cors": { "version": "2.8.5", "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", "dev": true, + "license": "MIT", "dependencies": { "object-assign": "^4", "vary": "^1" @@ -6341,6 +7806,7 @@ "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.0.tgz", "integrity": "sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==", "dev": true, + "license": "MIT", "dependencies": { "env-paths": "^2.2.1", "import-fresh": "^3.3.0", @@ -6362,27 +7828,12 @@ } } }, - "node_modules/critters": { - "version": "0.0.24", - "resolved": "https://registry.npmjs.org/critters/-/critters-0.0.24.tgz", - "integrity": "sha512-Oyqew0FGM0wYUSNqR0L6AteO5MpMoUU0rhKRieXeiKs+PmRTxiJMyaunYB2KF6fQ3dzChXKCpbFOEJx3OQ1v/Q==", - "deprecated": "Ownership of Critters has moved to the Nuxt team, who will be maintaining the project going forward. If you'd like to keep using Critters, please switch to the actively-maintained fork at https://github.com/danielroe/beasties", - "dev": true, - "dependencies": { - "chalk": "^4.1.0", - "css-select": "^5.1.0", - "dom-serializer": "^2.0.0", - "domhandler": "^5.0.2", - "htmlparser2": "^8.0.2", - "postcss": "^8.4.23", - "postcss-media-query-parser": "^0.2.3" - } - }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", "dev": true, + "license": "MIT", "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", @@ -6397,6 +7848,7 @@ "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-7.1.2.tgz", "integrity": "sha512-6WvYYn7l/XEGN8Xu2vWFt9nVzrCn39vKyTEFf/ExEyoksJjjSZV/0/35XPlMbpnr6VGhZIUg5yJrL8tGfes/FA==", "dev": true, + "license": "MIT", "dependencies": { "icss-utils": "^5.1.0", "postcss": "^8.4.33", @@ -6432,6 +7884,7 @@ "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.1.0.tgz", "integrity": "sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { "boolbase": "^1.0.0", "css-what": "^6.1.0", @@ -6448,6 +7901,7 @@ "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz", "integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==", "dev": true, + "license": "BSD-2-Clause", "engines": { "node": ">= 6" }, @@ -6460,6 +7914,7 @@ "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", "dev": true, + "license": "MIT", "bin": { "cssesc": "bin/cssesc" }, @@ -6471,13 +7926,15 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/custom-event/-/custom-event-1.0.1.tgz", "integrity": "sha512-GAj5FOq0Hd+RsCGVJxZuKaIDXDf3h6GQoNEjFgbLLI/trgtavwUbSnZ5pVfg27DVCaWjIohryS0JFwIJyT2cMg==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/date-format": { "version": "4.0.14", "resolved": "https://registry.npmjs.org/date-format/-/date-format-4.0.14.tgz", "integrity": "sha512-39BOQLs9ZjKh0/patS9nrT8wc3ioX3/eA/zgbKNopnF2wCqJEoxywwwElATYvRsXdnOxA/OQeQoFZ3rFjVajhg==", "dev": true, + "license": "MIT", "engines": { "node": ">=4.0" } @@ -6486,6 +7943,7 @@ "version": "4.4.0", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "license": "MIT", "dependencies": { "ms": "^2.1.3" }, @@ -6502,13 +7960,15 @@ "version": "0.1.4", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/default-browser": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/default-browser/-/default-browser-5.2.1.tgz", "integrity": "sha512-WY/3TUME0x3KPYdRRxEJJvXRHV4PyPoUsxtZa78lwItwRQRHhd2U9xOscaT/YTf8uCXIAjeJOFBVEh/7FtD8Xg==", "dev": true, + "license": "MIT", "dependencies": { "bundle-name": "^4.1.0", "default-browser-id": "^5.0.0" @@ -6525,6 +7985,7 @@ "resolved": "https://registry.npmjs.org/default-browser-id/-/default-browser-id-5.0.0.tgz", "integrity": "sha512-A6p/pu/6fyBcA1TRz/GqWYPViplrftcW2gZC9q79ngNCKAeR/X3gcEdXQHl4KNXV+3wgIJ1CPkJQ3IHM6lcsyA==", "dev": true, + "license": "MIT", "engines": { "node": ">=18" }, @@ -6532,23 +7993,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/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, - "dependencies": { - "execa": "^5.0.0" - }, - "engines": { - "node": ">= 10" - } - }, "node_modules/defaults": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz", "integrity": "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==", "dev": true, + "license": "MIT", "dependencies": { "clone": "^1.0.2" }, @@ -6561,6 +8011,7 @@ "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz", "integrity": "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==", "dev": true, + "license": "MIT", "engines": { "node": ">=12" }, @@ -6573,6 +8024,7 @@ "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", "dev": true, + "license": "MIT", "engines": { "node": ">= 0.8" } @@ -6582,16 +8034,19 @@ "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", "dev": true, + "license": "MIT", "engines": { "node": ">= 0.8", "npm": "1.2.8000 || >= 1.4.16" } }, "node_modules/detect-libc": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz", - "integrity": "sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==", + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.4.tgz", + "integrity": "sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==", "dev": true, + "license": "Apache-2.0", + "optional": true, "engines": { "node": ">=8" } @@ -6600,25 +8055,29 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz", "integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/di": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/di/-/di-0.0.1.tgz", "integrity": "sha512-uJaamHkagcZtHPqCIHZxnFrXlunQXgBOsZSUOWwFw31QJCAbyTBoHMW75YOTur5ZNx8pIeAKgf6GWIgaqqiLhA==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/dlv": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/dns-packet": { "version": "5.6.1", "resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-5.6.1.tgz", "integrity": "sha512-l4gcSouhcgIKRvyy99RNVOgxXiicE+2jZoNmaNmZ6JXiGajBOJAesk1OBlJuM5k2c+eudGdLxDqXuPCKIj6kpw==", "dev": true, + "license": "MIT", "dependencies": { "@leichtgewicht/ip-codec": "^2.0.1" }, @@ -6631,6 +8090,7 @@ "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", "dev": true, + "license": "Apache-2.0", "dependencies": { "esutils": "^2.0.2" }, @@ -6643,6 +8103,7 @@ "resolved": "https://registry.npmjs.org/dom-serialize/-/dom-serialize-2.2.1.tgz", "integrity": "sha512-Yra4DbvoW7/Z6LBN560ZwXMjoNOSAN2wRsKFGc4iBeso+mpIA6qj1vfdf9HpMaKAqG6wXTy+1SYEzmNpKXOSsQ==", "dev": true, + "license": "MIT", "dependencies": { "custom-event": "~1.0.0", "ent": "~2.2.0", @@ -6655,6 +8116,7 @@ "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", "dev": true, + "license": "MIT", "dependencies": { "domelementtype": "^2.3.0", "domhandler": "^5.0.2", @@ -6674,13 +8136,15 @@ "type": "github", "url": "https://github.com/sponsors/fb55" } - ] + ], + "license": "BSD-2-Clause" }, "node_modules/domhandler": { "version": "5.0.3", "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { "domelementtype": "^2.3.0" }, @@ -6696,6 +8160,7 @@ "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz", "integrity": "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { "dom-serializer": "^2.0.0", "domelementtype": "^2.3.0", @@ -6710,6 +8175,7 @@ "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", "dev": true, + "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", @@ -6723,6 +8189,7 @@ "version": "4.1.3", "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-4.1.3.tgz", "integrity": "sha512-M3BmBhwJRZsSx38lZyhE53Csddgzl5R7xGJNk7CVddZD6CcmwMCH8J+7AprIrQKH7TonKxaCjcv27Qmf+sQ+oA==", + "license": "MIT", "dependencies": { "end-of-stream": "^1.4.1", "inherits": "^2.0.3", @@ -6734,31 +8201,36 @@ "version": "0.2.0", "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/electron-to-chromium": { - "version": "1.5.90", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.90.tgz", - "integrity": "sha512-C3PN4aydfW91Natdyd449Kw+BzhLmof6tzy5W1pFC5SpQxVXT+oyiyOG9AgYYSN9OdA/ik3YkCrpwqI8ug5Tug==", - "dev": true + "version": "1.5.142", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.142.tgz", + "integrity": "sha512-Ah2HgkTu/9RhTDNThBtzu2Wirdy4DC9b0sMT1pUhbkZQ5U/iwmE+PHZX1MpjD5IkJCc2wSghgGG/B04szAx07w==", + "dev": true, + "license": "ISC" }, "node_modules/emoji-regex": { "version": "10.4.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.4.0.tgz", "integrity": "sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/emojis-list": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz", "integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==", "dev": true, + "license": "MIT", "engines": { "node": ">= 4" } @@ -6768,6 +8240,7 @@ "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", "dev": true, + "license": "MIT", "engines": { "node": ">= 0.8" } @@ -6777,6 +8250,7 @@ "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz", "integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==", "dev": true, + "license": "MIT", "optional": true, "dependencies": { "iconv-lite": "^0.6.2" @@ -6787,6 +8261,7 @@ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", "dev": true, + "license": "MIT", "optional": true, "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" @@ -6799,6 +8274,7 @@ "version": "1.4.4", "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "license": "MIT", "dependencies": { "once": "^1.4.0" } @@ -6808,6 +8284,7 @@ "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.6.4.tgz", "integrity": "sha512-ZCkIjSYNDyGn0R6ewHDtXgns/Zre/NT6Agvq1/WobF7JXgFff4SeDroKiCO3fNJreU9YG429Sc81o4w5ok/W5g==", "dev": true, + "license": "MIT", "dependencies": { "@types/cors": "^2.8.12", "@types/node": ">=10.0.0", @@ -6828,6 +8305,7 @@ "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.3.tgz", "integrity": "sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==", "dev": true, + "license": "MIT", "engines": { "node": ">=10.0.0" } @@ -6837,6 +8315,7 @@ "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", "dev": true, + "license": "MIT", "dependencies": { "ms": "^2.1.3" }, @@ -6854,6 +8333,7 @@ "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=10.0.0" }, @@ -6871,10 +8351,11 @@ } }, "node_modules/enhanced-resolve": { - "version": "5.18.0", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.0.tgz", - "integrity": "sha512-0/r0MySGYG8YqlayBZ6MuCfECmHFdJ5qyPh8s8wa5Hnm6SaFLSK1VYCbj+NKp090Nm1caZhD+QTnmxO7esYGyQ==", + "version": "5.18.1", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.1.tgz", + "integrity": "sha512-ZSW3ma5GkcQBIpwZTSRAI8N71Uuwgs93IezB7mf7R60tC8ZbJideoDNKjHn2O9KIlx6rkGTTEk1xUCK2E1Y2Yg==", "dev": true, + "license": "MIT", "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.2.0" @@ -6888,6 +8369,7 @@ "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.4.1.tgz", "integrity": "sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ==", "dev": true, + "license": "MIT", "dependencies": { "ansi-colors": "^4.1.1", "strip-ansi": "^6.0.1" @@ -6901,6 +8383,7 @@ "resolved": "https://registry.npmjs.org/ent/-/ent-2.2.2.tgz", "integrity": "sha512-kKvD1tO6BM+oK9HzCPpUdRb4vKFQY/FPTFmurMvh6LlN68VMrdj77w8yp51/kDbpkFOS9J8w5W6zIzgM2H8/hw==", "dev": true, + "license": "MIT", "dependencies": { "call-bound": "^1.0.3", "es-errors": "^1.3.0", @@ -6915,7 +8398,8 @@ "version": "4.5.0", "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", - "devOptional": true, + "dev": true, + "license": "BSD-2-Clause", "engines": { "node": ">=0.12" }, @@ -6928,6 +8412,7 @@ "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", "dev": true, + "license": "MIT", "engines": { "node": ">=6" } @@ -6937,6 +8422,7 @@ "resolved": "https://registry.npmjs.org/environment/-/environment-1.1.0.tgz", "integrity": "sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==", "dev": true, + "license": "MIT", "engines": { "node": ">=18" }, @@ -6948,13 +8434,15 @@ "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 + "dev": true, + "license": "MIT" }, "node_modules/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, + "license": "MIT", "optional": true, "dependencies": { "prr": "~1.0.1" @@ -6968,6 +8456,7 @@ "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", "dev": true, + "license": "MIT", "dependencies": { "is-arrayish": "^0.2.1" } @@ -6977,6 +8466,7 @@ "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", "dev": true, + "license": "MIT", "engines": { "node": ">= 0.4" } @@ -6986,21 +8476,24 @@ "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", "dev": true, + "license": "MIT", "engines": { "node": ">= 0.4" } }, "node_modules/es-module-lexer": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.6.0.tgz", - "integrity": "sha512-qqnD1yMU6tk/jnaMosogGySTZP8YtUgAffA9nMN+E/rjxcfRQ6IEk7IiozUjgxKoFHBGjTLnrHB/YC45r/59EQ==", - "dev": true + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", + "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", + "dev": true, + "license": "MIT" }, "node_modules/es-object-atoms": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", "dev": true, + "license": "MIT", "dependencies": { "es-errors": "^1.3.0" }, @@ -7009,11 +8502,12 @@ } }, "node_modules/esbuild": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.23.0.tgz", - "integrity": "sha512-1lvV17H2bMYda/WaFb2jLPeHU3zml2k4/yagNMG8Q/YtfMjCwEUZa2eXXMgZTVSL5q1n4H7sQ0X6CdJDqqeCFA==", + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.1.tgz", + "integrity": "sha512-BGO5LtrGC7vxnqucAe/rmvKdJllfGaYWdyABvyMoXQlfYMb2bbRuReWR5tEGE//4LcNJj9XrkovTqNYRFZHAMQ==", "dev": true, "hasInstallScript": true, + "license": "MIT", "bin": { "esbuild": "bin/esbuild" }, @@ -7021,37 +8515,39 @@ "node": ">=18" }, "optionalDependencies": { - "@esbuild/aix-ppc64": "0.23.0", - "@esbuild/android-arm": "0.23.0", - "@esbuild/android-arm64": "0.23.0", - "@esbuild/android-x64": "0.23.0", - "@esbuild/darwin-arm64": "0.23.0", - "@esbuild/darwin-x64": "0.23.0", - "@esbuild/freebsd-arm64": "0.23.0", - "@esbuild/freebsd-x64": "0.23.0", - "@esbuild/linux-arm": "0.23.0", - "@esbuild/linux-arm64": "0.23.0", - "@esbuild/linux-ia32": "0.23.0", - "@esbuild/linux-loong64": "0.23.0", - "@esbuild/linux-mips64el": "0.23.0", - "@esbuild/linux-ppc64": "0.23.0", - "@esbuild/linux-riscv64": "0.23.0", - "@esbuild/linux-s390x": "0.23.0", - "@esbuild/linux-x64": "0.23.0", - "@esbuild/netbsd-x64": "0.23.0", - "@esbuild/openbsd-arm64": "0.23.0", - "@esbuild/openbsd-x64": "0.23.0", - "@esbuild/sunos-x64": "0.23.0", - "@esbuild/win32-arm64": "0.23.0", - "@esbuild/win32-ia32": "0.23.0", - "@esbuild/win32-x64": "0.23.0" + "@esbuild/aix-ppc64": "0.25.1", + "@esbuild/android-arm": "0.25.1", + "@esbuild/android-arm64": "0.25.1", + "@esbuild/android-x64": "0.25.1", + "@esbuild/darwin-arm64": "0.25.1", + "@esbuild/darwin-x64": "0.25.1", + "@esbuild/freebsd-arm64": "0.25.1", + "@esbuild/freebsd-x64": "0.25.1", + "@esbuild/linux-arm": "0.25.1", + "@esbuild/linux-arm64": "0.25.1", + "@esbuild/linux-ia32": "0.25.1", + "@esbuild/linux-loong64": "0.25.1", + "@esbuild/linux-mips64el": "0.25.1", + "@esbuild/linux-ppc64": "0.25.1", + "@esbuild/linux-riscv64": "0.25.1", + "@esbuild/linux-s390x": "0.25.1", + "@esbuild/linux-x64": "0.25.1", + "@esbuild/netbsd-arm64": "0.25.1", + "@esbuild/netbsd-x64": "0.25.1", + "@esbuild/openbsd-arm64": "0.25.1", + "@esbuild/openbsd-x64": "0.25.1", + "@esbuild/sunos-x64": "0.25.1", + "@esbuild/win32-arm64": "0.25.1", + "@esbuild/win32-ia32": "0.25.1", + "@esbuild/win32-x64": "0.25.1" } }, "node_modules/esbuild-wasm": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/esbuild-wasm/-/esbuild-wasm-0.23.0.tgz", - "integrity": "sha512-6jP8UmWy6R6TUUV8bMuC3ZyZ6lZKI56x0tkxyCIqWwRRJ/DgeQKneh/Oid5EoGoPFLrGNkz47ZEtWAYuiY/u9g==", + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/esbuild-wasm/-/esbuild-wasm-0.25.1.tgz", + "integrity": "sha512-dZxPeDHcDIQ6ilml/NzYxnPbNkoVsHSFH3JGLSobttc5qYYgExMo8lh2XcB+w+AfiqykVDGK5PWanGB0gWaAWw==", "dev": true, + "license": "MIT", "bin": { "esbuild": "bin/esbuild" }, @@ -7064,6 +8560,7 @@ "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", "dev": true, + "license": "MIT", "engines": { "node": ">=6" } @@ -7072,13 +8569,15 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/escape-string-regexp": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", "dev": true, + "license": "MIT", "engines": { "node": ">=10" }, @@ -7092,6 +8591,7 @@ "integrity": "sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==", "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", "dev": true, + "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", @@ -7147,6 +8647,7 @@ "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-9.1.0.tgz", "integrity": "sha512-NSWl5BFQWEPi1j4TjVNItzYV7dZXZ+wP6I6ZhrBGpChQhZRUaElihE9uRRkcbRnNb76UMKDF3r+WTmNcGPKsqw==", "dev": true, + "license": "MIT", "bin": { "eslint-config-prettier": "bin/cli.js" }, @@ -7155,13 +8656,14 @@ } }, "node_modules/eslint-plugin-prettier": { - "version": "5.2.3", - "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.2.3.tgz", - "integrity": "sha512-qJ+y0FfCp/mQYQ/vWQ3s7eUlFEL4PyKfAJxsnYTJ4YT73nsJBWqmEpFryxV9OeUiqmsTsYJ5Y+KDNaeP31wrRw==", + "version": "5.2.6", + "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.2.6.tgz", + "integrity": "sha512-mUcf7QG2Tjk7H055Jk0lGBjbgDnfrvqjhXh9t2xLMSCjZVcw9Rb1V6sVNXO0th3jgeO7zllWPTNRil3JW94TnQ==", "dev": true, + "license": "MIT", "dependencies": { "prettier-linter-helpers": "^1.0.0", - "synckit": "^0.9.1" + "synckit": "^0.11.0" }, "engines": { "node": "^14.18.0 || >=16.0.0" @@ -7172,7 +8674,7 @@ "peerDependencies": { "@types/eslint": ">=8.0.0", "eslint": ">=8.0.0", - "eslint-config-prettier": "*", + "eslint-config-prettier": ">= 7.0.0 <10.0.0 || >=10.1.0", "prettier": ">=3.0.0" }, "peerDependenciesMeta": { @@ -7185,10 +8687,11 @@ } }, "node_modules/eslint-scope": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.2.0.tgz", - "integrity": "sha512-PHlWUfG6lvPc3yvP5A4PNyBL1W8fkDUccmI21JUu/+GKZBoH/W5u6usENXUrWFRsyoW5ACUjFGgAFQp5gUlb/A==", + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.3.0.tgz", + "integrity": "sha512-pUNxi75F8MJ/GdeKtVLSbYg4ZI34J6C0C7sbL4YOp2exGwen7ZsuBqKzUhXd0qMQ362yET3z+uPwKeg/0C2XCQ==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^5.2.0" @@ -7205,6 +8708,7 @@ "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz", "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==", "dev": true, + "license": "MIT", "dependencies": { "eslint-visitor-keys": "^1.1.0" }, @@ -7220,6 +8724,7 @@ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", "dev": true, + "license": "Apache-2.0", "engines": { "node": ">=4" } @@ -7229,6 +8734,7 @@ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", "dev": true, + "license": "Apache-2.0", "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, @@ -7241,6 +8747,7 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "dev": true, + "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -7257,6 +8764,7 @@ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "dev": true, + "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -7267,6 +8775,7 @@ "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^5.2.0" @@ -7283,6 +8792,7 @@ "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", "dev": true, + "license": "MIT", "dependencies": { "type-fest": "^0.20.2" }, @@ -7298,6 +8808,7 @@ "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", "dev": true, + "license": "MIT", "engines": { "node": ">= 4" } @@ -7306,13 +8817,15 @@ "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 + "dev": true, + "license": "MIT" }, "node_modules/eslint/node_modules/minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "dev": true, + "license": "ISC", "dependencies": { "brace-expansion": "^1.1.7" }, @@ -7325,6 +8838,7 @@ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", "dev": true, + "license": "(MIT OR CC0-1.0)", "engines": { "node": ">=10" }, @@ -7337,6 +8851,7 @@ "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { "acorn": "^8.9.0", "acorn-jsx": "^5.3.2", @@ -7354,6 +8869,7 @@ "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", "dev": true, + "license": "BSD-2-Clause", "bin": { "esparse": "bin/esparse.js", "esvalidate": "bin/esvalidate.js" @@ -7367,6 +8883,7 @@ "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", "dev": true, + "license": "BSD-3-Clause", "dependencies": { "estraverse": "^5.1.0" }, @@ -7379,6 +8896,7 @@ "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { "estraverse": "^5.2.0" }, @@ -7391,6 +8909,7 @@ "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", "dev": true, + "license": "BSD-2-Clause", "engines": { "node": ">=4.0" } @@ -7400,6 +8919,7 @@ "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", "dev": true, + "license": "BSD-2-Clause", "engines": { "node": ">=0.10.0" } @@ -7409,6 +8929,7 @@ "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", "dev": true, + "license": "MIT", "engines": { "node": ">= 0.6" } @@ -7417,72 +8938,32 @@ "version": "4.0.7", "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/events": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.8.x" } }, - "node_modules/execa": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", - "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", - "dev": true, - "dependencies": { - "cross-spawn": "^7.0.3", - "get-stream": "^6.0.0", - "human-signals": "^2.1.0", - "is-stream": "^2.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.1", - "onetime": "^5.1.2", - "signal-exit": "^3.0.3", - "strip-final-newline": "^2.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" - } - }, - "node_modules/execa/node_modules/onetime": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", - "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", - "dev": true, - "dependencies": { - "mimic-fn": "^2.1.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/execa/node_modules/signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "dev": true - }, "node_modules/exponential-backoff": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/exponential-backoff/-/exponential-backoff-3.1.1.tgz", - "integrity": "sha512-dX7e/LHVJ6W3DE1MHWi9S1EYzDESENfLrYohG2G++ovZrYOkm4Knwa0mc1cn84xJOR4KEU0WSchhLbd0UklbHw==", - "dev": true + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/exponential-backoff/-/exponential-backoff-3.1.2.tgz", + "integrity": "sha512-8QxYTVXUkuy7fIIoitQkPwGonB8F3Zj8eEO8Sqg9Zv/bkI7RJAzowee4gr81Hak/dUTpA2Z7VfQgoijjPNlUZA==", + "dev": true, + "license": "Apache-2.0" }, "node_modules/express": { "version": "4.21.2", "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", "dev": true, + "license": "MIT", "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", @@ -7529,6 +9010,7 @@ "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==", "dev": true, + "license": "MIT", "engines": { "node": ">= 0.6" } @@ -7538,6 +9020,7 @@ "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", "dev": true, + "license": "MIT", "dependencies": { "ms": "2.0.0" } @@ -7547,6 +9030,7 @@ "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", "dev": true, + "license": "MIT", "engines": { "node": ">= 0.8" } @@ -7556,6 +9040,7 @@ "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", "dev": true, + "license": "MIT", "dependencies": { "debug": "2.6.9", "encodeurl": "~2.0.0", @@ -7573,13 +9058,15 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/express/node_modules/statuses": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", "dev": true, + "license": "MIT", "engines": { "node": ">= 0.8" } @@ -7588,13 +9075,15 @@ "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/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, + "license": "MIT", "dependencies": { "chardet": "^0.7.0", "iconv-lite": "^0.4.24", @@ -7608,25 +9097,28 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/fast-diff": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.3.0.tgz", "integrity": "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==", - "dev": true + "dev": true, + "license": "Apache-2.0" }, "node_modules/fast-glob": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", - "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", "dev": true, + "license": "MIT", "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", "glob-parent": "^5.1.2", "merge2": "^1.3.0", - "micromatch": "^4.0.4" + "micromatch": "^4.0.8" }, "engines": { "node": ">=8.6.0" @@ -7637,6 +9129,7 @@ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", "dev": true, + "license": "ISC", "dependencies": { "is-glob": "^4.0.1" }, @@ -7648,13 +9141,15 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/fast-levenshtein": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/fast-uri": { "version": "3.0.6", @@ -7670,13 +9165,15 @@ "type": "opencollective", "url": "https://opencollective.com/fastify" } - ] + ], + "license": "BSD-3-Clause" }, "node_modules/fastq": { - "version": "1.19.0", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.0.tgz", - "integrity": "sha512-7SFSRCNjBQIZH/xZR3iy5iQYR8aGBE0h3VG6/cwlbrpdciNYBMotQav8c1XI3HjHH+NikUpP53nPdlZSdWmFzA==", + "version": "1.19.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz", + "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==", "dev": true, + "license": "ISC", "dependencies": { "reusify": "^1.0.4" } @@ -7686,6 +9183,7 @@ "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.4.tgz", "integrity": "sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g==", "dev": true, + "license": "Apache-2.0", "dependencies": { "websocket-driver": ">=0.5.1" }, @@ -7693,11 +9191,27 @@ "node": ">=0.8.0" } }, + "node_modules/fdir": { + "version": "6.4.4", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.4.tgz", + "integrity": "sha512-1NZP+GK4GfuAv3PqKvxQRDMjdSRZjnkq7KfhlNrCNNlZ0ygQFpebfrnfnq/W7fpUnAv9aGWmY1zKx7FYL3gwhg==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, "node_modules/file-entry-cache": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", "dev": true, + "license": "MIT", "dependencies": { "flat-cache": "^3.0.4" }, @@ -7710,6 +9224,7 @@ "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", "dev": true, + "license": "MIT", "dependencies": { "to-regex-range": "^5.0.1" }, @@ -7722,6 +9237,7 @@ "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", "dev": true, + "license": "MIT", "dependencies": { "debug": "2.6.9", "encodeurl": "~1.0.2", @@ -7740,6 +9256,7 @@ "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", "dev": true, + "license": "MIT", "dependencies": { "ms": "2.0.0" } @@ -7748,13 +9265,15 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/finalhandler/node_modules/on-finished": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", "integrity": "sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==", "dev": true, + "license": "MIT", "dependencies": { "ee-first": "1.1.1" }, @@ -7767,6 +9286,7 @@ "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-4.0.0.tgz", "integrity": "sha512-9ZonPT4ZAK4a+1pUPVPZJapbi7O5qbbJPdYw/NOQWZZbVLdDTYM3A4R9z/DpAM08IDaFGsvPgiGZ82WEwUDWjg==", "dev": true, + "license": "MIT", "dependencies": { "common-path-prefix": "^3.0.0", "pkg-dir": "^7.0.0" @@ -7783,6 +9303,7 @@ "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", "dev": true, + "license": "MIT", "dependencies": { "locate-path": "^6.0.0", "path-exists": "^4.0.0" @@ -7799,6 +9320,7 @@ "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", "dev": true, + "license": "BSD-3-Clause", "bin": { "flat": "cli.js" } @@ -7808,6 +9330,7 @@ "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", "dev": true, + "license": "MIT", "dependencies": { "flatted": "^3.2.9", "keyv": "^4.5.3", @@ -7818,10 +9341,11 @@ } }, "node_modules/flatted": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.2.tgz", - "integrity": "sha512-AiwGJM8YcNOaobumgtng+6NHuOqC3A7MixFeDafM3X9cIUM+xUXoS5Vfgf+OihAYe20fxqNM9yPBXJzRtZ/4eA==", - "dev": true + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", + "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", + "dev": true, + "license": "ISC" }, "node_modules/follow-redirects": { "version": "1.15.9", @@ -7834,6 +9358,7 @@ "url": "https://github.com/sponsors/RubenVerborgh" } ], + "license": "MIT", "engines": { "node": ">=4.0" }, @@ -7844,12 +9369,13 @@ } }, "node_modules/foreground-child": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.0.tgz", - "integrity": "sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==", + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", "dev": true, + "license": "ISC", "dependencies": { - "cross-spawn": "^7.0.0", + "cross-spawn": "^7.0.6", "signal-exit": "^4.0.1" }, "engines": { @@ -7864,6 +9390,7 @@ "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", "dev": true, + "license": "MIT", "engines": { "node": ">= 0.6" } @@ -7873,6 +9400,7 @@ "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz", "integrity": "sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==", "dev": true, + "license": "MIT", "engines": { "node": "*" }, @@ -7886,6 +9414,7 @@ "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", "dev": true, + "license": "MIT", "engines": { "node": ">= 0.6" } @@ -7895,6 +9424,7 @@ "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", "dev": true, + "license": "MIT", "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^4.0.0", @@ -7909,6 +9439,7 @@ "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-3.0.3.tgz", "integrity": "sha512-XUBA9XClHbnJWSfBzjkm6RvPsyg3sryZt06BEQoXcF7EK/xpGaQYJgQKDJSUH5SGZ76Y7pFx1QBnXz09rU5Fbw==", "dev": true, + "license": "ISC", "dependencies": { "minipass": "^7.0.3" }, @@ -7919,7 +9450,8 @@ "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "license": "ISC" }, "node_modules/fsevents": { "version": "2.3.3", @@ -7927,6 +9459,7 @@ "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", "dev": true, "hasInstallScript": true, + "license": "MIT", "optional": true, "os": [ "darwin" @@ -7940,6 +9473,7 @@ "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", "dev": true, + "license": "MIT", "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -7948,13 +9482,15 @@ "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==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/gensync": { "version": "1.0.0-beta.2", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", "dev": true, + "license": "MIT", "engines": { "node": ">=6.9.0" } @@ -7964,6 +9500,7 @@ "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", "dev": true, + "license": "ISC", "engines": { "node": "6.* || 8.* || >= 10.*" } @@ -7973,6 +9510,7 @@ "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.3.0.tgz", "integrity": "sha512-vpeMIQKxczTD/0s2CdEWHcb0eeJe6TFjxb+J5xgX7hScxqrGuyjmv4c1D4A/gelKfyox0gJJwIHF+fLjeaM8kQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=18" }, @@ -7981,17 +9519,18 @@ } }, "node_modules/get-intrinsic": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.7.tgz", - "integrity": "sha512-VW6Pxhsrk0KAOqs3WEd0klDiF/+V7gQOpAvY1jVU/LHmaD/kQO4523aiJuikX/QAKYiW6x8Jh+RJej1almdtCA==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", "dev": true, + "license": "MIT", "dependencies": { - "call-bind-apply-helpers": "^1.0.1", + "call-bind-apply-helpers": "^1.0.2", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", - "es-object-atoms": "^1.0.0", + "es-object-atoms": "^1.1.1", "function-bind": "^1.1.2", - "get-proto": "^1.0.0", + "get-proto": "^1.0.1", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", @@ -8009,6 +9548,7 @@ "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", "dev": true, + "license": "MIT", "dependencies": { "dunder-proto": "^1.0.1", "es-object-atoms": "^1.0.0" @@ -8017,23 +9557,12 @@ "node": ">= 0.4" } }, - "node_modules/get-stream": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", - "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/glob": { "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", "deprecated": "Glob versions prior to v9 are no longer supported", + "license": "ISC", "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -8054,6 +9583,7 @@ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", "dev": true, + "license": "ISC", "dependencies": { "is-glob": "^4.0.3" }, @@ -8065,12 +9595,14 @@ "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 + "dev": true, + "license": "BSD-2-Clause" }, "node_modules/glob/node_modules/brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -8080,6 +9612,7 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "license": "ISC", "dependencies": { "brace-expansion": "^1.1.7" }, @@ -8092,22 +9625,24 @@ "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", "dev": true, + "license": "MIT", "engines": { "node": ">=4" } }, "node_modules/globby": { - "version": "14.0.2", - "resolved": "https://registry.npmjs.org/globby/-/globby-14.0.2.tgz", - "integrity": "sha512-s3Fq41ZVh7vbbe2PN3nrW7yC7U7MFVc5c98/iTl9c2GawNMKx/J648KQRW6WKkuU8GIbbh2IXfIRQjOZnXcTnw==", + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-14.1.0.tgz", + "integrity": "sha512-0Ia46fDOaT7k4og1PDW4YbodWWr3scS2vAr2lTbsplOt2WkKp0vQbkI9wKis/T5LV/dqPjO3bpS/z6GTJB82LA==", "dev": true, + "license": "MIT", "dependencies": { "@sindresorhus/merge-streams": "^2.1.0", - "fast-glob": "^3.3.2", - "ignore": "^5.2.4", - "path-type": "^5.0.0", + "fast-glob": "^3.3.3", + "ignore": "^7.0.3", + "path-type": "^6.0.0", "slash": "^5.1.0", - "unicorn-magic": "^0.1.0" + "unicorn-magic": "^0.3.0" }, "engines": { "node": ">=18" @@ -8117,10 +9652,11 @@ } }, "node_modules/globby/node_modules/ignore": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", - "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.4.tgz", + "integrity": "sha512-gJzzk+PQNznz8ysRrC0aOkBNVRBDtE1n53IqyqEf3PXrYwomFs5q4pGMizBMJF+ykh03insJ27hB8gSrD2Hn8A==", "dev": true, + "license": "MIT", "engines": { "node": ">= 4" } @@ -8130,6 +9666,7 @@ "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", "dev": true, + "license": "MIT", "engines": { "node": ">= 0.4" }, @@ -8141,25 +9678,29 @@ "version": "4.2.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", - "dev": true + "dev": true, + "license": "ISC" }, "node_modules/graphemer": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/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 + "dev": true, + "license": "MIT" }, "node_modules/has-ansi": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", "integrity": "sha512-C8vBJ8DwUCx19vhm7urhTuUsr4/IyP6l4VzNQDv+ryHQObW3TTTp9yB68WpYgRe2bbaGuZ/se74IqFeVnMnLZg==", "dev": true, + "license": "MIT", "dependencies": { "ansi-regex": "^2.0.0" }, @@ -8172,6 +9713,7 @@ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -8181,6 +9723,7 @@ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } @@ -8190,6 +9733,7 @@ "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", "dev": true, + "license": "MIT", "engines": { "node": ">= 0.4" }, @@ -8202,6 +9746,7 @@ "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", "dev": true, + "license": "MIT", "dependencies": { "has-symbols": "^1.0.3" }, @@ -8217,6 +9762,7 @@ "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", "dev": true, + "license": "MIT", "dependencies": { "function-bind": "^1.1.2" }, @@ -8228,34 +9774,38 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/help-me/-/help-me-3.0.0.tgz", "integrity": "sha512-hx73jClhyk910sidBB7ERlnhMlFsJJIBqSVMFDwPN8o2v9nmp5KgLq1Xz1Bf1fCMMZ6mPrX159iG0VLy/fPMtQ==", + "license": "MIT", "dependencies": { "glob": "^7.1.6", "readable-stream": "^3.6.0" } }, "node_modules/hosted-git-info": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-7.0.2.tgz", - "integrity": "sha512-puUZAUKT5m8Zzvs72XWy3HtvVbTWljRE66cP60bxJzAqf2DgICo7lYTY2IHUmLnNpjYvw5bvmoHvPc0QO2a62w==", + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-8.1.0.tgz", + "integrity": "sha512-Rw/B2DNQaPBICNXEm8balFz9a6WpZrkCGpcWFpy7nCj+NyhSdqXipmfvtmWt9xGfp0wZnBxB+iVpLmQMYt47Tw==", "dev": true, + "license": "ISC", "dependencies": { "lru-cache": "^10.0.1" }, "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": "^18.17.0 || >=20.5.0" } }, "node_modules/hosted-git-info/node_modules/lru-cache": { "version": "10.4.3", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", - "dev": true + "dev": true, + "license": "ISC" }, "node_modules/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, + "license": "MIT", "dependencies": { "inherits": "^2.0.1", "obuf": "^1.0.0", @@ -8268,6 +9818,7 @@ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", "dev": true, + "license": "MIT", "dependencies": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", @@ -8282,43 +9833,30 @@ "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 + "dev": true, + "license": "MIT" }, "node_modules/hpack.js/node_modules/string_decoder": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, + "license": "MIT", "dependencies": { "safe-buffer": "~5.1.0" } }, - "node_modules/html-entities": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.5.2.tgz", - "integrity": "sha512-K//PSRMQk4FZ78Kyau+mZurHn3FH0Vwr+H36eE0rPbeYkRRi9YxceYPhuN60UwWorxyKHhqoAJl2OFKa4BVtaA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/mdevils" - }, - { - "type": "patreon", - "url": "https://patreon.com/mdevils" - } - ] - }, "node_modules/html-escaper": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/htmlparser2": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-8.0.2.tgz", - "integrity": "sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==", + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-10.0.0.tgz", + "integrity": "sha512-TwAZM+zE5Tq3lrEHvOlvwgj1XLWQCtaaibSN11Q+gGBAS7Y1uZSWwXXRe4iF6OXnaq1riyQAPFOBtYc77Mxq0g==", "dev": true, "funding": [ "https://github.com/fb55/htmlparser2?sponsor=1", @@ -8327,30 +9865,47 @@ "url": "https://github.com/sponsors/fb55" } ], + "license": "MIT", "dependencies": { "domelementtype": "^2.3.0", "domhandler": "^5.0.3", - "domutils": "^3.0.1", - "entities": "^4.4.0" + "domutils": "^3.2.1", + "entities": "^6.0.0" + } + }, + "node_modules/htmlparser2/node_modules/entities": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.0.tgz", + "integrity": "sha512-aKstq2TDOndCn4diEyp9Uq/Flu2i1GlLkc6XIDQSDMuaFE3OPW5OphLCyQ5SpSJZTb4reN+kTcYru5yIfXoRPw==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" } }, "node_modules/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 + "dev": true, + "license": "BSD-2-Clause" }, "node_modules/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 + "dev": true, + "license": "MIT" }, "node_modules/http-errors": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", "dev": true, + "license": "MIT", "dependencies": { "depd": "2.0.0", "inherits": "2.0.4", @@ -8367,21 +9922,24 @@ "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", "dev": true, + "license": "MIT", "engines": { "node": ">= 0.8" } }, "node_modules/http-parser-js": { - "version": "0.5.9", - "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.9.tgz", - "integrity": "sha512-n1XsPy3rXVxlqxVioEWdC+0+M+SQw0DpJynwtOPo1X+ZlvdzTLtDBIJJlDQTnwZIFJrZSzSGmIOUdP8tu+SgLw==", - "dev": true + "version": "0.5.10", + "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.10.tgz", + "integrity": "sha512-Pysuw9XpUq5dVc/2SMHpuTY01RFl8fttgcyunjL7eEMhGM3cI4eOmiCycJDVCo/7O7ClfQD3SaI6ftDzqOXYMA==", + "dev": true, + "license": "MIT" }, "node_modules/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, + "license": "MIT", "dependencies": { "eventemitter3": "^4.0.0", "follow-redirects": "^1.0.0", @@ -8396,6 +9954,7 @@ "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", "dev": true, + "license": "MIT", "dependencies": { "agent-base": "^7.1.0", "debug": "^4.3.4" @@ -8405,10 +9964,11 @@ } }, "node_modules/http-proxy-middleware": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-3.0.3.tgz", - "integrity": "sha512-usY0HG5nyDUwtqpiZdETNbmKtw3QQ1jwYFZ9wi5iHzX2BcILwQKtYDJPo7XHTsu5Z0B2Hj3W9NNnbd+AjFWjqg==", + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-3.0.5.tgz", + "integrity": "sha512-GLZZm1X38BPY4lkXA01jhwxvDoOkkXqjgVyUzVxiEK4iuRu03PZoYHhHRwxnfhQMDuaxi3vVri0YgSro/1oWqg==", "dev": true, + "license": "MIT", "dependencies": { "@types/http-proxy": "^1.17.15", "debug": "^4.3.6", @@ -8422,32 +9982,25 @@ } }, "node_modules/https-proxy-agent": { - "version": "7.0.5", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.5.tgz", - "integrity": "sha512-1e4Wqeblerz+tMKPIq2EMGiiWW1dIjZOksyHWSUm1rmuvw/how9hBHZ38lAGj5ID4Ik6EdkOw7NmWPy6LAwalw==", + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", "dev": true, + "license": "MIT", "dependencies": { - "agent-base": "^7.0.2", + "agent-base": "^7.1.2", "debug": "4" }, "engines": { "node": ">= 14" } }, - "node_modules/human-signals": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", - "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", - "dev": true, - "engines": { - "node": ">=10.17.0" - } - }, "node_modules/hyperdyperid": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/hyperdyperid/-/hyperdyperid-1.2.0.tgz", "integrity": "sha512-Y93lCzHYgGWdrJ66yIktxiaGULYc6oGiABxhcO5AufBeOyoIdZF7bIfLaOrbM0iGIOXQQgxxRrFEnb+Y6w1n4A==", "dev": true, + "license": "MIT", "engines": { "node": ">=10.18" } @@ -8457,6 +10010,7 @@ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", "dev": true, + "license": "MIT", "dependencies": { "safer-buffer": ">= 2.1.2 < 3" }, @@ -8469,6 +10023,7 @@ "resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-5.1.0.tgz", "integrity": "sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==", "dev": true, + "license": "ISC", "engines": { "node": "^10 || ^12 || >= 14" }, @@ -8493,27 +10048,30 @@ "type": "consulting", "url": "https://feross.org/support" } - ] + ], + "license": "BSD-3-Clause" }, "node_modules/ignore": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/ignore/-/ignore-6.0.2.tgz", "integrity": "sha512-InwqeHHN2XpumIkMvpl/DCJVrAHgCsG5+cn1XlnLWGwtZBm8QJfSusItfrwx81CTp5agNZqpKU2J/ccC5nGT4A==", "dev": true, + "license": "MIT", "engines": { "node": ">= 4" } }, "node_modules/ignore-walk": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-6.0.5.tgz", - "integrity": "sha512-VuuG0wCnjhnylG1ABXT3dAuIpTNDs/G8jlpmwXY03fXoXy/8ZK8/T+hMzt8L4WnrLCJgdybqgPagnF/f97cg3A==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-7.0.0.tgz", + "integrity": "sha512-T4gbf83A4NH95zvhVYZc+qWocBBGlpzUXLPGurJggw/WIOwicfXJChLDP/iBZnN5WqROSu5Bm3hhle4z8a8YGQ==", "dev": true, + "license": "ISC", "dependencies": { "minimatch": "^9.0.0" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^18.17.0 || >=20.5.0" } }, "node_modules/image-size": { @@ -8521,6 +10079,7 @@ "resolved": "https://registry.npmjs.org/image-size/-/image-size-0.5.5.tgz", "integrity": "sha512-6TDAlDPZxUFCv+fuOkIoXT/V/f3Qbq8e37p+YOiYrUv3v9cc3/6x78VdfPgFVaB9dZYeLUfKgHRebpkm/oP2VQ==", "dev": true, + "license": "MIT", "optional": true, "bin": { "image-size": "bin/image-size.js" @@ -8530,16 +10089,18 @@ } }, "node_modules/immutable": { - "version": "4.3.7", - "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.7.tgz", - "integrity": "sha512-1hqclzwYwjRDFLjcFxOM5AYkkG0rpFPpr1RLPMEuGczoS7YA8gLhy8SWXYRAA/XwfEHpfo3cw5JGioS32fnMRw==", - "dev": true + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/immutable/-/immutable-5.1.1.tgz", + "integrity": "sha512-3jatXi9ObIsPGr3N5hGw/vWWcTkq6hUYhpQz4k0wLC+owqWi/LiugIw9x0EdNZ2yGedKN/HzePiBvaJRXa0Ujg==", + "dev": true, + "license": "MIT" }, "node_modules/import-fresh": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", - "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", "dev": true, + "license": "MIT", "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" @@ -8556,6 +10117,7 @@ "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.8.19" } @@ -8565,6 +10127,7 @@ "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } @@ -8574,6 +10137,7 @@ "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "license": "ISC", "dependencies": { "once": "^1.3.0", "wrappy": "1" @@ -8582,15 +10146,17 @@ "node_modules/inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" }, "node_modules/ini": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/ini/-/ini-4.1.3.tgz", - "integrity": "sha512-X7rqawQBvfdjS10YU1y1YVreA3SsLrW9dX2CewP2EbBJM4ypVNLDkO5y04gejPwKIY9lR+7r9gn3rFPt/kmWFg==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ini/-/ini-5.0.0.tgz", + "integrity": "sha512-+N0ngpO3e7cRUWOJAS7qw0IZIVc6XPrW4MlFBdD066F2L4k1L6ker3hLqSq7iXxU5tgS4WGkIUElWn5vogAEnw==", "dev": true, + "license": "ISC", "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^18.17.0 || >=20.5.0" } }, "node_modules/ip-address": { @@ -8598,6 +10164,7 @@ "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-9.0.5.tgz", "integrity": "sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g==", "dev": true, + "license": "MIT", "dependencies": { "jsbn": "1.1.0", "sprintf-js": "^1.1.3" @@ -8611,6 +10178,7 @@ "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.2.0.tgz", "integrity": "sha512-Ag3wB2o37wslZS19hZqorUnrnzSkpOVy+IiiDEiTqNubEYpYuHWIf6K4psgN2ZWKExS4xhVCrRVfb/wfW8fWJA==", "dev": true, + "license": "MIT", "engines": { "node": ">= 10" } @@ -8619,13 +10187,15 @@ "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/is-binary-path": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", "dev": true, + "license": "MIT", "dependencies": { "binary-extensions": "^2.0.0" }, @@ -8638,6 +10208,7 @@ "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", "dev": true, + "license": "MIT", "dependencies": { "hasown": "^2.0.2" }, @@ -8653,6 +10224,7 @@ "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz", "integrity": "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==", "dev": true, + "license": "MIT", "bin": { "is-docker": "cli.js" }, @@ -8668,6 +10240,7 @@ "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -8677,6 +10250,7 @@ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-4.0.0.tgz", "integrity": "sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=12" }, @@ -8689,6 +10263,7 @@ "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", "dev": true, + "license": "MIT", "dependencies": { "is-extglob": "^2.1.1" }, @@ -8701,6 +10276,7 @@ "resolved": "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz", "integrity": "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==", "dev": true, + "license": "MIT", "dependencies": { "is-docker": "^3.0.0" }, @@ -8719,21 +10295,17 @@ "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz", "integrity": "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } }, - "node_modules/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 - }, "node_modules/is-network-error": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/is-network-error/-/is-network-error-1.1.0.tgz", "integrity": "sha512-tUdRRAnhT+OtCZR/LxZelH/C7QtjtFrTu5tXCA8pl55eTUElUHT+GPYV8MBMBvea/j+NxQqVt3LbWMRir7Gx9g==", "dev": true, + "license": "MIT", "engines": { "node": ">=16" }, @@ -8746,6 +10318,7 @@ "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.12.0" } @@ -8755,6 +10328,7 @@ "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } @@ -8764,6 +10338,7 @@ "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-3.0.0.tgz", "integrity": "sha512-gwsOE28k+23GP1B6vFl1oVh/WOzmawBrKwo5Ev6wMKzPkaXaCDIQKzLnvsA42DRlbVTWorkgTKIviAKCWkfUwA==", "dev": true, + "license": "MIT", "engines": { "node": ">=10" }, @@ -8776,6 +10351,7 @@ "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz", "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -8785,6 +10361,7 @@ "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==", "dev": true, + "license": "MIT", "dependencies": { "call-bound": "^1.0.2", "gopd": "^1.2.0", @@ -8798,23 +10375,12 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", - "dev": true, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/is-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, + "license": "MIT", "engines": { "node": ">=10" }, @@ -8826,13 +10392,15 @@ "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 + "dev": true, + "license": "MIT" }, "node_modules/is-wsl": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.0.tgz", "integrity": "sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw==", "dev": true, + "license": "MIT", "dependencies": { "is-inside-container": "^1.0.0" }, @@ -8847,13 +10415,15 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/isbinaryfile": { "version": "4.0.10", "resolved": "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-4.0.10.tgz", "integrity": "sha512-iHrqe5shvBUcFbmZq9zOQHBoeOhZJu6RQGrDpBgenUm/Am+F3JM2MgQj+rK3Z601fzrL5gLZWtAPH2OBaSVcyw==", "dev": true, + "license": "MIT", "engines": { "node": ">= 8.0.0" }, @@ -8865,13 +10435,15 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true + "dev": true, + "license": "ISC" }, "node_modules/isobject": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -8881,6 +10453,7 @@ "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", "dev": true, + "license": "BSD-3-Clause", "engines": { "node": ">=8" } @@ -8890,6 +10463,7 @@ "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.3.tgz", "integrity": "sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q==", "dev": true, + "license": "BSD-3-Clause", "dependencies": { "@babel/core": "^7.23.9", "@babel/parser": "^7.23.9", @@ -8906,6 +10480,7 @@ "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", "dev": true, + "license": "BSD-3-Clause", "dependencies": { "istanbul-lib-coverage": "^3.0.0", "make-dir": "^4.0.0", @@ -8920,6 +10495,7 @@ "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", "dev": true, + "license": "BSD-3-Clause", "dependencies": { "debug": "^4.1.1", "istanbul-lib-coverage": "^3.0.0", @@ -8934,6 +10510,7 @@ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "dev": true, + "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" } @@ -8943,6 +10520,7 @@ "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.7.tgz", "integrity": "sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==", "dev": true, + "license": "BSD-3-Clause", "dependencies": { "html-escaper": "^2.0.0", "istanbul-lib-report": "^3.0.0" @@ -8956,6 +10534,7 @@ "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", "dev": true, + "license": "BlueOak-1.0.0", "dependencies": { "@isaacs/cliui": "^8.0.2" }, @@ -8970,13 +10549,15 @@ "version": "4.6.1", "resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-4.6.1.tgz", "integrity": "sha512-VYz/BjjmC3klLJlLwA4Kw8ytk0zDSmbbDLNs794VnWmkcCB7I9aAL/D48VNQtmITyPvea2C3jdUMfc3kAoy0PQ==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/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, + "license": "MIT", "dependencies": { "@types/node": "*", "merge-stream": "^2.0.0", @@ -8991,6 +10572,7 @@ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", "dev": true, + "license": "MIT", "dependencies": { "has-flag": "^4.0.0" }, @@ -9006,6 +10588,7 @@ "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.7.tgz", "integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==", "dev": true, + "license": "MIT", "bin": { "jiti": "bin/jiti.js" } @@ -9014,6 +10597,7 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.3.0.tgz", "integrity": "sha512-mifzlm2+5nZ+lEcLJMoBK0/IH/bDg8XnJfd/Wq6IP+xoCjLZsTOnV2QpxlVbX9bMnkl5PdEjNtBJ9Cj1NjifhQ==", + "license": "MIT", "funding": { "type": "opencollective", "url": "https://opencollective.com/js-sdsl" @@ -9023,13 +10607,15 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/js-yaml": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", "dev": true, + "license": "MIT", "dependencies": { "argparse": "^2.0.1" }, @@ -9041,52 +10627,59 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-1.1.0.tgz", "integrity": "sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/jsesc": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", - "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", "dev": true, + "license": "MIT", "bin": { "jsesc": "bin/jsesc" }, "engines": { - "node": ">=4" + "node": ">=6" } }, "node_modules/json-buffer": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/json-parse-even-better-errors": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-3.0.2.tgz", - "integrity": "sha512-fi0NG4bPjCHunUJffmLd0gxssIgkNmArMvis4iNah6Owg1MCJjWhEcDLmsK6iGkJq3tHwbDkTlce70/tmXN4cQ==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-4.0.0.tgz", + "integrity": "sha512-lR4MXjGNgkJc7tkQ97kb2nuEMnNCyU//XYVH0MKTGcXEiSudQ5MKGKen3C5QubYy0vmq+JGitUg92uuywGEwIA==", "dev": true, + "license": "MIT", "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^18.17.0 || >=20.5.0" } }, "node_modules/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 + "dev": true, + "license": "MIT" }, "node_modules/json-stable-stringify-without-jsonify": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/json5": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", "dev": true, + "license": "MIT", "bin": { "json5": "lib/cli.js" }, @@ -9098,13 +10691,15 @@ "version": "3.3.1", "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.3.1.tgz", "integrity": "sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/jsonfile": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", "dev": true, + "license": "MIT", "optionalDependencies": { "graceful-fs": "^4.1.6" } @@ -9116,13 +10711,15 @@ "dev": true, "engines": [ "node >= 0.2.0" - ] + ], + "license": "MIT" }, "node_modules/karma": { "version": "6.4.4", "resolved": "https://registry.npmjs.org/karma/-/karma-6.4.4.tgz", "integrity": "sha512-LrtUxbdvt1gOpo3gxG+VAJlJAEMhbWlM4YrFQgql98FwF7+K8K12LYO4hnDdUkNjeztYrOXEMqgTajSWgmtI/w==", "dev": true, + "license": "MIT", "dependencies": { "@colors/colors": "1.5.0", "body-parser": "^1.19.0", @@ -9161,6 +10758,7 @@ "resolved": "https://registry.npmjs.org/karma-chrome-launcher/-/karma-chrome-launcher-3.2.0.tgz", "integrity": "sha512-rE9RkUPI7I9mAxByQWkGJFXfFD6lE4gC5nPuZdobf/QdTEJI6EU4yIay/cfU/xV4ZxlM5JiTv7zWYgA64NpS5Q==", "dev": true, + "license": "MIT", "dependencies": { "which": "^1.2.1" } @@ -9170,6 +10768,7 @@ "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", "dev": true, + "license": "ISC", "dependencies": { "isexe": "^2.0.0" }, @@ -9182,6 +10781,7 @@ "resolved": "https://registry.npmjs.org/karma-coverage/-/karma-coverage-2.2.1.tgz", "integrity": "sha512-yj7hbequkQP2qOSb20GuNSIyE//PgJWHwC2IydLE6XRtsnaflv+/OSGNssPjobYUlhVVagy99TQpqUt3vAUG7A==", "dev": true, + "license": "MIT", "dependencies": { "istanbul-lib-coverage": "^3.2.0", "istanbul-lib-instrument": "^5.1.0", @@ -9199,6 +10799,7 @@ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "dev": true, + "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -9209,6 +10810,7 @@ "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz", "integrity": "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==", "dev": true, + "license": "BSD-3-Clause", "dependencies": { "@babel/core": "^7.12.3", "@babel/parser": "^7.14.7", @@ -9225,6 +10827,7 @@ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "dev": true, + "license": "ISC", "dependencies": { "brace-expansion": "^1.1.7" }, @@ -9237,6 +10840,7 @@ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true, + "license": "ISC", "bin": { "semver": "bin/semver.js" } @@ -9246,6 +10850,7 @@ "resolved": "https://registry.npmjs.org/karma-jasmine/-/karma-jasmine-5.1.0.tgz", "integrity": "sha512-i/zQLFrfEpRyQoJF9fsCdTMOF5c2dK7C7OmsuKg2D0YSsuZSfQDiLuaiktbuio6F2wiCsZSnSnieIQ0ant/uzQ==", "dev": true, + "license": "MIT", "dependencies": { "jasmine-core": "^4.1.0" }, @@ -9261,6 +10866,7 @@ "resolved": "https://registry.npmjs.org/karma-jasmine-html-reporter/-/karma-jasmine-html-reporter-2.1.0.tgz", "integrity": "sha512-sPQE1+nlsn6Hwb5t+HHwyy0A1FNCVKuL1192b+XNauMYWThz2kweiBVW1DqloRpVvZIJkIoHVB7XRpK78n1xbQ==", "dev": true, + "license": "MIT", "peerDependencies": { "jasmine-core": "^4.0.0 || ^5.0.0", "karma": "^6.0.0", @@ -9272,6 +10878,7 @@ "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, + "license": "MIT", "dependencies": { "source-map-support": "^0.5.5" } @@ -9281,6 +10888,7 @@ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "dev": true, + "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -9291,6 +10899,7 @@ "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", "dev": true, + "license": "MIT", "dependencies": { "anymatch": "~3.1.2", "braces": "~3.0.2", @@ -9315,6 +10924,7 @@ "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", "dev": true, + "license": "ISC", "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.0", @@ -9325,40 +10935,28 @@ "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 + "dev": true, + "license": "MIT" }, "node_modules/karma/node_modules/glob-parent": { "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/karma/node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, "engines": { - "node": ">=8" + "node": ">= 6" } }, - "node_modules/karma/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true - }, "node_modules/karma/node_modules/is-fullwidth-code-point": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } @@ -9368,6 +10966,7 @@ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "dev": true, + "license": "ISC", "dependencies": { "brace-expansion": "^1.1.7" }, @@ -9380,6 +10979,7 @@ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", "dev": true, + "license": "MIT", "engines": { "node": ">=8.6" }, @@ -9392,6 +10992,7 @@ "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", "dev": true, + "license": "MIT", "dependencies": { "picomatch": "^2.2.1" }, @@ -9404,6 +11005,7 @@ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "dev": true, + "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" } @@ -9413,6 +11015,7 @@ "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "dev": true, + "license": "MIT", "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", @@ -9427,6 +11030,7 @@ "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.3.tgz", "integrity": "sha512-nZD7m9iCPC5g0pYmcaxogYKggSfLsdxl8of3Q/oIbqCqLLIO9IAF0GWjX1z9NZRHPiXv8Wex4yDCaZsgEw0Y8w==", "dev": true, + "license": "MIT", "engines": { "node": ">=14.14" } @@ -9436,6 +11040,7 @@ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", "dev": true, + "license": "MIT", "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", @@ -9453,6 +11058,7 @@ "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", "dev": true, + "license": "MIT", "dependencies": { "cliui": "^7.0.2", "escalade": "^3.1.1", @@ -9471,6 +11077,7 @@ "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", "dev": true, + "license": "ISC", "engines": { "node": ">=10" } @@ -9480,6 +11087,7 @@ "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", "dev": true, + "license": "MIT", "dependencies": { "json-buffer": "3.0.1" } @@ -9489,25 +11097,28 @@ "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.10.0" } }, "node_modules/launch-editor": { - "version": "2.9.1", - "resolved": "https://registry.npmjs.org/launch-editor/-/launch-editor-2.9.1.tgz", - "integrity": "sha512-Gcnl4Bd+hRO9P9icCP/RVVT2o8SFlPXofuCxvA2SaZuH45whSvf5p8x5oih5ftLiVhEI4sp5xDY+R+b3zJBh5w==", + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/launch-editor/-/launch-editor-2.10.0.tgz", + "integrity": "sha512-D7dBRJo/qcGX9xlvt/6wUYzQxjh5G1RvZPgPv8vi4KRU99DVQL/oW7tnVOCCTm2HGeo3C5HvGE5Yrh6UBoZ0vA==", "dev": true, + "license": "MIT", "dependencies": { "picocolors": "^1.0.0", "shell-quote": "^1.8.1" } }, "node_modules/less": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/less/-/less-4.2.0.tgz", - "integrity": "sha512-P3b3HJDBtSzsXUl0im2L7gTO5Ubg8mEN6G8qoTS77iXxXX4Hvu4Qj540PZDvQ8V6DmX6iXo98k7Md0Cm1PrLaA==", + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/less/-/less-4.2.2.tgz", + "integrity": "sha512-tkuLHQlvWUTeQ3doAqnHbNn8T6WX1KA8yvbKG9x4VtKtIjHsVKQZCH11zRgAfbDAXC2UNIg/K9BYAAcEzUIrNg==", "dev": true, + "license": "Apache-2.0", "dependencies": { "copy-anything": "^2.0.1", "parse-node-version": "^1.0.1", @@ -9534,6 +11145,7 @@ "resolved": "https://registry.npmjs.org/less-loader/-/less-loader-12.2.0.tgz", "integrity": "sha512-MYUxjSQSBUQmowc0l5nPieOYwMzGPUaTzB6inNW/bdPEG9zOL3eAAD1Qw5ZxSPk7we5dMojHwNODYMV1hq4EVg==", "dev": true, + "license": "MIT", "engines": { "node": ">= 18.12.0" }, @@ -9560,6 +11172,7 @@ "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", "dev": true, + "license": "MIT", "optional": true, "dependencies": { "pify": "^4.0.1", @@ -9574,6 +11187,7 @@ "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", "dev": true, + "license": "MIT", "optional": true, "bin": { "mime": "cli.js" @@ -9587,6 +11201,7 @@ "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", "dev": true, + "license": "ISC", "optional": true, "bin": { "semver": "bin/semver" @@ -9597,6 +11212,7 @@ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "dev": true, + "license": "BSD-3-Clause", "optional": true, "engines": { "node": ">=0.10.0" @@ -9606,6 +11222,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/leven/-/leven-2.1.0.tgz", "integrity": "sha512-nvVPLpIHUxCUoRLrFqTgSxXJ614d8AgQoWl7zPe/2VadE8+1dpU3LBhowRuBAcuwruWtOdD8oYC9jDNJjXDPyA==", + "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -9615,6 +11232,7 @@ "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", "dev": true, + "license": "MIT", "dependencies": { "prelude-ls": "^1.2.1", "type-check": "~0.4.0" @@ -9628,6 +11246,7 @@ "resolved": "https://registry.npmjs.org/license-webpack-plugin/-/license-webpack-plugin-4.0.2.tgz", "integrity": "sha512-771TFWFD70G1wLTC4oU2Cw4qvtmNrIw+wRvBtn+okgHl7slJVi7zfNcdmqDL72BojM30VNJ2UHylr1o77U37Jw==", "dev": true, + "license": "ISC", "dependencies": { "webpack-sources": "^3.0.0" }, @@ -9644,13 +11263,15 @@ "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 + "dev": true, + "license": "MIT" }, "node_modules/listr2": { - "version": "8.2.4", - "resolved": "https://registry.npmjs.org/listr2/-/listr2-8.2.4.tgz", - "integrity": "sha512-opevsywziHd3zHCVQGAj8zu+Z3yHNkkoYhWIGnq54RrCVwLz0MozotJEDnKsIBLvkfLGN6BLOyAeRrYI0pKA4g==", + "version": "8.2.5", + "resolved": "https://registry.npmjs.org/listr2/-/listr2-8.2.5.tgz", + "integrity": "sha512-iyAZCeyD+c1gPyE9qpFu8af0Y+MRtmKOncdGoA2S5EY8iFq99dmmvkNnHiWo+pj0s7yH7l3KPIgee77tKpXPWQ==", "dev": true, + "license": "MIT", "dependencies": { "cli-truncate": "^4.0.0", "colorette": "^2.0.20", @@ -9668,6 +11289,7 @@ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", "dev": true, + "license": "MIT", "engines": { "node": ">=12" }, @@ -9680,6 +11302,7 @@ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", "dev": true, + "license": "MIT", "engines": { "node": ">=12" }, @@ -9691,13 +11314,15 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/listr2/node_modules/strip-ansi": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", "dev": true, + "license": "MIT", "dependencies": { "ansi-regex": "^6.0.1" }, @@ -9713,6 +11338,7 @@ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.0.tgz", "integrity": "sha512-G8ura3S+3Z2G+mkgNRq8dqaFZAuxfsxpBB8OCTGRTCtp+l/v9nbFNmCUP1BZMts3G1142MsZfn6eeUKrr4PD1Q==", "dev": true, + "license": "MIT", "dependencies": { "ansi-styles": "^6.2.1", "string-width": "^7.0.0", @@ -9726,28 +11352,30 @@ } }, "node_modules/lmdb": { - "version": "3.0.13", - "resolved": "https://registry.npmjs.org/lmdb/-/lmdb-3.0.13.tgz", - "integrity": "sha512-UGe+BbaSUQtAMZobTb4nHvFMrmvuAQKSeaqAX2meTEQjfsbpl5sxdHD8T72OnwD4GU9uwNhYXIVe4QGs8N9Zyw==", + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/lmdb/-/lmdb-3.2.6.tgz", + "integrity": "sha512-SuHqzPl7mYStna8WRotY8XX/EUZBjjv3QyKIByeCLFfC9uXT/OIHByEcA07PzbMfQAM0KYJtLgtpMRlIe5dErQ==", "dev": true, "hasInstallScript": true, + "license": "MIT", + "optional": true, "dependencies": { - "msgpackr": "^1.10.2", + "msgpackr": "^1.11.2", "node-addon-api": "^6.1.0", "node-gyp-build-optional-packages": "5.2.2", - "ordered-binary": "^1.4.1", + "ordered-binary": "^1.5.3", "weak-lru-cache": "^1.2.2" }, "bin": { "download-lmdb-prebuilds": "bin/download-prebuilds.js" }, "optionalDependencies": { - "@lmdb/lmdb-darwin-arm64": "3.0.13", - "@lmdb/lmdb-darwin-x64": "3.0.13", - "@lmdb/lmdb-linux-arm": "3.0.13", - "@lmdb/lmdb-linux-arm64": "3.0.13", - "@lmdb/lmdb-linux-x64": "3.0.13", - "@lmdb/lmdb-win32-x64": "3.0.13" + "@lmdb/lmdb-darwin-arm64": "3.2.6", + "@lmdb/lmdb-darwin-x64": "3.2.6", + "@lmdb/lmdb-linux-arm": "3.2.6", + "@lmdb/lmdb-linux-arm64": "3.2.6", + "@lmdb/lmdb-linux-x64": "3.2.6", + "@lmdb/lmdb-win32-x64": "3.2.6" } }, "node_modules/loader-runner": { @@ -9755,6 +11383,7 @@ "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz", "integrity": "sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==", "dev": true, + "license": "MIT", "engines": { "node": ">=6.11.5" } @@ -9764,6 +11393,7 @@ "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-3.3.1.tgz", "integrity": "sha512-FMJTLMXfCLMLfJxcX9PFqX5qD88Z5MRGaZCVzfuqeZSPsyiBzs+pahDQjbIWz2QIzPZz0NX9Zy4FX3lmK6YHIg==", "dev": true, + "license": "MIT", "engines": { "node": ">= 12.13.0" } @@ -9773,6 +11403,7 @@ "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", "dev": true, + "license": "MIT", "dependencies": { "p-locate": "^5.0.0" }, @@ -9787,31 +11418,36 @@ "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/lodash.debounce": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/lodash.truncate": { "version": "4.4.2", "resolved": "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz", "integrity": "sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/log-symbols": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", "dev": true, + "license": "MIT", "dependencies": { "chalk": "^4.1.0", "is-unicode-supported": "^0.1.0" @@ -9828,6 +11464,7 @@ "resolved": "https://registry.npmjs.org/log-update/-/log-update-6.1.0.tgz", "integrity": "sha512-9ie8ItPR6tjY5uYJh8K/Zrv/RMZ5VOlOWvtZdEHYSTFKZfIBPQa9tOAEeAWhd+AnIneLJ22w5fjOYtoutpWq5w==", "dev": true, + "license": "MIT", "dependencies": { "ansi-escapes": "^7.0.0", "cli-cursor": "^5.0.0", @@ -9847,6 +11484,7 @@ "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-7.0.0.tgz", "integrity": "sha512-GdYO7a61mR0fOlAsvC9/rIHf7L96sBc6dEWzeOu+KAea5bZyQRPIpojrVoI4AXGJS/ycu/fBTdLrUkA4ODrvjw==", "dev": true, + "license": "MIT", "dependencies": { "environment": "^1.0.0" }, @@ -9862,6 +11500,7 @@ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", "dev": true, + "license": "MIT", "engines": { "node": ">=12" }, @@ -9874,6 +11513,7 @@ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", "dev": true, + "license": "MIT", "engines": { "node": ">=12" }, @@ -9886,6 +11526,7 @@ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-5.0.0.tgz", "integrity": "sha512-OVa3u9kkBbw7b8Xw5F9P+D/T9X+Z4+JruYVNapTjPYZYUznQ5YfWeFkOj606XYYW8yugTfC8Pj0hYqvi4ryAhA==", "dev": true, + "license": "MIT", "dependencies": { "get-east-asian-width": "^1.0.0" }, @@ -9901,6 +11542,7 @@ "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-7.1.0.tgz", "integrity": "sha512-bSiSngZ/jWeX93BqeIAbImyTbEihizcwNjFoRUIY/T1wWQsfsm2Vw1agPKylXvQTU7iASGdHhyqRlqQzfz+Htg==", "dev": true, + "license": "MIT", "dependencies": { "ansi-styles": "^6.2.1", "is-fullwidth-code-point": "^5.0.0" @@ -9917,6 +11559,7 @@ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", "dev": true, + "license": "MIT", "dependencies": { "ansi-regex": "^6.0.1" }, @@ -9932,127 +11575,7 @@ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.0.tgz", "integrity": "sha512-G8ura3S+3Z2G+mkgNRq8dqaFZAuxfsxpBB8OCTGRTCtp+l/v9nbFNmCUP1BZMts3G1142MsZfn6eeUKrr4PD1Q==", "dev": true, - "dependencies": { - "ansi-styles": "^6.2.1", - "string-width": "^7.0.0", - "strip-ansi": "^7.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/log-update": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/log-update/-/log-update-6.1.0.tgz", - "integrity": "sha512-9ie8ItPR6tjY5uYJh8K/Zrv/RMZ5VOlOWvtZdEHYSTFKZfIBPQa9tOAEeAWhd+AnIneLJ22w5fjOYtoutpWq5w==", - "dev": true, - "dependencies": { - "ansi-escapes": "^7.0.0", - "cli-cursor": "^5.0.0", - "slice-ansi": "^7.1.0", - "strip-ansi": "^7.1.0", - "wrap-ansi": "^9.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/log-update/node_modules/ansi-escapes": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-7.0.0.tgz", - "integrity": "sha512-GdYO7a61mR0fOlAsvC9/rIHf7L96sBc6dEWzeOu+KAea5bZyQRPIpojrVoI4AXGJS/ycu/fBTdLrUkA4ODrvjw==", - "dev": true, - "dependencies": { - "environment": "^1.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/log-update/node_modules/ansi-regex": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", - "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", - "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, - "node_modules/log-update/node_modules/ansi-styles": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", - "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", - "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/log-update/node_modules/is-fullwidth-code-point": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-5.0.0.tgz", - "integrity": "sha512-OVa3u9kkBbw7b8Xw5F9P+D/T9X+Z4+JruYVNapTjPYZYUznQ5YfWeFkOj606XYYW8yugTfC8Pj0hYqvi4ryAhA==", - "dev": true, - "dependencies": { - "get-east-asian-width": "^1.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/log-update/node_modules/slice-ansi": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-7.1.0.tgz", - "integrity": "sha512-bSiSngZ/jWeX93BqeIAbImyTbEihizcwNjFoRUIY/T1wWQsfsm2Vw1agPKylXvQTU7iASGdHhyqRlqQzfz+Htg==", - "dev": true, - "dependencies": { - "ansi-styles": "^6.2.1", - "is-fullwidth-code-point": "^5.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/chalk/slice-ansi?sponsor=1" - } - }, - "node_modules/log-update/node_modules/strip-ansi": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", - "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", - "dev": true, - "dependencies": { - "ansi-regex": "^6.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" - } - }, - "node_modules/log-update/node_modules/wrap-ansi": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.0.tgz", - "integrity": "sha512-G8ura3S+3Z2G+mkgNRq8dqaFZAuxfsxpBB8OCTGRTCtp+l/v9nbFNmCUP1BZMts3G1142MsZfn6eeUKrr4PD1Q==", - "dev": true, + "license": "MIT", "dependencies": { "ansi-styles": "^6.2.1", "string-width": "^7.0.0", @@ -10070,6 +11593,7 @@ "resolved": "https://registry.npmjs.org/log4js/-/log4js-6.9.1.tgz", "integrity": "sha512-1somDdy9sChrr9/f4UlzhdaGfDR2c/SaD2a4T7qEkG4jTS57/B3qmnjLYePwQ8cqWnUHZI0iAKxMBpCZICiZ2g==", "dev": true, + "license": "Apache-2.0", "dependencies": { "date-format": "^4.0.14", "debug": "^4.3.4", @@ -10086,6 +11610,7 @@ "resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.9.2.tgz", "integrity": "sha512-HgMmCqIJSAKqo68l0rS2AanEWfkxaZ5wNiEFb5ggm08lDs9Xl2KxBlX3PTcaD2chBM1gXAYf491/M2Rv8Jwayg==", "dev": true, + "license": "MIT", "engines": { "node": ">= 0.6.0" }, @@ -10099,6 +11624,7 @@ "resolved": "https://registry.npmjs.org/loglevel-colored-level-prefix/-/loglevel-colored-level-prefix-1.0.0.tgz", "integrity": "sha512-u45Wcxxc+SdAlh4yeF/uKlC1SPUPCy0gullSNKXod5I4bmifzk+Q4lSLExNEVn19tGaJipbZ4V4jbFn79/6mVA==", "dev": true, + "license": "MIT", "dependencies": { "chalk": "^1.1.3", "loglevel": "^1.4.1" @@ -10109,6 +11635,7 @@ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -10118,6 +11645,7 @@ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", "integrity": "sha512-kmCevFghRiWM7HB5zTPULl4r9bVFSWjz62MhqizDGUrq2NWuNMQyuv4tHHoKJHs69M/MF64lEcHdYIocrdWQYA==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -10127,6 +11655,7 @@ "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", "integrity": "sha512-U3lRVLMSlsCfjqYPbLyVv11M9CPW4I728d6TCKMAOJueEeB9/8o+eSsMnxPJD+Q+K909sdESg7C+tIkoH6on1A==", "dev": true, + "license": "MIT", "dependencies": { "ansi-styles": "^2.2.1", "escape-string-regexp": "^1.0.2", @@ -10143,6 +11672,7 @@ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.8.0" } @@ -10152,6 +11682,7 @@ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", "integrity": "sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg==", "dev": true, + "license": "MIT", "dependencies": { "ansi-regex": "^2.0.0" }, @@ -10164,6 +11695,7 @@ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", "integrity": "sha512-KKNVtd6pCYgPIKU4cp2733HWYCpplQhddZLBUryaAHou723x+FRzQ5Df824Fj+IyyuiQTRoub4SnIFfIcrp70g==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.8.0" } @@ -10173,15 +11705,17 @@ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", "dev": true, + "license": "ISC", "dependencies": { "yallist": "^3.0.2" } }, "node_modules/magic-string": { - "version": "0.30.11", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.11.tgz", - "integrity": "sha512-+Wri9p0QHMy+545hKww7YAu5NyzF8iomPL/RQazugQ9+Ez4Ic3mERMd8ZTX5rfK944j+560ZJi8iAwgak1Ac7A==", + "version": "0.30.17", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz", + "integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==", "dev": true, + "license": "MIT", "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0" } @@ -10191,6 +11725,7 @@ "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", "dev": true, + "license": "MIT", "dependencies": { "semver": "^7.5.3" }, @@ -10202,26 +11737,26 @@ } }, "node_modules/make-fetch-happen": { - "version": "13.0.1", - "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-13.0.1.tgz", - "integrity": "sha512-cKTUFc/rbKUd/9meOvgrpJ2WrNzymt6jfRDdwg5UCnVzv9dTpEj9JS5m3wtziXVCjluIXyL8pcaukYqezIzZQA==", + "version": "14.0.3", + "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-14.0.3.tgz", + "integrity": "sha512-QMjGbFTP0blj97EeidG5hk/QhKQ3T4ICckQGLgz38QF7Vgbk6e6FTARN8KhKxyBbWn8R0HU+bnw8aSoFPD4qtQ==", "dev": true, + "license": "ISC", "dependencies": { - "@npmcli/agent": "^2.0.0", - "cacache": "^18.0.0", + "@npmcli/agent": "^3.0.0", + "cacache": "^19.0.1", "http-cache-semantics": "^4.1.1", - "is-lambda": "^1.0.1", "minipass": "^7.0.2", - "minipass-fetch": "^3.0.0", + "minipass-fetch": "^4.0.0", "minipass-flush": "^1.0.5", "minipass-pipeline": "^1.2.4", - "negotiator": "^0.6.3", - "proc-log": "^4.2.0", + "negotiator": "^1.0.0", + "proc-log": "^5.0.0", "promise-retry": "^2.0.1", - "ssri": "^10.0.0" + "ssri": "^12.0.0" }, "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": "^18.17.0 || >=20.5.0" } }, "node_modules/math-intrinsics": { @@ -10229,6 +11764,7 @@ "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", "dev": true, + "license": "MIT", "engines": { "node": ">= 0.4" } @@ -10238,6 +11774,7 @@ "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", "dev": true, + "license": "MIT", "engines": { "node": ">= 0.6" } @@ -10247,6 +11784,7 @@ "resolved": "https://registry.npmjs.org/memfs/-/memfs-4.17.0.tgz", "integrity": "sha512-4eirfZ7thblFmqFjywlTmuWVSvccHAJbn1r8qQLzmTO11qcqpohOjmY2mFce6x7x7WtskzRqApPD0hv+Oa74jg==", "dev": true, + "license": "Apache-2.0", "dependencies": { "@jsonjoy.com/json-pack": "^1.0.3", "@jsonjoy.com/util": "^1.3.0", @@ -10266,6 +11804,7 @@ "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", "dev": true, + "license": "MIT", "funding": { "url": "https://github.com/sponsors/sindresorhus" } @@ -10274,13 +11813,15 @@ "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 + "dev": true, + "license": "MIT" }, "node_modules/merge2": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", "dev": true, + "license": "MIT", "engines": { "node": ">= 8" } @@ -10290,6 +11831,7 @@ "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", "dev": true, + "license": "MIT", "engines": { "node": ">= 0.6" } @@ -10299,6 +11841,7 @@ "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", "dev": true, + "license": "MIT", "dependencies": { "braces": "^3.0.3", "picomatch": "^2.3.1" @@ -10312,6 +11855,7 @@ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", "dev": true, + "license": "MIT", "engines": { "node": ">=8.6" }, @@ -10324,6 +11868,7 @@ "resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz", "integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==", "dev": true, + "license": "MIT", "bin": { "mime": "cli.js" }, @@ -10336,6 +11881,7 @@ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", "dev": true, + "license": "MIT", "engines": { "node": ">= 0.6" } @@ -10345,6 +11891,7 @@ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", "dev": true, + "license": "MIT", "dependencies": { "mime-db": "1.52.0" }, @@ -10357,6 +11904,7 @@ "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", "dev": true, + "license": "MIT", "engines": { "node": ">=6" } @@ -10366,6 +11914,7 @@ "resolved": "https://registry.npmjs.org/mimic-function/-/mimic-function-5.0.1.tgz", "integrity": "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==", "dev": true, + "license": "MIT", "engines": { "node": ">=18" }, @@ -10374,10 +11923,11 @@ } }, "node_modules/mini-css-extract-plugin": { - "version": "2.9.0", - "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-2.9.0.tgz", - "integrity": "sha512-Zs1YsZVfemekSZG+44vBsYTLQORkPMwnlv+aehcxK/NLKC+EGhDB39/YePYYqx/sTk6NnYpuqikhSn7+JIevTA==", + "version": "2.9.2", + "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-2.9.2.tgz", + "integrity": "sha512-GJuACcS//jtq4kCtd5ii/M0SZf7OZRH+BxdqXZHaJfb8TJiVl+NgQRPwiYt2EuqeSkNydn/7vP+bcE27C5mb9w==", "dev": true, + "license": "MIT", "dependencies": { "schema-utils": "^4.0.0", "tapable": "^2.2.1" @@ -10397,13 +11947,15 @@ "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 + "dev": true, + "license": "ISC" }, "node_modules/minimatch": { "version": "9.0.5", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", "dev": true, + "license": "ISC", "dependencies": { "brace-expansion": "^2.0.1" }, @@ -10418,6 +11970,7 @@ "version": "1.2.8", "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "license": "MIT", "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -10427,6 +11980,7 @@ "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", "dev": true, + "license": "ISC", "engines": { "node": ">=16 || 14 >=14.17" } @@ -10436,6 +11990,7 @@ "resolved": "https://registry.npmjs.org/minipass-collect/-/minipass-collect-2.0.1.tgz", "integrity": "sha512-D7V8PO9oaz7PWGLbCACuI1qEOsq7UKfLotx/C0Aet43fCUB/wfQ7DYeq2oR/svFJGYDHPr38SHATeaj/ZoKHKw==", "dev": true, + "license": "ISC", "dependencies": { "minipass": "^7.0.3" }, @@ -10444,17 +11999,18 @@ } }, "node_modules/minipass-fetch": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-3.0.5.tgz", - "integrity": "sha512-2N8elDQAtSnFV0Dk7gt15KHsS0Fyz6CbYZ360h0WTYV1Ty46li3rAXVOQj1THMNLdmrD9Vt5pBPtWtVkpwGBqg==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-4.0.1.tgz", + "integrity": "sha512-j7U11C5HXigVuutxebFadoYBbd7VSdZWggSe64NVdvWNBqGAiXPL2QVCehjmw7lY1oF9gOllYbORh+hiNgfPgQ==", "dev": true, + "license": "MIT", "dependencies": { "minipass": "^7.0.3", "minipass-sized": "^1.0.3", - "minizlib": "^2.1.2" + "minizlib": "^3.0.1" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^18.17.0 || >=20.5.0" }, "optionalDependencies": { "encoding": "^0.1.13" @@ -10465,6 +12021,7 @@ "resolved": "https://registry.npmjs.org/minipass-flush/-/minipass-flush-1.0.5.tgz", "integrity": "sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw==", "dev": true, + "license": "ISC", "dependencies": { "minipass": "^3.0.0" }, @@ -10477,6 +12034,7 @@ "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", "dev": true, + "license": "ISC", "dependencies": { "yallist": "^4.0.0" }, @@ -10488,13 +12046,15 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true + "dev": true, + "license": "ISC" }, "node_modules/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, + "license": "ISC", "dependencies": { "minipass": "^3.0.0" }, @@ -10507,6 +12067,7 @@ "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", "dev": true, + "license": "ISC", "dependencies": { "yallist": "^4.0.0" }, @@ -10518,13 +12079,15 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true + "dev": true, + "license": "ISC" }, "node_modules/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, + "license": "ISC", "dependencies": { "minipass": "^3.0.0" }, @@ -10537,6 +12100,7 @@ "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", "dev": true, + "license": "ISC", "dependencies": { "yallist": "^4.0.0" }, @@ -10548,44 +12112,28 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, - "node_modules/minizlib": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", - "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", "dev": true, - "dependencies": { - "minipass": "^3.0.0", - "yallist": "^4.0.0" - }, - "engines": { - "node": ">= 8" - } + "license": "ISC" }, - "node_modules/minizlib/node_modules/minipass": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "node_modules/minizlib": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.0.2.tgz", + "integrity": "sha512-oG62iEk+CYt5Xj2YqI5Xi9xWUeZhDI8jjQmC5oThVH5JGCTgIjr7ciJDzC7MBzYd//WvR1OTmP5Q38Q8ShQtVA==", "dev": true, + "license": "MIT", "dependencies": { - "yallist": "^4.0.0" + "minipass": "^7.1.2" }, "engines": { - "node": ">=8" + "node": ">= 18" } }, - "node_modules/minizlib/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, "node_modules/mkdirp": { "version": "0.5.6", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", "dev": true, + "license": "MIT", "dependencies": { "minimist": "^1.2.6" }, @@ -10597,6 +12145,7 @@ "version": "4.3.7", "resolved": "https://registry.npmjs.org/mqtt/-/mqtt-4.3.7.tgz", "integrity": "sha512-ew3qwG/TJRorTz47eW46vZ5oBw5MEYbQZVaEji44j5lAUSQSqIEoul7Kua/BatBW0H0kKQcC9kwUHa1qzaWHSw==", + "license": "MIT", "dependencies": { "commist": "^1.0.0", "concat-stream": "^2.0.0", @@ -10629,6 +12178,7 @@ "version": "4.3.7", "resolved": "https://registry.npmjs.org/mqtt-browser/-/mqtt-browser-4.3.7.tgz", "integrity": "sha512-4pxHxa3avIILr2CXhTKlArVpATqfyTu4zr5u2PoUwzgw0GDr5dpzZ0pmPgZyOoQBVgrVDEboCzb/b1Q0yWOm7g==", + "license": "MIT", "dependencies": { "mqtt": "4.3.7" } @@ -10637,6 +12187,7 @@ "version": "6.10.0", "resolved": "https://registry.npmjs.org/mqtt-packet/-/mqtt-packet-6.10.0.tgz", "integrity": "sha512-ja8+mFKIHdB1Tpl6vac+sktqy3gA8t9Mduom1BA75cI+R9AHnZOiaBQwpGiWnaVJLDGRdNhQmFaAqd7tkKSMGA==", + "license": "MIT", "dependencies": { "bl": "^4.0.2", "debug": "^4.1.1", @@ -10647,6 +12198,7 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "license": "ISC", "dependencies": { "yallist": "^4.0.0" }, @@ -10657,13 +12209,15 @@ "node_modules/mqtt/node_modules/yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "license": "ISC" }, "node_modules/mrmime": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.0.tgz", - "integrity": "sha512-eu38+hdgojoyq63s+yTpN4XMBdt5l8HhMhc4VKLO9KM5caLIBvUm4thi7fFaxyTmCKeNnXZ5pAlBwCUnhA09uw==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.1.tgz", + "integrity": "sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=10" } @@ -10671,13 +12225,16 @@ "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" }, "node_modules/msgpackr": { "version": "1.11.2", "resolved": "https://registry.npmjs.org/msgpackr/-/msgpackr-1.11.2.tgz", "integrity": "sha512-F9UngXRlPyWCDEASDpTf6c9uNhGPTqnTeLVt7bN+bU1eajoR/8V9ys2BRaV5C/e5ihE6sJ9uPIKaYt6bFuO32g==", "dev": true, + "license": "MIT", + "optional": true, "optionalDependencies": { "msgpackr-extract": "^3.0.2" } @@ -10688,6 +12245,7 @@ "integrity": "sha512-P0efT1C9jIdVRefqjzOQ9Xml57zpOXnIuS+csaB4MdZbTdmGDLo8XhzBG1N7aO11gKDDkJvBLULeFTo46wwreA==", "dev": true, "hasInstallScript": true, + "license": "MIT", "optional": true, "dependencies": { "node-gyp-build-optional-packages": "5.2.2" @@ -10709,6 +12267,7 @@ "resolved": "https://registry.npmjs.org/multicast-dns/-/multicast-dns-7.2.5.tgz", "integrity": "sha512-2eznPJP8z2BFLX50tf0LuODrpINqP1RVIm/CObbTcBRITQgmC/TjcREF1NeTBzIcR5XO/ukWo+YHOjBbFwIupg==", "dev": true, + "license": "MIT", "dependencies": { "dns-packet": "^5.2.2", "thunky": "^1.0.2" @@ -10718,18 +12277,19 @@ } }, "node_modules/mute-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-1.0.0.tgz", - "integrity": "sha512-avsJQhyd+680gKXyG/sQc0nXaC6rBkPOfyHYcFb9+hdkqQkR9bdnkJ0AMZhke0oesPqIO+mFFJ+IdBc7mst4IA==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-2.0.0.tgz", + "integrity": "sha512-WWdIxpyjEn+FhQJQQv9aQAYlHoNVdzIzUySNV1gHUPDSdZJ3yZn7pAAbQcV7B56Mvu881q9FZV+0Vx2xC44VWA==", "dev": true, + "license": "ISC", "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^18.17.0 || >=20.5.0" } }, "node_modules/nanoid": { - "version": "3.3.8", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz", - "integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==", + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", "dev": true, "funding": [ { @@ -10737,6 +12297,7 @@ "url": "https://github.com/sponsors/ai" } ], + "license": "MIT", "bin": { "nanoid": "bin/nanoid.cjs" }, @@ -10748,13 +12309,15 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/needle": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/needle/-/needle-3.3.1.tgz", "integrity": "sha512-6k0YULvhpw+RoLNiQCRKOl09Rv1dPLr8hHnVjHqdolKwDrdNyk+Hmrthi4lIGPPz3r39dLx0hsF5s40sZ3Us4Q==", "dev": true, + "license": "MIT", "optional": true, "dependencies": { "iconv-lite": "^0.6.3", @@ -10772,6 +12335,7 @@ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", "dev": true, + "license": "MIT", "optional": true, "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" @@ -10781,10 +12345,11 @@ } }, "node_modules/negotiator": { - "version": "0.6.4", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.4.tgz", - "integrity": "sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", + "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", "dev": true, + "license": "MIT", "engines": { "node": ">= 0.6" } @@ -10793,12 +12358,14 @@ "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 + "dev": true, + "license": "MIT" }, "node_modules/ngx-mask": { "version": "16.4.2", "resolved": "https://registry.npmjs.org/ngx-mask/-/ngx-mask-16.4.2.tgz", "integrity": "sha512-mQjcsTpctGu6HYKLf6/gjEUvW65D+46xvPIMYz0BDZXqHXrqKVluHXR3KF++TNOfdLLXwW6SvuHWd91NZN/C1A==", + "license": "MIT", "dependencies": { "tslib": "^2.3.0" }, @@ -10812,6 +12379,7 @@ "version": "17.0.0", "resolved": "https://registry.npmjs.org/ngx-mqtt/-/ngx-mqtt-17.0.0.tgz", "integrity": "sha512-54wVMyDOZkpTZEs0rTMWPP1Yz+6q3rRnHzIBnpqnBkDcyMfNrti45C7ijwnEIaPDzQHMOqVrDgh/6C4ocPPLJQ==", + "license": "MIT", "dependencies": { "mqtt-browser": "4.3.7", "mqtt-packet": "^6.10.0", @@ -10822,77 +12390,47 @@ "@angular/core": ">=14" } }, - "node_modules/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, - "hasInstallScript": true, - "optional": true, - "os": [ - "!win32" - ], - "dependencies": { - "node-addon-api": "^3.0.0", - "node-gyp-build": "^4.2.2" - } - }, - "node_modules/nice-napi/node_modules/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_modules/node-addon-api": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-6.1.0.tgz", "integrity": "sha512-+eawOlIgy680F0kBzPUNFhMZGtJ1YmqM6l4+Crf4IkImjYrO/mqPwRMh352g23uIaQKFItcQ64I7KMaJxHgAVA==", - "dev": true + "dev": true, + "license": "MIT", + "optional": true }, "node_modules/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, + "license": "(BSD-3-Clause OR GPL-2.0)", "engines": { "node": ">= 6.13.0" } }, "node_modules/node-gyp": { - "version": "10.3.1", - "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-10.3.1.tgz", - "integrity": "sha512-Pp3nFHBThHzVtNY7U6JfPjvT/DTE8+o/4xKsLQtBoU+j2HLsGlhcfzflAoUreaJbNmYnX+LlLi0qjV8kpyO6xQ==", + "version": "11.2.0", + "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-11.2.0.tgz", + "integrity": "sha512-T0S1zqskVUSxcsSTkAsLc7xCycrRYmtDHadDinzocrThjyQCn5kMlEBSj6H4qDbgsIOSLmmlRIeb0lZXj+UArA==", "dev": true, + "license": "MIT", "dependencies": { "env-paths": "^2.2.0", "exponential-backoff": "^3.1.1", - "glob": "^10.3.10", "graceful-fs": "^4.2.6", - "make-fetch-happen": "^13.0.0", - "nopt": "^7.0.0", - "proc-log": "^4.1.0", + "make-fetch-happen": "^14.0.3", + "nopt": "^8.0.0", + "proc-log": "^5.0.0", "semver": "^7.3.5", - "tar": "^6.2.1", - "which": "^4.0.0" + "tar": "^7.4.3", + "tinyglobby": "^0.2.12", + "which": "^5.0.0" }, "bin": { "node-gyp": "bin/node-gyp.js" }, "engines": { - "node": "^16.14.0 || >=18.0.0" - } - }, - "node_modules/node-gyp-build": { - "version": "4.8.4", - "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.4.tgz", - "integrity": "sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==", - "dev": true, - "optional": true, - "bin": { - "node-gyp-build": "bin.js", - "node-gyp-build-optional": "optional.js", - "node-gyp-build-test": "build-test.js" + "node": "^18.17.0 || >=20.5.0" } }, "node_modules/node-gyp-build-optional-packages": { @@ -10900,6 +12438,8 @@ "resolved": "https://registry.npmjs.org/node-gyp-build-optional-packages/-/node-gyp-build-optional-packages-5.2.2.tgz", "integrity": "sha512-s+w+rBWnpTMwSFbaE0UXsRlg7hU4FjekKU4eyAih5T8nJuNZT1nNsskXpxmeqSK9UzkBl6UgRlnKc8hz8IEqOw==", "dev": true, + "license": "MIT", + "optional": true, "dependencies": { "detect-libc": "^2.0.1" }, @@ -10909,24 +12449,14 @@ "node-gyp-build-optional-packages-test": "build-test.js" } }, - "node_modules/node-gyp/node_modules/glob": { - "version": "10.4.5", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", - "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "node_modules/node-gyp/node_modules/chownr": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz", + "integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==", "dev": true, - "dependencies": { - "foreground-child": "^3.1.0", - "jackspeak": "^3.1.2", - "minimatch": "^9.0.4", - "minipass": "^7.1.2", - "package-json-from-dist": "^1.0.0", - "path-scurry": "^1.11.1" - }, - "bin": { - "glob": "dist/esm/bin.mjs" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" } }, "node_modules/node-gyp/node_modules/isexe": { @@ -10934,15 +12464,51 @@ "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz", "integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==", "dev": true, + "license": "ISC", "engines": { "node": ">=16" } }, + "node_modules/node-gyp/node_modules/mkdirp": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-3.0.1.tgz", + "integrity": "sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==", + "dev": true, + "license": "MIT", + "bin": { + "mkdirp": "dist/cjs/src/bin.js" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/node-gyp/node_modules/tar": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/tar/-/tar-7.4.3.tgz", + "integrity": "sha512-5S7Va8hKfV7W5U6g3aYxXmlPoZVAwUMy9AOKyF2fVuZa2UD3qZjg578OrLRt8PcNN1PleVaL/5/yYATNL0ICUw==", + "dev": true, + "license": "ISC", + "dependencies": { + "@isaacs/fs-minipass": "^4.0.0", + "chownr": "^3.0.0", + "minipass": "^7.1.2", + "minizlib": "^3.0.1", + "mkdirp": "^3.0.1", + "yallist": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/node-gyp/node_modules/which": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/which/-/which-4.0.0.tgz", - "integrity": "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/which/-/which-5.0.0.tgz", + "integrity": "sha512-JEdGzHwwkrbWoGOlIHqQ5gtprKGOenpDHpxE9zVR1bWbOtYRyPPHMe9FaP6x61CmNaTThSkb0DAJte5jD+DmzQ==", "dev": true, + "license": "ISC", "dependencies": { "isexe": "^3.1.1" }, @@ -10950,42 +12516,40 @@ "node-which": "bin/which.js" }, "engines": { - "node": "^16.13.0 || >=18.0.0" + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/node-gyp/node_modules/yallist": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz", + "integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" } }, "node_modules/node-releases": { "version": "2.0.19", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/nopt": { - "version": "7.2.1", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-7.2.1.tgz", - "integrity": "sha512-taM24ViiimT/XntxbPyJQzCG+p4EKOpgD3mxFwW38mGjVUrfERQOeY4EDHjdnptttfHuHQXFx+lTP08Q+mLa/w==", + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-8.1.0.tgz", + "integrity": "sha512-ieGu42u/Qsa4TFktmaKEwM6MQH0pOWnaB3htzh0JRtx84+Mebc0cbZYN5bC+6WTZ4+77xrL9Pn5m7CV6VIkV7A==", "dev": true, + "license": "ISC", "dependencies": { - "abbrev": "^2.0.0" + "abbrev": "^3.0.0" }, "bin": { "nopt": "bin/nopt.js" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "node_modules/normalize-package-data": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-6.0.2.tgz", - "integrity": "sha512-V6gygoYb/5EmNI+MEGrWkC+e6+Rr7mTmfHrxDbLzxQogBkgzo76rkok0Am6thgSF7Mv2nLOajAJj5vDJZEFn7g==", - "dev": true, - "dependencies": { - "hosted-git-info": "^7.0.0", - "semver": "^7.3.5", - "validate-npm-package-license": "^3.0.4" - }, - "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": "^18.17.0 || >=20.5.0" } }, "node_modules/normalize-path": { @@ -10993,6 +12557,7 @@ "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -11002,114 +12567,110 @@ "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz", "integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.10.0" } }, "node_modules/npm-bundled": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-3.0.1.tgz", - "integrity": "sha512-+AvaheE/ww1JEwRHOrn4WHNzOxGtVp+adrg2AeZS/7KuxGUYFuBta98wYpfHBbJp6Tg6j1NKSEVHNcfZzJHQwQ==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-4.0.0.tgz", + "integrity": "sha512-IxaQZDMsqfQ2Lz37VvyyEtKLe8FsRZuysmedy/N06TU1RyVppYKXrO4xIhR0F+7ubIBox6Q7nir6fQI3ej39iA==", "dev": true, + "license": "ISC", "dependencies": { - "npm-normalize-package-bin": "^3.0.0" + "npm-normalize-package-bin": "^4.0.0" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^18.17.0 || >=20.5.0" } }, "node_modules/npm-install-checks": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/npm-install-checks/-/npm-install-checks-6.3.0.tgz", - "integrity": "sha512-W29RiK/xtpCGqn6f3ixfRYGk+zRyr+Ew9F2E20BfXxT5/euLdA/Nm7fO7OeTGuAmTs30cpgInyJ0cYe708YTZw==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/npm-install-checks/-/npm-install-checks-7.1.1.tgz", + "integrity": "sha512-u6DCwbow5ynAX5BdiHQ9qvexme4U3qHW3MWe5NqH+NeBm0LbiH6zvGjNNew1fY+AZZUtVHbOPF3j7mJxbUzpXg==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { "semver": "^7.1.1" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^18.17.0 || >=20.5.0" } }, "node_modules/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==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-4.0.0.tgz", + "integrity": "sha512-TZKxPvItzai9kN9H/TkmCtx/ZN/hvr3vUycjlfmH0ootY9yFBzNOpiXAdIn1Iteqsvk4lQn6B5PTrt+n6h8k/w==", "dev": true, + "license": "ISC", "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^18.17.0 || >=20.5.0" } }, "node_modules/npm-package-arg": { - "version": "11.0.3", - "resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-11.0.3.tgz", - "integrity": "sha512-sHGJy8sOC1YraBywpzQlIKBE4pBbGbiF95U6Auspzyem956E0+FtDtsx1ZxlOJkQCZ1AFXAY/yuvtFYrOxF+Bw==", + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-12.0.0.tgz", + "integrity": "sha512-ZTE0hbwSdTNL+Stx2zxSqdu2KZfNDcrtrLdIk7XGnQFYBWYDho/ORvXtn5XEePcL3tFpGjHCV3X3xrtDh7eZ+A==", "dev": true, + "license": "ISC", "dependencies": { - "hosted-git-info": "^7.0.0", - "proc-log": "^4.0.0", + "hosted-git-info": "^8.0.0", + "proc-log": "^5.0.0", "semver": "^7.3.5", - "validate-npm-package-name": "^5.0.0" + "validate-npm-package-name": "^6.0.0" }, "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": "^18.17.0 || >=20.5.0" } }, "node_modules/npm-packlist": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-8.0.2.tgz", - "integrity": "sha512-shYrPFIS/JLP4oQmAwDyk5HcyysKW8/JLTEA32S0Z5TzvpaeeX2yMFfoK1fjEBnCBvVyIB/Jj/GBFdm0wsgzbA==", + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-9.0.0.tgz", + "integrity": "sha512-8qSayfmHJQTx3nJWYbbUmflpyarbLMBc6LCAjYsiGtXxDB68HaZpb8re6zeaLGxZzDuMdhsg70jryJe+RrItVQ==", "dev": true, + "license": "ISC", "dependencies": { - "ignore-walk": "^6.0.4" + "ignore-walk": "^7.0.0" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^18.17.0 || >=20.5.0" } }, "node_modules/npm-pick-manifest": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/npm-pick-manifest/-/npm-pick-manifest-9.1.0.tgz", - "integrity": "sha512-nkc+3pIIhqHVQr085X9d2JzPzLyjzQS96zbruppqC9aZRm/x8xx6xhI98gHtsfELP2bE+loHq8ZaHFHhe+NauA==", + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/npm-pick-manifest/-/npm-pick-manifest-10.0.0.tgz", + "integrity": "sha512-r4fFa4FqYY8xaM7fHecQ9Z2nE9hgNfJR+EmoKv0+chvzWkBcORX3r0FpTByP+CbOVJDladMXnPQGVN8PBLGuTQ==", "dev": true, + "license": "ISC", "dependencies": { - "npm-install-checks": "^6.0.0", - "npm-normalize-package-bin": "^3.0.0", - "npm-package-arg": "^11.0.0", + "npm-install-checks": "^7.1.0", + "npm-normalize-package-bin": "^4.0.0", + "npm-package-arg": "^12.0.0", "semver": "^7.3.5" }, "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": "^18.17.0 || >=20.5.0" } }, "node_modules/npm-registry-fetch": { - "version": "17.1.0", - "resolved": "https://registry.npmjs.org/npm-registry-fetch/-/npm-registry-fetch-17.1.0.tgz", - "integrity": "sha512-5+bKQRH0J1xG1uZ1zMNvxW0VEyoNWgJpY9UDuluPFLKDfJ9u2JmmjmTJV1srBGQOROfdBMiVvnH2Zvpbm+xkVA==", + "version": "18.0.2", + "resolved": "https://registry.npmjs.org/npm-registry-fetch/-/npm-registry-fetch-18.0.2.tgz", + "integrity": "sha512-LeVMZBBVy+oQb5R6FDV9OlJCcWDU+al10oKpe+nsvcHnG24Z3uM3SvJYKfGJlfGjVU8v9liejCrUR/M5HO5NEQ==", "dev": true, + "license": "ISC", "dependencies": { - "@npmcli/redact": "^2.0.0", + "@npmcli/redact": "^3.0.0", "jsonparse": "^1.3.1", - "make-fetch-happen": "^13.0.0", + "make-fetch-happen": "^14.0.0", "minipass": "^7.0.2", - "minipass-fetch": "^3.0.0", - "minizlib": "^2.1.2", - "npm-package-arg": "^11.0.0", - "proc-log": "^4.0.0" - }, - "engines": { - "node": "^16.14.0 || >=18.0.0" - } - }, - "node_modules/npm-run-path": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", - "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", - "dev": true, - "dependencies": { - "path-key": "^3.0.0" + "minipass-fetch": "^4.0.0", + "minizlib": "^3.0.1", + "npm-package-arg": "^12.0.0", + "proc-log": "^5.0.0" }, "engines": { - "node": ">=8" + "node": "^18.17.0 || >=20.5.0" } }, "node_modules/nth-check": { @@ -11117,6 +12678,7 @@ "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { "boolbase": "^1.0.0" }, @@ -11128,6 +12690,7 @@ "version": "1.0.14", "resolved": "https://registry.npmjs.org/number-allocator/-/number-allocator-1.0.14.tgz", "integrity": "sha512-OrL44UTVAvkKdOdRQZIJpLkAdjXGTRda052sN4sO77bKEzYYqWKMBjQvrJFzqygI99gL6Z4u2xctPW1tB8ErvA==", + "license": "MIT", "dependencies": { "debug": "^4.3.1", "js-sdsl": "4.3.0" @@ -11138,15 +12701,17 @@ "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.10.0" } }, "node_modules/object-inspect": { - "version": "1.13.3", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.3.tgz", - "integrity": "sha512-kDCGIbxkDSXE3euJZZXzc6to7fCrKHNI/hSRQnRuQ+BWjFNzZwiFF8fj/6o2t2G9/jTj8PSIYTfCLelLZEeRpA==", + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", "dev": true, + "license": "MIT", "engines": { "node": ">= 0.4" }, @@ -11158,13 +12723,15 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/obuf/-/obuf-1.1.2.tgz", "integrity": "sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/on-finished": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", "dev": true, + "license": "MIT", "dependencies": { "ee-first": "1.1.1" }, @@ -11177,6 +12744,7 @@ "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, + "license": "MIT", "engines": { "node": ">= 0.8" } @@ -11185,6 +12753,7 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "license": "ISC", "dependencies": { "wrappy": "1" } @@ -11194,6 +12763,7 @@ "resolved": "https://registry.npmjs.org/onetime/-/onetime-7.0.0.tgz", "integrity": "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==", "dev": true, + "license": "MIT", "dependencies": { "mimic-function": "^5.0.0" }, @@ -11209,6 +12779,7 @@ "resolved": "https://registry.npmjs.org/open/-/open-10.1.0.tgz", "integrity": "sha512-mnkeQ1qP5Ue2wd+aivTD3NHd/lZ96Lu0jgf0pwktLPtx6cTZiH7tyeGRRHs0zX0rbrahXPnXlUnbeXyaBBuIaw==", "dev": true, + "license": "MIT", "dependencies": { "default-browser": "^5.2.1", "define-lazy-prop": "^3.0.0", @@ -11227,6 +12798,7 @@ "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", "dev": true, + "license": "MIT", "dependencies": { "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", @@ -11244,6 +12816,7 @@ "resolved": "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz", "integrity": "sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==", "dev": true, + "license": "MIT", "dependencies": { "bl": "^4.1.0", "chalk": "^4.1.0", @@ -11267,6 +12840,7 @@ "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", "dev": true, + "license": "MIT", "dependencies": { "restore-cursor": "^3.1.0" }, @@ -11279,6 +12853,7 @@ "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", "dev": true, + "license": "MIT", "dependencies": { "mimic-fn": "^2.1.0" }, @@ -11294,6 +12869,7 @@ "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", "dev": true, + "license": "MIT", "dependencies": { "onetime": "^5.1.0", "signal-exit": "^3.0.2" @@ -11306,25 +12882,23 @@ "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 + "dev": true, + "license": "ISC" }, "node_modules/ordered-binary": { "version": "1.5.3", "resolved": "https://registry.npmjs.org/ordered-binary/-/ordered-binary-1.5.3.tgz", "integrity": "sha512-oGFr3T+pYdTGJ+YFEILMpS3es+GiIbs9h/XQrclBXUtd44ey7XwfsMzM31f64I1SQOawDoDr/D823kNCADI8TA==", - "dev": true - }, - "node_modules/ordered-binary": { - "version": "1.5.2", - "resolved": "https://registry.npmjs.org/ordered-binary/-/ordered-binary-1.5.2.tgz", - "integrity": "sha512-JTo+4+4Fw7FreyAvlSLjb1BBVaxEQAacmjD3jjuyPZclpbEghTvQZbXBb2qPd2LeIMxiHwXBZUcpmG2Gl/mDEA==", - "dev": true + "dev": true, + "license": "MIT", + "optional": true }, "node_modules/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, + "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -11334,6 +12908,7 @@ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", "dev": true, + "license": "MIT", "dependencies": { "yocto-queue": "^0.1.0" }, @@ -11349,6 +12924,7 @@ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", "dev": true, + "license": "MIT", "dependencies": { "p-limit": "^3.0.2" }, @@ -11360,15 +12936,13 @@ } }, "node_modules/p-map": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", - "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-7.0.3.tgz", + "integrity": "sha512-VkndIv2fIB99swvQoA65bm+fsmt6UNdGeIB0oxBs+WhAhdh08QA04JXpI7rbB9r08/nkbysKoya9rtDERYOYMA==", "dev": true, - "dependencies": { - "aggregate-error": "^3.0.0" - }, + "license": "MIT", "engines": { - "node": ">=10" + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -11379,6 +12953,7 @@ "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-6.2.1.tgz", "integrity": "sha512-hEt02O4hUct5wtwg4H4KcWgDdm+l1bOaEy/hWzd8xtXB9BqxTWBBhb+2ImAtH4Cv4rPjV76xN3Zumqk3k3AhhQ==", "dev": true, + "license": "MIT", "dependencies": { "@types/retry": "0.12.2", "is-network-error": "^1.0.0", @@ -11396,6 +12971,7 @@ "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==", "dev": true, + "license": "MIT", "engines": { "node": ">= 4" } @@ -11404,37 +12980,39 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", - "dev": true + "dev": true, + "license": "BlueOak-1.0.0" }, "node_modules/pacote": { - "version": "18.0.6", - "resolved": "https://registry.npmjs.org/pacote/-/pacote-18.0.6.tgz", - "integrity": "sha512-+eK3G27SMwsB8kLIuj4h1FUhHtwiEUo21Tw8wNjmvdlpOEr613edv+8FUsTj/4F/VN5ywGE19X18N7CC2EJk6A==", - "dev": true, - "dependencies": { - "@npmcli/git": "^5.0.0", - "@npmcli/installed-package-contents": "^2.0.1", - "@npmcli/package-json": "^5.1.0", - "@npmcli/promise-spawn": "^7.0.0", - "@npmcli/run-script": "^8.0.0", - "cacache": "^18.0.0", + "version": "20.0.0", + "resolved": "https://registry.npmjs.org/pacote/-/pacote-20.0.0.tgz", + "integrity": "sha512-pRjC5UFwZCgx9kUFDVM9YEahv4guZ1nSLqwmWiLUnDbGsjs+U5w7z6Uc8HNR1a6x8qnu5y9xtGE6D1uAuYz+0A==", + "dev": true, + "license": "ISC", + "dependencies": { + "@npmcli/git": "^6.0.0", + "@npmcli/installed-package-contents": "^3.0.0", + "@npmcli/package-json": "^6.0.0", + "@npmcli/promise-spawn": "^8.0.0", + "@npmcli/run-script": "^9.0.0", + "cacache": "^19.0.0", "fs-minipass": "^3.0.0", "minipass": "^7.0.2", - "npm-package-arg": "^11.0.0", - "npm-packlist": "^8.0.0", - "npm-pick-manifest": "^9.0.0", - "npm-registry-fetch": "^17.0.0", - "proc-log": "^4.0.0", + "npm-package-arg": "^12.0.0", + "npm-packlist": "^9.0.0", + "npm-pick-manifest": "^10.0.0", + "npm-registry-fetch": "^18.0.0", + "proc-log": "^5.0.0", "promise-retry": "^2.0.1", - "sigstore": "^2.2.0", - "ssri": "^10.0.0", + "sigstore": "^3.0.0", + "ssri": "^12.0.0", "tar": "^6.1.11" }, "bin": { "pacote": "bin/index.js" }, "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": "^18.17.0 || >=20.5.0" } }, "node_modules/parent-module": { @@ -11442,6 +13020,7 @@ "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", "dev": true, + "license": "MIT", "dependencies": { "callsites": "^3.0.0" }, @@ -11454,6 +13033,7 @@ "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", "dev": true, + "license": "MIT", "dependencies": { "@babel/code-frame": "^7.0.0", "error-ex": "^1.3.1", @@ -11471,24 +13051,26 @@ "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 + "dev": true, + "license": "MIT" }, "node_modules/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, + "license": "MIT", "engines": { "node": ">= 0.10" } }, "node_modules/parse5": { - "version": "7.2.1", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.2.1.tgz", - "integrity": "sha512-BuBYQYlv1ckiPdQi/ohiivi9Sagc9JG+Ozs0r7b/0iK3sKmrb0b9FdWdBbOdx6hBCM/F9Ir82ofnBhtZOjCRPQ==", - "devOptional": true, + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz", + "integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==", + "license": "MIT", "dependencies": { - "entities": "^4.5.0" + "entities": "^6.0.0" }, "funding": { "url": "https://github.com/inikulin/parse5?sponsor=1" @@ -11499,6 +13081,7 @@ "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, + "license": "MIT", "dependencies": { "entities": "^4.3.0", "parse5": "^7.0.0", @@ -11513,6 +13096,7 @@ "resolved": "https://registry.npmjs.org/parse5-sax-parser/-/parse5-sax-parser-7.0.0.tgz", "integrity": "sha512-5A+v2SNsq8T6/mG3ahcz8ZtQ0OUFTatxPbeidoMB7tkJSGDY3tdfl4MHovtLQHkEn5CGxijNWRQHhRQ6IRpXKg==", "dev": true, + "license": "MIT", "dependencies": { "parse5": "^7.0.0" }, @@ -11520,11 +13104,24 @@ "url": "https://github.com/inikulin/parse5?sponsor=1" } }, + "node_modules/parse5/node_modules/entities": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.0.tgz", + "integrity": "sha512-aKstq2TDOndCn4diEyp9Uq/Flu2i1GlLkc6XIDQSDMuaFE3OPW5OphLCyQ5SpSJZTb4reN+kTcYru5yIfXoRPw==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, "node_modules/parseurl": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", "dev": true, + "license": "MIT", "engines": { "node": ">= 0.8" } @@ -11534,6 +13131,7 @@ "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } @@ -11542,6 +13140,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -11551,6 +13150,7 @@ "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } @@ -11559,13 +13159,15 @@ "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 + "dev": true, + "license": "MIT" }, "node_modules/path-scurry": { "version": "1.11.1", "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", "dev": true, + "license": "BlueOak-1.0.0", "dependencies": { "lru-cache": "^10.2.0", "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" @@ -11581,21 +13183,24 @@ "version": "10.4.3", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", - "dev": true + "dev": true, + "license": "ISC" }, "node_modules/path-to-regexp": { "version": "0.1.12", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/path-type": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-5.0.0.tgz", - "integrity": "sha512-5HviZNaZcfqP95rwpv+1HDgUamezbqdSYTyzjTvwtJSnIH+3vnbmWsItli8OFEndS984VT55M3jduxZbX351gg==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-6.0.0.tgz", + "integrity": "sha512-Vj7sf++t5pBD637NSfkxpHSMfWaeig5+DKWLhcqIYx6mWQz5hdJTGDVMQiJcw1ZYkhs7AazKDGpRVji1LJCZUQ==", "dev": true, + "license": "MIT", "engines": { - "node": ">=12" + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -11605,13 +13210,15 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", - "dev": true + "dev": true, + "license": "ISC" }, "node_modules/picomatch": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", "dev": true, + "license": "MIT", "engines": { "node": ">=12" }, @@ -11624,18 +13231,20 @@ "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", "dev": true, + "license": "MIT", "optional": true, "engines": { "node": ">=6" } }, "node_modules/piscina": { - "version": "4.6.1", - "resolved": "https://registry.npmjs.org/piscina/-/piscina-4.6.1.tgz", - "integrity": "sha512-z30AwWGtQE+Apr+2WBZensP2lIvwoaMcOPkQlIEmSGMJNUvaYACylPYrQM6wSdUNJlnDVMSpLv7xTMJqlVshOA==", + "version": "4.8.0", + "resolved": "https://registry.npmjs.org/piscina/-/piscina-4.8.0.tgz", + "integrity": "sha512-EZJb+ZxDrQf3dihsUL7p42pjNyrNIFJCrRHPMgxu/svsj+P3xS3fuEWp7k2+rfsavfl1N0G29b1HGs7J0m8rZA==", "dev": true, + "license": "MIT", "optionalDependencies": { - "nice-napi": "^1.0.2" + "@napi-rs/nice": "^1.0.1" } }, "node_modules/pkg-dir": { @@ -11643,6 +13252,7 @@ "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-7.0.0.tgz", "integrity": "sha512-Ie9z/WINcxxLp27BKOCHGde4ITq9UklYKDzVo1nhk5sqGEXU3FpkwP5GM2voTGJkGd9B3Otl+Q4uwSOeSUtOBA==", "dev": true, + "license": "MIT", "dependencies": { "find-up": "^6.3.0" }, @@ -11658,6 +13268,7 @@ "resolved": "https://registry.npmjs.org/find-up/-/find-up-6.3.0.tgz", "integrity": "sha512-v2ZsoEuVHYy8ZIlYqwPe/39Cy+cFDzp4dXPaxNvkEuouymu+2Jbz0PxpKarJHYJTmv2HWT3O382qY8l4jMWthw==", "dev": true, + "license": "MIT", "dependencies": { "locate-path": "^7.1.0", "path-exists": "^5.0.0" @@ -11674,6 +13285,7 @@ "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-7.2.0.tgz", "integrity": "sha512-gvVijfZvn7R+2qyPX8mAuKcFGDf6Nc61GdvGafQsHL0sBIxfKzA+usWn4GFC/bk+QdwPUD4kWFJLhElipq+0VA==", "dev": true, + "license": "MIT", "dependencies": { "p-locate": "^6.0.0" }, @@ -11689,6 +13301,7 @@ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-4.0.0.tgz", "integrity": "sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==", "dev": true, + "license": "MIT", "dependencies": { "yocto-queue": "^1.0.0" }, @@ -11704,6 +13317,7 @@ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-6.0.0.tgz", "integrity": "sha512-wPrq66Llhl7/4AGC6I+cqxT07LhXvWL08LNXz1fENOw0Ap4sRZZ/gZpTTJ5jpurzzzfS2W/Ge9BY3LgLjCShcw==", "dev": true, + "license": "MIT", "dependencies": { "p-limit": "^4.0.0" }, @@ -11719,15 +13333,17 @@ "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-5.0.0.tgz", "integrity": "sha512-RjhtfwJOxzcFmNOi6ltcbcu4Iu+FL3zEj83dk4kAS+fVpTxXLO1b38RvJgT/0QwvV/L3aY9TAnyv0EOqW4GoMQ==", "dev": true, + "license": "MIT", "engines": { "node": "^12.20.0 || ^14.13.1 || >=16.0.0" } }, "node_modules/pkg-dir/node_modules/yocto-queue": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.1.1.tgz", - "integrity": "sha512-b4JR1PFR10y1mKjhHY9LaGo6tmrgjit7hxVIeAmyMw3jegXR4dhYqLaQF5zMXZxY7tLpMyJeLjr1C4rLmkVe8g==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.2.1.tgz", + "integrity": "sha512-AyeEbWOu/TAXdxlV9wmGcR0+yh2j3vYPGOECcIj2S7MkrLyC7ne+oye2BKTItt0ii2PHk4cDy+95+LshzbXnGg==", "dev": true, + "license": "MIT", "engines": { "node": ">=12.20" }, @@ -11736,9 +13352,9 @@ } }, "node_modules/postcss": { - "version": "8.4.41", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.41.tgz", - "integrity": "sha512-TesUflQ0WKZqAvg52PWL6kHgLKP6xB6heTOdoYM0Wt2UHyxNa4K25EZZMgKns3BH1RLVbZCREPpLY0rhnNoHVQ==", + "version": "8.5.2", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.2.tgz", + "integrity": "sha512-MjOadfU3Ys9KYoX0AdkBlFEF1Vx37uCCeN4ZHnmwm9FfpbsGWMZeBLMmmpY+6Ocqod7mkdZ0DT31OlbsFrLlkA==", "dev": true, "funding": [ { @@ -11754,10 +13370,11 @@ "url": "https://github.com/sponsors/ai" } ], + "license": "MIT", "dependencies": { - "nanoid": "^3.3.7", - "picocolors": "^1.0.1", - "source-map-js": "^1.2.0" + "nanoid": "^3.3.8", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" }, "engines": { "node": "^10 || ^12 || >=14" @@ -11768,6 +13385,7 @@ "resolved": "https://registry.npmjs.org/postcss-loader/-/postcss-loader-8.1.1.tgz", "integrity": "sha512-0IeqyAsG6tYiDRCYKQJLAmgQr47DX6N7sFSWvQxt6AcupX8DIdmykuk/o/tx0Lze3ErGHJEp5OSRxrelC6+NdQ==", "dev": true, + "license": "MIT", "dependencies": { "cosmiconfig": "^9.0.0", "jiti": "^1.20.0", @@ -11798,13 +13416,15 @@ "version": "0.2.3", "resolved": "https://registry.npmjs.org/postcss-media-query-parser/-/postcss-media-query-parser-0.2.3.tgz", "integrity": "sha512-3sOlxmbKcSHMjlUXQZKQ06jOswE7oVkXPxmZdoB1r5l0q6gTFTQSHxNxOrCccElbW7dxNytifNEo8qidX2Vsig==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/postcss-modules-extract-imports": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.1.0.tgz", "integrity": "sha512-k3kNe0aNFQDAZGbin48pL2VNidTF0w4/eASDsxlyspobzU3wZQLOGj7L9gfRe0Jo9/4uud09DsjFNH7winGv8Q==", "dev": true, + "license": "ISC", "engines": { "node": "^10 || ^12 || >= 14" }, @@ -11817,6 +13437,7 @@ "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.2.0.tgz", "integrity": "sha512-5kcJm/zk+GJDSfw+V/42fJ5fhjL5YbFDl8nVdXkJPLLW+Vf9mTD5Xe0wqIaDnLuL2U6cDNpTr+UQ+v2HWIBhzw==", "dev": true, + "license": "MIT", "dependencies": { "icss-utils": "^5.0.0", "postcss-selector-parser": "^7.0.0", @@ -11834,6 +13455,7 @@ "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-3.2.1.tgz", "integrity": "sha512-m9jZstCVaqGjTAuny8MdgE88scJnCiQSlSrOWcTQgM2t32UBe+MUmFSO5t7VMSfAf/FJKImAxBav8ooCHJXCJA==", "dev": true, + "license": "ISC", "dependencies": { "postcss-selector-parser": "^7.0.0" }, @@ -11849,6 +13471,7 @@ "resolved": "https://registry.npmjs.org/postcss-modules-values/-/postcss-modules-values-4.0.0.tgz", "integrity": "sha512-RDxHkAiEGI78gS2ofyvCsu7iycRv7oqw5xMWn9iMoR0N/7mf9D50ecQqUo5BZ9Zh2vH4bCUR/ktCqbB9m8vJjQ==", "dev": true, + "license": "ISC", "dependencies": { "icss-utils": "^5.0.0" }, @@ -11860,10 +13483,11 @@ } }, "node_modules/postcss-selector-parser": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.0.0.tgz", - "integrity": "sha512-9RbEr1Y7FFfptd/1eEdntyjMwLeghW1bHX9GWjXo19vx4ytPQhANltvVxDggzJl7mnWM+dX28kb6cyS/4iQjlQ==", + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz", + "integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==", "dev": true, + "license": "MIT", "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" @@ -11876,22 +13500,25 @@ "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 + "dev": true, + "license": "MIT" }, "node_modules/prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", "dev": true, + "license": "MIT", "engines": { "node": ">= 0.8.0" } }, "node_modules/prettier": { - "version": "3.4.2", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.4.2.tgz", - "integrity": "sha512-e9MewbtFo+Fevyuxn/4rrcDAaq0IYxPGLvObpQjiZBMAzB9IGmzlnG9RZy3FFas+eBMu2vA0CszMeduow5dIuQ==", + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.5.3.tgz", + "integrity": "sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw==", "dev": true, + "license": "MIT", "bin": { "prettier": "bin/prettier.cjs" }, @@ -11907,6 +13534,7 @@ "resolved": "https://registry.npmjs.org/prettier-eslint/-/prettier-eslint-13.0.0.tgz", "integrity": "sha512-P5K31qWgUOQCtJL/3tpvEe28KfP49qbr6MTVEXC7I2k7ci55bP3YDr+glhyCdhIzxGCVp2f8eobfQ5so52RIIA==", "dev": true, + "license": "MIT", "dependencies": { "@typescript-eslint/parser": "^3.0.0", "common-tags": "^1.4.0", @@ -11930,6 +13558,7 @@ "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.11.tgz", "integrity": "sha512-Zt1yodBx1UcyiePMSkWnU4hPqhwq7hGi2nFL1LeA3EUl+q2LQx16MISgJ0+z7dnmgvP9QtIleuETGOiOH1RcIw==", "dev": true, + "license": "MIT", "dependencies": { "@babel/highlight": "^7.10.4" } @@ -11939,6 +13568,7 @@ "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.4.3.tgz", "integrity": "sha512-J6KFFz5QCYUJq3pf0mjEcCJVERbzv71PUIDczuh9JkwGEzced6CO5ADLHB1rbf/+oPBtoPfMYNOpGDzCANlbXw==", "dev": true, + "license": "MIT", "dependencies": { "ajv": "^6.12.4", "debug": "^4.1.1", @@ -11960,6 +13590,7 @@ "integrity": "sha512-FagtKFz74XrTl7y6HCzQpwDfXP0yhxe9lHLD1UZxjvZIcbyRz8zTFF/yYNfSfzU414eDwZ1SrO0Qvtyf+wFMQg==", "deprecated": "Use @eslint/config-array instead", "dev": true, + "license": "Apache-2.0", "dependencies": { "@humanwhocodes/object-schema": "^1.2.0", "debug": "^4.1.1", @@ -11974,13 +13605,15 @@ "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", "deprecated": "Use @eslint/object-schema instead", - "dev": true + "dev": true, + "license": "BSD-3-Clause" }, "node_modules/prettier-eslint/node_modules/@typescript-eslint/parser": { "version": "3.10.1", "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-3.10.1.tgz", "integrity": "sha512-Ug1RcWcrJP02hmtaXVS3axPPTTPnZjupqhgj+NnZ6BCkwSImWk/283347+x9wN+lqOdK9Eo3vsyiyDHgsmiEJw==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { "@types/eslint-visitor-keys": "^1.0.0", "@typescript-eslint/experimental-utils": "3.10.1", @@ -12009,6 +13642,7 @@ "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-3.10.1.tgz", "integrity": "sha512-+3+FCUJIahE9q0lDi1WleYzjCwJs5hIsbugIgnbB+dSCYUxl8L6PwmsyOPFZde2hc1DlTo/xnkOgiTLSyAbHiQ==", "dev": true, + "license": "MIT", "engines": { "node": "^8.10.0 || ^10.13.0 || >=11.10.1" }, @@ -12022,6 +13656,7 @@ "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-3.10.1.tgz", "integrity": "sha512-QbcXOuq6WYvnB3XPsZpIwztBoquEYLXh2MtwVU+kO8jgYCiv4G5xrSP/1wg4tkvrEE+esZVquIPX/dxPlePk1w==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { "@typescript-eslint/types": "3.10.1", "@typescript-eslint/visitor-keys": "3.10.1", @@ -12050,6 +13685,7 @@ "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-3.10.1.tgz", "integrity": "sha512-9JgC82AaQeglebjZMgYR5wgmfUdUc+EitGUUMW8u2nDckaeimzW+VsoLV6FoimPv2id3VQzfjwBxEMVz08ameQ==", "dev": true, + "license": "MIT", "dependencies": { "eslint-visitor-keys": "^1.1.0" }, @@ -12066,6 +13702,7 @@ "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", "dev": true, + "license": "MIT", "bin": { "acorn": "bin/acorn" }, @@ -12078,6 +13715,7 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "dev": true, + "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -12094,6 +13732,7 @@ "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", "dev": true, + "license": "MIT", "dependencies": { "sprintf-js": "~1.0.2" } @@ -12103,6 +13742,7 @@ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "dev": true, + "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -12114,6 +13754,7 @@ "integrity": "sha512-VHZ8gX+EDfz+97jGcgyGCyRia/dPOd6Xh9yPv8Bl1+SoaIwD+a/vlrOmGRUyOYu7MwUhc7CxqeaDZU13S4+EpA==", "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", "dev": true, + "license": "MIT", "dependencies": { "@babel/code-frame": "7.12.11", "@eslint/eslintrc": "^0.4.3", @@ -12171,6 +13812,7 @@ "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^4.1.1" @@ -12184,6 +13826,7 @@ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", "dev": true, + "license": "Apache-2.0", "engines": { "node": ">=4" } @@ -12193,6 +13836,7 @@ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", "dev": true, + "license": "Apache-2.0", "engines": { "node": ">=10" } @@ -12202,6 +13846,7 @@ "resolved": "https://registry.npmjs.org/espree/-/espree-7.3.1.tgz", "integrity": "sha512-v3JCNCE64umkFpmkFGqzVKsOT0tN1Zr+ueqLZfpV1Ob8e+CEgPWa+OxCoGH3tnhimMKIaBm4m/vaRpJ/krRz2g==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { "acorn": "^7.4.0", "acorn-jsx": "^5.3.1", @@ -12216,6 +13861,7 @@ "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", "dev": true, + "license": "BSD-2-Clause", "engines": { "node": ">=4.0" } @@ -12225,6 +13871,7 @@ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", "dev": true, + "license": "ISC", "dependencies": { "is-glob": "^4.0.1" }, @@ -12237,6 +13884,7 @@ "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", "dev": true, + "license": "MIT", "dependencies": { "type-fest": "^0.20.2" }, @@ -12252,6 +13900,7 @@ "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", "dev": true, + "license": "MIT", "engines": { "node": ">= 4" } @@ -12261,6 +13910,7 @@ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", "dev": true, + "license": "MIT", "dependencies": { "argparse": "^1.0.7", "esprima": "^4.0.0" @@ -12273,13 +13923,15 @@ "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 + "dev": true, + "license": "MIT" }, "node_modules/prettier-eslint/node_modules/minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "dev": true, + "license": "ISC", "dependencies": { "brace-expansion": "^1.1.7" }, @@ -12292,6 +13944,7 @@ "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz", "integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==", "dev": true, + "license": "MIT", "bin": { "prettier": "bin-prettier.js" }, @@ -12306,13 +13959,15 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", - "dev": true + "dev": true, + "license": "BSD-3-Clause" }, "node_modules/prettier-eslint/node_modules/type-fest": { "version": "0.20.2", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", "dev": true, + "license": "(MIT OR CC0-1.0)", "engines": { "node": ">=10" }, @@ -12325,6 +13980,7 @@ "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.9.10.tgz", "integrity": "sha512-w6fIxVE/H1PkLKcCPsFqKE7Kv7QUwhU8qQY2MueZXWx5cPZdwFupLgKK3vntcK98BtNHZtAF4LA/yl2a7k8R6Q==", "dev": true, + "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -12338,6 +13994,7 @@ "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz", "integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==", "dev": true, + "license": "MIT", "dependencies": { "fast-diff": "^1.1.2" }, @@ -12350,6 +14007,7 @@ "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-23.6.0.tgz", "integrity": "sha512-zf9NV1NSlDLDjycnwm6hpFATCGl/K1lt0R/GdkAK2O5LN/rwJoB+Mh93gGJjut4YbmecbfgLWVGSTCr0Ewvvbw==", "dev": true, + "license": "MIT", "dependencies": { "ansi-regex": "^3.0.0", "ansi-styles": "^3.2.0" @@ -12360,6 +14018,7 @@ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", "dev": true, + "license": "MIT", "dependencies": { "color-convert": "^1.9.0" }, @@ -12372,6 +14031,7 @@ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", "dev": true, + "license": "MIT", "dependencies": { "color-name": "1.1.3" } @@ -12380,42 +14040,41 @@ "version": "1.1.3", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/proc-log": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-4.2.0.tgz", - "integrity": "sha512-g8+OnU/L2v+wyiVK+D5fA34J7EH8jZ8DDlvwhRCMxmMj7UCBvxiO1mGeN+36JXIKF4zevU4kRBd8lVgG9vLelA==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-5.0.0.tgz", + "integrity": "sha512-Azwzvl90HaF0aCz1JrDdXQykFakSSNPaPoiZ9fm5qJIMHioDZEi7OAdRwSm6rSoPtY3Qutnm3L7ogmg3dc+wbQ==", "dev": true, + "license": "ISC", "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^18.17.0 || >=20.5.0" } }, "node_modules/process-nextick-args": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "license": "MIT" }, "node_modules/progress": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.4.0" } }, - "node_modules/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 - }, "node_modules/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, + "license": "MIT", "dependencies": { "err-code": "^2.0.2", "retry": "^0.12.0" @@ -12429,6 +14088,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, + "license": "MIT", "dependencies": { "forwarded": "0.2.0", "ipaddr.js": "1.9.1" @@ -12442,6 +14102,7 @@ "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", "dev": true, + "license": "MIT", "engines": { "node": ">= 0.10" } @@ -12451,12 +14112,14 @@ "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz", "integrity": "sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw==", "dev": true, + "license": "MIT", "optional": true }, "node_modules/pump": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.2.tgz", "integrity": "sha512-tUPXtzlGM8FE3P0ZL6DVs/3P58k9nk8/jZeQCurTJylQA8qFYzHFfhBJkuqyE0FifOsQ0uKWekiZ5g8wtr28cw==", + "license": "MIT", "dependencies": { "end-of-stream": "^1.1.0", "once": "^1.3.1" @@ -12466,13 +14129,15 @@ "version": "1.4.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", "integrity": "sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/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, + "license": "MIT", "engines": { "node": ">=0.9" } @@ -12482,6 +14147,7 @@ "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", "dev": true, + "license": "BSD-3-Clause", "dependencies": { "side-channel": "^1.0.6" }, @@ -12510,13 +14176,15 @@ "type": "consulting", "url": "https://feross.org/support" } - ] + ], + "license": "MIT" }, "node_modules/randombytes": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", "dev": true, + "license": "MIT", "dependencies": { "safe-buffer": "^5.1.0" } @@ -12526,6 +14194,7 @@ "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", "dev": true, + "license": "MIT", "engines": { "node": ">= 0.6" } @@ -12535,6 +14204,7 @@ "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", "dev": true, + "license": "MIT", "dependencies": { "bytes": "3.1.2", "http-errors": "2.0.0", @@ -12549,6 +14219,7 @@ "version": "3.6.2", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "license": "MIT", "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", @@ -12559,10 +14230,11 @@ } }, "node_modules/readdirp": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.1.tgz", - "integrity": "sha512-h80JrZu/MHUZCyHu5ciuoI0+WxsCxzxJTILn6Fs8rxSnFPh+UVHYfeIxK1nVGugMqkfC4vJcBOYbkfkwYK0+gw==", + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", "dev": true, + "license": "MIT", "engines": { "node": ">= 14.18.0" }, @@ -12575,19 +14247,22 @@ "version": "0.2.2", "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.2.2.tgz", "integrity": "sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==", - "dev": true + "dev": true, + "license": "Apache-2.0" }, "node_modules/regenerate": { "version": "1.4.2", "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", "integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/regenerate-unicode-properties": { "version": "10.2.0", "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.2.0.tgz", "integrity": "sha512-DqHn3DwbmmPVzeKj9woBadqmXxLvQoQIwu7nopMc72ztvxVmVk2SBhSnx67zuye5TP+lJsb/TBQsjLKhnDf3MA==", "dev": true, + "license": "MIT", "dependencies": { "regenerate": "^1.4.2" }, @@ -12599,28 +14274,32 @@ "version": "0.14.1", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/regenerator-transform": { "version": "0.15.2", "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.15.2.tgz", "integrity": "sha512-hfMp2BoF0qOk3uc5V20ALGDS2ddjQaLrdl7xrGXvAIow7qeWRM2VA2HuCHkUKk9slq3VwEwLNK3DFBqDfPGYtg==", "dev": true, + "license": "MIT", "dependencies": { "@babel/runtime": "^7.8.4" } }, "node_modules/regex-parser": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/regex-parser/-/regex-parser-2.3.0.tgz", - "integrity": "sha512-TVILVSz2jY5D47F4mA4MppkBrafEaiUWJO/TcZHEIuI13AqoZMkK1WMA4Om1YkYbTx+9Ki1/tSUXbceyr9saRg==", - "dev": true + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/regex-parser/-/regex-parser-2.3.1.tgz", + "integrity": "sha512-yXLRqatcCuKtVHsWrNg0JL3l1zGfdXeEvDa0bdu4tCDQw0RpMDZsqbkyRTUnKMR0tXF627V2oEWjBEaEdqTwtQ==", + "dev": true, + "license": "MIT" }, "node_modules/regexpp": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" }, @@ -12633,6 +14312,7 @@ "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-6.2.0.tgz", "integrity": "sha512-H66BPQMrv+V16t8xtmq+UC0CBpiTBA60V8ibS1QVReIp8T1z8hwFxqcGzm9K6lgsN7sB5edVH8a+ze6Fqm4weA==", "dev": true, + "license": "MIT", "dependencies": { "regenerate": "^1.4.2", "regenerate-unicode-properties": "^10.2.0", @@ -12649,13 +14329,15 @@ "version": "0.8.0", "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.8.0.tgz", "integrity": "sha512-RvwtGe3d7LvWiDQXeQw8p5asZUmfU1G/l6WbUXeHta7Y2PEIvBTwH6E2EfmYUK8pxcxEdEmaomqyp0vZZ7C+3Q==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/regjsparser": { "version": "0.12.0", "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.12.0.tgz", "integrity": "sha512-cnE+y8bz4NhMjISKbgeVJtqNbtf5QpjZP+Bslo+UqkIt9QPnX9q095eiRRASJG1/tz6dlNr6Z5NsBiWYokp6EQ==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { "jsesc": "~3.0.2" }, @@ -12668,6 +14350,7 @@ "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.0.2.tgz", "integrity": "sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g==", "dev": true, + "license": "MIT", "bin": { "jsesc": "bin/jsesc" }, @@ -12678,13 +14361,15 @@ "node_modules/reinterval": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/reinterval/-/reinterval-1.1.0.tgz", - "integrity": "sha512-QIRet3SYrGp0HUHO88jVskiG6seqUGC5iAG7AwI/BV4ypGcuqk9Du6YQBUOUqm9c8pw1eyLoIaONifRua1lsEQ==" + "integrity": "sha512-QIRet3SYrGp0HUHO88jVskiG6seqUGC5iAG7AwI/BV4ypGcuqk9Du6YQBUOUqm9c8pw1eyLoIaONifRua1lsEQ==", + "license": "MIT" }, "node_modules/require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -12694,6 +14379,7 @@ "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, + "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -12702,19 +14388,22 @@ "version": "0.8.7", "resolved": "https://registry.npmjs.org/require-relative/-/require-relative-0.8.7.tgz", "integrity": "sha512-AKGr4qvHiryxRb19m3PsLRGuKVAbJLUD7E6eOaHkfKhwc+vSgVOCY5xNvm9EkolBKTOf0GrQAZKLimOCz81Khg==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/requires-port": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/resolve": { "version": "1.22.8", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", "dev": true, + "license": "MIT", "dependencies": { "is-core-module": "^2.13.0", "path-parse": "^1.0.7", @@ -12732,6 +14421,7 @@ "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", "dev": true, + "license": "MIT", "engines": { "node": ">=4" } @@ -12741,6 +14431,7 @@ "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, + "license": "MIT", "dependencies": { "adjust-sourcemap-loader": "^4.0.0", "convert-source-map": "^1.7.0", @@ -12757,6 +14448,7 @@ "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.4.tgz", "integrity": "sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==", "dev": true, + "license": "MIT", "dependencies": { "big.js": "^5.2.2", "emojis-list": "^3.0.0", @@ -12771,6 +14463,7 @@ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "dev": true, + "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" } @@ -12780,6 +14473,7 @@ "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-5.1.0.tgz", "integrity": "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==", "dev": true, + "license": "MIT", "dependencies": { "onetime": "^7.0.0", "signal-exit": "^4.1.0" @@ -12796,15 +14490,17 @@ "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==", "dev": true, + "license": "MIT", "engines": { "node": ">= 4" } }, "node_modules/reusify": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", - "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", "dev": true, + "license": "MIT", "engines": { "iojs": ">=1.0.0", "node": ">=0.10.0" @@ -12813,7 +14509,8 @@ "node_modules/rfdc": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz", - "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==" + "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==", + "license": "MIT" }, "node_modules/rimraf": { "version": "3.0.2", @@ -12821,6 +14518,7 @@ "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", "deprecated": "Rimraf versions prior to v4 are no longer supported", "dev": true, + "license": "ISC", "dependencies": { "glob": "^7.1.3" }, @@ -12832,12 +14530,13 @@ } }, "node_modules/rollup": { - "version": "4.22.4", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.22.4.tgz", - "integrity": "sha512-vD8HJ5raRcWOyymsR6Z3o6+RzfEPCnVLMFJ6vRslO1jt4LO6dUo5Qnpg7y4RkZFM2DMe3WUirkI5c16onjrc6A==", + "version": "4.34.8", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.34.8.tgz", + "integrity": "sha512-489gTVMzAYdiZHFVA/ig/iYFllCcWFHMvUHI1rpFmkoUtRlQxqh6/yiNqnYibjMZ2b/+FUQwldG+aLsEt6bglQ==", "dev": true, + "license": "MIT", "dependencies": { - "@types/estree": "1.0.5" + "@types/estree": "1.0.6" }, "bin": { "rollup": "dist/bin/rollup" @@ -12847,22 +14546,25 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.22.4", - "@rollup/rollup-android-arm64": "4.22.4", - "@rollup/rollup-darwin-arm64": "4.22.4", - "@rollup/rollup-darwin-x64": "4.22.4", - "@rollup/rollup-linux-arm-gnueabihf": "4.22.4", - "@rollup/rollup-linux-arm-musleabihf": "4.22.4", - "@rollup/rollup-linux-arm64-gnu": "4.22.4", - "@rollup/rollup-linux-arm64-musl": "4.22.4", - "@rollup/rollup-linux-powerpc64le-gnu": "4.22.4", - "@rollup/rollup-linux-riscv64-gnu": "4.22.4", - "@rollup/rollup-linux-s390x-gnu": "4.22.4", - "@rollup/rollup-linux-x64-gnu": "4.22.4", - "@rollup/rollup-linux-x64-musl": "4.22.4", - "@rollup/rollup-win32-arm64-msvc": "4.22.4", - "@rollup/rollup-win32-ia32-msvc": "4.22.4", - "@rollup/rollup-win32-x64-msvc": "4.22.4", + "@rollup/rollup-android-arm-eabi": "4.34.8", + "@rollup/rollup-android-arm64": "4.34.8", + "@rollup/rollup-darwin-arm64": "4.34.8", + "@rollup/rollup-darwin-x64": "4.34.8", + "@rollup/rollup-freebsd-arm64": "4.34.8", + "@rollup/rollup-freebsd-x64": "4.34.8", + "@rollup/rollup-linux-arm-gnueabihf": "4.34.8", + "@rollup/rollup-linux-arm-musleabihf": "4.34.8", + "@rollup/rollup-linux-arm64-gnu": "4.34.8", + "@rollup/rollup-linux-arm64-musl": "4.34.8", + "@rollup/rollup-linux-loongarch64-gnu": "4.34.8", + "@rollup/rollup-linux-powerpc64le-gnu": "4.34.8", + "@rollup/rollup-linux-riscv64-gnu": "4.34.8", + "@rollup/rollup-linux-s390x-gnu": "4.34.8", + "@rollup/rollup-linux-x64-gnu": "4.34.8", + "@rollup/rollup-linux-x64-musl": "4.34.8", + "@rollup/rollup-win32-arm64-msvc": "4.34.8", + "@rollup/rollup-win32-ia32-msvc": "4.34.8", + "@rollup/rollup-win32-x64-msvc": "4.34.8", "fsevents": "~2.3.2" } }, @@ -12871,6 +14573,7 @@ "resolved": "https://registry.npmjs.org/run-applescript/-/run-applescript-7.0.0.tgz", "integrity": "sha512-9by4Ij99JUr/MCFBUkDKLWK3G9HVXmabKz9U5MlIAIuvuzkiOicRYs8XJLxX+xahD+mLiiCYDqF9dKAgtzKP1A==", "dev": true, + "license": "MIT", "engines": { "node": ">=18" }, @@ -12897,14 +14600,16 @@ "url": "https://feross.org/support" } ], + "license": "MIT", "dependencies": { "queue-microtask": "^1.2.2" } }, "node_modules/rxjs": { - "version": "7.8.1", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", - "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==", + "version": "7.8.2", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz", + "integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==", + "license": "Apache-2.0", "dependencies": { "tslib": "^2.1.0" } @@ -12926,13 +14631,15 @@ "type": "consulting", "url": "https://feross.org/support" } - ] + ], + "license": "MIT" }, "node_modules/safe-regex-test": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.1.0.tgz", "integrity": "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==", "dev": true, + "license": "MIT", "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", @@ -12949,16 +14656,18 @@ "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/sass": { - "version": "1.77.6", - "resolved": "https://registry.npmjs.org/sass/-/sass-1.77.6.tgz", - "integrity": "sha512-ByXE1oLD79GVq9Ht1PeHWCPMPB8XHpBuz1r85oByKHjZY6qV6rWnQovQzXJXuQ/XyE1Oj3iPk3lo28uzaRA2/Q==", + "version": "1.85.0", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.85.0.tgz", + "integrity": "sha512-3ToiC1xZ1Y8aU7+CkgCI/tqyuPXEmYGJXO7H4uqp0xkLXUqp88rQQ4j1HmP37xSJLbCJPaIiv+cT1y+grssrww==", "dev": true, + "license": "MIT", "dependencies": { - "chokidar": ">=3.0.0 <4.0.0", - "immutable": "^4.0.0", + "chokidar": "^4.0.0", + "immutable": "^5.0.2", "source-map-js": ">=0.6.2 <2.0.0" }, "bin": { @@ -12966,13 +14675,17 @@ }, "engines": { "node": ">=14.0.0" + }, + "optionalDependencies": { + "@parcel/watcher": "^2.4.1" } }, "node_modules/sass-loader": { - "version": "16.0.0", - "resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-16.0.0.tgz", - "integrity": "sha512-n13Z+3rU9A177dk4888czcVFiC8CL9dii4qpXWUg3YIIgZEvi9TCFKjOQcbK0kJM7DJu9VucrZFddvNfYCPwtw==", + "version": "16.0.5", + "resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-16.0.5.tgz", + "integrity": "sha512-oL+CMBXrj6BZ/zOq4os+UECPL+bWqt6OAC6DWS8Ln8GZRcMDjlJ4JC3FBDuHJdYaFWIdKNIBYmtZtK2MaMkNIw==", "dev": true, + "license": "MIT", "dependencies": { "neo-async": "^2.6.2" }, @@ -13008,78 +14721,20 @@ } } }, - "node_modules/sass/node_modules/chokidar": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", - "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", - "dev": true, - "dependencies": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" - }, - "engines": { - "node": ">= 8.10.0" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" - } - }, - "node_modules/sass/node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/sass/node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true, - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/sass/node_modules/readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "dev": true, - "dependencies": { - "picomatch": "^2.2.1" - }, - "engines": { - "node": ">=8.10.0" - } - }, "node_modules/sax": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/sax/-/sax-1.4.1.tgz", "integrity": "sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==", "dev": true, + "license": "ISC", "optional": true }, "node_modules/schema-utils": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.0.tgz", - "integrity": "sha512-Gf9qqc58SpCA/xdziiHz35F4GNIWYWZrEshUc/G/r5BnLph6xpKuLeoJoQuj5WfBIx/eQLf+hmVPYHaxJu7V2g==", + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.2.tgz", + "integrity": "sha512-Gn/JaSk/Mt9gYubxTtSn/QCV4em9mpAPiR1rqy/Ocu19u/G9J5WWdNoUT4SiV6mFC3y6cxyFcFwdzPM3FgxGAQ==", "dev": true, + "license": "MIT", "dependencies": { "@types/json-schema": "^7.0.9", "ajv": "^8.9.0", @@ -13099,6 +14754,7 @@ "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", "dev": true, + "license": "MIT", "dependencies": { "ajv": "^8.0.0" }, @@ -13115,13 +14771,15 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz", "integrity": "sha512-mEugaLK+YfkijB4fx0e6kImuJdCIt2LxCRcbEYPqRGCs4F2ogyfZU5IAZRdjCP8JPq2AtdNoC/Dux63d9Kiryg==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/selfsigned": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-2.4.1.tgz", "integrity": "sha512-th5B4L2U+eGLq1TVh7zNRGBapioSORUeymIydxgFpwww9d2qyKvtuPU2jJuHvYAwwqi2Y596QBL3eEqcPEYL8Q==", "dev": true, + "license": "MIT", "dependencies": { "@types/node-forge": "^1.3.0", "node-forge": "^1" @@ -13131,10 +14789,11 @@ } }, "node_modules/semver": { - "version": "7.6.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", - "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "version": "7.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", + "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==", "dev": true, + "license": "ISC", "bin": { "semver": "bin/semver.js" }, @@ -13147,6 +14806,7 @@ "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", "dev": true, + "license": "MIT", "dependencies": { "debug": "2.6.9", "depd": "2.0.0", @@ -13171,6 +14831,7 @@ "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", "dev": true, + "license": "MIT", "dependencies": { "ms": "2.0.0" } @@ -13179,13 +14840,15 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/send/node_modules/mime": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", "dev": true, + "license": "MIT", "bin": { "mime": "cli.js" }, @@ -13198,6 +14861,7 @@ "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", "dev": true, + "license": "MIT", "engines": { "node": ">= 0.8" } @@ -13207,6 +14871,7 @@ "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", "dev": true, + "license": "BSD-3-Clause", "dependencies": { "randombytes": "^2.1.0" } @@ -13216,6 +14881,7 @@ "resolved": "https://registry.npmjs.org/serve-index/-/serve-index-1.9.1.tgz", "integrity": "sha512-pXHfKNP4qujrtteMrSBb0rc8HJ9Ms/GrXwcUtUtD5s4ewDJI8bT3Cz2zTVRMKtri49pLx2e0Ya8ziP5Ya2pZZw==", "dev": true, + "license": "MIT", "dependencies": { "accepts": "~1.3.4", "batch": "0.6.1", @@ -13234,6 +14900,7 @@ "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", "dev": true, + "license": "MIT", "dependencies": { "ms": "2.0.0" } @@ -13243,6 +14910,7 @@ "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==", "dev": true, + "license": "MIT", "engines": { "node": ">= 0.6" } @@ -13252,6 +14920,7 @@ "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", "integrity": "sha512-lks+lVC8dgGyh97jxvxeYTWQFvh4uw4yC12gVl63Cg30sjPX4wuGcdkICVXDAESr6OJGjqGA8Iz5mkeN6zlD7A==", "dev": true, + "license": "MIT", "dependencies": { "depd": "~1.1.2", "inherits": "2.0.3", @@ -13266,25 +14935,29 @@ "version": "2.0.3", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", "integrity": "sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==", - "dev": true + "dev": true, + "license": "ISC" }, "node_modules/serve-index/node_modules/ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/serve-index/node_modules/setprototypeof": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==", - "dev": true + "dev": true, + "license": "ISC" }, "node_modules/serve-static": { "version": "1.16.2", "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", "dev": true, + "license": "MIT", "dependencies": { "encodeurl": "~2.0.0", "escape-html": "~1.0.3", @@ -13300,6 +14973,7 @@ "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", "dev": true, + "license": "MIT", "engines": { "node": ">= 0.8" } @@ -13308,13 +14982,15 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", - "dev": true + "dev": true, + "license": "ISC" }, "node_modules/shallow-clone": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==", "dev": true, + "license": "MIT", "dependencies": { "kind-of": "^6.0.2" }, @@ -13327,6 +15003,7 @@ "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", "dev": true, + "license": "MIT", "dependencies": { "shebang-regex": "^3.0.0" }, @@ -13339,6 +15016,7 @@ "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } @@ -13348,6 +15026,7 @@ "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.2.tgz", "integrity": "sha512-AzqKpGKjrj7EM6rKVQEPpB288oCfnrEIuyoT9cyF4nmGa7V8Zk6f7RRqYisX8X9m+Q7bd632aZW4ky7EhbQztA==", "dev": true, + "license": "MIT", "engines": { "node": ">= 0.4" }, @@ -13360,6 +15039,7 @@ "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", "dev": true, + "license": "MIT", "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.3", @@ -13379,6 +15059,7 @@ "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", "dev": true, + "license": "MIT", "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.3" @@ -13395,6 +15076,7 @@ "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", "dev": true, + "license": "MIT", "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", @@ -13413,6 +15095,7 @@ "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", "dev": true, + "license": "MIT", "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", @@ -13432,6 +15115,7 @@ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", "dev": true, + "license": "ISC", "engines": { "node": ">=14" }, @@ -13440,20 +15124,21 @@ } }, "node_modules/sigstore": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/sigstore/-/sigstore-2.3.1.tgz", - "integrity": "sha512-8G+/XDU8wNsJOQS5ysDVO0Etg9/2uA5gR9l4ZwijjlwxBcrU6RPfwi2+jJmbP+Ap1Hlp/nVAaEO4Fj22/SL2gQ==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/sigstore/-/sigstore-3.1.0.tgz", + "integrity": "sha512-ZpzWAFHIFqyFE56dXqgX/DkDRZdz+rRcjoIk/RQU4IX0wiCv1l8S7ZrXDHcCc+uaf+6o7w3h2l3g6GYG5TKN9Q==", "dev": true, + "license": "Apache-2.0", "dependencies": { - "@sigstore/bundle": "^2.3.2", - "@sigstore/core": "^1.0.0", - "@sigstore/protobuf-specs": "^0.3.2", - "@sigstore/sign": "^2.3.2", - "@sigstore/tuf": "^2.3.4", - "@sigstore/verify": "^1.2.1" + "@sigstore/bundle": "^3.1.0", + "@sigstore/core": "^2.0.0", + "@sigstore/protobuf-specs": "^0.4.0", + "@sigstore/sign": "^3.1.0", + "@sigstore/tuf": "^3.1.0", + "@sigstore/verify": "^2.1.0" }, "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": "^18.17.0 || >=20.5.0" } }, "node_modules/slash": { @@ -13461,6 +15146,7 @@ "resolved": "https://registry.npmjs.org/slash/-/slash-5.1.0.tgz", "integrity": "sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg==", "dev": true, + "license": "MIT", "engines": { "node": ">=14.16" }, @@ -13473,6 +15159,7 @@ "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-5.0.0.tgz", "integrity": "sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ==", "dev": true, + "license": "MIT", "dependencies": { "ansi-styles": "^6.0.0", "is-fullwidth-code-point": "^4.0.0" @@ -13489,6 +15176,7 @@ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", "dev": true, + "license": "MIT", "engines": { "node": ">=12" }, @@ -13501,6 +15189,7 @@ "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", "dev": true, + "license": "MIT", "engines": { "node": ">= 6.0.0", "npm": ">= 3.0.0" @@ -13511,6 +15200,7 @@ "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.8.1.tgz", "integrity": "sha512-oZ7iUCxph8WYRHHcjBEc9unw3adt5CmSNlppj/5Q4k2RIrhl8Z5yY2Xr4j9zj0+wzVZ0bxmYoGSzKJnRl6A4yg==", "dev": true, + "license": "MIT", "dependencies": { "accepts": "~1.3.4", "base64id": "~2.0.0", @@ -13529,6 +15219,7 @@ "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.5.5.tgz", "integrity": "sha512-eLDQas5dzPgOWCk9GuuJC2lBqItuhKI4uxGgo9aIV7MYbk2h9Q6uULEh8WBzThoI7l+qU9Ast9fVUmkqPP9wYg==", "dev": true, + "license": "MIT", "dependencies": { "debug": "~4.3.4", "ws": "~8.17.1" @@ -13539,6 +15230,7 @@ "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", "dev": true, + "license": "MIT", "dependencies": { "ms": "^2.1.3" }, @@ -13556,6 +15248,7 @@ "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=10.0.0" }, @@ -13577,6 +15270,7 @@ "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.4.tgz", "integrity": "sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==", "dev": true, + "license": "MIT", "dependencies": { "@socket.io/component-emitter": "~3.1.0", "debug": "~4.3.1" @@ -13590,6 +15284,7 @@ "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", "dev": true, + "license": "MIT", "dependencies": { "ms": "^2.1.3" }, @@ -13607,6 +15302,7 @@ "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", "dev": true, + "license": "MIT", "dependencies": { "ms": "^2.1.3" }, @@ -13624,6 +15320,7 @@ "resolved": "https://registry.npmjs.org/sockjs/-/sockjs-0.3.24.tgz", "integrity": "sha512-GJgLTZ7vYb/JtPSSZ10hsOYIvEYsjbNU+zPdIHcUaWVNUEPivzxku31865sSSud0Da0W4lEeOPlmw93zLQchuQ==", "dev": true, + "license": "MIT", "dependencies": { "faye-websocket": "^0.11.3", "uuid": "^8.3.2", @@ -13631,10 +15328,11 @@ } }, "node_modules/socks": { - "version": "2.8.3", - "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.3.tgz", - "integrity": "sha512-l5x7VUUWbjVFbafGLxPWkYsHIhEvmF85tbIeFZWc8ZPtoMyybuEhL7Jye/ooC4/d48FgOjSJXgsF/AJPYCW8Zw==", + "version": "2.8.4", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.4.tgz", + "integrity": "sha512-D3YaD0aRxR3mEcqnidIs7ReYJFVzWdd6fXJYUM8ixcQcJRGTka/b3saV0KflYhyVJXKhb947GndU35SxYNResQ==", "dev": true, + "license": "MIT", "dependencies": { "ip-address": "^9.0.5", "smart-buffer": "^4.2.0" @@ -13649,6 +15347,7 @@ "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.5.tgz", "integrity": "sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw==", "dev": true, + "license": "MIT", "dependencies": { "agent-base": "^7.1.2", "debug": "^4.3.4", @@ -13663,6 +15362,7 @@ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==", "dev": true, + "license": "BSD-3-Clause", "engines": { "node": ">= 8" } @@ -13672,6 +15372,7 @@ "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", "dev": true, + "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" } @@ -13681,6 +15382,7 @@ "resolved": "https://registry.npmjs.org/source-map-loader/-/source-map-loader-5.0.0.tgz", "integrity": "sha512-k2Dur7CbSLcAH73sBcIkV5xjPV4SzqO1NJ7+XaQl8if3VODDUj3FNchNGpqgJSKbvUfJuhVdv8K2Eu8/TNl2eA==", "dev": true, + "license": "MIT", "dependencies": { "iconv-lite": "^0.6.3", "source-map-js": "^1.0.2" @@ -13701,6 +15403,7 @@ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", "dev": true, + "license": "MIT", "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" }, @@ -13713,6 +15416,7 @@ "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", "dev": true, + "license": "MIT", "dependencies": { "buffer-from": "^1.0.0", "source-map": "^0.6.0" @@ -13723,6 +15427,7 @@ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "dev": true, + "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" } @@ -13732,6 +15437,7 @@ "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.2.0.tgz", "integrity": "sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==", "dev": true, + "license": "Apache-2.0", "dependencies": { "spdx-expression-parse": "^3.0.0", "spdx-license-ids": "^3.0.0" @@ -13741,13 +15447,15 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.5.0.tgz", "integrity": "sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==", - "dev": true + "dev": true, + "license": "CC-BY-3.0" }, "node_modules/spdx-expression-parse": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", "dev": true, + "license": "MIT", "dependencies": { "spdx-exceptions": "^2.1.0", "spdx-license-ids": "^3.0.0" @@ -13757,13 +15465,15 @@ "version": "3.0.21", "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.21.tgz", "integrity": "sha512-Bvg/8F5XephndSK3JffaRqdT+gyhfqIPwDHpX80tJrF8QQRYMo8sNMeaZ2Dp5+jhwKnUmIOyFFQfHRkjJm5nXg==", - "dev": true + "dev": true, + "license": "CC0-1.0" }, "node_modules/spdy": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/spdy/-/spdy-4.0.2.tgz", "integrity": "sha512-r46gZQZQV+Kl9oItvl1JZZqJKGr+oEkB08A6BzkiR7593/7IbtuncXHd2YoYeTsG4157ZssMu9KYvUHLcjcDoA==", "dev": true, + "license": "MIT", "dependencies": { "debug": "^4.1.0", "handle-thing": "^2.0.0", @@ -13780,6 +15490,7 @@ "resolved": "https://registry.npmjs.org/spdy-transport/-/spdy-transport-3.0.0.tgz", "integrity": "sha512-hsLVFE5SjA6TCisWeJXFKniGGOpBgMLmerfO2aCyCU5s7nJ/rpAepqmFifv/GCbSbueEeAJJnmSQ2rKC/g8Fcw==", "dev": true, + "license": "MIT", "dependencies": { "debug": "^4.1.0", "detect-node": "^2.0.4", @@ -13793,6 +15504,7 @@ "version": "3.2.2", "resolved": "https://registry.npmjs.org/split2/-/split2-3.2.2.tgz", "integrity": "sha512-9NThjpgZnifTkJpzTZ7Eue85S49QwpNhZTq6GRJwObb6jnLFNGB7Qm73V5HewTROPyxD0C29xqmaI68bQtV+hg==", + "license": "ISC", "dependencies": { "readable-stream": "^3.0.0" } @@ -13801,18 +15513,20 @@ "version": "1.1.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz", "integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==", - "dev": true + "dev": true, + "license": "BSD-3-Clause" }, "node_modules/ssri": { - "version": "10.0.6", - "resolved": "https://registry.npmjs.org/ssri/-/ssri-10.0.6.tgz", - "integrity": "sha512-MGrFH9Z4NP9Iyhqn16sDtBpRRNJ0Y2hNa6D65h736fVSaPCHr4DM4sWUNvVaSuC+0OBGhwsrydQwmgfg5LncqQ==", + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-12.0.0.tgz", + "integrity": "sha512-S7iGNosepx9RadX82oimUkvr0Ct7IjJbEbs4mJcTxst8um95J3sDYU1RBEOvdu6oL1Wek2ODI5i4MAw+dZ6cAQ==", "dev": true, + "license": "ISC", "dependencies": { "minipass": "^7.0.3" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^18.17.0 || >=20.5.0" } }, "node_modules/statuses": { @@ -13820,6 +15534,7 @@ "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==", "dev": true, + "license": "MIT", "engines": { "node": ">= 0.6" } @@ -13827,13 +15542,15 @@ "node_modules/stream-shift": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.3.tgz", - "integrity": "sha512-76ORR0DO1o1hlKwTbi/DM3EXWGf3ZJYO8cXX5RJwnul2DEg2oyoZyjLNoQM8WsvZiFKCRfC1O0J7iCvie3RZmQ==" + "integrity": "sha512-76ORR0DO1o1hlKwTbi/DM3EXWGf3ZJYO8cXX5RJwnul2DEg2oyoZyjLNoQM8WsvZiFKCRfC1O0J7iCvie3RZmQ==", + "license": "MIT" }, "node_modules/streamroller": { "version": "3.1.5", "resolved": "https://registry.npmjs.org/streamroller/-/streamroller-3.1.5.tgz", "integrity": "sha512-KFxaM7XT+irxvdqSP1LGLgNWbYN7ay5owZ3r/8t77p+EtSUAfUgtl7be3xtqtOmGUl9K9YPO2ca8133RlTjvKw==", "dev": true, + "license": "MIT", "dependencies": { "date-format": "^4.0.14", "debug": "^4.3.4", @@ -13847,6 +15564,7 @@ "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==", + "license": "MIT", "dependencies": { "safe-buffer": "~5.2.0" } @@ -13856,6 +15574,7 @@ "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", "dev": true, + "license": "MIT", "dependencies": { "emoji-regex": "^10.3.0", "get-east-asian-width": "^1.0.0", @@ -13874,6 +15593,7 @@ "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "dev": true, + "license": "MIT", "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", @@ -13887,13 +15607,15 @@ "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 + "dev": true, + "license": "MIT" }, "node_modules/string-width-cjs/node_modules/is-fullwidth-code-point": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } @@ -13903,6 +15625,7 @@ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", "dev": true, + "license": "MIT", "engines": { "node": ">=12" }, @@ -13915,6 +15638,7 @@ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", "dev": true, + "license": "MIT", "dependencies": { "ansi-regex": "^6.0.1" }, @@ -13930,6 +15654,7 @@ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "dev": true, + "license": "MIT", "dependencies": { "ansi-regex": "^5.0.1" }, @@ -13943,6 +15668,7 @@ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "dev": true, + "license": "MIT", "dependencies": { "ansi-regex": "^5.0.1" }, @@ -13955,6 +15681,7 @@ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } @@ -13964,24 +15691,17 @@ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } }, - "node_modules/strip-final-newline": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", - "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", - "dev": true, - "engines": { - "node": ">=6" - } - }, "node_modules/strip-json-comments": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" }, @@ -13994,6 +15714,7 @@ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, + "license": "MIT", "dependencies": { "has-flag": "^4.0.0" }, @@ -14006,6 +15727,7 @@ "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", "dev": true, + "license": "MIT", "engines": { "node": ">= 0.4" }, @@ -14018,24 +15740,26 @@ "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-4.0.0.tgz", "integrity": "sha512-b19dMThMV4HVFynSAM1++gBHAbk2Tc/osgLIBZMKsyqh34jb2e8Os7T6ZW/Bt3pJFdBTd2JwAnAAEQV7rSNvcQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.10" } }, "node_modules/synckit": { - "version": "0.9.2", - "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.9.2.tgz", - "integrity": "sha512-vrozgXDQwYO72vHjUb/HnFbQx1exDjoKzqx23aXEg2a9VIg2TSFZ8FmeZpTjUCFMYw7mpX4BE2SFu8wI7asYsw==", + "version": "0.11.4", + "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.11.4.tgz", + "integrity": "sha512-Q/XQKRaJiLiFIBNN+mndW7S/RHxvwzuZS6ZwmRzUBqJBv/5QIKCEwkBC8GBf8EQJKYnaFs0wOZbKTXBPj8L9oQ==", "dev": true, + "license": "MIT", "dependencies": { - "@pkgr/core": "^0.1.0", - "tslib": "^2.6.2" + "@pkgr/core": "^0.2.3", + "tslib": "^2.8.1" }, "engines": { "node": "^14.18.0 || >=16.0.0" }, "funding": { - "url": "https://opencollective.com/unts" + "url": "https://opencollective.com/synckit" } }, "node_modules/table": { @@ -14043,6 +15767,7 @@ "resolved": "https://registry.npmjs.org/table/-/table-6.9.0.tgz", "integrity": "sha512-9kY+CygyYM6j02t5YFHbNz2FN5QmYGv9zAjVp4lCDjlCw7amdckXlEt/bjMhUIfj4ThGRE4gCUH5+yGnNuPo5A==", "dev": true, + "license": "BSD-3-Clause", "dependencies": { "ajv": "^8.0.1", "lodash.truncate": "^4.4.2", @@ -14058,13 +15783,15 @@ "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 + "dev": true, + "license": "MIT" }, "node_modules/table/node_modules/is-fullwidth-code-point": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } @@ -14074,6 +15801,7 @@ "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", "dev": true, + "license": "MIT", "dependencies": { "ansi-styles": "^4.0.0", "astral-regex": "^2.0.0", @@ -14091,6 +15819,7 @@ "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "dev": true, + "license": "MIT", "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", @@ -14105,6 +15834,7 @@ "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=6" } @@ -14114,6 +15844,7 @@ "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz", "integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==", "dev": true, + "license": "ISC", "dependencies": { "chownr": "^2.0.0", "fs-minipass": "^2.0.0", @@ -14131,6 +15862,7 @@ "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", "dev": true, + "license": "ISC", "dependencies": { "minipass": "^3.0.0" }, @@ -14143,6 +15875,7 @@ "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", "dev": true, + "license": "ISC", "dependencies": { "yallist": "^4.0.0" }, @@ -14155,6 +15888,34 @@ "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", "dev": true, + "license": "ISC", + "engines": { + "node": ">=8" + } + }, + "node_modules/tar/node_modules/minizlib": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", + "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", + "dev": true, + "license": "MIT", + "dependencies": { + "minipass": "^3.0.0", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/tar/node_modules/minizlib/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, "engines": { "node": ">=8" } @@ -14164,6 +15925,7 @@ "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", "dev": true, + "license": "MIT", "bin": { "mkdirp": "bin/cmd.js" }, @@ -14175,13 +15937,15 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true + "dev": true, + "license": "ISC" }, "node_modules/terser": { - "version": "5.31.6", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.31.6.tgz", - "integrity": "sha512-PQ4DAriWzKj+qgehQ7LK5bQqCFNMmlhjR2PFFLuqGCpuCAauxemVBWwWOxo3UIwWQx8+Pr61Df++r76wDmkQBg==", + "version": "5.39.0", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.39.0.tgz", + "integrity": "sha512-LBAhFyLho16harJoWMg/nZsQYgTrg5jXOn2nCYjRUcZZEdE3qa2zb8QEDRUGVZBW4rlazf2fxkg8tztybTaqWw==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { "@jridgewell/source-map": "^0.3.3", "acorn": "^8.8.2", @@ -14196,10 +15960,11 @@ } }, "node_modules/terser-webpack-plugin": { - "version": "5.3.11", - "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.11.tgz", - "integrity": "sha512-RVCsMfuD0+cTt3EwX8hSl2Ks56EbFHWmhluwcqoPKtBnfjiT6olaq7PRIRfhyU8nnC2MrnDrBLfrD/RGE+cVXQ==", + "version": "5.3.14", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.14.tgz", + "integrity": "sha512-vkZjpUjb6OMS7dhV+tILUW6BhpDR7P2L/aQSAv+Uwk+m8KATX9EccViHTJR2qDtACKPIYndLGCyl3FMo+r2LMw==", "dev": true, + "license": "MIT", "dependencies": { "@jridgewell/trace-mapping": "^0.3.25", "jest-worker": "^27.4.5", @@ -14233,31 +15998,52 @@ "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/thingies": { "version": "1.21.0", "resolved": "https://registry.npmjs.org/thingies/-/thingies-1.21.0.tgz", "integrity": "sha512-hsqsJsFMsV+aD4s3CWKk85ep/3I9XzYV/IXaSouJMYIoDlgyi11cBhsqYe9/geRfB0YIikBQg6raRaM+nIMP9g==", "dev": true, + "license": "Unlicense", "engines": { "node": ">=10.18" }, - "peerDependencies": { - "tslib": "^2" + "peerDependencies": { + "tslib": "^2" + } + }, + "node_modules/thunky": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/thunky/-/thunky-1.1.0.tgz", + "integrity": "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyglobby": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.13.tgz", + "integrity": "sha512-mEwzpUgrLySlveBwEVDMKk5B57bhLPYovRfPAXD5gA/98Opn0rCDj3GtLwFvCvH5RK9uPCExUROW5NjDwvqkxw==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.4.4", + "picomatch": "^4.0.2" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" } }, - "node_modules/thunky": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/thunky/-/thunky-1.1.0.tgz", - "integrity": "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==", - "dev": true - }, "node_modules/tmp": { "version": "0.0.33", "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", "dev": true, + "license": "MIT", "dependencies": { "os-tmpdir": "~1.0.2" }, @@ -14270,6 +16056,7 @@ "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", "dev": true, + "license": "MIT", "dependencies": { "is-number": "^7.0.0" }, @@ -14282,6 +16069,7 @@ "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.6" } @@ -14291,6 +16079,7 @@ "resolved": "https://registry.npmjs.org/tree-dump/-/tree-dump-1.0.2.tgz", "integrity": "sha512-dpev9ABuLWdEubk+cIaI9cHwRNNDjkBBLXTwI4UCUFdQ5xXKqNXoK4FEciw/vxf+NQ7Cb7sGUyeUtORvHIdRXQ==", "dev": true, + "license": "Apache-2.0", "engines": { "node": ">=10.0" }, @@ -14307,15 +16096,17 @@ "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", "dev": true, + "license": "MIT", "bin": { "tree-kill": "cli.js" } }, "node_modules/ts-api-utils": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.0.0.tgz", - "integrity": "sha512-xCt/TOAc+EOHS1XPnijD3/yzpH6qg2xppZO1YDqGoVsNXfQfzHpOdNuXwrwOU8u4ITXJyDCTyt8w5g1sZv9ynQ==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz", + "integrity": "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=18.12" }, @@ -14326,13 +16117,15 @@ "node_modules/tslib": { "version": "2.8.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" }, "node_modules/tsutils": { "version": "3.21.0", "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", "dev": true, + "license": "MIT", "dependencies": { "tslib": "^1.8.1" }, @@ -14347,20 +16140,22 @@ "version": "1.14.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "dev": true + "dev": true, + "license": "0BSD" }, "node_modules/tuf-js": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/tuf-js/-/tuf-js-2.2.1.tgz", - "integrity": "sha512-GwIJau9XaA8nLVbUXsN3IlFi7WmQ48gBUrl3FTkkL/XLu/POhBzfmX9hd33FNMX1qAsfl6ozO1iMmW9NC8YniA==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/tuf-js/-/tuf-js-3.0.1.tgz", + "integrity": "sha512-+68OP1ZzSF84rTckf3FA95vJ1Zlx/uaXyiiKyPd1pA4rZNkpEvDAKmsu1xUSmbF/chCRYgZ6UZkDwC7PmzmAyA==", "dev": true, + "license": "MIT", "dependencies": { - "@tufjs/models": "2.0.1", - "debug": "^4.3.4", - "make-fetch-happen": "^13.0.1" + "@tufjs/models": "3.0.1", + "debug": "^4.3.6", + "make-fetch-happen": "^14.0.1" }, "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": "^18.17.0 || >=20.5.0" } }, "node_modules/type-check": { @@ -14368,6 +16163,7 @@ "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", "dev": true, + "license": "MIT", "dependencies": { "prelude-ls": "^1.2.1" }, @@ -14380,6 +16176,7 @@ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", "dev": true, + "license": "(MIT OR CC0-1.0)", "engines": { "node": ">=10" }, @@ -14392,6 +16189,7 @@ "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", "dev": true, + "license": "MIT", "dependencies": { "media-typer": "0.3.0", "mime-types": "~2.1.24" @@ -14404,18 +16202,21 @@ "version": "1.0.9", "resolved": "https://registry.npmjs.org/typed-assert/-/typed-assert-1.0.9.tgz", "integrity": "sha512-KNNZtayBCtmnNmbo5mG47p1XsCyrx6iVqomjcZnec/1Y5GGARaxPs6r49RnSPeUP3YjNYiU9sQHAtY4BBvnZwg==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/typedarray": { "version": "0.0.6", "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", - "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==" + "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==", + "license": "MIT" }, "node_modules/typescript": { "version": "5.5.4", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.4.tgz", "integrity": "sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q==", "dev": true, + "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -14443,6 +16244,7 @@ "url": "https://github.com/sponsors/faisalman" } ], + "license": "MIT", "bin": { "ua-parser-js": "script/cli.js" }, @@ -14451,16 +16253,18 @@ } }, "node_modules/undici-types": { - "version": "6.20.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz", - "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==", - "dev": true + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true, + "license": "MIT" }, "node_modules/unicode-canonical-property-names-ecmascript": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.1.tgz", "integrity": "sha512-dA8WbNeb2a6oQzAQ55YlT5vQAWGV9WXOsi3SskE3bcCdM0P4SDd+24zS/OCacdRq5BkdsRj9q3Pg6YyQoxIGqg==", "dev": true, + "license": "MIT", "engines": { "node": ">=4" } @@ -14470,6 +16274,7 @@ "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz", "integrity": "sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==", "dev": true, + "license": "MIT", "dependencies": { "unicode-canonical-property-names-ecmascript": "^2.0.0", "unicode-property-aliases-ecmascript": "^2.0.0" @@ -14483,6 +16288,7 @@ "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.2.0.tgz", "integrity": "sha512-4IehN3V/+kkr5YeSSDDQG8QLqO26XpL2XP3GQtqwlT/QYSECAwFztxVHjlbh0+gjJ3XmNLS0zDsbgs9jWKExLg==", "dev": true, + "license": "MIT", "engines": { "node": ">=4" } @@ -14492,15 +16298,17 @@ "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, + "license": "MIT", "engines": { "node": ">=4" } }, "node_modules/unicorn-magic": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.1.0.tgz", - "integrity": "sha512-lRfVq8fE8gz6QMBuDM6a+LO3IAzTi05H6gCVaUpir2E1Rwpo4ZUog45KpNXKC/Mn3Yb9UDuHumeFTo9iV/D9FQ==", + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.3.0.tgz", + "integrity": "sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA==", "dev": true, + "license": "MIT", "engines": { "node": ">=18" }, @@ -14509,27 +16317,29 @@ } }, "node_modules/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==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-4.0.0.tgz", + "integrity": "sha512-XSnEewXmQ+veP7xX2dS5Q4yZAvO40cBN2MWkJ7D/6sW4Dg6wYBNwM1Vrnz1FhH5AdeLIlUXRI9e28z1YZi71NQ==", "dev": true, + "license": "ISC", "dependencies": { - "unique-slug": "^4.0.0" + "unique-slug": "^5.0.0" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^18.17.0 || >=20.5.0" } }, "node_modules/unique-slug": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-4.0.0.tgz", - "integrity": "sha512-WrcA6AyEfqDX5bWige/4NQfPZMtASNVxdmWR76WESYQVAACSgWcR6e9i0mofqqBxYFtL4oAxPIptY73/0YE1DQ==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-5.0.0.tgz", + "integrity": "sha512-9OdaqO5kwqR+1kVgHAhsp5vPNU0hnxRa26rBFNfNgM7M6pNtgzeBn3s/xbyCQL3dcjzOatcef6UUHpB/6MaETg==", "dev": true, + "license": "ISC", "dependencies": { "imurmurhash": "^0.1.4" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^18.17.0 || >=20.5.0" } }, "node_modules/universalify": { @@ -14537,6 +16347,7 @@ "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", "dev": true, + "license": "MIT", "engines": { "node": ">= 4.0.0" } @@ -14546,14 +16357,15 @@ "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", "dev": true, + "license": "MIT", "engines": { "node": ">= 0.8" } }, "node_modules/update-browserslist-db": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.2.tgz", - "integrity": "sha512-PPypAm5qvlD7XMZC3BujecnaOxwhrtoFR+Dqkk5Aa/6DssiH0ibKoketaj9w8LP7Bont1rYeoV5plxD7RTEPRg==", + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", + "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==", "dev": true, "funding": [ { @@ -14569,6 +16381,7 @@ "url": "https://github.com/sponsors/ai" } ], + "license": "MIT", "dependencies": { "escalade": "^3.2.0", "picocolors": "^1.1.1" @@ -14585,6 +16398,7 @@ "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { "punycode": "^2.1.0" } @@ -14594,6 +16408,7 @@ "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", "dev": true, + "license": "MIT", "engines": { "node": ">=6" } @@ -14601,13 +16416,15 @@ "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "license": "MIT" }, "node_modules/utils-merge": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", "dev": true, + "license": "MIT", "engines": { "node": ">= 0.4.0" } @@ -14617,6 +16434,7 @@ "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", "dev": true, + "license": "MIT", "bin": { "uuid": "dist/bin/uuid" } @@ -14625,25 +16443,28 @@ "version": "2.4.0", "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.4.0.tgz", "integrity": "sha512-ocyWc3bAHBB/guyqJQVI5o4BZkPhznPYUG2ea80Gond/BgNWpap8TOmLSeeQG7bnh2KMISxskdADG59j7zruhw==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/validate-npm-package-license": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", "dev": true, + "license": "Apache-2.0", "dependencies": { "spdx-correct": "^3.0.0", "spdx-expression-parse": "^3.0.0" } }, "node_modules/validate-npm-package-name": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-5.0.1.tgz", - "integrity": "sha512-OljLrQ9SQdOUqTaQxqL5dEfZWrXExyyWsozYlAWFawPVNuD83igl7uJD2RTkNMbniIYgt8l81eCJGIdQF7avLQ==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-6.0.0.tgz", + "integrity": "sha512-d7KLgL1LD3U3fgnvWEY1cQXoO/q6EQ1BSz48Sa149V/5zVTAbgmZIpyI8TRi6U9/JNyeYLlTKsEMPtLC27RFUg==", "dev": true, + "license": "ISC", "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^18.17.0 || >=20.5.0" } }, "node_modules/vary": { @@ -14651,25 +16472,31 @@ "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", "dev": true, + "license": "MIT", "engines": { "node": ">= 0.8" } }, "node_modules/vite": { - "version": "5.4.14", - "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.14.tgz", - "integrity": "sha512-EK5cY7Q1D8JNhSaPKVK4pwBFvaTmZxEnoKXLG/U9gmdDcihQGNzFlgIvaxezFR4glP1LsuiedwMBqCXH3wZccA==", + "version": "6.3.3", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.3.3.tgz", + "integrity": "sha512-5nXH+QsELbFKhsEfWLkHrvgRpTdGJzqOZ+utSdmPTvwHmvU6ITTm3xx+mRusihkcI8GeC7lCDyn3kDtiki9scw==", "dev": true, + "license": "MIT", + "peer": true, "dependencies": { - "esbuild": "^0.21.3", - "postcss": "^8.4.43", - "rollup": "^4.20.0" + "esbuild": "^0.25.0", + "fdir": "^6.4.4", + "picomatch": "^4.0.2", + "postcss": "^8.5.3", + "rollup": "^4.34.9", + "tinyglobby": "^0.2.13" }, "bin": { "vite": "bin/vite.js" }, "engines": { - "node": "^18.0.0 || >=20.0.0" + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" }, "funding": { "url": "https://github.com/vitejs/vite?sponsor=1" @@ -14678,19 +16505,25 @@ "fsevents": "~2.3.3" }, "peerDependencies": { - "@types/node": "^18.0.0 || >=20.0.0", + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "jiti": ">=1.21.0", "less": "*", "lightningcss": "^1.21.0", "sass": "*", "sass-embedded": "*", "stylus": "*", "sugarss": "*", - "terser": "^5.4.0" + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" }, "peerDependenciesMeta": { "@types/node": { "optional": true }, + "jiti": { + "optional": true + }, "less": { "optional": true }, @@ -14711,419 +16544,312 @@ }, "terser": { "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true } } }, - "node_modules/vite/node_modules/@esbuild/aix-ppc64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", - "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", - "cpu": [ - "ppc64" - ], - "dev": true, - "optional": true, - "os": [ - "aix" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/@esbuild/android-arm": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", - "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", + "node_modules/vite/node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.40.0.tgz", + "integrity": "sha512-+Fbls/diZ0RDerhE8kyC6hjADCXA1K4yVNlH0EYfd2XjyH0UGgzaQ8MlT0pCXAThfxv3QUAczHaL+qSv1E4/Cg==", "cpu": [ "arm" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "android" ], - "engines": { - "node": ">=12" - } + "peer": true }, - "node_modules/vite/node_modules/@esbuild/android-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", - "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", + "node_modules/vite/node_modules/@rollup/rollup-android-arm64": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.40.0.tgz", + "integrity": "sha512-PPA6aEEsTPRz+/4xxAmaoWDqh67N7wFbgFUJGMnanCFs0TV99M0M8QhhaSCks+n6EbQoFvLQgYOGXxlMGQe/6w==", "cpu": [ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "android" ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/@esbuild/android-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", - "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } + "peer": true }, - "node_modules/vite/node_modules/@esbuild/darwin-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", - "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", + "node_modules/vite/node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.40.0.tgz", + "integrity": "sha512-GwYOcOakYHdfnjjKwqpTGgn5a6cUX7+Ra2HeNj/GdXvO2VJOOXCiYYlRFU4CubFM67EhbmzLOmACKEfvp3J1kQ==", "cpu": [ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "darwin" ], - "engines": { - "node": ">=12" - } + "peer": true }, - "node_modules/vite/node_modules/@esbuild/darwin-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", - "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", + "node_modules/vite/node_modules/@rollup/rollup-darwin-x64": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.40.0.tgz", + "integrity": "sha512-CoLEGJ+2eheqD9KBSxmma6ld01czS52Iw0e2qMZNpPDlf7Z9mj8xmMemxEucinev4LgHalDPczMyxzbq+Q+EtA==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "darwin" ], - "engines": { - "node": ">=12" - } + "peer": true }, - "node_modules/vite/node_modules/@esbuild/freebsd-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", - "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", + "node_modules/vite/node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.40.0.tgz", + "integrity": "sha512-r7yGiS4HN/kibvESzmrOB/PxKMhPTlz+FcGvoUIKYoTyGd5toHp48g1uZy1o1xQvybwwpqpe010JrcGG2s5nkg==", "cpu": [ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "freebsd" ], - "engines": { - "node": ">=12" - } + "peer": true }, - "node_modules/vite/node_modules/@esbuild/freebsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", - "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", + "node_modules/vite/node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.40.0.tgz", + "integrity": "sha512-mVDxzlf0oLzV3oZOr0SMJ0lSDd3xC4CmnWJ8Val8isp9jRGl5Dq//LLDSPFrasS7pSm6m5xAcKaw3sHXhBjoRw==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "freebsd" ], - "engines": { - "node": ">=12" - } + "peer": true }, - "node_modules/vite/node_modules/@esbuild/linux-arm": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", - "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", + "node_modules/vite/node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.40.0.tgz", + "integrity": "sha512-y/qUMOpJxBMy8xCXD++jeu8t7kzjlOCkoxxajL58G62PJGBZVl/Gwpm7JK9+YvlB701rcQTzjUZ1JgUoPTnoQA==", "cpu": [ "arm" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ], - "engines": { - "node": ">=12" - } + "peer": true }, - "node_modules/vite/node_modules/@esbuild/linux-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", - "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", + "node_modules/vite/node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.40.0.tgz", + "integrity": "sha512-GoCsPibtVdJFPv/BOIvBKO/XmwZLwaNWdyD8TKlXuqp0veo2sHE+A/vpMQ5iSArRUz/uaoj4h5S6Pn0+PdhRjg==", "cpu": [ - "arm64" + "arm" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ], - "engines": { - "node": ">=12" - } + "peer": true }, - "node_modules/vite/node_modules/@esbuild/linux-ia32": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", - "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", + "node_modules/vite/node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.40.0.tgz", + "integrity": "sha512-L5ZLphTjjAD9leJzSLI7rr8fNqJMlGDKlazW2tX4IUF9P7R5TMQPElpH82Q7eNIDQnQlAyiNVfRPfP2vM5Avvg==", "cpu": [ - "ia32" + "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ], - "engines": { - "node": ">=12" - } + "peer": true }, - "node_modules/vite/node_modules/@esbuild/linux-loong64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", - "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", + "node_modules/vite/node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.40.0.tgz", + "integrity": "sha512-ATZvCRGCDtv1Y4gpDIXsS+wfFeFuLwVxyUBSLawjgXK2tRE6fnsQEkE4csQQYWlBlsFztRzCnBvWVfcae/1qxQ==", "cpu": [ - "loong64" + "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ], - "engines": { - "node": ">=12" - } + "peer": true }, - "node_modules/vite/node_modules/@esbuild/linux-mips64el": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", - "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", + "node_modules/vite/node_modules/@rollup/rollup-linux-loongarch64-gnu": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.40.0.tgz", + "integrity": "sha512-wG9e2XtIhd++QugU5MD9i7OnpaVb08ji3P1y/hNbxrQ3sYEelKJOq1UJ5dXczeo6Hj2rfDEL5GdtkMSVLa/AOg==", "cpu": [ - "mips64el" + "loong64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ], - "engines": { - "node": ">=12" - } + "peer": true }, - "node_modules/vite/node_modules/@esbuild/linux-ppc64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", - "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", + "node_modules/vite/node_modules/@rollup/rollup-linux-powerpc64le-gnu": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.40.0.tgz", + "integrity": "sha512-vgXfWmj0f3jAUvC7TZSU/m/cOE558ILWDzS7jBhiCAFpY2WEBn5jqgbqvmzlMjtp8KlLcBlXVD2mkTSEQE6Ixw==", "cpu": [ "ppc64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ], - "engines": { - "node": ">=12" - } + "peer": true }, - "node_modules/vite/node_modules/@esbuild/linux-riscv64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", - "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", + "node_modules/vite/node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.40.0.tgz", + "integrity": "sha512-uJkYTugqtPZBS3Z136arevt/FsKTF/J9dEMTX/cwR7lsAW4bShzI2R0pJVw+hcBTWF4dxVckYh72Hk3/hWNKvA==", "cpu": [ "riscv64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ], - "engines": { - "node": ">=12" - } + "peer": true }, - "node_modules/vite/node_modules/@esbuild/linux-s390x": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", - "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", + "node_modules/vite/node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.40.0.tgz", + "integrity": "sha512-SpnYlAfKPOoVsQqmTFJ0usx0z84bzGOS9anAC0AZ3rdSo3snecihbhFTlJZ8XMwzqAcodjFU4+/SM311dqE5Sw==", "cpu": [ "s390x" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ], - "engines": { - "node": ">=12" - } + "peer": true }, - "node_modules/vite/node_modules/@esbuild/linux-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", - "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", + "node_modules/vite/node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.40.0.tgz", + "integrity": "sha512-RcDGMtqF9EFN8i2RYN2W+64CdHruJ5rPqrlYw+cgM3uOVPSsnAQps7cpjXe9be/yDp8UC7VLoCoKC8J3Kn2FkQ==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/@esbuild/netbsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", - "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/@esbuild/openbsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", - "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=12" - } + "peer": true }, - "node_modules/vite/node_modules/@esbuild/sunos-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", - "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", + "node_modules/vite/node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.40.0.tgz", + "integrity": "sha512-HZvjpiUmSNx5zFgwtQAV1GaGazT2RWvqeDi0hV+AtC8unqqDSsaFjPxfsO6qPtKRRg25SisACWnJ37Yio8ttaw==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ - "sunos" + "linux" ], - "engines": { - "node": ">=12" - } + "peer": true }, - "node_modules/vite/node_modules/@esbuild/win32-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", - "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", + "node_modules/vite/node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.40.0.tgz", + "integrity": "sha512-UtZQQI5k/b8d7d3i9AZmA/t+Q4tk3hOC0tMOMSq2GlMYOfxbesxG4mJSeDp0EHs30N9bsfwUvs3zF4v/RzOeTQ==", "cpu": [ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "win32" ], - "engines": { - "node": ">=12" - } + "peer": true }, - "node_modules/vite/node_modules/@esbuild/win32-ia32": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", - "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", + "node_modules/vite/node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.40.0.tgz", + "integrity": "sha512-+m03kvI2f5syIqHXCZLPVYplP8pQch9JHyXKZ3AGMKlg8dCyr2PKHjwRLiW53LTrN/Nc3EqHOKxUxzoSPdKddA==", "cpu": [ "ia32" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "win32" ], - "engines": { - "node": ">=12" - } + "peer": true }, - "node_modules/vite/node_modules/@esbuild/win32-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", - "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", + "node_modules/vite/node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.40.0.tgz", + "integrity": "sha512-lpPE1cLfP5oPzVjKMx10pgBmKELQnFJXHgvtHCtuJWOv8MxqdEIMNtgHgBFf7Ea2/7EuVwa9fodWUfXAlXZLZQ==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "win32" ], - "engines": { - "node": ">=12" - } + "peer": true }, - "node_modules/vite/node_modules/esbuild": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", - "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", + "node_modules/vite/node_modules/@types/estree": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.7.tgz", + "integrity": "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==", "dev": true, - "hasInstallScript": true, - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=12" - }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.21.5", - "@esbuild/android-arm": "0.21.5", - "@esbuild/android-arm64": "0.21.5", - "@esbuild/android-x64": "0.21.5", - "@esbuild/darwin-arm64": "0.21.5", - "@esbuild/darwin-x64": "0.21.5", - "@esbuild/freebsd-arm64": "0.21.5", - "@esbuild/freebsd-x64": "0.21.5", - "@esbuild/linux-arm": "0.21.5", - "@esbuild/linux-arm64": "0.21.5", - "@esbuild/linux-ia32": "0.21.5", - "@esbuild/linux-loong64": "0.21.5", - "@esbuild/linux-mips64el": "0.21.5", - "@esbuild/linux-ppc64": "0.21.5", - "@esbuild/linux-riscv64": "0.21.5", - "@esbuild/linux-s390x": "0.21.5", - "@esbuild/linux-x64": "0.21.5", - "@esbuild/netbsd-x64": "0.21.5", - "@esbuild/openbsd-x64": "0.21.5", - "@esbuild/sunos-x64": "0.21.5", - "@esbuild/win32-arm64": "0.21.5", - "@esbuild/win32-ia32": "0.21.5", - "@esbuild/win32-x64": "0.21.5" - } + "license": "MIT", + "peer": true }, "node_modules/vite/node_modules/postcss": { - "version": "8.5.1", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.1.tgz", - "integrity": "sha512-6oz2beyjc5VMn/KV1pPw8fliQkhBXrVn1Z3TVyqZxU8kZpzEKhBdmCFqI6ZbmGtamQvQGuU1sgPTk8ZrXDD7jQ==", + "version": "8.5.3", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.3.tgz", + "integrity": "sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==", "dev": true, "funding": [ { @@ -15139,6 +16865,8 @@ "url": "https://github.com/sponsors/ai" } ], + "license": "MIT", + "peer": true, "dependencies": { "nanoid": "^3.3.8", "picocolors": "^1.1.1", @@ -15148,11 +16876,53 @@ "node": "^10 || ^12 || >=14" } }, + "node_modules/vite/node_modules/rollup": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.40.0.tgz", + "integrity": "sha512-Noe455xmA96nnqH5piFtLobsGbCij7Tu+tb3c1vYjNbTkfzGqXqQXG3wJaYXkRZuQ0vEYN4bhwg7QnIrqB5B+w==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@types/estree": "1.0.7" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.40.0", + "@rollup/rollup-android-arm64": "4.40.0", + "@rollup/rollup-darwin-arm64": "4.40.0", + "@rollup/rollup-darwin-x64": "4.40.0", + "@rollup/rollup-freebsd-arm64": "4.40.0", + "@rollup/rollup-freebsd-x64": "4.40.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.40.0", + "@rollup/rollup-linux-arm-musleabihf": "4.40.0", + "@rollup/rollup-linux-arm64-gnu": "4.40.0", + "@rollup/rollup-linux-arm64-musl": "4.40.0", + "@rollup/rollup-linux-loongarch64-gnu": "4.40.0", + "@rollup/rollup-linux-powerpc64le-gnu": "4.40.0", + "@rollup/rollup-linux-riscv64-gnu": "4.40.0", + "@rollup/rollup-linux-riscv64-musl": "4.40.0", + "@rollup/rollup-linux-s390x-gnu": "4.40.0", + "@rollup/rollup-linux-x64-gnu": "4.40.0", + "@rollup/rollup-linux-x64-musl": "4.40.0", + "@rollup/rollup-win32-arm64-msvc": "4.40.0", + "@rollup/rollup-win32-ia32-msvc": "4.40.0", + "@rollup/rollup-win32-x64-msvc": "4.40.0", + "fsevents": "~2.3.2" + } + }, "node_modules/void-elements": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-2.0.1.tgz", "integrity": "sha512-qZKX4RnBzH2ugr8Lxa7x+0V6XD9Sb/ouARtiasEQCHB1EVU4NXtmHsDDrx1dO4ne5fc3J6EW05BP1Dl0z0iung==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -15162,6 +16932,7 @@ "resolved": "https://registry.npmjs.org/vue-eslint-parser/-/vue-eslint-parser-7.1.1.tgz", "integrity": "sha512-8FdXi0gieEwh1IprIBafpiJWcApwrU+l2FEj8c1HtHFdNXMd0+2jUSjBVmcQYohf/E72irwAXEXLga6TQcB3FA==", "dev": true, + "license": "MIT", "dependencies": { "debug": "^4.1.1", "eslint-scope": "^5.0.0", @@ -15185,6 +16956,7 @@ "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", "dev": true, + "license": "MIT", "bin": { "acorn": "bin/acorn" }, @@ -15197,6 +16969,7 @@ "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^4.1.1" @@ -15210,6 +16983,7 @@ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", "dev": true, + "license": "Apache-2.0", "engines": { "node": ">=4" } @@ -15219,6 +16993,7 @@ "resolved": "https://registry.npmjs.org/espree/-/espree-6.2.1.tgz", "integrity": "sha512-ysCxRQY3WaXJz9tdbWOwuWr5Y/XrPTGX9Kiz3yoUXwW0VZ4w30HTkQLaGx/+ttFjF8i+ACbArnB4ce68a9m5hw==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { "acorn": "^7.1.1", "acorn-jsx": "^5.2.0", @@ -15233,15 +17008,17 @@ "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", "dev": true, + "license": "BSD-2-Clause", "engines": { "node": ">=4.0" } }, "node_modules/watchpack": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.1.tgz", - "integrity": "sha512-8wrBCMtVhqcXP2Sup1ctSkga6uc2Bx0IIvKyT7yTFier5AXHooSI+QyQQAtTb7+E0IUCCKyTFmXqdqgum2XWGg==", + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.2.tgz", + "integrity": "sha512-TnbFSbcOCcDgjZ4piURLCbJ3nJhznVh9kw6F6iokjiFPl8ONxe9A6nMDVXDiNbrSfLILs6vB07F7wLBrwPYzJw==", "dev": true, + "license": "MIT", "dependencies": { "glob-to-regexp": "^0.4.1", "graceful-fs": "^4.1.2" @@ -15255,6 +17032,7 @@ "resolved": "https://registry.npmjs.org/wbuf/-/wbuf-1.7.3.tgz", "integrity": "sha512-O84QOnr0icsbFGLS0O3bI5FswxzRr8/gHwWkDlQFskhSPryQXvrTMxjxGP4+iWYoauLoBvfDpkrOauZ+0iZpDA==", "dev": true, + "license": "MIT", "dependencies": { "minimalistic-assert": "^1.0.0" } @@ -15264,6 +17042,7 @@ "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", "integrity": "sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==", "dev": true, + "license": "MIT", "dependencies": { "defaults": "^1.0.3" } @@ -15272,21 +17051,24 @@ "version": "1.2.2", "resolved": "https://registry.npmjs.org/weak-lru-cache/-/weak-lru-cache-1.2.2.tgz", "integrity": "sha512-DEAoo25RfSYMuTGc9vPJzZcZullwIqRDSI9LOy+fkCJPi6hykCnfKaXTuPBDuXAUcqHXyOgFtHNp/kB2FjYHbw==", - "dev": true + "dev": true, + "license": "MIT", + "optional": true }, "node_modules/webpack": { - "version": "5.94.0", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.94.0.tgz", - "integrity": "sha512-KcsGn50VT+06JH/iunZJedYGUJS5FGjow8wb9c0v5n1Om8O1g4L6LjtfxwlXIATopoQu+vOXXa7gYisWxCoPyg==", - "dev": true, - "dependencies": { - "@types/estree": "^1.0.5", - "@webassemblyjs/ast": "^1.12.1", - "@webassemblyjs/wasm-edit": "^1.12.1", - "@webassemblyjs/wasm-parser": "^1.12.1", - "acorn": "^8.7.1", - "acorn-import-attributes": "^1.9.5", - "browserslist": "^4.21.10", + "version": "5.98.0", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.98.0.tgz", + "integrity": "sha512-UFynvx+gM44Gv9qFgj0acCQK2VE1CtdfwFdimkapco3hlPCJ/zeq73n2yVKimVbtm+TnApIugGhLJnkU6gjYXA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/eslint-scope": "^3.7.7", + "@types/estree": "^1.0.6", + "@webassemblyjs/ast": "^1.14.1", + "@webassemblyjs/wasm-edit": "^1.14.1", + "@webassemblyjs/wasm-parser": "^1.14.1", + "acorn": "^8.14.0", + "browserslist": "^4.24.0", "chrome-trace-event": "^1.0.2", "enhanced-resolve": "^5.17.1", "es-module-lexer": "^1.2.1", @@ -15298,9 +17080,9 @@ "loader-runner": "^4.2.0", "mime-types": "^2.1.27", "neo-async": "^2.6.2", - "schema-utils": "^3.2.0", + "schema-utils": "^4.3.0", "tapable": "^2.1.1", - "terser-webpack-plugin": "^5.3.10", + "terser-webpack-plugin": "^5.3.11", "watchpack": "^2.4.1", "webpack-sources": "^3.2.3" }, @@ -15325,6 +17107,7 @@ "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-7.4.2.tgz", "integrity": "sha512-xOO8n6eggxnwYpy1NlzUKpvrjfJTvae5/D6WOK0S2LSo7vjmo5gCM1DbLUmFqrMTJP+W/0YZNctm7jasWvLuBA==", "dev": true, + "license": "MIT", "dependencies": { "colorette": "^2.0.10", "memfs": "^4.6.0", @@ -15350,10 +17133,11 @@ } }, "node_modules/webpack-dev-server": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-5.0.4.tgz", - "integrity": "sha512-dljXhUgx3HqKP2d8J/fUMvhxGhzjeNVarDLcbO/EWMSgRizDkxHQDZQaLFL5VJY9tRBj2Gz+rvCEYYvhbqPHNA==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-5.2.0.tgz", + "integrity": "sha512-90SqqYXA2SK36KcT6o1bvwvZfJFcmoamqeJY7+boioffX9g9C0wjjJRGUrQIuh43pb0ttX7+ssavmj/WN2RHtA==", "dev": true, + "license": "MIT", "dependencies": { "@types/bonjour": "^3.5.13", "@types/connect-history-api-fallback": "^1.5.4", @@ -15368,23 +17152,20 @@ "colorette": "^2.0.10", "compression": "^1.7.4", "connect-history-api-fallback": "^2.0.0", - "default-gateway": "^6.0.3", - "express": "^4.17.3", + "express": "^4.21.2", "graceful-fs": "^4.2.6", - "html-entities": "^2.4.0", - "http-proxy-middleware": "^2.0.3", + "http-proxy-middleware": "^2.0.7", "ipaddr.js": "^2.1.0", "launch-editor": "^2.6.1", "open": "^10.0.3", "p-retry": "^6.2.0", - "rimraf": "^5.0.5", "schema-utils": "^4.2.0", "selfsigned": "^2.4.1", "serve-index": "^1.9.1", "sockjs": "^0.3.24", "spdy": "^4.0.2", - "webpack-dev-middleware": "^7.1.0", - "ws": "^8.16.0" + "webpack-dev-middleware": "^7.4.2", + "ws": "^8.18.0" }, "bin": { "webpack-dev-server": "bin/webpack-dev-server.js" @@ -15413,6 +17194,7 @@ "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", "dev": true, + "license": "MIT", "dependencies": { "anymatch": "~3.1.2", "braces": "~3.0.2", @@ -15432,31 +17214,12 @@ "fsevents": "~2.3.2" } }, - "node_modules/webpack-dev-server/node_modules/glob": { - "version": "10.4.5", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", - "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", - "dev": true, - "dependencies": { - "foreground-child": "^3.1.0", - "jackspeak": "^3.1.2", - "minimatch": "^9.0.4", - "minipass": "^7.1.2", - "package-json-from-dist": "^1.0.0", - "path-scurry": "^1.11.1" - }, - "bin": { - "glob": "dist/esm/bin.mjs" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/webpack-dev-server/node_modules/glob-parent": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", "dev": true, + "license": "ISC", "dependencies": { "is-glob": "^4.0.1" }, @@ -15465,10 +17228,11 @@ } }, "node_modules/webpack-dev-server/node_modules/http-proxy-middleware": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.7.tgz", - "integrity": "sha512-fgVY8AV7qU7z/MmXJ/rxwbrtQH4jBQ9m7kp3llF0liB7glmFeVZFBepQb32T3y8n8k2+AEYuMPCpinYW+/CuRA==", + "version": "2.0.9", + "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.9.tgz", + "integrity": "sha512-c1IyJYLYppU574+YI7R4QyX2ystMtVXZwIdzazUIPIJsHuWNd+mho2j+bKoHftndicGj9yh+xjd+l0yj7VeT1Q==", "dev": true, + "license": "MIT", "dependencies": { "@types/http-proxy": "^1.17.8", "http-proxy": "^1.18.1", @@ -15493,6 +17257,7 @@ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", "dev": true, + "license": "MIT", "engines": { "node": ">=8.6" }, @@ -15505,6 +17270,7 @@ "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", "dev": true, + "license": "MIT", "dependencies": { "picomatch": "^2.2.1" }, @@ -15512,26 +17278,12 @@ "node": ">=8.10.0" } }, - "node_modules/webpack-dev-server/node_modules/rimraf": { - "version": "5.0.10", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-5.0.10.tgz", - "integrity": "sha512-l0OE8wL34P4nJH/H2ffoaniAokM2qSmrtXHmlpvYr5AVVX8msAyW0l8NVJFDxlSK4u3Uh/f41cQheDVdnYijwQ==", - "dev": true, - "dependencies": { - "glob": "^10.3.7" - }, - "bin": { - "rimraf": "dist/esm/bin.mjs" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/webpack-dev-server/node_modules/ws": { - "version": "8.18.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", - "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", + "version": "8.18.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.1.tgz", + "integrity": "sha512-RKW2aJZMXeMxVpnZ6bck+RswznaxmzdULiBr6KY7XkTnW8uvt0iT9H5DkHUChXrc+uurzwa0rVI16n/Xzjdz1w==", "dev": true, + "license": "MIT", "engines": { "node": ">=10.0.0" }, @@ -15553,6 +17305,7 @@ "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-6.0.1.tgz", "integrity": "sha512-hXXvrjtx2PLYx4qruKl+kyRSLc52V+cCvMxRjmKwoA+CBbbF5GfIBtR6kCvl0fYGqTUPKB+1ktVmTHqMOzgCBg==", "dev": true, + "license": "MIT", "dependencies": { "clone-deep": "^4.0.1", "flat": "^5.0.2", @@ -15567,6 +17320,7 @@ "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz", "integrity": "sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==", "dev": true, + "license": "MIT", "engines": { "node": ">=10.13.0" } @@ -15576,6 +17330,7 @@ "resolved": "https://registry.npmjs.org/webpack-subresource-integrity/-/webpack-subresource-integrity-5.1.0.tgz", "integrity": "sha512-sacXoX+xd8r4WKsy9MvH/q/vBtEHr86cpImXwyg74pFIpERKt6FmB8cXpeuh0ZLgclOlHI4Wcll7+R5L02xk9Q==", "dev": true, + "license": "MIT", "dependencies": { "typed-assert": "^1.0.8" }, @@ -15592,36 +17347,12 @@ } } }, - "node_modules/webpack/node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/webpack/node_modules/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, - "peerDependencies": { - "ajv": "^6.9.1" - } - }, "node_modules/webpack/node_modules/eslint-scope": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^4.1.1" @@ -15635,6 +17366,7 @@ "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", "dev": true, + "license": "BSD-2-Clause", "engines": { "node": ">=4.0" } @@ -15643,37 +17375,15 @@ "version": "2.3.1", "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", - "dev": true - }, - "node_modules/webpack/node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true - }, - "node_modules/webpack/node_modules/schema-utils": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", - "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", "dev": true, - "dependencies": { - "@types/json-schema": "^7.0.8", - "ajv": "^6.12.5", - "ajv-keywords": "^3.5.2" - }, - "engines": { - "node": ">= 10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - } + "license": "MIT" }, "node_modules/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, + "license": "Apache-2.0", "dependencies": { "http-parser-js": ">=0.5.1", "safe-buffer": ">=5.1.0", @@ -15688,6 +17398,7 @@ "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.4.tgz", "integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==", "dev": true, + "license": "Apache-2.0", "engines": { "node": ">=0.8.0" } @@ -15697,6 +17408,7 @@ "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", "dev": true, + "license": "ISC", "dependencies": { "isexe": "^2.0.0" }, @@ -15711,13 +17423,15 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/wildcard/-/wildcard-2.0.1.tgz", "integrity": "sha512-CC1bOL87PIWSBhDcTrdeLo6eGT7mCFtrg0uIJtqJUFyK+eJnzl8A1niH56uu7KMa5XFrtiV+AQuHO3n7DsHnLQ==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/word-wrap": { "version": "1.2.5", "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -15727,6 +17441,7 @@ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", "dev": true, + "license": "MIT", "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", @@ -15742,6 +17457,7 @@ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", "dev": true, + "license": "MIT", "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", @@ -15758,13 +17474,15 @@ "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 + "dev": true, + "license": "MIT" }, "node_modules/wrap-ansi-cjs/node_modules/is-fullwidth-code-point": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } @@ -15774,6 +17492,7 @@ "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "dev": true, + "license": "MIT", "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", @@ -15787,13 +17506,15 @@ "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 + "dev": true, + "license": "MIT" }, "node_modules/wrap-ansi/node_modules/is-fullwidth-code-point": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } @@ -15803,6 +17524,7 @@ "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "dev": true, + "license": "MIT", "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", @@ -15815,12 +17537,14 @@ "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "license": "ISC" }, "node_modules/ws": { "version": "7.5.10", "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz", "integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==", + "license": "MIT", "engines": { "node": ">=8.3.0" }, @@ -15841,6 +17565,7 @@ "version": "4.0.2", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "license": "MIT", "engines": { "node": ">=0.4" } @@ -15850,6 +17575,7 @@ "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", "dev": true, + "license": "ISC", "engines": { "node": ">=10" } @@ -15858,13 +17584,15 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", - "dev": true + "dev": true, + "license": "ISC" }, "node_modules/yargs": { "version": "17.7.2", "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", "dev": true, + "license": "MIT", "dependencies": { "cliui": "^8.0.1", "escalade": "^3.1.1", @@ -15883,6 +17611,7 @@ "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", "dev": true, + "license": "ISC", "engines": { "node": ">=12" } @@ -15891,13 +17620,15 @@ "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 + "dev": true, + "license": "MIT" }, "node_modules/yargs/node_modules/is-fullwidth-code-point": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } @@ -15907,6 +17638,7 @@ "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "dev": true, + "license": "MIT", "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", @@ -15921,6 +17653,7 @@ "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", "dev": true, + "license": "MIT", "engines": { "node": ">=10" }, @@ -15933,6 +17666,7 @@ "resolved": "https://registry.npmjs.org/yoctocolors-cjs/-/yoctocolors-cjs-2.1.2.tgz", "integrity": "sha512-cYVsTjKl8b+FrnidjibDWskAv7UKOfcwaVZdp/it9n1s9fU3IkgDbhdIRKCW4JDsAlECJY0ytoVPT3sK6kideA==", "dev": true, + "license": "MIT", "engines": { "node": ">=18" }, @@ -15941,9 +17675,10 @@ } }, "node_modules/zone.js": { - "version": "0.14.10", - "resolved": "https://registry.npmjs.org/zone.js/-/zone.js-0.14.10.tgz", - "integrity": "sha512-YGAhaO7J5ywOXW6InXNlLmfU194F8lVgu7bRntUF3TiG8Y3nBK0x1UJJuHUP/e8IyihkjCYqhCScpSwnlaSRkQ==" + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/zone.js/-/zone.js-0.15.0.tgz", + "integrity": "sha512-9oxn0IIjbCZkJ67L+LkhYWRyAy7axphb3VgE2MBDlOqnmHMPWGYMxJxBYFueFq/JGY2GMwS0rU+UCLunEmy5UA==", + "license": "MIT" } } } diff --git a/modules/ui/package.json b/modules/ui/package.json index ae6d57f2d..e209d38df 100644 --- a/modules/ui/package.json +++ b/modules/ui/package.json @@ -18,34 +18,36 @@ }, "private": true, "dependencies": { - "@angular/animations": "^18.2.4", - "@angular/cdk": "^18.2.0", - "@angular/common": "^18.2.4", - "@angular/compiler": "^18.2.4", - "@angular/core": "^18.2.4", - "@angular/forms": "^18.2.4", - "@angular/material": "^18.2.0", - "@angular/platform-browser": "^18.2.4", - "@angular/platform-browser-dynamic": "^18.2.4", - "@angular/router": "^18.2.4", - "@ngrx/component-store": "^18.0.2", - "@ngrx/effects": "^18.0.2", - "@ngrx/store": "^18.0.2", + "@angular/animations": "^19.0.1", + "@angular/cdk": "^19.0.1", + "@angular/common": "^19.0.1", + "@angular/compiler": "^19.0.1", + "@angular/core": "^19.0.1", + "@angular/forms": "^19.0.1", + "@angular/material": "^19.0.1", + "@angular/platform-browser": "^19.0.1", + "@angular/platform-browser-dynamic": "^19.0.1", + "@angular/router": "^19.0.1", + "@ngrx/component-store": "19.0.0-beta.0", + "@ngrx/effects": "19.0.0-beta.0", + "@ngrx/operators": "^19.0.0-beta.0", + "@ngrx/signals": "^19.0.0-beta.0", + "@ngrx/store": "19.0.0-beta.0", "ngx-mask": "^16.4.2", "ngx-mqtt": "^17.0.0", "rxjs": "~7.8.0", "tslib": "^2.6.2", - "zone.js": "^0.14.10" + "zone.js": "^0.15.0" }, "devDependencies": { - "@angular-devkit/build-angular": "^18.1.4", - "@angular-eslint/builder": "18.3.0", - "@angular-eslint/eslint-plugin": "^18.3.0", - "@angular-eslint/eslint-plugin-template": "^18.3.0", - "@angular-eslint/schematics": "^18.3.0", - "@angular-eslint/template-parser": "18.3.0", - "@angular/cli": "~18.2.4", - "@angular/compiler-cli": "^18.2.4", + "@angular-devkit/build-angular": "^19.0.2", + "@angular-eslint/builder": "19.0.0-alpha.4", + "@angular-eslint/eslint-plugin": "19.0.0-alpha.4", + "@angular-eslint/eslint-plugin-template": "19.0.0-alpha.4", + "@angular-eslint/schematics": "19.0.0-alpha.4", + "@angular-eslint/template-parser": "19.0.0-alpha.4", + "@angular/cli": "~19.0.2", + "@angular/compiler-cli": "^19.0.1", "@types/jasmine": "~4.3.6", "@typescript-eslint/eslint-plugin": "^8.2.0", "@typescript-eslint/parser": "^8.2.0", diff --git a/modules/ui/src/app/app-routing.module.ts b/modules/ui/src/app/app-routing.module.ts index a9864fe63..abf61afef 100644 --- a/modules/ui/src/app/app-routing.module.ts +++ b/modules/ui/src/app/app-routing.module.ts @@ -13,45 +13,65 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { NgModule } from '@angular/core'; -import { RouterModule, Routes } from '@angular/router'; +import { Routes } from '@angular/router'; +import { ReportsComponent } from './pages/reports/reports.component'; +import { DevicesComponent } from './pages/devices/devices.component'; +import { CanDeactivateGuard } from './guards/can-deactivate.guard'; +import { TestrunComponent } from './pages/testrun/testrun.component'; +import { RiskAssessmentComponent } from './pages/risk-assessment/risk-assessment.component'; +import { CertificatesComponent } from './pages/certificates/certificates.component'; +import { SettingsComponent } from './pages/settings/settings.component'; +import { GeneralSettingsComponent } from './pages/general-settings/general-settings.component'; +import { CanActivateGuard } from './guards/can-activate.guard'; -const routes: Routes = [ +export const routes: Routes = [ + { + path: 'settings', + component: SettingsComponent, + title: 'Testrun - Settings', + children: [ + { + path: '', + redirectTo: 'general', + pathMatch: 'full', + }, + { + path: 'certificates', + component: CertificatesComponent, + title: 'Testrun - Certificates', + }, + { + path: 'general', + component: GeneralSettingsComponent, + title: 'Testrun - General Settings', + }, + ], + }, { path: 'testing', - loadChildren: () => - import('./pages/testrun/testrun.module').then(m => m.TestrunModule), - title: 'Testrun', + component: TestrunComponent, + title: 'Testrun - Testing', }, { path: 'devices', - loadChildren: () => - import('./pages/devices/devices.module').then(m => m.DevicesModule), + component: DevicesComponent, + canDeactivate: [CanDeactivateGuard], title: 'Testrun - Devices', }, { path: 'reports', - loadChildren: () => - import('./pages/reports/reports.module').then(m => m.ReportsModule), + component: ReportsComponent, title: 'Testrun - Reports', }, { path: 'risk-assessment', - loadChildren: () => - import('./pages/risk-assessment/risk-assessment.module').then( - m => m.RiskAssessmentModule - ), + component: RiskAssessmentComponent, + canDeactivate: [CanDeactivateGuard], title: 'Testrun - Risk Assessment', }, { path: '', - redirectTo: 'devices', - pathMatch: 'full', + canActivate: [CanActivateGuard], + component: DevicesComponent, }, ]; - -@NgModule({ - imports: [RouterModule.forRoot(routes, { useHash: true })], - exports: [RouterModule], -}) -export class AppRoutingModule {} diff --git a/modules/ui/src/app/app.component.html b/modules/ui/src/app/app.component.html index cb22dd809..fe53f12c4 100644 --- a/modules/ui/src/app/app.component.html +++ b/modules/ui/src/app/app.component.html @@ -15,57 +15,61 @@ --> - - + +
- - - - - - - - + + @@ -74,236 +78,81 @@ - + aria-label="Testrun ui logo" + (keydown.enter)="onNavigationClick()">

Testrun

- - +
+ + - - - - + + + +
-
- - - - No ports detected. Please connect and configure network and - device connections in the - - - Selected port is missing! Please define a valid one using - - System settings - panel. - - - - Further information is required in your device configurations. - Please update your - Devices - to continue testing. - - - - Step 1: To perform a device test, please, select ports in - System settings - panel. - - - Step 2: To perform a device test please - Create a Device - first. - - - Step 3: Once device is created, you are able to - start testing. - - - The device is now being tested. Why not take the time to complete - the device - Risk Assessment questionnaire? - -
+ +
- - - - - - - - + + Testrun mat-button routerLink="{{ route }}" routerLinkActive="app-sidebar-button-active" - [matTooltip]="label" + (keydown.space)="onNavigationClick()" (keydown.enter)="onNavigationClick()"> - {{ icon }} + {{ + icon + }} {{ label }} + + +
+ + + No ports detected. Please connect and configure network and device + connections in the System settings panel. + + + Selected port is missing! Please define a valid one using System + settings panel. + + + + Further information is required in your device configurations. + Please update your Devices to continue testing. + + +
+
+ + +
+ + + + + + + + +
+
diff --git a/modules/ui/src/app/app.component.scss b/modules/ui/src/app/app.component.scss index c8cd1e7ce..3af857820 100644 --- a/modules/ui/src/app/app.component.scss +++ b/modules/ui/src/app/app.component.scss @@ -14,103 +14,77 @@ * limitations under the License. */ @use '@angular/material' as mat; -@import '../theming/colors'; -@import '../theming/variables'; +@use 'm3-theme' as *; +@use 'colors'; +@use 'variables'; -$toolbar-height: 56px; -$nav-close-width: 80px; -$nav-open-width: 236px; -$nav-close-btn-width: 48px; -$nav-open-btn-width: 210px; +$toolbar-height: 64px; +$content-padding-top: 18px; +$content-padding-bottom: 16px; +$nav-width: 96px; .app-container { height: 100%; + background-color: colors.$surface-container-low; } .spacer { flex: 1 1 auto; } -.mat-drawer-content { - height: calc(100% - 56px); - background: $white; -} - -.mat-drawer-side { - margin-top: $toolbar-height; -} - -.active-menu { - .app-sidebar { - width: $nav-open-width; - align-items: start; - } - - .app-sidebar-button { - width: $nav-open-btn-width; - height: $toolbar-height; - border-radius: 0 100px 100px 0; - margin: 0; - padding: 0 24px; - } - - .app-sidebar-button:first-child { - margin-top: 8px; - } - - .app-sidebar-button-active { - border: 1px solid mat.m2-get-color-from-palette($color-primary, 50); - background-color: mat.m2-get-color-from-palette($color-primary, 50); - } - - .app-sidebar-button-active > .mat-icon, - .sidebar-button-label { - color: $grey-800; - } - - .sidebar-button-label { - font-size: 14px; - font-weight: 500; - line-height: 20px; - letter-spacing: 0.25px; - } -} - .app-sidebar { display: flex; flex-direction: column; - background-color: $white; + background-color: colors.$surface-container-low; height: 100%; - gap: 8px; - width: $nav-close-width; + gap: 40px; + width: $nav-width; + align-items: center; + box-sizing: border-box; + padding-top: 104px; +} + +.nav-items-container { + width: 100%; + display: flex; + flex-direction: column; align-items: center; + justify-content: start; + gap: 4px; + flex-grow: 1; } -.app-sidebar-button, -.app-toolbar-button { - border-radius: 20px; +.app-sidebar-button { + display: flex; + flex-direction: column; + border-radius: variables.$corner-large; border: 1px solid transparent; min-width: 48px; - padding: 0; box-sizing: border-box; - height: 34px; - margin: 11px 0; + padding: 10px; line-height: 50% !important; + justify-content: center; + gap: 4px; + align-self: stretch; + height: unset; + width: 86px; + margin: 0 auto; } .app-sidebar-button { - width: $nav-close-btn-width; - display: flex; - justify-content: flex-start; - padding-inline: 8px; -} + --mat-text-button-with-icon-horizontal-padding: 8px; -.app-sidebar-button:first-child { - margin-top: 19px; + padding-inline: 8px; } -.app-toolbar-button-menu { - margin: 11px 17px 11px 16px; +.sidebar-button-label { + color: colors.$on-surface-variant; + text-align: center; + font-family: variables.$font-text; + font-size: 12px; + font-weight: 500; + line-height: 16px; + letter-spacing: 0.1px; } .app-sidebar-button:disabled { @@ -122,45 +96,80 @@ $nav-open-btn-width: 210px; margin-right: 0; width: 24px; font-size: 24px; - color: $dark-grey; + color: colors.$on-surface-variant; height: 24px; } .app-sidebar-button > .mat-icon { - margin: 0 3px; + margin: 4px; min-width: 24px; - line-height: 18px !important; } -.app-sidebar-button-active { - border: 1px solid mat.m2-get-color-from-palette($color-primary, 500); - background-color: mat.m2-get-color-from-palette($color-primary, 500); +.app-toolbar-button-help-tips { + display: none; +} + +.app-sidebar-button-active, +:host:has(app-help-tip) .app-toolbar-button-help-tips { + .material-symbols-outlined { + font-variation-settings: + 'FILL' 1, + 'wght' 400, + 'GRAD' 0, + 'opsz' 24; + } + + & > .mat-icon { + color: colors.$on-secondary-fixed-variant; + } + .sidebar-button-label { + color: colors.$secondary; + } + + .mat-mdc-button-persistent-ripple::before { + opacity: 1; + background: colors.$secondary-fixed; + } } -.app-sidebar-button-active > .mat-icon { - color: $white; +:host:has(app-help-tip) .app-toolbar-button-help-tips { + display: block; } .logo-link { - color: $grey-800; + color: colors.$on-surface; text-decoration: none; - font-size: 18px; + font-size: 22px; display: flex; flex-wrap: nowrap; align-items: center; - justify-content: center; - gap: 16px; - width: 120px; + gap: 10px; + flex-shrink: 0; + padding: 4px; + border: 1px solid transparent; + + &:focus-visible { + outline: none; + border: 1px solid colors.$black; + border-radius: 4px; + } + + &:active, + :focus-visible { + text-decoration: underline; + text-underline-offset: 2px; + } } .logo-link .mat-icon { - width: 36px; - height: 23px; - line-height: 18px !important; + width: 40px; + height: 26px; + flex-shrink: 0; + line-height: 22px !important; } .main-heading { - font-size: 18px; + font-size: 22px; line-height: 24px; } @@ -170,17 +179,30 @@ $nav-open-btn-width: 210px; left: 0; z-index: 3; height: $toolbar-height; - padding: 0; - background-color: $white; - border-bottom: 1px solid $light-grey; - color: $grey-800; + padding: 0 16px; + background-color: colors.$surface-container-low; + color: colors.$grey-800; + width: 100%; +} + +.app-bar-buttons { + display: flex; + padding: 4px 0 4px 24px; + justify-content: flex-end; + align-items: center; + gap: 8px; } .app-content { position: static; display: grid; grid-template-rows: 1fr; - margin-top: $toolbar-height; + margin-top: calc($toolbar-height + $content-padding-top); + height: calc( + 100% - $toolbar-height - $content-padding-top - $content-padding-bottom + ); + border-radius: variables.$corner-large; + background: colors.$white; } .app-content-main { @@ -193,24 +215,31 @@ $nav-open-btn-width: 210px; .settings-drawer { width: 320px; box-shadow: none; - border-left: 1px solid $light-grey; -} - -.app-toolbar-button.app-toolbar-button-certificates { - margin-left: 72px; + border-left: 1px solid colors.$light-grey; } app-version { - margin-top: auto; margin-bottom: 16px; max-width: 100%; - width: $nav-close-width; + width: $nav-width; display: flex; justify-content: center; } -.separator { - width: 1px; - height: 28px; - background-color: $light-grey; +:host { + display: block; + width: 100%; + height: 100%; + container-type: size; + container-name: app-root; +} +@container app-root (height < 600px) { + .app-sidebar { + gap: 4px; + padding-top: 82px; + } +} + +.closed-tip { + display: none; } diff --git a/modules/ui/src/app/app.component.spec.ts b/modules/ui/src/app/app.component.spec.ts index bddb6546a..47ff1b0d8 100644 --- a/modules/ui/src/app/app.component.spec.ts +++ b/modules/ui/src/app/app.component.spec.ts @@ -31,7 +31,6 @@ import { MatIconModule } from '@angular/material/icon'; import { MatToolbarModule } from '@angular/material/toolbar'; import { MatSidenavModule } from '@angular/material/sidenav'; import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; -import { AppRoutingModule } from './app-routing.module'; import SpyObj = jasmine.SpyObj; import { BypassComponent } from './components/bypass/bypass.component'; import { CalloutComponent } from './components/callout/callout.component'; @@ -63,7 +62,6 @@ import { selectSystemStatus, } from './store/selectors'; import { MatIconTestingModule } from '@angular/material/icon/testing'; -import { CertificatesComponent } from './pages/certificates/certificates.component'; import { of } from 'rxjs'; import { WINDOW } from './providers/window.provider'; import { LiveAnnouncer } from '@angular/cdk/a11y'; @@ -74,6 +72,12 @@ import { WifiComponent } from './components/wifi/wifi.component'; import { MatTooltipModule } from '@angular/material/tooltip'; import { Profile } from './model/profile'; import { TestrunStatus } from './model/testrun-status'; +import { SpinnerComponent } from './components/spinner/spinner.component'; +import { ShutdownAppComponent } from './components/shutdown-app/shutdown-app.component'; +import { TestingCompleteComponent } from './components/testing-complete/testing-complete.component'; +import { VersionComponent } from './components/version/version.component'; +import { MOCK_MODULES } from './mocks/device.mock'; +import { HelpTips } from './model/tip-config'; const windowMock = { location: { @@ -115,11 +119,9 @@ describe('AppComponent', () => { 'getTestModules', 'testrunInProgress', 'fetchProfiles', - 'fetchCertificates', 'getHistory', ]); - mockService.fetchCertificates.and.returnValue(of([])); mockFocusManagerService = jasmine.createSpyObj('mockFocusManagerService', [ 'focusFirstElementInContainer', ]); @@ -128,9 +130,9 @@ describe('AppComponent', () => { TestBed.configureTestingModule({ imports: [ + AppComponent, RouterTestingModule, HttpClientTestingModule, - AppRoutingModule, MatButtonModule, BrowserAnimationsModule, MatIconModule, @@ -139,9 +141,18 @@ describe('AppComponent', () => { BypassComponent, CalloutComponent, MatIconTestingModule, - CertificatesComponent, WifiComponent, MatTooltipModule, + FakeSpinnerComponent, + FakeShutdownAppComponent, + FakeVersionComponent, + FakeTestingCompleteComponent, + RouterTestingModule.withRoutes([ + { path: 'devices', children: [] }, + { path: 'settings', children: [] }, + { path: 'testing', children: [] }, + { path: 'reports', children: [] }, + ]), ], providers: [ { provide: TestRunService, useValue: mockService }, @@ -173,16 +184,27 @@ describe('AppComponent', () => { { provide: FocusManagerService, useValue: mockFocusManagerService }, { provide: WINDOW, useValue: windowMock }, ], - declarations: [ - AppComponent, - FakeGeneralSettingsComponent, - FakeSpinnerComponent, - FakeShutdownAppComponent, - FakeVersionComponent, - FakeTestingCompleteComponent, - ], + }).overrideComponent(AppComponent, { + remove: { + imports: [ + SpinnerComponent, + ShutdownAppComponent, + TestingCompleteComponent, + VersionComponent, + ], + }, + add: { + imports: [ + FakeSpinnerComponent, + FakeShutdownAppComponent, + FakeVersionComponent, + FakeTestingCompleteComponent, + ], + }, }); + mockService.fetchDevices.and.returnValue(of([])); + mockService.getTestModules.and.returnValue(of([...MOCK_MODULES])); mockMqttService.getNetworkAdapters.and.returnValue(of(MOCK_ADAPTERS)); store = TestBed.inject(MockStore); fixture = TestBed.createComponent(AppComponent); @@ -204,10 +226,10 @@ describe('AppComponent', () => { expect(sideBar).toBeDefined(); }); - it('should render menu button', () => { - const button = compiled.querySelector('.app-sidebar-button-menu'); + it('should render side button menu', () => { + const sideButtonMenu = compiled.querySelector('app-side-button-menu'); - expect(button).toBeDefined(); + expect(sideButtonMenu).toBeDefined(); }); it('should render runtime button', () => { @@ -289,143 +311,17 @@ describe('AppComponent', () => { expect(router.url).toBe(Routes.Reports); })); - it('should call toggleSettingsBtn focus when settingsDrawer close on closeSetting', fakeAsync(() => { - fixture.detectChanges(); - - spyOn(component.settingsDrawer, 'close').and.returnValue( - Promise.resolve('close') - ); - spyOn(component.toggleSettingsBtn, 'focus'); - - component.closeSetting(true); - tick(); - - component.settingsDrawer.close().then(() => { - expect(component.toggleSettingsBtn.focus).toHaveBeenCalled(); - }); - })); - - it('should call focusFirstElementInContainer if settingsDrawer opened not from toggleBtn', fakeAsync(() => { + it('should navigate to the settings when "settings" button is clicked', fakeAsync(() => { fixture.detectChanges(); - spyOn(component.settingsDrawer, 'close').and.returnValue( - Promise.resolve('close') - ); - - component.openGeneralSettings(false, false); - tick(); - component.closeSetting(false); - flush(); - - component.settingsDrawer.close().then(() => { - expect( - mockFocusManagerService.focusFirstElementInContainer - ).toHaveBeenCalled(); - }); - })); - - it('should update interfaces and config', () => { - fixture.detectChanges(); - - spyOn(component.settings, 'getSystemInterfaces'); - spyOn(component.settings, 'getSystemConfig'); - - component.openGeneralSettings(false, false); - - expect(component.settings.getSystemInterfaces).toHaveBeenCalled(); - expect(component.settings.getSystemConfig).toHaveBeenCalled(); - }); - - it('should call settingsDrawer open on openSetting', fakeAsync(() => { - fixture.detectChanges(); - spyOn(component.settingsDrawer, 'open'); - - component.openSetting(false); - tick(); - - expect(component.settingsDrawer.open).toHaveBeenCalledTimes(1); - })); - - it('should announce settingsDrawer disabled on openSetting and settings are disabled', fakeAsync(() => { - fixture.detectChanges(); - - spyOn(component.settingsDrawer, 'open').and.returnValue( - Promise.resolve('open') - ); - - component.openSetting(true); - tick(); - - expect(mockLiveAnnouncer.announce).toHaveBeenCalledWith( - 'The settings panel is disabled' - ); - })); - - it('should call settingsDrawer open on click settings button', () => { - fixture.detectChanges(); - - const settingsBtn = compiled.querySelector( + const settingsButton = compiled.querySelector( '.app-toolbar-button-general-settings' ) as HTMLButtonElement; - spyOn(component.settingsDrawer, 'open'); - - settingsBtn.click(); - - expect(component.settingsDrawer.open).toHaveBeenCalledTimes(1); - }); - - describe('menu button', () => { - beforeEach(() => { - mockFocusManagerService.focusFirstElementInContainer.calls.reset(); - store.overrideSelector(selectHasDevices, false); - fixture.detectChanges(); - }); - - it('should dispatch toggleMenu action', () => { - spyOn(component.appStore, 'toggleMenu'); - - const menuBtn = compiled.querySelector( - '.app-toolbar-button-menu' - ) as HTMLButtonElement; - - menuBtn.click(); - - expect(component.appStore.toggleMenu).toHaveBeenCalled(); - }); - - it('should focus navigation on tab press if menu button was clicked', () => { - component.appStore.updateFocusNavigation(true); - fixture.detectChanges(); - spyOn(component.appStore, 'updateFocusNavigation'); - const menuBtn = compiled.querySelector( - '.app-toolbar-button-menu' - ) as HTMLButtonElement; - - menuBtn.dispatchEvent(new KeyboardEvent('keydown', { key: 'Tab' })); - const navigation = compiled.querySelector('.app-sidebar'); - - expect(component.appStore.updateFocusNavigation).toHaveBeenCalledWith( - false - ); - expect( - mockFocusManagerService.focusFirstElementInContainer - ).toHaveBeenCalledWith(navigation); - }); - - it('should not focus navigation button on tab press if menu button was not clicked', () => { - component.appStore.updateFocusNavigation(false); - fixture.detectChanges(); - const menuBtn = compiled.querySelector( - '.app-toolbar-button-menu' - ) as HTMLButtonElement; - - menuBtn.dispatchEvent(new KeyboardEvent('keydown', { key: 'Tab' })); + settingsButton?.click(); + tick(); - expect( - mockFocusManagerService.focusFirstElementInContainer - ).not.toHaveBeenCalled(); - }); - }); + expect(router.url).toBe(Routes.Settings); + })); it('should have spinner', () => { const spinner = compiled.querySelector('app-spinner'); @@ -468,176 +364,69 @@ describe('AppComponent', () => { }); }); - describe('Callout component visibility', () => { + describe('Help tip component visibility', () => { describe('with no connection settings', () => { beforeEach(() => { store.overrideSelector(selectHasConnectionSettings, false); fixture.detectChanges(); }); - it('should have callout component with "Step 1" text', () => { - const callout = compiled.querySelector('app-callout'); - const calloutContent = callout?.innerHTML.trim(); - - expect(callout).toBeTruthy(); - expect(calloutContent).toContain('Step 1'); - }); - - it('should have callout content with "System settings" link ', () => { - const calloutLinkEl = compiled.querySelector( - '.message-link' - ) as HTMLAnchorElement; - const calloutLinkContent = calloutLinkEl.innerHTML.trim(); - - expect(calloutLinkEl).toBeTruthy(); - expect(calloutLinkContent).toContain('System settings'); - }); - - keyboardCases.forEach(testCase => { - it(`should call openSetting on keydown ${testCase.name} "Connection settings" link`, fakeAsync(() => { - const spyOpenSetting = spyOn(component, 'openSetting'); - const calloutLinkEl = compiled.querySelector( - '.message-link' - ) as HTMLAnchorElement; - - calloutLinkEl.dispatchEvent(testCase.event); - flush(); - - expect(spyOpenSetting).toHaveBeenCalled(); - })); - }); - }); - - describe('with system status as "Idle"', () => { - beforeEach(() => { - component.appStore.updateIsStatusLoaded(true); - store.overrideSelector(selectHasConnectionSettings, true); - store.overrideSelector(selectHasDevices, true); - store.overrideSelector(selectSystemStatus, MOCK_PROGRESS_DATA_IDLE); - - fixture.detectChanges(); - }); - - it('should have callout component with "Step 3" text', () => { - const callout = compiled.querySelector('app-callout'); - const calloutContent = callout?.innerHTML.trim(); - - expect(callout).toBeTruthy(); - expect(calloutContent).toContain('Step 3'); - }); - - it('should NOT have callout component with "Step 3" if has reports', () => { - store.overrideSelector(selectReports, [...HISTORY]); - store.refreshState(); - fixture.detectChanges(); - - const callout = compiled.querySelector('app-callout'); - - expect(callout).toBeFalsy(); - }); - }); - - describe('with systemStatus data IN Progress and without riskProfiles', () => { - beforeEach(() => { - store.overrideSelector(selectHasConnectionSettings, true); - store.overrideSelector(selectHasDevices, true); - store.overrideSelector(selectHasRiskProfiles, false); - store.overrideSelector( - selectStatus, - MOCK_PROGRESS_DATA_IN_PROGRESS.status - ); - fixture.detectChanges(); - }); - - it('should have callout component with "The device is now being tested" text', () => { - const callout = compiled.querySelector('app-callout'); - const calloutContent = callout?.innerHTML.trim(); + it('should have help tip component with "Step 1" text', () => { + const helpTip = compiled.querySelector('app-help-tip'); + const helpTipTitle = compiled.querySelector('app-help-tip .title'); + const helpTipContent = helpTipTitle?.innerHTML.trim(); - expect(callout).toBeTruthy(); - expect(calloutContent).toContain('The device is now being tested'); + expect(helpTip).toBeTruthy(); + expect(helpTipContent).toContain('Step 1'); }); - it('should have callout component with "Risk Assessment" link', () => { - const callout = compiled.querySelector('app-callout'); - const calloutLinkEl = compiled.querySelector( - '.message-link' + it('should have help tip content with "Go to Settings" link ', () => { + const helpTipLinkEl = compiled.querySelector( + '.tip-action-link' ) as HTMLAnchorElement; - const calloutLinkContent = calloutLinkEl.innerHTML.trim(); + const helpTipLinkContent = helpTipLinkEl.innerHTML.trim(); - expect(callout).toBeTruthy(); - expect(calloutLinkContent).toContain('Risk Assessment'); + expect(helpTipLinkEl).toBeTruthy(); + expect(helpTipLinkContent).toContain('Go to Settings'); }); }); - describe('with systemStatus data IN Progress and without riskProfiles', () => { - beforeEach(() => { - store.overrideSelector(selectHasConnectionSettings, true); - store.overrideSelector(selectHasDevices, true); - store.overrideSelector(selectHasRiskProfiles, false); - store.overrideSelector( - selectStatus, - MOCK_PROGRESS_DATA_IN_PROGRESS.status - ); - fixture.detectChanges(); - }); - - it('should have callout component with "The device is now being tested" text', () => { - const callout = compiled.querySelector('app-callout'); - const calloutContent = callout?.innerHTML.trim(); - - expect(callout).toBeTruthy(); - expect(calloutContent).toContain('The device is now being tested'); - }); - - it('should have callout component with "Risk Assessment" link', () => { - const callout = compiled.querySelector('app-callout'); - const calloutLinkEl = compiled.querySelector( - '.message-link' - ) as HTMLAnchorElement; - const calloutLinkContent = calloutLinkEl.innerHTML.trim(); - - expect(callout).toBeTruthy(); - expect(calloutLinkContent).toContain('Risk Assessment'); - }); - }); - - describe('with no devices setted', () => { + describe('with no devices set', () => { beforeEach(() => { store.overrideSelector(selectHasDevices, false); fixture.detectChanges(); }); - it('should have callout component', () => { - const callout = compiled.querySelector('app-callout'); + it('should have helpTip component', () => { + const helpTip = compiled.querySelector('app-help-tip'); - expect(callout).toBeTruthy(); + expect(helpTip).toBeTruthy(); }); - it('should have callout component with "Step 2" text', () => { - const callout = compiled.querySelector('app-callout'); - const calloutContent = callout?.innerHTML.trim(); + it('should have help tip component with "Step 2" text', () => { + const helpTipTitle = compiled.querySelector('app-help-tip .title'); + const helpTipTitleContent = helpTipTitle?.innerHTML.trim(); - expect(callout).toBeTruthy(); - expect(calloutContent).toContain('Step 2'); + expect(helpTipTitleContent).toContain('Step 2'); }); - it('should have callout content with "Create a Device" link ', () => { - const calloutLinkEl = compiled.querySelector( - '.message-link' + it('should have help tip content with "Create Device" link ', () => { + const helpTipLinkEl = compiled.querySelector( + '.tip-action-link' ) as HTMLAnchorElement; - const calloutLinkContent = calloutLinkEl.innerHTML.trim(); + const helpTipLinkContent = helpTipLinkEl.innerHTML.trim(); - expect(calloutLinkEl).toBeTruthy(); - expect(calloutLinkContent).toContain('Create a Device'); + expect(helpTipLinkEl).toBeTruthy(); + expect(helpTipLinkContent).toContain('Device'); }); keyboardCases.forEach(testCase => { - it(`should navigate to the device-repository on keydown ${testCase.name} "Create a Device" link`, fakeAsync(() => { - const calloutLinkEl = compiled.querySelector( - '.message-link' + it(`should navigate to the device-repository on keydown ${testCase.name} "Create Device" link`, fakeAsync(() => { + const helpTipLinkEl = compiled.querySelector( + '.tip-action-link' ) as HTMLAnchorElement; - calloutLinkEl.dispatchEvent(testCase.event); + helpTipLinkEl.dispatchEvent(testCase.event); flush(); expect(router.url).toBe(Routes.Devices); @@ -645,11 +434,11 @@ describe('AppComponent', () => { }); it('should navigate to the device-repository on click "Create a Device" link', fakeAsync(() => { - const calloutLinkEl = compiled.querySelector( - '.message-link' + const helpTipLinkEl = compiled.querySelector( + '.tip-action-link' ) as HTMLAnchorElement; - calloutLinkEl.click(); + helpTipLinkEl.click(); flush(); expect(router.url).toBe(Routes.Devices); @@ -659,7 +448,35 @@ describe('AppComponent', () => { })); }); - describe('with devices setted but without systemStatus data', () => { + describe('with system status as "Idle"', () => { + beforeEach(() => { + component.appStore.updateIsStatusLoaded(true); + store.overrideSelector(selectHasConnectionSettings, true); + store.overrideSelector(selectHasDevices, true); + store.overrideSelector(selectSystemStatus, MOCK_PROGRESS_DATA_IDLE); + + fixture.detectChanges(); + }); + + it('should have help tip with "Step 3" title', () => { + const helpTipTitle = compiled.querySelector('app-help-tip .title'); + const helpTipTitleContent = helpTipTitle?.innerHTML.trim(); + + expect(helpTipTitleContent).toContain('Step 3'); + }); + + it('should NOT have help tip with "Step 3" if has reports', () => { + store.overrideSelector(selectReports, [...HISTORY]); + store.refreshState(); + fixture.detectChanges(); + + const helpTip = compiled.querySelector('app-help-tip'); + + expect(helpTip).toBeFalsy(); + }); + }); + + describe('with devices set but without systemStatus data', () => { beforeEach(() => { store.overrideSelector(selectHasDevices, true); component.appStore.updateIsStatusLoaded(true); @@ -669,69 +486,114 @@ describe('AppComponent', () => { fixture.detectChanges(); }); - it('should have callout component with "Step 3" text', () => { - const callout = compiled.querySelector('app-callout'); - const calloutContent = callout?.innerHTML.trim(); + it('should have help tip with "Step 3" text', () => { + const helpTipTitle = compiled.querySelector('app-help-tip .title'); + const helpTipTitleContent = helpTipTitle?.innerHTML.trim(); - expect(callout).toBeTruthy(); - expect(calloutContent).toContain('Step 3'); + expect(helpTipTitleContent).toContain('Step 3'); }); - it('should have callout component with "testing" link', () => { - const callout = compiled.querySelector('app-callout'); - const calloutLinkEl = compiled.querySelector( - '.message-link' + it('should have help tip with "Start Testrun" link', () => { + const helpTipLinkEl = compiled.querySelector( + '.tip-action-link' ) as HTMLAnchorElement; - const calloutLinkContent = calloutLinkEl.innerHTML.trim(); + const helpTipLinkContent = helpTipLinkEl.innerHTML.trim(); - expect(callout).toBeTruthy(); - expect(calloutLinkContent).toContain('testing'); + expect(helpTipLinkEl).toBeTruthy(); + expect(helpTipLinkContent).toContain(HelpTips.step3.action); }); keyboardCases.forEach(testCase => { - it(`should navigate to the runtime on keydown ${testCase.name} "Run the Test" link`, fakeAsync(() => { - const calloutLinkEl = compiled.querySelector( - '.message-link' + it(`should navigate to the testing on keydown ${testCase.name} "Start Testrun" link`, fakeAsync(() => { + const helpTipLinkEl = compiled.querySelector( + '.tip-action-link' ) as HTMLAnchorElement; - calloutLinkEl.dispatchEvent(testCase.event); + helpTipLinkEl.dispatchEvent(testCase.event); flush(); expect(router.url).toBe(Routes.Testing); })); }); + + it('should add "closed-tip" class to the tip on click "close" button on tip', fakeAsync(() => { + const helpTipEl = compiled.querySelector('app-help-tip') as HTMLElement; + const helpTipCloseBtn = compiled.querySelector( + 'app-help-tip .close-button' + ) as HTMLButtonElement; + + helpTipCloseBtn.click(); + tick(100); + + expect(helpTipEl.classList.contains('closed-tip')).toBeTrue(); + })); + + it('should remove "closed-tip" class from the tip on click toolbar "help tips" button', fakeAsync(() => { + const helpTipEl = compiled.querySelector('app-help-tip') as HTMLElement; + helpTipEl.classList.add('closed-tip'); + const helpTipsBtn = compiled.querySelector( + '.app-toolbar-button-help-tips' + ) as HTMLButtonElement; + + helpTipsBtn.click(); + tick(100); + + expect( + mockFocusManagerService.focusFirstElementInContainer + ).toHaveBeenCalledWith(helpTipEl); + expect(helpTipEl.classList.contains('closed-tip')).toBeFalse(); + })); }); - describe('with devices setted, without systemStatus data, but run the tests', () => { + describe('with devices set and systemStatus data', () => { beforeEach(() => { store.overrideSelector(selectHasDevices, true); + store.overrideSelector( + selectSystemStatus, + MOCK_PROGRESS_DATA_IN_PROGRESS + ); fixture.detectChanges(); }); - it('should not have callout component', () => { - const callout = compiled.querySelector('app-callout'); + it('should not have help tip', () => { + const helpTip = compiled.querySelector('app-help-tip'); - expect(callout).toBeNull(); + expect(helpTip).toBeNull(); }); }); - describe('with devices setted and systemStatus data', () => { + describe('with systemStatus data IN Progress and without riskProfiles', () => { beforeEach(() => { + store.overrideSelector(selectHasConnectionSettings, true); store.overrideSelector(selectHasDevices, true); + store.overrideSelector(selectHasRiskProfiles, false); store.overrideSelector( - selectSystemStatus, - MOCK_PROGRESS_DATA_IN_PROGRESS + selectStatus, + MOCK_PROGRESS_DATA_IN_PROGRESS.status ); fixture.detectChanges(); }); - it('should not have callout component', () => { - const callout = compiled.querySelector('app-callout'); + it('should have help tip with "Risk Assessment" title', () => { + const helpTipTitle = compiled.querySelector('app-help-tip .title'); + const helpTipTitleContent = helpTipTitle?.innerHTML.trim(); + + expect(helpTipTitleContent).toContain('Risk Assessment'); + }); + + it('should have help tip with "Create risk profile" link', () => { + const helpTipLinkEl = compiled.querySelector( + '.tip-action-link' + ) as HTMLAnchorElement; + const helpTipLinkContent = helpTipLinkEl.innerHTML.trim(); - expect(callout).toBeNull(); + expect(helpTipLinkEl).toBeTruthy(); + expect(helpTipLinkContent).toContain(HelpTips.step4.action); }); }); + }); + describe('Callout component visibility', () => { describe('error', () => { describe('with settingMissedError with one port is missed', () => { beforeEach(() => { @@ -811,40 +673,6 @@ describe('AppComponent', () => { }); }); - it('should not call toggleSettingsBtn focus on closeSetting when device length is 0', async () => { - fixture.detectChanges(); - - spyOn(component.settingsDrawer, 'close').and.returnValue( - Promise.resolve('close') - ); - const spyToggle = spyOn(component.toggleSettingsBtn, 'focus'); - - await component.closeSetting(false); - - expect(spyToggle).toHaveBeenCalledTimes(0); - }); - - it('should render certificates button', () => { - const generalSettingsButton = compiled.querySelector( - '.app-toolbar-button-certificates' - ); - - expect(generalSettingsButton).toBeDefined(); - }); - - it('should call certificates open on click certificates button', () => { - fixture.detectChanges(); - - const settingsBtn = compiled.querySelector( - '.app-toolbar-button-certificates' - ) as HTMLButtonElement; - spyOn(component.certDrawer, 'open'); - - settingsBtn.click(); - - expect(component.certDrawer.open).toHaveBeenCalledTimes(1); - }); - it('should set focus to first focusable elem when close callout', fakeAsync(() => { component.calloutClosed('mockId'); tick(100); @@ -855,17 +683,6 @@ describe('AppComponent', () => { })); }); -@Component({ - selector: 'app-settings', - template: '
', -}) -class FakeGeneralSettingsComponent { - @Input() settingsDisable = false; - @Output() closeSettingEvent = new EventEmitter(); - getSystemInterfaces = () => {}; - getSystemConfig = () => {}; -} - @Component({ selector: 'app-spinner', template: '
', diff --git a/modules/ui/src/app/app.component.ts b/modules/ui/src/app/app.component.ts index ba8d33831..2f1799022 100644 --- a/modules/ui/src/app/app.component.ts +++ b/modules/ui/src/app/app.component.ts @@ -16,33 +16,64 @@ import { AfterViewInit, Component, + effect, ElementRef, - QueryList, + viewChild, + inject, ViewChild, - ViewChildren, + ChangeDetectorRef, + Renderer2, + HostListener, } from '@angular/core'; -import { MatIconRegistry } from '@angular/material/icon'; +import { MatIconModule, MatIconRegistry } from '@angular/material/icon'; import { DomSanitizer } from '@angular/platform-browser'; -import { MatDrawer } from '@angular/material/sidenav'; +import { MatSidenavModule } from '@angular/material/sidenav'; import { StatusOfTestrun } from './model/testrun-status'; -import { NavigationEnd, Router } from '@angular/router'; +import { NavigationEnd, Router, RouterModule } from '@angular/router'; import { CalloutType } from './model/callout-type'; import { Routes } from './model/routes'; import { FocusManagerService } from './services/focus-manager.service'; -import { State, Store } from '@ngrx/store'; +import { Store } from '@ngrx/store'; import { AppState } from './store/state'; -import { setIsOpenAddDevice } from './store/actions'; -import { SettingsComponent } from './pages/settings/settings.component'; +import { setIsOpenAddDevice, setIsOpenProfile } from './store/actions'; import { AppStore } from './app.store'; import { TestRunService } from './services/test-run.service'; -import { LiveAnnouncer } from '@angular/cdk/a11y'; import { filter, take } from 'rxjs/operators'; -import { skip, timer } from 'rxjs'; +import { timer } from 'rxjs'; +import { MatToolbarModule } from '@angular/material/toolbar'; +import { MatFormFieldModule } from '@angular/material/form-field'; +import { MatButtonToggleModule } from '@angular/material/button-toggle'; +import { MatInputModule } from '@angular/material/input'; +import { MatSnackBarModule } from '@angular/material/snack-bar'; +import { TestingCompleteComponent } from './components/testing-complete/testing-complete.component'; +import { BypassComponent } from './components/bypass/bypass.component'; +import { ShutdownAppComponent } from './components/shutdown-app/shutdown-app.component'; +import { SpinnerComponent } from './components/spinner/spinner.component'; +import { MatTooltipModule } from '@angular/material/tooltip'; +import { MatButton, MatButtonModule } from '@angular/material/button'; +import { VersionComponent } from './components/version/version.component'; +import { MatSelectModule } from '@angular/material/select'; +import { WifiComponent } from './components/wifi/wifi.component'; +import { MatRadioModule } from '@angular/material/radio'; +import { CalloutComponent } from './components/callout/callout.component'; +import { ReactiveFormsModule } from '@angular/forms'; +import { CommonModule } from '@angular/common'; +import { SideButtonMenuComponent } from './components/side-button-menu/side-button-menu.component'; +import { Observable } from 'rxjs/internal/Observable'; +import { of } from 'rxjs/internal/observable/of'; +import { HelpTipComponent } from './components/help-tip/help-tip.component'; +import { HelpTips } from './model/tip-config'; + +export interface AddMenuItem { + icon?: string; + svgIcon?: string; + label: string; + description?: string; + onClick: () => void; + disabled$: Observable; +} -const DEVICES_LOGO_URL = '/assets/icons/devices.svg'; const DEVICES_RUN_URL = '/assets/icons/device_run.svg'; -const REPORTS_LOGO_URL = '/assets/icons/reports.svg'; -const RISK_ASSESSMENT_LOGO_URL = '/assets/icons/risk-assessment.svg'; const TESTRUN_LOGO_URL = '/assets/icons/testrun_logo_small.svg'; const TESTRUN_LOGO_COLOR_URL = '/assets/icons/testrun_logo_color.svg'; const CLOSE_URL = '/assets/icons/close.svg'; @@ -50,62 +81,140 @@ const DRAFT_URL = '/assets/icons/draft.svg'; const PILOT_URL = '/assets/icons/pilot.svg'; const QUALIFICATION_URL = '/assets/icons/qualification.svg'; +const navKeys = [ + 'Tab', + 'ArrowUp', + 'ArrowDown', + 'ArrowLeft', + 'ArrowRight', + 'Home', + 'End', + 'PageUp', + 'PageDown', + 'Escape', + 'Enter', +]; + @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.scss'], + imports: [ + MatButtonModule, + MatIconModule, + MatToolbarModule, + MatSidenavModule, + MatButtonToggleModule, + MatRadioModule, + MatInputModule, + MatSelectModule, + MatTooltipModule, + ReactiveFormsModule, + MatFormFieldModule, + MatSnackBarModule, + SpinnerComponent, + BypassComponent, + VersionComponent, + CalloutComponent, + HelpTipComponent, + ShutdownAppComponent, + WifiComponent, + TestingCompleteComponent, + RouterModule, + CommonModule, + SideButtonMenuComponent, + ], providers: [AppStore], }) export class AppComponent implements AfterViewInit { + private matIconRegistry = inject(MatIconRegistry); + private domSanitizer = inject(DomSanitizer); + private route = inject(Router); + private store = inject>(Store); + private readonly focusManagerService = inject(FocusManagerService); + private testRunService = inject(TestRunService); + private cdr = inject(ChangeDetectorRef); + appStore = inject(AppStore); + private renderer = inject(Renderer2); + private el = inject(ElementRef); + public readonly CalloutType = CalloutType; public readonly StatusOfTestrun = StatusOfTestrun; + public readonly HelpTips = HelpTips; public readonly Routes = Routes; - private openedSettingFromToggleBtn = true; - - @ViewChild('settingsDrawer') public settingsDrawer!: MatDrawer; - @ViewChild('certDrawer') public certDrawer!: MatDrawer; - @ViewChild('toggleSettingsBtn') public toggleSettingsBtn!: HTMLButtonElement; - @ViewChild('toggleCertificatesBtn') - public toggleCertificatesBtn!: HTMLButtonElement; - @ViewChild('navigation') public navigation!: ElementRef; - @ViewChild('settings') public settings!: SettingsComponent; - @ViewChildren('riskAssessmentLink') - riskAssessmentLink!: QueryList; viewModel$ = this.appStore.viewModel$; - constructor( - private matIconRegistry: MatIconRegistry, - private domSanitizer: DomSanitizer, - private route: Router, - private store: Store, - private state: State, - private readonly focusManagerService: FocusManagerService, - private testRunService: TestRunService, - public appStore: AppStore, - private liveAnnouncer: LiveAnnouncer - ) { + readonly riskAssessmentLink = viewChild('riskAssessmentLink'); + private skipCount = 0; + @ViewChild('settingButton', { static: false }) settingButton!: MatButton; + settingTipTarget!: HTMLElement; + deviceTipTarget!: HTMLElement; + testingTipTarget!: HTMLElement; + riskAssessmentTipTarget!: HTMLElement; + isClosedTip = false; + + @HostListener('mousedown') + onMousedown() { + this.renderer.addClass(document.body as HTMLElement, 'using-mouse'); + } + + @HostListener('keydown', ['$event']) + onKeydown(e: KeyboardEvent) { + if (navKeys.includes(e.key)) { + this.renderer.removeClass(document.body as HTMLElement, 'using-mouse'); + } + } + + navigateToRuntime = () => { + this.route.navigate([Routes.Testing]); + this.appStore.setIsOpenStartTestrun(); + }; + + navigateToAddDevice = () => { + this.route.navigate([Routes.Devices]); + this.store.dispatch(setIsOpenAddDevice({ isOpenAddDevice: true })); + }; + + navigateToAddRiskAssessment = () => { + this.route.navigate([Routes.RiskAssessment]); + this.store.dispatch(setIsOpenProfile({ isOpenCreateProfile: true })); + }; + + menuItems: AddMenuItem[] = [ + { + svgIcon: 'testrun_logo_small', + label: 'Start Testing', + description: 'Configure your testing tasks', + onClick: this.navigateToRuntime, + disabled$: this.appStore.testrunButtonDisabled$, + }, + { + icon: 'home_iot_device', + label: 'Create new device', + onClick: this.navigateToAddDevice, + disabled$: of(false), + }, + { + icon: 'rule', + label: 'Create new Risk profile', + onClick: this.navigateToAddRiskAssessment, + disabled$: of(false), + }, + ]; + + constructor() { this.appStore.getDevices(); this.appStore.getRiskProfiles(); this.appStore.getSystemStatus(); this.appStore.getReports(); this.appStore.getTestModules(); this.appStore.getNetworkAdapters(); - this.matIconRegistry.addSvgIcon( - 'devices', - this.domSanitizer.bypassSecurityTrustResourceUrl(DEVICES_LOGO_URL) - ); + this.appStore.getInterfaces(); + this.appStore.getSystemConfig(); this.matIconRegistry.addSvgIcon( 'device_run', this.domSanitizer.bypassSecurityTrustResourceUrl(DEVICES_RUN_URL) ); - this.matIconRegistry.addSvgIcon( - 'reports', - this.domSanitizer.bypassSecurityTrustResourceUrl(REPORTS_LOGO_URL) - ); - this.matIconRegistry.addSvgIcon( - 'risk_assessment', - this.domSanitizer.bypassSecurityTrustResourceUrl(RISK_ASSESSMENT_LOGO_URL) - ); this.matIconRegistry.addSvgIcon( 'testrun_logo_small', this.domSanitizer.bypassSecurityTrustResourceUrl(TESTRUN_LOGO_URL) @@ -130,30 +239,41 @@ export class AppComponent implements AfterViewInit { 'qualification', this.domSanitizer.bypassSecurityTrustResourceUrl(QUALIFICATION_URL) ); + effect(() => { + if (this.skipCount === 0 && this.riskAssessmentLink()) { + this.riskAssessmentLink()?.nativeElement.focus(); + } else if (this.skipCount > 0) { + this.skipCount--; + } + }); } ngAfterViewInit() { + this.settingTipTarget = this.settingButton._elementRef.nativeElement; + this.deviceTipTarget = document.querySelector( + '.app-sidebar-button.app-sidebar-button-devices' + ) as HTMLElement; + this.testingTipTarget = document.querySelector( + '.app-sidebar-button.app-sidebar-button-testrun' + ) as HTMLElement; + + this.riskAssessmentTipTarget = document.querySelector( + '.app-sidebar-button.app-sidebar-button-risk-assessment' + ) as HTMLElement; + this.viewModel$ .pipe( filter(({ isStatusLoaded }) => isStatusLoaded === true), take(1) ) .subscribe(({ systemStatus }) => { - let skipCount = 0; if (systemStatus === StatusOfTestrun.InProgress) { // link should not be focused after page is just loaded - skipCount = 1; + this.skipCount = 1; } - this.riskAssessmentLink.changes.pipe(skip(skipCount)).subscribe(() => { - if (this.riskAssessmentLink.length > 0) { - this.riskAssessmentLink.first.nativeElement.focus(); - } - }); }); - } - get isRiskAssessmentRoute(): boolean { - return this.route.url === Routes.RiskAssessment; + this.cdr.detectChanges(); } get isDevicesRoute(): boolean { @@ -163,76 +283,34 @@ export class AppComponent implements AfterViewInit { navigateToDeviceRepository(): void { this.route.navigate([Routes.Devices]); } - navigateToAddDevice(): void { - this.route.navigate([Routes.Devices]); - this.store.dispatch(setIsOpenAddDevice({ isOpenAddDevice: true })); - } - - navigateToRuntime(): void { - this.route.navigate([Routes.Testing]); - this.appStore.setIsOpenStartTestrun(); - } - navigateToRiskAssessment(): void { - this.route.navigate([Routes.RiskAssessment]).then(() => { - this.appStore.setFocusOnPage(); + navigateToSettings(): void { + this.route.navigate([Routes.Settings]).then(() => { + timer(100).subscribe(() => { + this.appStore.setFocusOnPage( + window.document.querySelector('app-general-settings') + ); + }); }); } - async closeCertificates(): Promise { - await this.certDrawer.close(); - } + onCLoseTip(isClosed: boolean): void { + this.isClosedTip = isClosed; + const helpTipButton = window.document.querySelector( + '.app-toolbar-button-help-tips' + ) as HTMLButtonElement; + const helpTipEl = this.el.nativeElement.querySelector('app-help-tip'); - async closeSetting(hasDevices: boolean): Promise { - return await this.settingsDrawer.close().then(() => { - if (hasDevices) { - this.toggleSettingsBtn.focus(); - } // else device create window will be opened - if (!this.openedSettingFromToggleBtn) { - this.focusManagerService.focusFirstElementInContainer(); + timer(100).subscribe(() => { + if (isClosed) { + this.renderer.addClass(helpTipEl, 'closed-tip'); + helpTipButton.focus(); + } else { + this.renderer.removeClass(helpTipEl, 'closed-tip'); + this.focusManagerService.focusFirstElementInContainer(helpTipEl); } }); } - - async openSetting(isSettingsDisabled: boolean): Promise { - return await this.openGeneralSettings(false, isSettingsDisabled); - } - - public toggleMenu(event: MouseEvent) { - event.stopPropagation(); - this.appStore.toggleMenu(); - } - - /** - * When side menu is opened - */ - skipToNavigation(event: Event, focusNavigation: boolean) { - if (focusNavigation) { - event.preventDefault(); // if not prevented, second element will be focused - this.focusManagerService.focusFirstElementInContainer( - this.navigation.nativeElement - ); - this.appStore.updateFocusNavigation(false); // user will be navigated according to normal flow on tab - } - } - - async openGeneralSettings( - openSettingFromToggleBtn: boolean, - isSettingsDisabled: boolean - ) { - this.openedSettingFromToggleBtn = openSettingFromToggleBtn; - this.settings.getSystemInterfaces(); - this.settings.getSystemConfig(); - await this.settingsDrawer.open(); - if (isSettingsDisabled) { - await this.liveAnnouncer.announce('The settings panel is disabled'); - } - } - - async openCert() { - await this.certDrawer.open(); - } - consentShown() { this.appStore.setContent(); } @@ -248,7 +326,8 @@ export class AppComponent implements AfterViewInit { take(1) ) .subscribe(() => { - this.appStore.setFocusOnPage(); + const mainContainer = window.document.querySelector('#main'); + this.appStore.setFocusOnPage(mainContainer); }); } diff --git a/modules/ui/src/app/app.module.ts b/modules/ui/src/app/app.module.ts deleted file mode 100644 index 4ab788cbc..000000000 --- a/modules/ui/src/app/app.module.ts +++ /dev/null @@ -1,113 +0,0 @@ -/** - * Copyright 2023 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import { HTTP_INTERCEPTORS, HttpClientModule } from '@angular/common/http'; -import { importProvidersFrom, NgModule } from '@angular/core'; -import { MatButtonModule } from '@angular/material/button'; -import { MatButtonToggleModule } from '@angular/material/button-toggle'; -import { MatIconModule } from '@angular/material/icon'; -import { MatSidenavModule } from '@angular/material/sidenav'; -import { MatToolbarModule } from '@angular/material/toolbar'; -import { MatRadioModule } from '@angular/material/radio'; -import { BrowserModule } from '@angular/platform-browser'; -import { NoopAnimationsModule } from '@angular/platform-browser/animations'; - -import { AppRoutingModule } from './app-routing.module'; -import { AppComponent } from './app.component'; -import { SettingsComponent } from './pages/settings/settings.component'; -import { ReactiveFormsModule } from '@angular/forms'; -import { MatFormFieldModule } from '@angular/material/form-field'; -import { MatSnackBarModule } from '@angular/material/snack-bar'; -import { MatInputModule } from '@angular/material/input'; -import { MatSelectModule } from '@angular/material/select'; -import { MatTooltipModule } from '@angular/material/tooltip'; -import { ErrorInterceptor } from './interceptors/error.interceptor'; -import { LoadingInterceptor } from './interceptors/loading.interceptor'; -import { SpinnerComponent } from './components/spinner/spinner.component'; -import { BypassComponent } from './components/bypass/bypass.component'; -import { VersionComponent } from './components/version/version.component'; -import { CalloutComponent } from './components/callout/callout.component'; -import { StoreModule } from '@ngrx/store'; -import { appFeatureKey, rootReducer } from './store/reducers'; -import { EffectsModule } from '@ngrx/effects'; -import { AppEffects } from './store/effects'; -import { CdkTrapFocus } from '@angular/cdk/a11y'; -import { SettingsDropdownComponent } from './pages/settings/components/settings-dropdown/settings-dropdown.component'; -import { ShutdownAppComponent } from './components/shutdown-app/shutdown-app.component'; -import { WindowProvider } from './providers/window.provider'; -import { CertificatesComponent } from './pages/certificates/certificates.component'; -import { LOADER_TIMEOUT_CONFIG_TOKEN } from './services/loaderConfig'; -import { WifiComponent } from './components/wifi/wifi.component'; -import { TestingCompleteComponent } from './components/testing-complete/testing-complete.component'; - -import { MqttModule, IMqttServiceOptions } from 'ngx-mqtt'; -import { MatNativeDateModule } from '@angular/material/core'; - -export const MQTT_SERVICE_OPTIONS: IMqttServiceOptions = { - hostname: window.location.hostname, - port: 9001, -}; - -@NgModule({ - declarations: [AppComponent, SettingsComponent], - imports: [ - BrowserModule, - AppRoutingModule, - NoopAnimationsModule, - MatButtonModule, - MatIconModule, - MatToolbarModule, - MatSidenavModule, - MatButtonToggleModule, - MatRadioModule, - MatInputModule, - MatSelectModule, - MatTooltipModule, - HttpClientModule, - ReactiveFormsModule, - MatFormFieldModule, - MatSnackBarModule, - SpinnerComponent, - BypassComponent, - VersionComponent, - CalloutComponent, - StoreModule.forRoot({ [appFeatureKey]: rootReducer }), - EffectsModule.forRoot([AppEffects]), - CdkTrapFocus, - SettingsDropdownComponent, - ShutdownAppComponent, - CertificatesComponent, - MqttModule.forRoot(MQTT_SERVICE_OPTIONS), - WifiComponent, - TestingCompleteComponent, - ], - providers: [ - WindowProvider, - { - provide: HTTP_INTERCEPTORS, - useClass: ErrorInterceptor, - multi: true, - }, - { - provide: HTTP_INTERCEPTORS, - useClass: LoadingInterceptor, - multi: true, - }, - { provide: LOADER_TIMEOUT_CONFIG_TOKEN, useValue: 1000 }, - importProvidersFrom(MatNativeDateModule), - ], - bootstrap: [AppComponent], -}) -export class AppModule {} diff --git a/modules/ui/src/app/app.store.spec.ts b/modules/ui/src/app/app.store.spec.ts index 40b2d26dc..94c0db89d 100644 --- a/modules/ui/src/app/app.store.spec.ts +++ b/modules/ui/src/app/app.store.spec.ts @@ -45,6 +45,8 @@ import { setDevices, updateAdapters, setTestModules, + fetchSystemConfig, + fetchInterfaces, } from './store/actions'; import { MOCK_PROGRESS_DATA_IN_PROGRESS } from './mocks/testrun.mock'; import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; @@ -185,9 +187,7 @@ describe('AppStore', () => { isTestingComplete: false, riskProfiles: [], hasConnectionSettings: true, - isMenuOpen: false, interfaces: {}, - focusNavigation: false, settingMissedError: null, calloutState: new Map(), hasInternetConnection: false, @@ -262,7 +262,7 @@ describe('AppStore', () => { describe('setFocusOnPage', () => { it('should call focusFirstElementInContainer', fakeAsync(() => { - appStore.setFocusOnPage(); + appStore.setFocusOnPage(null); tick(101); @@ -513,6 +513,7 @@ describe('AppStore', () => { started: '2023-06-22T09:20:00.123Z', finished: '2023-06-22T09:26:00.123Z', report: 'https://api.testrun.io/report.pdf', + export: 'https://api.testrun.io/export.pdf', tags: [], tests: { total: 3, @@ -522,5 +523,21 @@ describe('AppStore', () => { store.refreshState(); }); }); + + describe('getInterfaces', () => { + it('should dispatch action fetchInterfaces', () => { + appStore.getInterfaces(); + + expect(store.dispatch).toHaveBeenCalledWith(fetchInterfaces()); + }); + }); + + describe('getSystemConfig', () => { + it('should dispatch action fetchSystemConfig', () => { + appStore.getSystemConfig(); + + expect(store.dispatch).toHaveBeenCalledWith(fetchSystemConfig()); + }); + }); }); }); diff --git a/modules/ui/src/app/app.store.ts b/modules/ui/src/app/app.store.ts index 9b07fddf7..7f89b544f 100644 --- a/modules/ui/src/app/app.store.ts +++ b/modules/ui/src/app/app.store.ts @@ -14,9 +14,9 @@ * limitations under the License. */ -import { Injectable } from '@angular/core'; +import { Injectable, inject } from '@angular/core'; import { ComponentStore } from '@ngrx/component-store'; -import { tap, withLatestFrom } from 'rxjs/operators'; +import { tap } from 'rxjs/operators'; import { selectHasConnectionSettings, selectHasDevices, @@ -52,6 +52,8 @@ import { fetchReports, setTestModules, updateAdapters, + fetchInterfaces, + fetchSystemConfig, } from './store/actions'; import { ResultOfTestrun, TestrunStatus } from './model/testrun-status'; import { @@ -64,6 +66,7 @@ import { FocusManagerService } from './services/focus-manager.service'; import { TestRunMqttService } from './services/test-run-mqtt.service'; import { NotificationService } from './services/notification.service'; import { Profile } from './model/profile'; +import { map } from 'rxjs/internal/operators/map'; export const CONSENT_SHOWN_KEY = 'CONSENT_SHOWN'; export const CALLOUT_STATE_KEY = 'CALLOUT_STATE'; @@ -72,15 +75,16 @@ export interface AppComponentState { isStatusLoaded: boolean; systemStatus: TestrunStatus | null; calloutState: Map; - isMenuOpen: boolean; - /** - * Indicates, if side menu should be focused on keyboard navigation after menu is opened - */ - focusNavigation: boolean; settingMissedError: SettingMissedError | null; } @Injectable() export class AppStore extends ComponentStore { + private store = inject>(Store); + private testRunService = inject(TestRunService); + private testRunMqttService = inject(TestRunMqttService); + private focusManagerService = inject(FocusManagerService); + private notificationService = inject(NotificationService); + private consentShown$ = this.select(state => state.consentShown); private calloutState$ = this.select(state => state.calloutState); private isStatusLoaded$ = this.select(state => state.isStatusLoaded); @@ -93,8 +97,6 @@ export class AppStore extends ComponentStore { private hasConnectionSetting$ = this.store.select( selectHasConnectionSettings ); - private isMenuOpened$ = this.select(state => state.isMenuOpen); - private focusNavigation$ = this.select(state => state.focusNavigation); private interfaces$: Observable = this.store.select(selectInterfaces); private systemConfig$: Observable = @@ -109,6 +111,33 @@ export class AppStore extends ComponentStore { ); riskProfiles$: Observable = this.store.select(selectRiskProfiles); + testrunButtonDisabled$ = combineLatest([ + this.hasDevices$, + this.isAllDevicesOutdated$, + this.isStatusLoaded$, + this.systemStatus$, + this.hasConnectionSetting$, + ]).pipe( + map( + ([ + hasDevices, + isAllDevicesOutdated, + isStatusLoaded, + systemStatus, + hasConnectionSettings, + ]) => { + return !( + hasConnectionSettings === true && + hasDevices && + (!systemStatus || + !this.testRunService.testrunInProgress(systemStatus)) && + isStatusLoaded === true && + !isAllDevicesOutdated + ); + } + ) + ); + viewModel$ = this.select({ consentShown: this.consentShown$, hasDevices: this.hasDevices$, @@ -122,12 +151,10 @@ export class AppStore extends ComponentStore { isTestingComplete: this.isTestingComplete$, riskProfiles: this.riskProfiles$, hasConnectionSettings: this.hasConnectionSetting$, - isMenuOpen: this.isMenuOpened$, interfaces: this.interfaces$, settingMissedError: this.settingMissedError$, calloutState: this.calloutState$, hasInternetConnection: this.hasInternetConnection$, - focusNavigation: this.focusNavigation$, }); updateConsent = this.updater((state, consentShown: boolean) => ({ @@ -151,16 +178,6 @@ export class AppStore extends ComponentStore { isStatusLoaded, })); - updateFocusNavigation = this.updater((state, focusNavigation: boolean) => ({ - ...state, - focusNavigation, - })); - - updateIsMenuOpened = this.updater((state, isMenuOpen: boolean) => ({ - ...state, - isMenuOpen, - })); - updateSettingMissedError = this.updater( (state, settingMissedError: SettingMissedError | null) => ({ ...state, @@ -245,14 +262,16 @@ export class AppStore extends ComponentStore { ); }); - setFocusOnPage = this.effect(trigger$ => { - return trigger$.pipe( - delay(100), - tap(() => { - this.focusManagerService.focusFirstElementInContainer(); - }) - ); - }); + setFocusOnPage = this.effect( + trigger$ => { + return trigger$.pipe( + delay(100), + tap(element => { + this.focusManagerService.focusFirstElementInContainer(element); + }) + ); + } + ); getReports = this.effect(trigger$ => { return trigger$.pipe( @@ -293,18 +312,6 @@ export class AppStore extends ComponentStore { ); }); - toggleMenu = this.effect(trigger$ => { - return trigger$.pipe( - withLatestFrom(this.isMenuOpened$), - tap(([, opened]) => { - this.updateIsMenuOpened(!opened); - if (!opened) { - this.updateFocusNavigation(true); - } - }) - ); - }); - checkInterfacesInConfig = this.effect(() => { return combineLatest([ this.interfaces$.pipe(skip(1)), @@ -345,13 +352,23 @@ export class AppStore extends ComponentStore { ); }); - constructor( - private store: Store, - private testRunService: TestRunService, - private testRunMqttService: TestRunMqttService, - private focusManagerService: FocusManagerService, - private notificationService: NotificationService - ) { + getInterfaces = this.effect(trigger$ => { + return trigger$.pipe( + tap(() => { + this.store.dispatch(fetchInterfaces()); + }) + ); + }); + + getSystemConfig = this.effect(trigger$ => { + return trigger$.pipe( + tap(() => { + this.store.dispatch(fetchSystemConfig()); + }) + ); + }); + + constructor() { // @ts-expect-error get object is defined in index.html const calloutState = sessionStorage.getObject(CALLOUT_STATE_KEY); @@ -362,8 +379,6 @@ export class AppStore extends ComponentStore { calloutState: calloutState ? new Map(Object.entries(calloutState)) : new Map(), - isMenuOpen: false, - focusNavigation: false, settingMissedError: null, }); } diff --git a/modules/ui/src/app/components/bypass/bypass.component.spec.ts b/modules/ui/src/app/components/bypass/bypass.component.spec.ts index 8ba531e7e..a69229d8a 100644 --- a/modules/ui/src/app/components/bypass/bypass.component.spec.ts +++ b/modules/ui/src/app/components/bypass/bypass.component.spec.ts @@ -25,6 +25,7 @@ import SpyObj = jasmine.SpyObj; template: '' + '
', + standalone: false, }) class TestBypassComponent {} diff --git a/modules/ui/src/app/components/bypass/bypass.component.ts b/modules/ui/src/app/components/bypass/bypass.component.ts index 3a7d2819c..08f2363f5 100644 --- a/modules/ui/src/app/components/bypass/bypass.component.ts +++ b/modules/ui/src/app/components/bypass/bypass.component.ts @@ -13,20 +13,21 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { Component } from '@angular/core'; +import { Component, inject } from '@angular/core'; import { CommonModule } from '@angular/common'; import { MatButtonModule } from '@angular/material/button'; import { FocusManagerService } from '../../services/focus-manager.service'; @Component({ selector: 'app-bypass', - standalone: true, + imports: [CommonModule, MatButtonModule], templateUrl: './bypass.component.html', styleUrls: ['./bypass.component.scss'], }) export class BypassComponent { - constructor(private readonly focusManagerService: FocusManagerService) {} + private readonly focusManagerService = inject(FocusManagerService); + skipToMainContent(event: Event) { event.preventDefault(); this.focusManagerService.focusFirstElementInContainer(); diff --git a/modules/ui/src/app/components/callout/callout.component.html b/modules/ui/src/app/components/callout/callout.component.html index 36f0c981f..918dc0c16 100644 --- a/modules/ui/src/app/components/callout/callout.component.html +++ b/modules/ui/src/app/components/callout/callout.component.html @@ -13,25 +13,39 @@ See the License for the specific language governing permissions and limitations under the License. --> -
+
- {{ type }} + fontSet="material-symbols-outlined"> + {{ type() }} - +

- +
+ {{ action() }} + +
+
diff --git a/modules/ui/src/app/components/callout/callout.component.scss b/modules/ui/src/app/components/callout/callout.component.scss index bd1008638..994e6c580 100644 --- a/modules/ui/src/app/components/callout/callout.component.scss +++ b/modules/ui/src/app/components/callout/callout.component.scss @@ -14,8 +14,9 @@ * limitations under the License. */ @use '@angular/material' as mat; -@import '../../../theming/colors'; -@import '../../../theming/variables'; +@use 'm3-theme' as *; +@use 'colors'; +@use 'variables'; :host { width: 100%; @@ -31,89 +32,90 @@ } } +.check_circle { + color: colors.$on-tertiary-container; + background-color: rgba(20, 108, 46, 0.1); +} + +.info { + color: colors.$primary; + background-color: rgba(127, 207, 255, 0.16); +} + +.error { + color: colors.$on-error-container; + background-color: rgba(179, 38, 30, 0.1); +} + +.warning { + color: colors.$orange-40; + background-color: colors.$orange-98; +} + +.warning .callout-border { + color: colors.$orange-60; +} + .callout-container { + position: relative; + overflow: hidden; display: flex; box-sizing: border-box; height: auto; min-height: 48px; - padding: 6px 24px; - border-radius: 8px; + padding: 6px 6px 6px 16px; + border-radius: variables.$corner-medium; align-items: center; gap: 16px; + margin: 24px 32px; } .callout-icon { flex-shrink: 0; padding: 4px 0; + color: inherit; } -.callout-container.info { - margin: 24px 32px; - background-color: mat.m2-get-color-from-palette($color-primary, 50); - - .callout-icon { - color: mat.m2-get-color-from-palette($color-primary, 700); - } - - .info-pilot { - width: 24px; - display: flex; - justify-content: center; - } -} - -.callout-container.warning_amber { - background-color: $yellow-50; - - .callout-icon { - color: $orange-700; - } +.callout-context { + margin: 0; + padding: 6px 0; + color: colors.$on-surface-variant; + font-family: variables.$font-secondary; + font-size: 14px; + line-height: 20px; + letter-spacing: 0.2px; } -.callout-container.error { - margin: 24px 32px; - background-color: $red-50; - - .callout-icon { - color: $red-700; - } +.callout-action-container { + display: flex; + margin-left: auto; } -.callout-container.check_circle { - margin: 24px 32px; - background-color: $green-50; - - .callout-icon { - color: $green-800; +@media (max-width: 800px) { + .callout-action-container { + flex-direction: column; } } -.callout-container.error_outline { - display: flex; - align-items: flex-start; - background: $color-background-grey; - - .callout-icon { - color: $grey-700; - } - - .callout-context { - font-weight: bold; - } +.callout-action { + color: inherit; + padding: 10px 16px; } -.callout-context { - margin: 0; - padding: 6px 0; - color: $grey-800; - font-family: $font-secondary; - font-size: 14px; - line-height: 20px; - letter-spacing: 0.2px; +.callout-action-link { + text-decoration: none; + cursor: pointer; + font-weight: 500; + font-size: var(--mdc-text-button-label-text-size); + font-family: var(--mdc-text-button-label-text-font); } -.callout-close-button { - margin-left: auto; - margin-right: -20px; - color: $warn; +.callout-border { + display: block; + height: 3px; + border-bottom: 3px solid; + width: 100%; + position: absolute; + bottom: 0; + margin-left: -16px; } diff --git a/modules/ui/src/app/components/callout/callout.component.spec.ts b/modules/ui/src/app/components/callout/callout.component.spec.ts index fab52c949..7b6a98cd8 100644 --- a/modules/ui/src/app/components/callout/callout.component.spec.ts +++ b/modules/ui/src/app/components/callout/callout.component.spec.ts @@ -29,7 +29,7 @@ describe('CalloutComponent', () => { }).compileComponents(); fixture = TestBed.createComponent(CalloutComponent); component = fixture.componentInstance; - component.type = 'mockValue'; + fixture.componentRef.setInput('type', 'mockValue'); compiled = fixture.nativeElement as HTMLElement; fixture.detectChanges(); }); @@ -66,4 +66,27 @@ describe('CalloutComponent', () => { expect(calloutClosedSpy).toHaveBeenCalled(); }); }); + + describe('action', () => { + beforeEach(() => { + fixture.componentRef.setInput('action', 'action'); + fixture.detectChanges(); + }); + + it('should have action link', () => { + const closeButton = compiled.querySelector('.callout-action-link'); + + expect(closeButton).toBeTruthy(); + }); + + it('should emit event', () => { + const calloutClosedSpy = spyOn(component.onAction, 'emit'); + const actionLink = compiled.querySelector( + '.callout-action-link' + ) as HTMLAnchorElement; + actionLink?.click(); + + expect(calloutClosedSpy).toHaveBeenCalled(); + }); + }); }); diff --git a/modules/ui/src/app/components/callout/callout.component.ts b/modules/ui/src/app/components/callout/callout.component.ts index 8fa46d9f0..2081f9f22 100644 --- a/modules/ui/src/app/components/callout/callout.component.ts +++ b/modules/ui/src/app/components/callout/callout.component.ts @@ -16,9 +16,8 @@ import { ChangeDetectionStrategy, Component, - EventEmitter, - Input, - Output, + input, + output, } from '@angular/core'; import { CommonModule } from '@angular/common'; import { MatIconModule } from '@angular/material/icon'; @@ -36,15 +35,17 @@ import { ProgramTypeIconComponent } from '../program-type-icon/program-type-icon ProgramTypeIconComponent, ], selector: 'app-callout', - standalone: true, + styleUrls: ['./callout.component.scss'], templateUrl: './callout.component.html', }) export class CalloutComponent { readonly CalloutType = CalloutType; readonly ProgramType = ProgramType; - @Input() id: string | null = null; - @Input() type = ''; - @Input() closable = false; - @Output() calloutClosed = new EventEmitter(); + id = input(null); + type = input(''); + closable = input(false); + action = input(); + calloutClosed = output(); + onAction = output(); } diff --git a/modules/ui/src/app/components/device-item/device-item.component.html b/modules/ui/src/app/components/device-item/device-item.component.html index f1586f9a0..7b728b33c 100644 --- a/modules/ui/src/app/components/device-item/device-item.component.html +++ b/modules/ui/src/app/components/device-item/device-item.component.html @@ -18,85 +18,77 @@ [tabIndex]="tabIndex" (click)="itemClick()" [attr.aria-label]="label" - class="device-item" + class="device-item device-item-basic" type="button"> - +
- + class="device-item device-item-basic non-interactive"> +
- -

- {{ device.test_pack }} - - - {{ device.manufacturer }} -

-

- {{ device.model }} -

-

- {{ device.mac_addr }} -

-
-
+ [class.device-item-outdated]="device.status === DeviceStatus.INVALID"> -
+ + + {{ device.test_pack }} + + +

+ {{ device.manufacturer }} +

+ + error + +
+

{{ device.manufacturer }}

+ + edit_square +
+
+ {{ INVALID_DEVICE }} +
+
+ Under test +
+

+ {{ device.model }} +

+
diff --git a/modules/ui/src/app/components/device-item/device-item.component.scss b/modules/ui/src/app/components/device-item/device-item.component.scss index b3c8e9885..7ea530c0b 100644 --- a/modules/ui/src/app/components/device-item/device-item.component.scss +++ b/modules/ui/src/app/components/device-item/device-item.component.scss @@ -14,145 +14,145 @@ * limitations under the License. */ @use '@angular/material' as mat; -@import '../../../theming/colors'; -@import '../../../theming/variables'; +@use 'm3-theme' as *; +@use 'colors'; +@use 'variables'; +@use 'mixins'; $icon-width: 80px; $border-radius: 12px; .device-item { display: grid; - width: $device-item-width; - height: 80px; - border-radius: $border-radius; - border: 1px solid $blue-300; - background: $white; + grid-column-gap: 24px; box-sizing: border-box; - grid-template-columns: 1fr 1fr; - padding: 0; - grid-column-gap: 8px; - grid-row-gap: 4px; - font-family: $font-primary; + font-family: variables.$font-primary; + border-radius: variables.$corner-large; + align-items: center; + border: none; +} + +.device-item-basic { + padding: 0 24px 0 32px; + width: variables.$device-item-width; + height: 92px; + box-shadow: + 0px 1px 2px 0px rgba(0, 0, 0, 0.3), + 0px 1px 3px 1px rgba(0, 0, 0, 0.15); + background: colors.$surface-container; + grid-template-columns: auto 1fr; grid-template-areas: - 'manufacturer manufacturer' - 'name address'; + 'icon manufacturer' + 'icon name'; &:hover { cursor: pointer; + background: colors.$primary-container; } &.non-interactive { + background: colors.$primary-container; &:hover { cursor: default; } } -} -.device-item-with-actions { - display: grid; - width: $device-item-width; - min-height: calc($icon-width - 2px); - border-radius: $border-radius; - border: 1px solid $blue-300; - background: $white; - box-sizing: border-box; - grid-template-columns: 1fr $icon-width; - grid-column-gap: 1px; - padding: 0; - font-family: $font-primary; - grid-template-areas: 'edit start'; - - &.device-item-outdated { - border-color: $red-300; + .item-manufacturer { + display: block; + max-width: 100%; + box-sizing: border-box; + text-overflow: ellipsis; + white-space: nowrap; + overflow: hidden; + margin: 0; + } + + .item-name { + width: 230px; + box-sizing: border-box; + text-align: start; } + + .item-mac-address { + margin: 0; + } +} + +.button-edit:has(.item-status) { + grid-template-columns: min-content auto min-content; + grid-template-areas: + 'icon manufacturer status' + 'icon name name'; } .button-edit { - display: grid; - grid-area: edit; - background: $white; - box-sizing: border-box; - grid-template-columns: 1fr 1fr; - padding: 0 6px 0 0; - grid-column-gap: 8px; - grid-row-gap: 4px; - font-family: $font-primary; + width: 100%; + height: 100%; + background: inherit; + grid-template-columns: min-content auto; grid-template-areas: - 'manufacturer status' - 'name address'; - border-radius: $border-radius 0 0 $border-radius; - border: none; + 'icon manufacturer' + 'icon name'; &:hover { cursor: pointer; - .item-manufacturer-text { - max-width: 82px; - } - .item-manufacturer-icon { + position: absolute; + right: 0; visibility: visible; width: 24px; } } &:disabled { - pointer-events: none; - opacity: 0.5; + @include mixins.disabled; } } -.device-item:not(.non-interective):hover { - border-color: mat.m2-get-color-from-palette($color-primary, 600); -} - -.device-item-with-actions:not(.device-item-outdated):has( - .button-edit:not(:disabled):hover - ) { - border-color: mat.m2-get-color-from-palette($color-primary, 600); -} - -.device-item-with-actions:not(.device-item-outdated):has( - .button-start:not(:disabled):hover - ) { - border-color: mat.m2-get-color-from-palette($color-primary, 600); -} - .item-status { - margin-right: 16px; - grid-area: status; - justify-self: end; align-self: end; - font-size: 8px; + white-space: nowrap; + grid-area: status; + justify-self: start; + font-size: 11px; + font-weight: 500; line-height: 16px; letter-spacing: 0.64px; text-transform: uppercase; max-width: 100%; - border-radius: 2px; - background: $red-700; - color: $white; - padding: 0px 4px; + border-radius: 200px; + padding: 0 7px; + &-under-test { + background: colors.$on-secondary-container; + color: colors.$on-secondary; + } + &-invalid { + background: colors.$error-container; + color: colors.$on-error-container; + } } .item-manufacturer { display: flex; - padding: 0 0 0 16px; grid-area: manufacturer; justify-self: start; align-self: end; - color: #1f1f1f; + color: colors.$on-surface; justify-content: flex-start; font-size: 16px; font-weight: 500; line-height: 24px; text-align: start; + position: relative; + padding-right: variables.$icon-size; .item-manufacturer-text { + max-width: 240px; margin: 0; text-overflow: ellipsis; white-space: nowrap; overflow: hidden; - max-width: 106px; } .item-manufacturer-icon { @@ -166,10 +166,10 @@ $border-radius: 12px; } .item-name { - padding: 0 2px 0 16px; + align-self: start; grid-area: name; justify-self: start; - color: $grey-800; + color: colors.$on-surface-variant; font-size: 14px; font-style: normal; font-weight: 400; @@ -181,68 +181,22 @@ $border-radius: 12px; max-width: -webkit-fill-available; max-width: -moz-available; text-align: left; + margin: 0; } -.item-mac-address { - padding-right: 16px; - grid-area: address; - justify-self: end; - color: $grey-700; - font-family: Roboto, sans-serif; - font-size: 12px; - padding-top: 2px; - line-height: 20px; - max-width: 100%; +app-program-type-icon, +mat-icon { + grid-area: icon; } -.button-start { - grid-area: start; - width: $icon-width; +.device-item-outdated { height: 100%; - background-color: mat.m2-get-color-from-palette($color-primary, 50); - justify-self: end; - border-radius: 0 $border-radius $border-radius 0; - - &:hover, - &:focus-visible { - background-color: mat.m2-get-color-from-palette($color-primary, 600); - - .button-start-icon { - color: $white; - } - } - &:disabled { - pointer-events: none; - background: rgba(60, 64, 67, 0.12); - } -} - -.button-start-icon { - margin: 0; - width: 30px; - height: 24px; -} - -.device-item { - .item-manufacturer { - display: block; - max-width: 100%; - box-sizing: border-box; - padding: 0 16px; - text-overflow: ellipsis; - white-space: nowrap; - overflow: hidden; - margin: 0; - } - - .item-name { - width: 230px; - box-sizing: border-box; - text-align: start; - margin: 0; + .item-manufacturer, + .item-name, + mat-icon { + color: colors.$on-error-container; } - - .item-mac-address { - margin: 0; + .item-manufacturer-text { + max-width: 150px; } } diff --git a/modules/ui/src/app/components/device-item/device-item.component.spec.ts b/modules/ui/src/app/components/device-item/device-item.component.spec.ts index b92c7d3cf..350fa7548 100644 --- a/modules/ui/src/app/components/device-item/device-item.component.spec.ts +++ b/modules/ui/src/app/components/device-item/device-item.component.spec.ts @@ -22,7 +22,6 @@ import { } from '../../model/device'; import { DeviceItemComponent } from './device-item.component'; -import { DevicesModule } from '../../pages/devices/devices.module'; import { MatIconTestingModule } from '@angular/material/icon/testing'; import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; @@ -34,7 +33,6 @@ describe('DeviceItemComponent', () => { beforeEach(() => { TestBed.configureTestingModule({ imports: [ - DevicesModule, DeviceItemComponent, MatIconTestingModule, BrowserAnimationsModule, @@ -70,11 +68,9 @@ describe('DeviceItemComponent', () => { it('should display information about device', () => { const name = compiled.querySelector('.item-name'); const manufacturer = compiled.querySelector('.item-manufacturer'); - const mac = compiled.querySelector('.item-mac-address'); expect(name?.textContent?.trim()).toEqual('O3-DIN-CPU'); expect(manufacturer?.textContent?.trim()).toEqual('Delta'); - expect(mac?.textContent?.trim()).toEqual('00:1e:42:35:73:c4'); }); it('should have qualification icon if testing type is qualification', () => { @@ -125,24 +121,25 @@ describe('DeviceItemComponent', () => { expect(status?.textContent?.trim()).toEqual('Outdated'); }); - it('should disable start buttons', () => { - const startBtn = compiled.querySelector('.button-start') as HTMLElement; + it('should have error icon', () => { + const icon = compiled.querySelector('mat-icon')?.textContent?.trim(); - expect(startBtn.getAttribute('disabled')).toBeTruthy(); + expect(icon).toEqual('error'); }); }); - it('should emit device on click edit button', () => { - const clickSpy = spyOn(component.itemClicked, 'emit'); - const editBtn = compiled.querySelector('.button-edit') as HTMLElement; - editBtn.click(); + it('should have item status as Under test', () => { + component.disabled = true; + fixture.detectChanges(); + const status = compiled.querySelector('.item-status'); - expect(clickSpy).toHaveBeenCalledWith(component.device); + expect(status).toBeTruthy(); + expect(status?.textContent?.trim()).toEqual('Under test'); }); - it('should emit device on click start button', () => { - const clickSpy = spyOn(component.startTestrunClicked, 'emit'); - const editBtn = compiled.querySelector('.button-start') as HTMLElement; + it('should emit device on click edit button', () => { + const clickSpy = spyOn(component.itemClicked, 'emit'); + const editBtn = compiled.querySelector('.button-edit') as HTMLElement; editBtn.click(); expect(clickSpy).toHaveBeenCalledWith(component.device); @@ -152,22 +149,18 @@ describe('DeviceItemComponent', () => { component.disabled = true; fixture.detectChanges(); - const startBtn = compiled.querySelector('.button-start') as HTMLElement; const editBtn = compiled.querySelector('.button-edit') as HTMLElement; expect(editBtn.getAttribute('disabled')).not.toBeNull(); - expect(startBtn.getAttribute('disabled')).toBeTruthy(); }); it('should not disable buttons if disable set to false', () => { component.disabled = false; fixture.detectChanges(); - const startBtn = compiled.querySelector('.button-start') as HTMLElement; const editBtn = compiled.querySelector('.button-edit') as HTMLElement; expect(editBtn.getAttribute('disabled')).toBeNull(); - expect(startBtn.getAttribute('disabled')).toBeFalsy(); }); }); }); diff --git a/modules/ui/src/app/components/device-item/device-item.component.ts b/modules/ui/src/app/components/device-item/device-item.component.ts index 72690210f..d78dbfd6d 100644 --- a/modules/ui/src/app/components/device-item/device-item.component.ts +++ b/modules/ui/src/app/components/device-item/device-item.component.ts @@ -31,7 +31,7 @@ import { ProgramType } from '../../model/program-type'; selector: 'app-device-item', templateUrl: './device-item.component.html', styleUrls: ['./device-item.component.scss'], - standalone: true, + imports: [ CommonModule, MatButtonModule, @@ -50,15 +50,11 @@ export class DeviceItemComponent { @Input() deviceView!: string; @Input() disabled = false; @Output() itemClicked = new EventEmitter(); - @Output() startTestrunClicked = new EventEmitter(); readonly DeviceView = DeviceView; itemClick(): void { this.itemClicked.emit(this.device); } - startTestrunClick(): void { - this.startTestrunClicked.emit(this.device); - } get label() { const deviceStatus = diff --git a/modules/ui/src/app/components/device-tests/device-tests.component.scss b/modules/ui/src/app/components/device-tests/device-tests.component.scss index e7529f269..42bd1646d 100644 --- a/modules/ui/src/app/components/device-tests/device-tests.component.scss +++ b/modules/ui/src/app/components/device-tests/device-tests.component.scss @@ -13,12 +13,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -@import '../../../theming/colors'; -@import '../../../theming/variables'; - -:host { - overflow: hidden; -} +@use 'colors'; +@use 'variables'; +@use 'mixins'; .device-tests-form { height: 100%; @@ -27,26 +24,21 @@ } .disabled { - pointer-events: none; - opacity: 0.6; + @include mixins.disabled; } .device-tests-title { margin: 20px 0 8px; font-size: 18px; line-height: 24px; - color: $grey-800; + color: colors.$on-surface-variant; } .device-tests-description { margin: 0; - font-family: $font-secondary; + font-family: variables.$font-text; font-size: 14px; line-height: 20px; letter-spacing: 0.2px; - color: $grey-800; -} - -.device-form-test-modules { - overflow: auto; + color: colors.$on-surface-variant; } diff --git a/modules/ui/src/app/components/device-tests/device-tests.component.spec.ts b/modules/ui/src/app/components/device-tests/device-tests.component.spec.ts index 0599af372..b959bc1db 100644 --- a/modules/ui/src/app/components/device-tests/device-tests.component.spec.ts +++ b/modules/ui/src/app/components/device-tests/device-tests.component.spec.ts @@ -65,14 +65,14 @@ describe('DeviceTestsComponent', () => { }); it('should fill tests with device test values if device not present', () => { - component.deviceTestModules = { + fixture.componentRef.setInput('deviceTestModules', { connection: { enabled: false, }, dns: { enabled: true, }, - }; + }); component.ngOnInit(); expect(component.test_modules.controls[0].value).toEqual(false); diff --git a/modules/ui/src/app/components/device-tests/device-tests.component.ts b/modules/ui/src/app/components/device-tests/device-tests.component.ts index 58e1a3c4d..a0df852e4 100644 --- a/modules/ui/src/app/components/device-tests/device-tests.component.ts +++ b/modules/ui/src/app/components/device-tests/device-tests.component.ts @@ -16,6 +16,8 @@ import { ChangeDetectionStrategy, Component, + effect, + input, Input, OnInit, } from '@angular/core'; @@ -31,7 +33,7 @@ import { MatCheckboxModule } from '@angular/material/checkbox'; @Component({ selector: 'app-device-tests', - standalone: true, + imports: [CommonModule, MatCheckboxModule, ReactiveFormsModule], templateUrl: './device-tests.component.html', styleUrls: ['./device-tests.component.scss'], @@ -39,11 +41,15 @@ import { MatCheckboxModule } from '@angular/material/checkbox'; }) export class DeviceTestsComponent implements OnInit { @Input() deviceForm!: FormGroup; - @Input() deviceTestModules?: TestModules | null; @Input() testModules: TestModule[] = []; // For initiate test run form tests should be displayed and disabled for change @Input() disabled = false; + deviceTestModules = input(); + deviceTestModulesEffect = effect(() => { + this.fillTestModulesFormControls(); + }); + get test_modules() { return this.deviceForm?.controls['test_modules'] as FormArray; } @@ -54,11 +60,12 @@ export class DeviceTestsComponent implements OnInit { fillTestModulesFormControls() { this.test_modules.controls = []; - if (this.deviceTestModules) { + if (this.deviceTestModules()) { this.testModules.forEach(test => { this.test_modules.push( new FormControl( - (this.deviceTestModules as TestModules)[test.name]?.enabled || false + (this.deviceTestModules() as TestModules)[test.name]?.enabled || + false ) ); }); diff --git a/modules/ui/src/app/components/download-report-pdf/download-report-pdf.component.ts b/modules/ui/src/app/components/download-report-pdf/download-report-pdf.component.ts index 9262a11f2..bc68e1193 100644 --- a/modules/ui/src/app/components/download-report-pdf/download-report-pdf.component.ts +++ b/modules/ui/src/app/components/download-report-pdf/download-report-pdf.component.ts @@ -17,13 +17,12 @@ import { ChangeDetectionStrategy, Component } from '@angular/core'; import { CommonModule, DatePipe } from '@angular/common'; import { DownloadReportComponent } from '../download-report/download-report.component'; import { ReportActionComponent } from '../report-action/report-action.component'; -import { MatIcon } from '@angular/material/icon'; @Component({ selector: 'app-download-report-pdf', templateUrl: './download-report-pdf.component.html', - standalone: true, - imports: [CommonModule, DownloadReportComponent, MatIcon], + + imports: [CommonModule, DownloadReportComponent], providers: [DatePipe], changeDetection: ChangeDetectionStrategy.OnPush, }) diff --git a/modules/ui/src/app/components/download-report-zip/download-report-zip.component.spec.ts b/modules/ui/src/app/components/download-report-zip/download-report-zip.component.spec.ts index c0a2bd18b..ae35a64bc 100644 --- a/modules/ui/src/app/components/download-report-zip/download-report-zip.component.spec.ts +++ b/modules/ui/src/app/components/download-report-zip/download-report-zip.component.spec.ts @@ -43,7 +43,8 @@ describe('DownloadReportZipComponent', () => { fixture = TestBed.createComponent(DownloadReportZipComponent); compiled = fixture.nativeElement as HTMLElement; component = fixture.componentInstance; - component.url = 'localhost:8080'; + component.report = 'localhost:8080'; + component.export = 'localhost:8080'; component.data = MOCK_PROGRESS_DATA_COMPLIANT; }); @@ -64,7 +65,8 @@ describe('DownloadReportZipComponent', () => { ariaLabel: 'Download zip', data: { profiles: [], - url: 'localhost:8080', + report: 'localhost:8080', + export: 'localhost:8080', isPilot: false, }, autoFocus: true, diff --git a/modules/ui/src/app/components/download-report-zip/download-report-zip.component.ts b/modules/ui/src/app/components/download-report-zip/download-report-zip.component.ts index 742210937..8fa2645b5 100644 --- a/modules/ui/src/app/components/download-report-zip/download-report-zip.component.ts +++ b/modules/ui/src/app/components/download-report-zip/download-report-zip.component.ts @@ -20,6 +20,7 @@ import { HostListener, Input, OnInit, + inject, } from '@angular/core'; import { CommonModule, DatePipe } from '@angular/common'; import { Profile } from '../../model/profile'; @@ -33,7 +34,7 @@ import { TestingType } from '../../model/device'; selector: 'app-download-report-zip', templateUrl: './download-report-zip.component.html', styleUrl: './download-report-zip.component.scss', - standalone: true, + imports: [CommonModule, MatTooltipModule], providers: [DatePipe, MatTooltip], changeDetection: ChangeDetectionStrategy.OnPush, @@ -42,8 +43,12 @@ export class DownloadReportZipComponent extends ReportActionComponent implements OnInit { + dialog = inject(MatDialog); + tooltip = inject(MatTooltip); + @Input() profiles: Profile[] = []; - @Input() url: string | null | undefined = null; + @Input() report: string | null | undefined = null; + @Input() export: string | null | undefined = null; @HostListener('click', ['$event']) @HostListener('keydown.enter', ['$event']) @@ -56,7 +61,8 @@ export class DownloadReportZipComponent ariaLabel: 'Download zip', data: { profiles: this.profiles, - url: this.url, + report: this.report, + export: this.export, isPilot: this.data?.device.test_pack === TestingType.Pilot, }, autoFocus: true, @@ -87,11 +93,7 @@ export class DownloadReportZipComponent } } - constructor( - datePipe: DatePipe, - public dialog: MatDialog, - public tooltip: MatTooltip - ) { - super(datePipe); + constructor() { + super(); } } diff --git a/modules/ui/src/app/components/download-report/download-report.component.html b/modules/ui/src/app/components/download-report/download-report.component.html index 839458ae8..0e27377ed 100644 --- a/modules/ui/src/app/components/download-report/download-report.component.html +++ b/modules/ui/src/app/components/download-report/download-report.component.html @@ -17,6 +17,7 @@ { provide: MAT_DIALOG_DATA, useValue: { profiles: [PROFILE_MOCK_2, PROFILE_MOCK], - url: 'localhost:8080', + report: 'localhost:8080', + export: 'localhost:8080', }, }, { provide: TestRunService, useValue: testRunServiceMock }, @@ -78,7 +79,8 @@ describe('DownloadZipModalComponent', () => { TestBed.overrideProvider(MAT_DIALOG_DATA, { useValue: { profiles: [PROFILE_MOCK_2, PROFILE_MOCK, PROFILE_MOCK_3], - url: 'localhost:8080', + report: 'localhost:8080', + export: 'localhost:8080', isPilot: true, }, }); @@ -220,7 +222,8 @@ describe('DownloadZipModalComponent', () => { TestBed.overrideProvider(MAT_DIALOG_DATA, { useValue: { profiles: [], - url: 'localhost:8080', + report: 'localhost:8080', + export: 'localhost:8080', }, }); diff --git a/modules/ui/src/app/components/download-zip-modal/download-zip-modal.component.ts b/modules/ui/src/app/components/download-zip-modal/download-zip-modal.component.ts index bb4c54b62..a4d10da2c 100644 --- a/modules/ui/src/app/components/download-zip-modal/download-zip-modal.component.ts +++ b/modules/ui/src/app/components/download-zip-modal/download-zip-modal.component.ts @@ -1,9 +1,9 @@ import { ChangeDetectionStrategy, Component, - Inject, OnDestroy, OnInit, + inject, } from '@angular/core'; import { MAT_DIALOG_DATA, @@ -24,7 +24,7 @@ import { MatSelectModule } from '@angular/material/select'; import { MatOptionModule } from '@angular/material/core'; import { TestRunService } from '../../services/test-run.service'; import { Routes } from '../../model/routes'; -import { Router, RouterLink } from '@angular/router'; +import { Router } from '@angular/router'; import { TestrunStatus, StatusOfTestrun, @@ -38,7 +38,8 @@ interface DialogData { profiles: Profile[]; testrunStatus?: TestrunStatus; isTestingComplete?: boolean; - url: string | null; + report: string | null; + export: string | null; isPilot?: boolean; } @@ -55,7 +56,7 @@ export interface DialogCloseResult { @Component({ selector: 'app-download-zip-modal', - standalone: true, + imports: [ CommonModule, MatDialogActions, @@ -66,7 +67,6 @@ export interface DialogCloseResult { MatFormField, MatSelectModule, MatOptionModule, - RouterLink, DownloadReportComponent, ], templateUrl: './download-zip-modal.component.html', @@ -77,6 +77,12 @@ export class DownloadZipModalComponent extends EscapableDialogComponent implements OnDestroy, OnInit { + private readonly testRunService = inject(TestRunService); + override dialogRef: MatDialogRef; + data = inject(MAT_DIALOG_DATA); + private route = inject(Router); + private focusManagerService = inject(FocusManagerService); + private destroy$: Subject = new Subject(); readonly NO_PROFILE = { name: 'No Risk Profile selected', @@ -87,14 +93,14 @@ export class DownloadZipModalComponent public readonly ResultOfTestrun = ResultOfTestrun; profiles: Profile[] = []; selectedProfile: Profile; - constructor( - private readonly testRunService: TestRunService, - public override dialogRef: MatDialogRef, - @Inject(MAT_DIALOG_DATA) public data: DialogData, - private route: Router, - private focusManagerService: FocusManagerService - ) { - super(dialogRef); + constructor() { + const dialogRef = + inject>(MatDialogRef); + + super(); + this.dialogRef = dialogRef; + const data = this.data; + this.profiles = data.profiles.filter( profile => profile.status === ProfileStatus.VALID ); @@ -123,9 +129,12 @@ export class DownloadZipModalComponent ); return; } - if (this.data.url != null && typeof result.profile === 'string') { + if ( + (this.data.report != null || this.data.export != null) && + typeof result.profile === 'string' + ) { this.testRunService.downloadZip( - this.getZipLink(this.data.url), + this.getZipLink(this.data), result.profile ); if (this.data.isPilot) { @@ -177,7 +186,7 @@ export class DownloadZipModalComponent return data.status; } - private getZipLink(reportURL: string): string { - return reportURL.replace('report', 'export'); + private getZipLink(data: DialogData): string { + return data.export || data.report!.replace('report', 'export'); } } diff --git a/modules/ui/src/app/components/dynamic-form/dynamic-form.component.html b/modules/ui/src/app/components/dynamic-form/dynamic-form.component.html index cdbf27695..2a71722dd 100644 --- a/modules/ui/src/app/components/dynamic-form/dynamic-form.component.html +++ b/modules/ui/src/app/components/dynamic-form/dynamic-form.component.html @@ -125,7 +125,9 @@ matInput id="{{ formControlName }}-group" [formControlName]="formControlName" /> - {{ description }} + {{ + description + }} - {{ description }} + {{ + description + }} - {{ description }} + {{ + description + }} The field is required diff --git a/modules/ui/src/app/components/dynamic-form/dynamic-form.component.scss b/modules/ui/src/app/components/dynamic-form/dynamic-form.component.scss index 7fda3a91a..3de387ce9 100644 --- a/modules/ui/src/app/components/dynamic-form/dynamic-form.component.scss +++ b/modules/ui/src/app/components/dynamic-form/dynamic-form.component.scss @@ -14,31 +14,33 @@ * limitations under the License. */ @use '@angular/material' as mat; -@import 'src/theming/colors'; -@import 'src/theming/variables'; +@use 'm3-theme' as *; +@use 'colors'; +@use 'variables'; .field-label { - margin: 0; - color: $grey-800; - font-size: 18px; + font-family: variables.$font-text; + font-style: normal; + font-weight: 500; + font-size: 16px; line-height: 24px; - padding-top: 24px; - padding-bottom: 16px; + letter-spacing: 0.1px; + color: colors.$on-surface-variant; + padding: 8px 16px; display: inline-block; &:has(+ .field-select-multiple.ng-invalid.ng-dirty) { - color: mat.m2-get-color-from-palette($color-warn, 700); + color: mat.get-theme-color($light-theme, error, 40); } } mat-form-field { width: 100%; } .field-hint { - font-family: $font-secondary; + font-family: variables.$font-secondary; font-size: 12px; font-weight: 400; line-height: 16px; text-align: left; - padding-top: 8px; } .form-field { @@ -50,9 +52,11 @@ mat-form-field { } .field-select-multiple { + margin-bottom: 16px; + .field-select-checkbox { &:has(::ng-deep .mat-mdc-checkbox-checked) { - background: mat.m2-get-color-from-palette($color-primary, 50); + background: mat.get-theme-color($light-theme, primary, 95); } ::ng-deep .mdc-checkbox__ripple { display: none; diff --git a/modules/ui/src/app/components/dynamic-form/dynamic-form.component.spec.ts b/modules/ui/src/app/components/dynamic-form/dynamic-form.component.spec.ts index ef63d45d7..1754893f7 100644 --- a/modules/ui/src/app/components/dynamic-form/dynamic-form.component.spec.ts +++ b/modules/ui/src/app/components/dynamic-form/dynamic-form.component.spec.ts @@ -16,7 +16,7 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; import { DynamicFormComponent } from './dynamic-form.component'; -import { Component, ViewChild, ViewEncapsulation } from '@angular/core'; +import { Component, ViewEncapsulation, viewChild, inject } from '@angular/core'; import { FormBuilder, FormGroup, @@ -30,12 +30,16 @@ import { NoopAnimationsModule } from '@angular/platform-browser/animations'; @Component({ template: '
', + standalone: false, }) class DummyComponent { - @ViewChild('dynamicForm') public dynamicForm!: DynamicFormComponent; + private readonly fb = inject(FormBuilder); + + readonly dynamicForm = + viewChild.required('dynamicForm'); public testForm!: FormGroup; public format = PROFILE_FORM; - constructor(private readonly fb: FormBuilder) { + constructor() { this.testForm = this.fb.group({ test: [''], }); @@ -68,7 +72,7 @@ describe('DynamicFormComponent', () => { dummy = fixture.componentInstance; compiled = fixture.nativeElement as HTMLElement; fixture.detectChanges(); - component = dummy.dynamicForm; + component = dummy.dynamicForm(); }); it('should create', () => { @@ -232,4 +236,15 @@ describe('DynamicFormComponent', () => { }); } }); + + describe('adjustSubscriptWrapperHeights', () => { + it('should set height for hint wrapper', () => { + component.adjustSubscriptWrapperHeights(); + + const wrapper = compiled.querySelector( + '.mat-mdc-form-field-subscript-wrapper' + ); + expect(wrapper?.clientHeight).toEqual(20); + }); + }); }); diff --git a/modules/ui/src/app/components/dynamic-form/dynamic-form.component.ts b/modules/ui/src/app/components/dynamic-form/dynamic-form.component.ts index d3e1fa3da..b0abc5eb3 100644 --- a/modules/ui/src/app/components/dynamic-form/dynamic-form.component.ts +++ b/modules/ui/src/app/components/dynamic-form/dynamic-form.component.ts @@ -15,9 +15,12 @@ */ import { Component, + HostListener, inject, Input, OnInit, + Renderer2, + viewChildren, ViewEncapsulation, } from '@angular/core'; import { @@ -48,12 +51,11 @@ import { MatInputModule } from '@angular/material/input'; import { MatFormFieldModule } from '@angular/material/form-field'; import { MatCheckboxModule } from '@angular/material/checkbox'; import { TextFieldModule } from '@angular/cdk/text-field'; -import { DeviceValidators } from '../../pages/devices/components/device-form/device.validators'; import { ProfileValidators } from '../../pages/risk-assessment/profile-form/profile.validators'; import { DomSanitizer } from '@angular/platform-browser'; @Component({ selector: 'app-dynamic-form', - standalone: true, + imports: [ MatFormField, MatOption, @@ -78,22 +80,26 @@ import { DomSanitizer } from '@angular/platform-browser'; encapsulation: ViewEncapsulation.None, }) export class DynamicFormComponent implements OnInit { + readonly formFields = viewChildren(MatFormField); + + private fb = inject(FormBuilder); + private profileValidators = inject(ProfileValidators); + private domSanitizer = inject(DomSanitizer); + private renderer = inject(Renderer2); + public readonly FormControlType = FormControlType; @Input() format: QuestionFormat[] = []; @Input() optionKey: string | undefined; + @HostListener('window:resize') + onResize() { + this.adjustSubscriptWrapperHeights(); + } parentContainer = inject(ControlContainer); get formGroup() { return this.parentContainer.control as FormGroup; } - - constructor( - private fb: FormBuilder, - private deviceValidators: DeviceValidators, - private profileValidators: ProfileValidators, - private domSanitizer: DomSanitizer - ) {} getControl(name: string | number) { return this.formGroup.get(name.toString()) as AbstractControl; } @@ -183,4 +189,25 @@ export class DynamicFormComponent implements OnInit { this.getOptionValue(option) ); } + + adjustSubscriptWrapperHeights(): void { + this.formFields().forEach(formField => { + const matFormField = formField._elementRef.nativeElement; + if (matFormField) { + const subscriptWrapper = matFormField.querySelector( + '.mat-mdc-form-field-subscript-wrapper' + ) as HTMLElement; + const hint = matFormField.querySelector( + '.mat-mdc-form-field-hint' + ) as HTMLElement; + if (subscriptWrapper && hint) { + this.renderer.setStyle( + subscriptWrapper, + 'height', + `${hint.offsetHeight}px` + ); + } + } + }); + } } diff --git a/modules/ui/src/app/components/empty-message/empty-message.component.html b/modules/ui/src/app/components/empty-message/empty-message.component.html new file mode 100644 index 000000000..ad15f66f0 --- /dev/null +++ b/modules/ui/src/app/components/empty-message/empty-message.component.html @@ -0,0 +1,28 @@ + +
+
+ empty message image +
+ + {{ header() }} + {{ message() }} + {{ messageNext() }} + + +
diff --git a/modules/ui/src/app/components/empty-message/empty-message.component.scss b/modules/ui/src/app/components/empty-message/empty-message.component.scss new file mode 100644 index 000000000..2729a3476 --- /dev/null +++ b/modules/ui/src/app/components/empty-message/empty-message.component.scss @@ -0,0 +1,73 @@ +/** + * Copyright 2023 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +@use 'variables'; +@use 'colors'; +@use 'mixins'; + +.empty-message.vertical { + display: flex; + flex-direction: column; + align-items: center; + gap: 12px; + text-align: center; + .empty-message-main { + padding-top: 12px; + } +} + +.empty-message.horizontal { + display: grid; + grid-template-columns: auto 1fr; + align-items: end; + gap: 32px; + max-width: 886px; + .empty-message-img { + grid-row: 1 / 3; + justify-self: end; + } + .empty-message-text { + align-self: start; + padding-top: 12px; + &.one-line { + grid-row: 1 / 3; + align-self: center; + padding-top: 0; + } + } + .empty-message-main { + padding-top: 8px; + } +} + +.empty-message-header { + @include mixins.headline-small; +} + +.empty-message-main { + font-family: variables.$font-secondary; + font-weight: 400; + font-size: 16px; + line-height: 24px; + letter-spacing: 0px; + color: colors.$on-surface-variant; + display: block; +} + +.empty-message-text { + .empty-message-main.next-line { + padding-top: 0; + } +} diff --git a/modules/ui/src/app/components/empty-message/empty-message.component.spec.ts b/modules/ui/src/app/components/empty-message/empty-message.component.spec.ts new file mode 100644 index 000000000..7fecdbff0 --- /dev/null +++ b/modules/ui/src/app/components/empty-message/empty-message.component.spec.ts @@ -0,0 +1,60 @@ +/** + * Copyright 2023 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { EmptyMessageComponent } from './empty-message.component'; + +describe('EmptyMessageComponent', () => { + let compiled: HTMLElement; + let component: EmptyMessageComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [EmptyMessageComponent], + }).compileComponents(); + + fixture = TestBed.createComponent(EmptyMessageComponent); + component = fixture.componentInstance; + compiled = fixture.nativeElement as HTMLElement; + fixture.componentRef.setInput('image', 'image.csv'); + fixture.componentRef.setInput('header', 'header text'); + fixture.componentRef.setInput('message', 'message text'); + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); + + it('should have image', () => { + const image = compiled.querySelector('img') as HTMLImageElement; + + expect(image?.src).toContain('image.csv'); + }); + + it('should have header', () => { + const text = compiled.querySelector('.empty-message-header'); + + expect(text?.textContent?.trim()).toEqual('header text'); + }); + + it('should have message', () => { + const text = compiled.querySelector('.empty-message-main'); + + expect(text?.textContent?.trim()).toEqual('message text'); + }); +}); diff --git a/modules/ui/src/app/components/shutdown-app-modal/shutdown-app-modal.component.scss b/modules/ui/src/app/components/empty-message/empty-message.component.ts similarity index 56% rename from modules/ui/src/app/components/shutdown-app-modal/shutdown-app-modal.component.scss rename to modules/ui/src/app/components/empty-message/empty-message.component.ts index 85ddce69d..bf51345e2 100644 --- a/modules/ui/src/app/components/shutdown-app-modal/shutdown-app-modal.component.scss +++ b/modules/ui/src/app/components/empty-message/empty-message.component.ts @@ -13,32 +13,19 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -@import '../../../theming/colors'; +import { Component, input } from '@angular/core'; +import { CommonModule } from '@angular/common'; -:host { - display: grid; - overflow: hidden; - width: 570px; - box-sizing: border-box; - padding: 24px 16px 8px 24px; - gap: 10px; -} - -.modal-title { - color: $grey-900; - font-size: 18px; - line-height: 24px; -} - -.modal-content { - font-family: Roboto, sans-serif; - font-size: 14px; - line-height: 20px; - letter-spacing: 0.2px; - color: $grey-800; -} - -.modal-actions { - padding: 0; - min-height: 30px; +@Component({ + selector: 'app-empty-message', + imports: [CommonModule], + templateUrl: './empty-message.component.html', + styleUrl: './empty-message.component.scss', +}) +export class EmptyMessageComponent { + image = input(); + header = input(); + message = input(); + messageNext = input(); + isHorizontal = input(false); } diff --git a/modules/ui/src/app/components/shutdown-app-modal/shutdown-app-modal.component.html b/modules/ui/src/app/components/empty-page/empty-page.component.html similarity index 54% rename from modules/ui/src/app/components/shutdown-app-modal/shutdown-app-modal.component.html rename to modules/ui/src/app/components/empty-page/empty-page.component.html index 0614dfc28..c0daf35f8 100644 --- a/modules/ui/src/app/components/shutdown-app-modal/shutdown-app-modal.component.html +++ b/modules/ui/src/app/components/empty-page/empty-page.component.html @@ -13,26 +13,11 @@ See the License for the specific language governing permissions and limitations under the License. --> -{{ data.title }} - - - - - - + + + diff --git a/modules/ui/src/app/components/empty-page/empty-page.component.spec.ts b/modules/ui/src/app/components/empty-page/empty-page.component.spec.ts new file mode 100644 index 000000000..fb842f6c4 --- /dev/null +++ b/modules/ui/src/app/components/empty-page/empty-page.component.spec.ts @@ -0,0 +1,37 @@ +/** + * Copyright 2023 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { EmptyPageComponent } from './empty-page.component'; + +describe('EmptyPageComponent', () => { + let component: EmptyPageComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [EmptyPageComponent], + }).compileComponents(); + + fixture = TestBed.createComponent(EmptyPageComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/modules/ui/src/app/pages/testrun/testrun-routing.module.ts b/modules/ui/src/app/components/empty-page/empty-page.component.ts similarity index 59% rename from modules/ui/src/app/pages/testrun/testrun-routing.module.ts rename to modules/ui/src/app/components/empty-page/empty-page.component.ts index 903ce0bed..f3ed1a15e 100644 --- a/modules/ui/src/app/pages/testrun/testrun-routing.module.ts +++ b/modules/ui/src/app/components/empty-page/empty-page.component.ts @@ -13,14 +13,17 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { NgModule } from '@angular/core'; -import { RouterModule, Routes } from '@angular/router'; -import { TestrunComponent } from './testrun.component'; +import { Component, input } from '@angular/core'; +import { EmptyMessageComponent } from '../empty-message/empty-message.component'; -const routes: Routes = [{ path: '', component: TestrunComponent }]; - -@NgModule({ - imports: [RouterModule.forChild(routes)], - exports: [RouterModule], +@Component({ + selector: 'app-empty-page', + imports: [EmptyMessageComponent], + templateUrl: './empty-page.component.html', }) -export class TestrunRoutingModule {} +export class EmptyPageComponent { + image = input(); + header = input(); + message = input(); + messageNext = input(); +} diff --git a/modules/ui/src/app/components/escapable-dialog/escapable-dialog.component.ts b/modules/ui/src/app/components/escapable-dialog/escapable-dialog.component.ts index d1a842dab..023dcca25 100644 --- a/modules/ui/src/app/components/escapable-dialog/escapable-dialog.component.ts +++ b/modules/ui/src/app/components/escapable-dialog/escapable-dialog.component.ts @@ -13,19 +13,23 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { Component } from '@angular/core'; +import { Component, inject } from '@angular/core'; import { CommonModule } from '@angular/common'; import { filter, take } from 'rxjs'; import { MatDialogRef } from '@angular/material/dialog'; @Component({ selector: 'app-escapable-dialog', - standalone: true, + imports: [CommonModule], template: '', }) export class EscapableDialogComponent { - constructor(public dialogRef: MatDialogRef) { + dialogRef = inject>(MatDialogRef); + + constructor() { + const dialogRef = this.dialogRef; + this.dialogRef .keydownEvents() .pipe( diff --git a/modules/ui/src/app/components/help-tip/help-tip.component.html b/modules/ui/src/app/components/help-tip/help-tip.component.html new file mode 100644 index 000000000..3d1873f3f --- /dev/null +++ b/modules/ui/src/app/components/help-tip/help-tip.component.html @@ -0,0 +1,49 @@ + +
diff --git a/modules/ui/src/app/components/help-tip/help-tip.component.scss b/modules/ui/src/app/components/help-tip/help-tip.component.scss new file mode 100644 index 000000000..7c3e2953f --- /dev/null +++ b/modules/ui/src/app/components/help-tip/help-tip.component.scss @@ -0,0 +1,111 @@ +/** + * Copyright 2023 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +@use '@angular/material' as mat; +@use 'm3-theme' as *; +@use 'colors'; +@use 'variables'; + +.tip { + position: absolute; + z-index: 100; + width: 256px; + box-sizing: border-box; + + &.left { + width: 276px; + } +} + +.tip-container { + position: relative; + border-radius: 28px; + background: colors.$primary; + color: colors.$white; + box-shadow: + 0px 4px 8px 3px rgba(0, 0, 0, 0.15), + 0px 1px 3px 0px rgba(0, 0, 0, 0.3); + + p { + margin: 0; + } +} + +.tip-container::before { + content: ''; + position: absolute; + border-radius: 4px; + height: 20px; + width: 20px; + background: colors.$primary; + box-sizing: border-box; + transform: rotate(45deg) translate(-50%); +} + +.top .tip-container::before { + top: 0; + left: 50%; +} + +.left .tip-container::before { + top: 50%; + left: 0; +} + +.heading { + display: flex; + justify-content: space-between; + align-items: flex-end; + padding: 4px 4px 0 24px; +} + +.title { + font-family: variables.$font-text; + font-size: 16px; + font-weight: 500; + line-height: 24px; +} + +.close-button { + margin-bottom: 4px; + + mat-icon { + color: colors.$white; + } +} + +.tip-content { + padding: 0 24px; + font-family: variables.$font-text; + font-size: 14px; + line-height: 20px; +} + +.tip-action-container { + display: flex; + justify-content: flex-end; + padding: 16px 12px 14px 24px; + + .tip-action-link { + display: inline-block; + padding: 10px 24px; + text-decoration: none; + cursor: pointer; + font-family: variables.$font-text; + font-size: 14px; + font-weight: 500; + line-height: 20px; + } +} diff --git a/modules/ui/src/app/components/help-tip/help-tip.component.spec.ts b/modules/ui/src/app/components/help-tip/help-tip.component.spec.ts new file mode 100644 index 000000000..89160cedb --- /dev/null +++ b/modules/ui/src/app/components/help-tip/help-tip.component.spec.ts @@ -0,0 +1,134 @@ +import { + ComponentFixture, + fakeAsync, + TestBed, + tick, +} from '@angular/core/testing'; + +import { HelpTipComponent } from './help-tip.component'; +import { HelpTips } from '../../model/tip-config'; +import SpyObj = jasmine.SpyObj; +import { LiveAnnouncer } from '@angular/cdk/a11y'; +import { FocusManagerService } from '../../services/focus-manager.service'; + +describe('HelpTipComponent', () => { + let component: HelpTipComponent; + let fixture: ComponentFixture; + let compiled: HTMLElement; + + const mockLiveAnnouncer: SpyObj = jasmine.createSpyObj([ + 'announce', + 'clear', + ]); + + const mockFocusManagerService: SpyObj = + jasmine.createSpyObj('mockFocusManagerService', [ + 'focusFirstElementInContainer', + ]); + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [HelpTipComponent], + providers: [ + { provide: LiveAnnouncer, useValue: mockLiveAnnouncer }, + { provide: FocusManagerService, useValue: mockFocusManagerService }, + ], + }).compileComponents(); + + fixture = TestBed.createComponent(HelpTipComponent); + component = fixture.componentInstance; + fixture.componentRef.setInput('data', HelpTips.step1); + compiled = fixture.nativeElement as HTMLElement; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); + + it('should set focus to first focusable elem', fakeAsync(() => { + component.ngOnInit(); + tick(200); + + expect( + mockFocusManagerService.focusFirstElementInContainer + ).toHaveBeenCalled(); + })); + + it('should have provided data', () => { + const tipTitle = compiled.querySelector('.tip-container .title'); + const tipContent = compiled.querySelector('.tip-container .tip-content'); + + expect(tipTitle?.innerHTML.trim()).toContain(HelpTips.step1.title); + expect(tipContent?.innerHTML.trim()).toContain(HelpTips.step1.content); + }); + + it('should have class provided from arrowPosition', () => { + const tipEl = compiled.querySelector('.tip'); + + expect(tipEl?.classList).toContain('top'); + }); + + describe('#updateTipPosition', () => { + beforeEach(() => { + const mockTarget = document.createElement('div'); + spyOn(mockTarget, 'getBoundingClientRect').and.returnValue({ + top: 100, + left: 100, + height: 100, + width: 100, + bottom: 100, + right: 100, + } as DOMRect); + fixture.componentRef.setInput('target', mockTarget); + fixture.detectChanges(); + }); + + it('should update tip position when data.position as "bottom"', () => { + component.ngOnInit(); + + expect(component.tipPosition.left).toBe(22); + expect(component.tipPosition.top).toBe(114); + }); + + it('should update tip position when data.position as "right"', fakeAsync(() => { + fixture.componentRef.setInput('data', HelpTips.step2); + tick(); + + component.ngOnInit(); + + expect(component.tipPosition.left).toBe(100); + expect(component.tipPosition.top).toBe(68); + })); + + it('should update tip position when data.position as "left"', fakeAsync(() => { + const mockData = { ...HelpTips.step2, position: 'left' }; + fixture.componentRef.setInput('data', mockData); + tick(); + + component.ngOnInit(); + + expect(component.tipPosition.left).toBe(-170); + expect(component.tipPosition.top).toBe(150); + })); + + it('should update tip position when data.position as "top"', fakeAsync(() => { + const mockData = { ...HelpTips.step2, position: 'top' }; + fixture.componentRef.setInput('data', mockData); + tick(); + + component.ngOnInit(); + + expect(component.tipPosition.left).toBe(22); + expect(component.tipPosition.top).toBe(86); + })); + + it('should call updateTipPosition on window resize', () => { + spyOn(component, 'updateTipPosition'); + + window.dispatchEvent(new Event('resize')); + + expect(component.updateTipPosition).toHaveBeenCalled(); + }); + }); +}); diff --git a/modules/ui/src/app/components/help-tip/help-tip.component.ts b/modules/ui/src/app/components/help-tip/help-tip.component.ts new file mode 100644 index 000000000..300602165 --- /dev/null +++ b/modules/ui/src/app/components/help-tip/help-tip.component.ts @@ -0,0 +1,122 @@ +/** + * Copyright 2023 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { + ChangeDetectionStrategy, + Component, + HostListener, + inject, + input, + OnInit, + output, +} from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { MatIconModule } from '@angular/material/icon'; +import { MatButtonModule } from '@angular/material/button'; +import { TipConfig } from '../../model/tip-config'; +import { LiveAnnouncer } from '@angular/cdk/a11y'; +import { FocusManagerService } from '../../services/focus-manager.service'; +import { timer } from 'rxjs/internal/observable/timer'; + +@Component({ + selector: 'app-help-tip', + imports: [CommonModule, MatIconModule, MatButtonModule], + templateUrl: './help-tip.component.html', + styleUrl: './help-tip.component.scss', + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class HelpTipComponent implements OnInit { + data = input(); + target = input(); + action = input(); + onAction = output(); + onCLoseTip = output(); + tipPosition = { top: 0, left: 0 }; + private readonly liveAnnouncer = inject(LiveAnnouncer); + private readonly focusManagerService = inject(FocusManagerService); + + @HostListener('window:resize') + onResize() { + this.updateTipPosition(this.target()); + } + + ngOnInit() { + this.updateTipPosition(this.target()); + this.liveAnnouncer.announce( + `${this.data()?.title} ${this.data()?.content}` + ); + this.setFocus(); + } + + private setFocus(): void { + const helpTipEl = window.document.querySelector('.tip'); + timer(200).subscribe(() => { + this.focusManagerService.focusFirstElementInContainer(helpTipEl); + }); + } + + updateTipPosition(target: HTMLElement | undefined | null) { + if (!target) { + return; + } + + const targetRect = target.getBoundingClientRect(); + + const tipWidth = 256; + const arrowOffset = 14; + const topOffset = 82; + + let top = 0; + let left = 0; + + switch (this.data()?.position) { + case 'left': + top = targetRect.top + window.scrollY + targetRect.height / 2; + left = targetRect.left + window.scrollX - tipWidth - arrowOffset; + break; + case 'right': + top = + targetRect.top + window.scrollY - topOffset + targetRect.height / 2; + left = targetRect.right + window.scrollX; + break; + case 'top': + top = targetRect.top + window.scrollY - arrowOffset; // Position above the button + left = + targetRect.left + + window.scrollX + + targetRect.width / 2 - + tipWidth / 2; // Center horizontally above button + break; + case 'bottom': + top = targetRect.bottom + window.scrollY + arrowOffset; // Position below the button + left = + targetRect.left + + window.scrollX + + targetRect.width / 2 - + tipWidth / 2; // Center horizontally below button + break; + default: + throw new Error('Unsupported tip position!'); + } + + this.tipPosition = { top, left }; + } + + onActionClick(event: Event): void { + event.preventDefault(); + event.stopPropagation(); + this.onAction.emit(); + } +} diff --git a/modules/ui/src/app/components/list-item/list-item.component.html b/modules/ui/src/app/components/list-item/list-item.component.html new file mode 100644 index 000000000..2e01cce09 --- /dev/null +++ b/modules/ui/src/app/components/list-item/list-item.component.html @@ -0,0 +1,41 @@ + +
+ + +
+ + + + diff --git a/modules/ui/src/app/components/wifi/wifi.component.scss b/modules/ui/src/app/components/list-item/list-item.component.scss similarity index 55% rename from modules/ui/src/app/components/wifi/wifi.component.scss rename to modules/ui/src/app/components/list-item/list-item.component.scss index bc0ac542e..71f40550d 100644 --- a/modules/ui/src/app/components/wifi/wifi.component.scss +++ b/modules/ui/src/app/components/list-item/list-item.component.scss @@ -13,28 +13,36 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -@import '../../../theming/colors'; +@use 'variables'; +@use 'colors'; +@use '@angular/material' as mat; -$icon-size: 24px; +::ng-deep { + .list-item-menu { + width: 220px; + } +} -.app-toolbar-button { - border-radius: 20px; - border: 1px solid transparent; - min-width: 48px; - padding: 0; - box-sizing: border-box; - height: 34px; - margin: 11px 0; - line-height: 50% !important; +.list-item { + border-radius: variables.$corner-large; + background-color: colors.$surface-container; + height: 92px; + padding: 0 24px 0 32px; + display: grid; + grid-template-columns: auto 40px; + align-items: center; &.disabled { - opacity: 0.6; + cursor: not-allowed; } } -.wifi-icon { - margin-right: 0; - width: $icon-size; - font-size: $icon-size; - color: $dark-grey; - height: $icon-size; +.example-menu { + left: 12px; +} + +.list-item-menu-item { + padding: 16px 24px; + &.cdk-mouse-focused::before { + content: none; + } } diff --git a/modules/ui/src/app/components/list-item/list-item.component.spec.ts b/modules/ui/src/app/components/list-item/list-item.component.spec.ts new file mode 100644 index 000000000..f671c57c7 --- /dev/null +++ b/modules/ui/src/app/components/list-item/list-item.component.spec.ts @@ -0,0 +1,107 @@ +/** + * Copyright 2023 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { MatButtonModule } from '@angular/material/button'; +import { MatIconModule } from '@angular/material/icon'; +import { MatMenuModule } from '@angular/material/menu'; +import { NoopAnimationsModule } from '@angular/platform-browser/animations'; +import { ListItemComponent } from './list-item.component'; +import { HarnessLoader } from '@angular/cdk/testing'; +import { TestbedHarnessEnvironment } from '@angular/cdk/testing/testbed'; +import { + MatMenuHarness, + MatMenuItemHarness, +} from '@angular/material/menu/testing'; + +interface Entity { + id: number; + name: string; +} + +describe('ListItemComponent', () => { + let component: ListItemComponent; + let fixture: ComponentFixture>; + let compiled: HTMLElement; + let loader: HarnessLoader; + const testActions = [ + { action: 'Edit', icon: 'edit_icon' }, + { action: 'Delete', icon: 'delete_icon' }, + ]; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [ + ListItemComponent, + MatButtonModule, + MatIconModule, + MatMenuModule, + NoopAnimationsModule, + ], + }).compileComponents(); + + fixture = TestBed.createComponent(ListItemComponent); + component = fixture.componentInstance; + fixture.componentRef.setInput('actions', testActions); + fixture.componentRef.setInput('entity', { id: 1, name: 'test' } as Entity); + compiled = fixture.nativeElement as HTMLElement; + loader = TestbedHarnessEnvironment.loader(fixture); + fixture.detectChanges(); + }); + + describe('menu', () => { + let menu; + let items: MatMenuItemHarness[]; + + beforeEach(async () => { + menu = await loader.getHarness(MatMenuHarness); + await menu.open(); + items = await menu.getItems(); + }); + + it('should render actions in the menu', async () => { + expect(items.length).toBe(2); + + const text0 = await items[0].getText(); + const text1 = await items[1].getText(); + + expect(text0).toContain('Edit'); + expect(text1).toContain('Delete'); + }); + + it('should emit the correct action when a menu item is clicked', async () => { + const menuItemClickedSpy = spyOn(component.menuItemClicked, 'emit'); + + await items[0].click(); + + expect(menuItemClickedSpy).toHaveBeenCalledWith('Edit'); + }); + + it('should display the correct icons for actions', async () => { + const text0 = await items[0].getText(); + const text1 = await items[1].getText(); + + expect(text0).toContain('edit'); + expect(text1).toContain('delete'); + }); + }); + + it('should render menu button', () => { + const button = compiled.querySelector('button[mat-icon-button]'); + + expect(button).toBeTruthy(); + expect(button?.textContent).toContain('more_vert'); + }); +}); diff --git a/modules/ui/src/app/components/list-item/list-item.component.ts b/modules/ui/src/app/components/list-item/list-item.component.ts new file mode 100644 index 000000000..2728ee503 --- /dev/null +++ b/modules/ui/src/app/components/list-item/list-item.component.ts @@ -0,0 +1,117 @@ +/** + * Copyright 2023 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { + Component, + HostListener, + inject, + input, + output, + OnInit, + ChangeDetectionStrategy, + NgZone, +} from '@angular/core'; +import { MatButtonModule } from '@angular/material/button'; +import { MatIconModule } from '@angular/material/icon'; +import { CommonModule } from '@angular/common'; +import { MatMenu, MatMenuItem, MatMenuTrigger } from '@angular/material/menu'; +import { EntityAction } from '../../model/entity-action'; +import { MatTooltip } from '@angular/material/tooltip'; + +@Component({ + selector: 'app-list-item', + imports: [ + MatButtonModule, + MatIconModule, + MatMenuTrigger, + MatMenu, + MatMenuItem, + CommonModule, + ], + providers: [MatTooltip], + templateUrl: './list-item.component.html', + styleUrl: './list-item.component.scss', + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class ListItemComponent implements OnInit { + private container?: HTMLElement; + private zone = inject(NgZone); + entity = input.required(); + actions = input([]); + menuItemClicked = output(); + tooltip = inject(MatTooltip); + isDisabled = input<(arg: T) => boolean>(); + tooltipMessage = input<(arg: T) => string>(); + + get disabled() { + const isDisabledFn = this.isDisabled(); + if (isDisabledFn) { + return isDisabledFn(this.entity()); + } + return false; + } + + @HostListener('mouseenter', ['$event']) + onEvent(event: MouseEvent): void { + this.zone.run(() => { + this.updateMessage(); + if (!this.tooltip.message) return; + this.tooltip.show(0, { x: event.clientX, y: event.clientY }); + this.container = document.querySelector( + '.mat-mdc-tooltip-panel:has(.list-item-tooltip)' + ) as HTMLElement; + }); + } + + @HostListener('mousemove', ['$event']) + onMoveEvent(event: MouseEvent): void { + this.zone.run(() => { + if (!this.tooltip.message) return; + if (!this.container) { + this.container = document.querySelector( + '.mat-mdc-tooltip-panel:has(.list-item-tooltip)' + ) as HTMLElement; + } + + this.container.style.top = event.clientY + 'px'; + this.container.style.left = event.clientX + 'px'; + }); + } + + @HostListener('mouseleave') + outEvent(): void { + this.tooltip.hide(); + } + + ngOnInit() { + this.updateMessage(); + this.tooltip.positionAtOrigin = true; + this.tooltip.tooltipClass = 'list-item-tooltip'; + } + + trackByAction(index: number, item: EntityAction) { + return item.action; + } + + private updateMessage() { + const tooltipMessageFn = this.tooltipMessage(); + if (tooltipMessageFn) { + const tooltipMessage = tooltipMessageFn(this.entity()); + if (tooltipMessage) { + this.tooltip.message = tooltipMessage; + } + } + } +} diff --git a/modules/ui/src/app/components/list-layout/list-layout.component.html b/modules/ui/src/app/components/list-layout/list-layout.component.html new file mode 100644 index 000000000..55ffb7c1b --- /dev/null +++ b/modules/ui/src/app/components/list-layout/list-layout.component.html @@ -0,0 +1,87 @@ + + + + + +

{{ title() }}

+ + + + +
+ + search +
+
+
+
+
+ + {{ + title() === LayoutType.Device ? 'New device' : 'New risk profile' + }} +
+ + + + + +
+
+
+ +
+ +
+
+
+
+ +

{{ title() }}

+
+ +
+
diff --git a/modules/ui/src/app/components/list-layout/list-layout.component.scss b/modules/ui/src/app/components/list-layout/list-layout.component.scss new file mode 100644 index 000000000..c00a11764 --- /dev/null +++ b/modules/ui/src/app/components/list-layout/list-layout.component.scss @@ -0,0 +1,153 @@ +/** + * Copyright 2023 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +@use 'mixins'; +@use 'colors'; +@use 'variables'; + +:host { + position: relative; + height: 100%; + display: block; +} + +:host ::ng-deep .mat-drawer-inner-container { + overflow: hidden; + display: grid; + grid-template-rows: max-content 1fr; +} + +.entity-list app-list-item:has(.selected) { + ::ng-deep .list-item { + background-color: colors.$primary-container; + } +} + +.content { + height: 100%; +} + +.content-empty { + @include mixins.content-empty; +} + +.title { + color: colors.$on-surface; + font-family: variables.$font-primary; + font-size: 32px; + font-style: normal; + font-weight: 400; + line-height: 40px; + &-empty { + padding: 24px 0 8px 32px; + margin: 0; + } +} + +.add-entity-button { + font-family: variables.$font-text; + font-size: 16px; + border-radius: 16px; + width: fit-content; + height: 56px; + color: colors.$on-secondary-container; + background-color: colors.$secondary-container; + &:disabled { + opacity: 0.5; + pointer-events: none; + } +} + +.layout-container { + height: 100%; +} + +.layout-container-left-panel { + background-color: colors.$surface-container-low; + width: 435px; + padding-right: 16px; + overflow: hidden; +} + +.layout-container-left-panel-toolbar { + border-radius: variables.$corner-large; + background-color: colors.$surface; + padding: 12px 0 8px 16px; + ::ng-deep mat-toolbar-row:not(:first-child) { + margin-top: 32px; + } +} + +.search-field { + display: flex; + padding: 4px 4px 4px 20px; + align-items: center; + gap: 4px; + flex: 1 0 0; + align-self: stretch; + border-radius: variables.$corner-extra-large; + background-color: colors.$surface-container-high; + height: 40px; + width: 100%; + input { + width: calc(100% - #{variables.$icon-size * 2}); + height: 100%; + border: 0; + background: inherit; + font-size: 16px; + font-family: variables.$font-text; + color: colors.$on-surface-variant; + } +} + +::ng-deep .using-mouse .search-field input:focus { + outline: none; +} + +.entity-list-container { + overflow-y: auto; +} + +.entity-list { + display: grid; + grid-template-columns: 1fr; + gap: 8px; + padding: 8px 0; +} + +.add-entity-button { + font-family: variables.$font-text; + font-size: 16px; + border-radius: 16px; + width: fit-content; + height: 56px; + color: colors.$on-secondary-container; + background-color: colors.$secondary-container; +} + +.fake-list-item { + border-radius: 16px; + background-color: colors.$primary-container; + height: 92px; + padding: 0 24px 0 32px; + display: flex; + gap: 24px; + align-items: center; + color: colors.$on-surface; + font-weight: 500; + font-family: variables.$font-text; + font-size: 16px; + letter-spacing: 0; +} diff --git a/modules/ui/src/app/components/list-layout/list-layout.component.spec.ts b/modules/ui/src/app/components/list-layout/list-layout.component.spec.ts new file mode 100644 index 000000000..02089934e --- /dev/null +++ b/modules/ui/src/app/components/list-layout/list-layout.component.spec.ts @@ -0,0 +1,150 @@ +/** + * Copyright 2023 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { ListLayoutComponent } from './list-layout.component'; +import { MatSidenavModule } from '@angular/material/sidenav'; +import { MatToolbarModule } from '@angular/material/toolbar'; +import { MatIconModule } from '@angular/material/icon'; +import { MatButtonModule } from '@angular/material/button'; +import { ListItemComponent } from '../list-item/list-item.component'; +import { Component } from '@angular/core'; +import { NoopAnimationsModule } from '@angular/platform-browser/animations'; + +interface Entity { + id: number; + name: string; +} +@Component({ + selector: 'app-host-component', + imports: [ListLayoutComponent, ListItemComponent], + template: ` + + + +
{{ entity.name }}
+
+ + +
Empty
+
+ `, +}) +class HostComponent { + title = 'Test Title'; + addEntityText = 'Add Entity'; + entities: Entity[] = []; + actions = [{ label: 'Edit', value: 'edit' }]; + onAddEntity = jasmine.createSpy('onAddEntity'); + onMenuItemClicked = jasmine.createSpy('onMenuItemClicked'); +} + +describe('ListLayoutComponent', () => { + let component: HostComponent; + let fixture: ComponentFixture; + let compiled: HTMLElement; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [ + HostComponent, + MatSidenavModule, + MatToolbarModule, + MatIconModule, + MatButtonModule, + NoopAnimationsModule, + ], + }).compileComponents(); + + fixture = TestBed.createComponent(HostComponent); + component = fixture.componentInstance; + compiled = fixture.nativeElement as HTMLElement; + fixture.detectChanges(); + }); + + it('should create the component', () => { + expect(component).toBeTruthy(); + }); + + describe('with no entities', () => { + beforeEach(() => { + component.entities = []; + fixture.detectChanges(); + }); + + it('should display the title', () => { + const titleElement = compiled.querySelector('.title'); + + expect(titleElement?.textContent).toBe('Test Title'); + }); + + it('should has empty content', () => { + const emptyContent = compiled.querySelector('.content-empty'); + + expect(emptyContent).toBeTruthy(); + }); + }); + + describe('with entities', () => { + beforeEach(() => { + component.entities = [ + { id: 1, name: 'Entity 1' }, + { id: 2, name: 'Entity 2' }, + ]; + fixture.detectChanges(); + }); + + it('should display the title', () => { + const titleElement = compiled.querySelector('.title'); + + expect(titleElement?.textContent).toBe('Test Title'); + }); + + it('should display add entity button', () => { + const buttonElement = compiled.querySelector('.add-entity-button'); + + expect(buttonElement?.textContent).toContain('Add Entity'); + }); + + it('should display search field', () => { + const searchElement = compiled.querySelector('.search-field'); + + expect(searchElement).toBeTruthy(); + }); + + it('should emit addEntity event when add entity button is clicked', () => { + const buttonElement = compiled.querySelector( + '.add-entity-button' + ) as HTMLButtonElement; + + buttonElement.click(); + expect(component.onAddEntity).toHaveBeenCalled(); + }); + + it('should have entity list', () => { + const listItemComponent = compiled.querySelectorAll('app-list-item'); + + expect(listItemComponent.length).toEqual(2); + }); + }); +}); diff --git a/modules/ui/src/app/components/list-layout/list-layout.component.ts b/modules/ui/src/app/components/list-layout/list-layout.component.ts new file mode 100644 index 000000000..a89412897 --- /dev/null +++ b/modules/ui/src/app/components/list-layout/list-layout.component.ts @@ -0,0 +1,141 @@ +/** + * Copyright 2023 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { + ChangeDetectionStrategy, + Component, + computed, + inject, + input, + output, + signal, + TemplateRef, +} from '@angular/core'; +import { CommonModule, DatePipe } from '@angular/common'; +import { MatButtonModule } from '@angular/material/button'; +import { MatSidenavModule } from '@angular/material/sidenav'; +import { MatToolbarModule } from '@angular/material/toolbar'; +import { MatIconModule } from '@angular/material/icon'; +import { ListItemComponent } from '../list-item/list-item.component'; +import { EntityAction, EntityActionResult } from '../../model/entity-action'; +import { Device } from '../../model/device'; +import { LayoutType } from '../../model/layout-type'; +import { Profile } from '../../model/profile'; + +@Component({ + selector: 'app-list-layout', + imports: [ + CommonModule, + MatButtonModule, + MatSidenavModule, + MatToolbarModule, + MatIconModule, + ListItemComponent, + ], + providers: [DatePipe], + templateUrl: './list-layout.component.html', + styleUrl: './list-layout.component.scss', + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class ListLayoutComponent { + private datePipe = inject(DatePipe); + readonly LayoutType = LayoutType; + title = input(''); + addEntityText = input(''); + entityDisabled = input<(arg: T) => boolean>(); + entityTooltip = input<(arg: T) => string>(); + isOpenEntityForm = input(false); + initialEntity = input(null); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + emptyContent = input>(); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + content = input>(); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + itemTemplate = input>(); + entities = input([]); + actions = input([]); + actionsFn = input<(arg: T) => EntityAction[]>(); + searchText = signal(''); + filtered = computed(() => { + return this.entities().filter(this.filter(this.searchText())); + }); + addEntity = output(); + menuItemClicked = output>(); + + getActions = (entity: T) => { + if (this.actionsFn()) { + // @ts-expect-error actionsFn is defined + return this.actionsFn()(entity); + } + return this.actions(); + }; + + updateQuery(e: Event) { + const input = e.target as HTMLInputElement; + const value = input.value; + if (value.trim() === '') { + input.value = ''; + } else { + const inputValue = value.trim(); + const searchValue = inputValue.length > 2 ? inputValue : ''; + this.searchText.set(searchValue); + } + } + + filter(searchText: string) { + return (item: T) => { + const filterItem = this.getObjectForFilter(item); + return Object.values(filterItem).some(value => { + return typeof value === 'string' + ? value.toLowerCase().includes(searchText.toLowerCase()) + : false; + }); + }; + } + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + getObjectForFilter(item: any) { + if (this.title() === LayoutType.Device) { + const device = item as Device; + return { + model: device.model, + manufacturer: device.manufacturer, + }; + } else if (this.title() === LayoutType.Profile) { + const profile = item as Profile; + return { + name: profile.name, + risk: profile.risk, + created: this.getFormattedDateString(profile.created), + }; + } else { + return item; + } + } + + getFormattedDateString(createdDate: string | undefined) { + return createdDate + ? this.datePipe.transform(createdDate, 'dd MMM yyyy') + : ''; + } + + onMenuItemClick(action: string, entity: T, index: number) { + this.menuItemClicked.emit({ + action, + entity, + index, + }); + } +} diff --git a/modules/ui/src/app/components/no-entity-selected/no-entity-selected.component.html b/modules/ui/src/app/components/no-entity-selected/no-entity-selected.component.html new file mode 100644 index 000000000..03556f807 --- /dev/null +++ b/modules/ui/src/app/components/no-entity-selected/no-entity-selected.component.html @@ -0,0 +1,21 @@ + + + diff --git a/modules/ui/src/app/pages/devices/devices-routing.module.ts b/modules/ui/src/app/components/no-entity-selected/no-entity-selected.component.scss similarity index 52% rename from modules/ui/src/app/pages/devices/devices-routing.module.ts rename to modules/ui/src/app/components/no-entity-selected/no-entity-selected.component.scss index 53ed9ef0a..4a7bf4668 100644 --- a/modules/ui/src/app/pages/devices/devices-routing.module.ts +++ b/modules/ui/src/app/components/no-entity-selected/no-entity-selected.component.scss @@ -13,21 +13,34 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { NgModule } from '@angular/core'; -import { RouterModule, Routes } from '@angular/router'; -import { DevicesComponent } from './devices.component'; -import { CanDeactivateGuard } from '../../guards/can-deactivate.guard'; +@use 'colors'; +@use 'variables'; -const routes: Routes = [ - { - path: '', - component: DevicesComponent, - canDeactivate: [CanDeactivateGuard], - }, -]; +:host { + height: 100%; + display: flex; + align-items: center; + justify-content: center; + background-image: url(/assets/icons/cornerstone.svg); + position: relative; +} -@NgModule({ - imports: [RouterModule.forChild(routes)], - exports: [RouterModule], -}) -export class DevicesRoutingModule {} +.dog-image { + position: absolute; + bottom: 0; + right: 26px; + width: 20%; + height: auto; + display: block; +} + +::ng-deep app-empty-message { + .empty-message { + gap: 16px !important; + } + + .empty-message-header { + color: colors.$on-surface-variant !important; + font-family: variables.$font-secondary !important; + } +} diff --git a/modules/ui/src/app/pages/risk-assessment/risk-assessment-routing.module.ts b/modules/ui/src/app/components/no-entity-selected/no-entity-selected.component.ts similarity index 57% rename from modules/ui/src/app/pages/risk-assessment/risk-assessment-routing.module.ts rename to modules/ui/src/app/components/no-entity-selected/no-entity-selected.component.ts index 927da40bc..3924c2aa3 100644 --- a/modules/ui/src/app/pages/risk-assessment/risk-assessment-routing.module.ts +++ b/modules/ui/src/app/components/no-entity-selected/no-entity-selected.component.ts @@ -13,14 +13,16 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { NgModule } from '@angular/core'; -import { RouterModule, Routes } from '@angular/router'; -import { RiskAssessmentComponent } from './risk-assessment.component'; - -const routes: Routes = [{ path: '', component: RiskAssessmentComponent }]; - -@NgModule({ - imports: [RouterModule.forChild(routes)], - exports: [RouterModule], +import { Component, input } from '@angular/core'; +import { EmptyMessageComponent } from '../empty-message/empty-message.component'; +@Component({ + selector: 'app-no-entity-selected', + imports: [EmptyMessageComponent], + templateUrl: './no-entity-selected.component.html', + styleUrl: './no-entity-selected.component.scss', }) -export class RiskAssessmentRoutingModule {} +export class NoEntitySelectedComponent { + image = input(); + header = input(); + message = input(); +} diff --git a/modules/ui/src/app/components/program-type-icon/program-type-icon.component.ts b/modules/ui/src/app/components/program-type-icon/program-type-icon.component.ts index 45a34ddfc..4066d98e0 100644 --- a/modules/ui/src/app/components/program-type-icon/program-type-icon.component.ts +++ b/modules/ui/src/app/components/program-type-icon/program-type-icon.component.ts @@ -18,19 +18,16 @@ import { MatIcon } from '@angular/material/icon'; @Component({ selector: 'app-program-type-icon', - standalone: true, + imports: [MatIcon], template: ` `, styles: ` :host { display: inline-flex; align-items: center; - padding-right: 4px; } .icon { display: flex; - width: 16px; - height: 16px; line-height: 16px; } `, diff --git a/modules/ui/src/app/components/report-action/report-action.component.ts b/modules/ui/src/app/components/report-action/report-action.component.ts index debf132fa..a7a6a0965 100644 --- a/modules/ui/src/app/components/report-action/report-action.component.ts +++ b/modules/ui/src/app/components/report-action/report-action.component.ts @@ -1,18 +1,24 @@ -import { ChangeDetectionStrategy, Component, Input } from '@angular/core'; +import { + ChangeDetectionStrategy, + Component, + Input, + inject, +} from '@angular/core'; import { CommonModule, DatePipe } from '@angular/common'; import { TestrunStatus } from '../../model/testrun-status'; @Component({ selector: 'app-report-action', - standalone: true, + imports: [CommonModule], template: '', providers: [DatePipe], changeDetection: ChangeDetectionStrategy.OnPush, }) export class ReportActionComponent { + private datePipe = inject(DatePipe); + @Input() data!: TestrunStatus; - constructor(private datePipe: DatePipe) {} getTestRunId(data: TestrunStatus) { if (!data.device) { diff --git a/modules/ui/src/app/components/shutdown-app/shutdown-app.component.spec.ts b/modules/ui/src/app/components/shutdown-app/shutdown-app.component.spec.ts index 522234f5e..f2bba357b 100644 --- a/modules/ui/src/app/components/shutdown-app/shutdown-app.component.spec.ts +++ b/modules/ui/src/app/components/shutdown-app/shutdown-app.component.spec.ts @@ -25,9 +25,9 @@ import { MatDialogModule, MatDialogRef } from '@angular/material/dialog'; import { TestRunService } from '../../services/test-run.service'; import SpyObj = jasmine.SpyObj; import { of } from 'rxjs'; -import { ShutdownAppModalComponent } from '../shutdown-app-modal/shutdown-app-modal.component'; import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; import { WINDOW } from '../../providers/window.provider'; +import { SimpleDialogComponent } from '../simple-dialog/simple-dialog.component'; describe('ShutdownAppComponent', () => { let component: ShutdownAppComponent; @@ -71,7 +71,7 @@ describe('ShutdownAppComponent', () => { mockService.shutdownTestrun.and.returnValue(of(false)); spyOn(component.dialog, 'open').and.returnValue({ afterClosed: () => of(true), - } as MatDialogRef); + } as MatDialogRef); tick(); component.openShutdownModal(); @@ -83,7 +83,7 @@ describe('ShutdownAppComponent', () => { mockService.shutdownTestrun.and.returnValue(of(true)); spyOn(component.dialog, 'open').and.returnValue({ afterClosed: () => of(true), - } as MatDialogRef); + } as MatDialogRef); tick(); component.openShutdownModal(); diff --git a/modules/ui/src/app/components/shutdown-app/shutdown-app.component.ts b/modules/ui/src/app/components/shutdown-app/shutdown-app.component.ts index 3e59eb768..131576b1f 100644 --- a/modules/ui/src/app/components/shutdown-app/shutdown-app.component.ts +++ b/modules/ui/src/app/components/shutdown-app/shutdown-app.component.ts @@ -16,49 +16,50 @@ import { ChangeDetectionStrategy, Component, - Inject, Input, OnDestroy, + inject, } from '@angular/core'; import { CommonModule } from '@angular/common'; import { MatButtonModule } from '@angular/material/button'; import { MatIcon } from '@angular/material/icon'; import { MatDialog } from '@angular/material/dialog'; -import { ShutdownAppModalComponent } from '../shutdown-app-modal/shutdown-app-modal.component'; import { Subject, takeUntil } from 'rxjs'; import { TestRunService } from '../../services/test-run.service'; import { WINDOW } from '../../providers/window.provider'; import { MatTooltipModule } from '@angular/material/tooltip'; +import { SimpleDialogComponent } from '../simple-dialog/simple-dialog.component'; @Component({ selector: 'app-shutdown-app', - standalone: true, + imports: [CommonModule, MatButtonModule, MatIcon, MatTooltipModule], templateUrl: './shutdown-app.component.html', - styleUrl: './shutdown-app.component.scss', changeDetection: ChangeDetectionStrategy.OnPush, }) export class ShutdownAppComponent implements OnDestroy { + dialog = inject(MatDialog); + private testRunService = inject(TestRunService); + private window = inject(WINDOW); + @Input() disable: boolean = false; private destroy$: Subject = new Subject(); - constructor( - public dialog: MatDialog, - private testRunService: TestRunService, - @Inject(WINDOW) private window: Window - ) {} openShutdownModal() { - const dialogRef = this.dialog.open(ShutdownAppModalComponent, { + const dialogRef = this.dialog.open(SimpleDialogComponent, { ariaLabel: 'Shutdown Testrun', data: { - title: 'Shutdown Testrun', - content: 'Do you want to stop Testrun?', + icon: 'power_settings_new', + title: 'Shutdown Testrun?', + content: + 'Testrun will shutdown and all testing processes will be stopped.', + confirmName: 'Stop Server & Quit', }, autoFocus: true, hasBackdrop: true, disableClose: true, - panelClass: 'shutdown-app-dialog', + panelClass: ['simple-dialog', 'shutdown-app-dialog'], }); dialogRef diff --git a/modules/ui/src/app/components/side-button-menu/side-button-menu.component.html b/modules/ui/src/app/components/side-button-menu/side-button-menu.component.html new file mode 100644 index 000000000..678434e95 --- /dev/null +++ b/modules/ui/src/app/components/side-button-menu/side-button-menu.component.html @@ -0,0 +1,56 @@ +
+ + +
+ + +
+ + + +
+ +
diff --git a/modules/ui/src/app/components/side-button-menu/side-button-menu.component.scss b/modules/ui/src/app/components/side-button-menu/side-button-menu.component.scss new file mode 100644 index 000000000..9fafcf71a --- /dev/null +++ b/modules/ui/src/app/components/side-button-menu/side-button-menu.component.scss @@ -0,0 +1,77 @@ +@use 'colors'; +@use 'variables'; +@use '@angular/material' as mat; + +:host { + display: flex; + justify-content: center; + width: 100%; +} + +.side-add-button-container { + position: relative; +} + +.side-add-menu-trigger { + position: absolute; + top: 0; + right: -20px; +} + +.side-add-menu-triangle { + position: absolute; + top: 18px; + left: -12px; +} + +::ng-deep .side-add-menu { + overflow: visible !important; + width: 278px; + border-radius: 4px; + padding: 0 8px; +} + +.side-add-button { + --mdc-fab-container-color: #{colors.$primary-container}; + --mat-icon-color: #{colors.$primary}; +} + +.side-add-menu-button { + gap: 12px; + border-radius: 4px; + width: 100%; + height: 56px; + display: grid; + background: inherit; + grid-template-columns: min-content auto; +} + +::ng-deep .using-mouse .side-add-menu-button { + &.cdk-mouse-focused, + &.cdk-program-focused { + background: inherit !important; + } + &.cdk-mouse-focused::before, + &.cdk-program-focused::before { + content: none; + } +} + +.side-add-menu-button-description { + color: colors.$on-surface-variant; + font-family: variables.$font-text; + font-size: 12px; + font-style: normal; + font-weight: 400; + line-height: 16px; + letter-spacing: 0.1px; +} + +.side-add-menu-button-label { + color: colors.$on-surface; + font-family: variables.$font-text; + font-size: 16px; + font-style: normal; + font-weight: 400; + line-height: 24px; +} diff --git a/modules/ui/src/app/components/side-button-menu/side-button-menu.component.spec.ts b/modules/ui/src/app/components/side-button-menu/side-button-menu.component.spec.ts new file mode 100644 index 000000000..289b12daa --- /dev/null +++ b/modules/ui/src/app/components/side-button-menu/side-button-menu.component.spec.ts @@ -0,0 +1,126 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { MatMenuModule } from '@angular/material/menu'; +import { MatButtonModule } from '@angular/material/button'; +import { MatIconModule } from '@angular/material/icon'; +import { By } from '@angular/platform-browser'; +import { of } from 'rxjs'; +import { SideButtonMenuComponent } from './side-button-menu.component'; +import { + MatMenuHarness, + MatMenuItemHarness, +} from '@angular/material/menu/testing'; +import { HarnessLoader } from '@angular/cdk/testing'; +import { TestbedHarnessEnvironment } from '@angular/cdk/testing/testbed'; + +describe('SideButtonMenuComponent', () => { + let component: SideButtonMenuComponent; + let fixture: ComponentFixture; + let loader: HarnessLoader; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [ + SideButtonMenuComponent, + MatMenuModule, + MatButtonModule, + MatIconModule, + ], + }).compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(SideButtonMenuComponent); + component = fixture.componentInstance; + loader = TestbedHarnessEnvironment.loader(fixture); + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); + + it('should render menu button', () => { + const button = fixture.debugElement.query(By.css('.side-add-button')); + expect(button).toBeTruthy(); + }); + + describe('menu', () => { + let menu; + let items: MatMenuItemHarness[]; + const onClickSpy = jasmine.createSpy('onClick'); + + beforeEach(async () => { + fixture.componentRef.setInput('menuItems', [ + { + icon: 'home', + label: 'Home', + onClick: () => {}, + disabled$: of(true), + }, + { + icon: 'settings', + label: 'Settings', + description: 'Settings description', + onClick: onClickSpy, + disabled$: of(false), + }, + ]); + fixture.detectChanges(); + + menu = await loader.getHarness(MatMenuHarness); + await menu.open(); + items = await menu.getItems(); + }); + + it('should render menu items', async () => { + expect(items.length).toBe(2); + + const text0 = await items[0].getText(); + const text1 = await items[1].getText(); + + expect(text0).toContain('Home'); + expect(text1).toContain('Settings'); + expect(text1).toContain('Settings description'); + }); + + it('should emit the correct action when a menu item is clicked', async () => { + await items[1].click(); + + expect(onClickSpy).toHaveBeenCalled(); + }); + + ['Escape', 'Tab'].forEach((key: string) => { + it(`should focus side button on ${key} press`, async () => { + const button = document.querySelector( + '.side-add-button' + ) as HTMLButtonElement; + const buttonFocusSpy = spyOn(button, 'focus'); + const firstItemElement = await items[0].host(); + await firstItemElement.dispatchEvent('keydown', { key: key }); + + expect(buttonFocusSpy).toHaveBeenCalled(); + }); + + it(`should close menu on ${key} press`, async () => { + const closeMenuSpy = spyOn(component.menuTrigger(), 'closeMenu'); + const firstItemElement = await items[0].host(); + await firstItemElement.dispatchEvent('keydown', { key: key }); + + expect(closeMenuSpy).toHaveBeenCalled(); + }); + }); + + it('should display the correct icons for actions', async () => { + const text0 = await items[0].getText(); + const text1 = await items[1].getText(); + + expect(text0).toContain('home'); + expect(text1).toContain('settings'); + }); + + it('should disable menu item when observable emits true', async () => { + const disabled = await items[0].isDisabled(); + expect(disabled).toBeTrue(); + }); + }); +}); diff --git a/modules/ui/src/app/components/side-button-menu/side-button-menu.component.ts b/modules/ui/src/app/components/side-button-menu/side-button-menu.component.ts new file mode 100644 index 000000000..f355d6706 --- /dev/null +++ b/modules/ui/src/app/components/side-button-menu/side-button-menu.component.ts @@ -0,0 +1,36 @@ +import { Component, input, viewChild } from '@angular/core'; +import { MatButtonModule } from '@angular/material/button'; +import { MatIconModule } from '@angular/material/icon'; +import { MatMenuModule, MatMenuTrigger } from '@angular/material/menu'; +import { CommonModule } from '@angular/common'; +import { AddMenuItem } from '../../app.component'; +import { MatTooltip } from '@angular/material/tooltip'; + +@Component({ + selector: 'app-side-button-menu', + imports: [ + MatButtonModule, + MatIconModule, + MatMenuModule, + CommonModule, + MatTooltip, + ], + templateUrl: './side-button-menu.component.html', + styleUrl: './side-button-menu.component.scss', +}) +export class SideButtonMenuComponent { + readonly menuTrigger = viewChild.required('menuTrigger'); + menuItems = input([]); + + focusButton(event: Event) { + event.preventDefault(); + event.stopPropagation(); + const button = document.querySelector( + '.side-add-button' + ) as HTMLButtonElement; + if (button) { + button.focus(); + } + this.menuTrigger().closeMenu(); + } +} diff --git a/modules/ui/src/app/components/simple-dialog/simple-dialog.component.html b/modules/ui/src/app/components/simple-dialog/simple-dialog.component.html index 975773924..c2201f377 100644 --- a/modules/ui/src/app/components/simple-dialog/simple-dialog.component.html +++ b/modules/ui/src/app/components/simple-dialog/simple-dialog.component.html @@ -13,12 +13,17 @@ See the License for the specific language governing permissions and limitations under the License. --> +{{ + data.icon + }} {{ data.title }}

{{ data.content }}

- + diff --git a/modules/ui/src/app/components/simple-dialog/simple-dialog.component.scss b/modules/ui/src/app/components/simple-dialog/simple-dialog.component.scss index a1b69727e..924e516f2 100644 --- a/modules/ui/src/app/components/simple-dialog/simple-dialog.component.scss +++ b/modules/ui/src/app/components/simple-dialog/simple-dialog.component.scss @@ -13,32 +13,44 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -@import '../../../theming/colors'; +@use 'colors'; +@use 'variables'; +@use 'mixins'; + +::ng-deep :root { + --mat-dialog-container-max-width: 570px; +} :host { - display: grid; - overflow: hidden; - width: 570px; - padding: 24px 16px 8px 24px; + @include mixins.dialog; + padding: 24px; gap: 10px; } +.simple-dialog-icon { + text-align: center; +} + .simple-dialog-title { - color: $grey-900; - font-size: 18px; - line-height: 24px; + @include mixins.headline-small(); padding: 0; + text-align: center; + color: colors.$on-surface; + font-family: variables.$font-primary; } .simple-dialog-content { - font-family: Roboto, sans-serif; + font-family: variables.$font-text; font-size: 14px; line-height: 20px; letter-spacing: 0.2px; - color: $grey-800; + color: colors.$on-surface-variant; } .simple-dialog-actions { padding: 0; min-height: 30px; + button { + font-family: variables.$font-text; + } } diff --git a/modules/ui/src/app/components/simple-dialog/simple-dialog.component.spec.ts b/modules/ui/src/app/components/simple-dialog/simple-dialog.component.spec.ts index 7ac624c6c..e001f776d 100644 --- a/modules/ui/src/app/components/simple-dialog/simple-dialog.component.spec.ts +++ b/modules/ui/src/app/components/simple-dialog/simple-dialog.component.spec.ts @@ -48,6 +48,7 @@ describe('DeleteFormComponent', () => { fixture = TestBed.createComponent(SimpleDialogComponent); component = fixture.componentInstance; component.data = { + icon: 'favorite', title: 'title?', content: 'content', }; @@ -59,6 +60,12 @@ describe('DeleteFormComponent', () => { expect(component).toBeTruthy(); }); + it('should has icon', () => { + const title = compiled.querySelector('mat-icon') as HTMLElement; + + expect(title.innerHTML).toEqual('favorite'); + }); + it('should has title', () => { const title = compiled.querySelector('.simple-dialog-title') as HTMLElement; diff --git a/modules/ui/src/app/components/simple-dialog/simple-dialog.component.ts b/modules/ui/src/app/components/simple-dialog/simple-dialog.component.ts index 4f790d59e..de11568c7 100644 --- a/modules/ui/src/app/components/simple-dialog/simple-dialog.component.ts +++ b/modules/ui/src/app/components/simple-dialog/simple-dialog.component.ts @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { Component, Inject } from '@angular/core'; +import { Component, inject } from '@angular/core'; import { MAT_DIALOG_DATA, MatDialogModule, @@ -24,30 +24,43 @@ import { EscapableDialogComponent } from '../escapable-dialog/escapable-dialog.c import { ComponentWithAnnouncement } from '../component-with-announcement'; import { LiveAnnouncer } from '@angular/cdk/a11y'; import { FocusManagerService } from '../../services/focus-manager.service'; +import { MatIconModule } from '@angular/material/icon'; +import { CommonModule } from '@angular/common'; interface DialogData { + icon?: string; title?: string; content?: string; + confirmName?: string; } @Component({ selector: 'app-simple-dialog', templateUrl: './simple-dialog.component.html', styleUrls: ['./simple-dialog.component.scss'], - standalone: true, - imports: [MatDialogModule, MatButtonModule], + + imports: [MatDialogModule, MatButtonModule, MatIconModule, CommonModule], }) export class SimpleDialogComponent extends ComponentWithAnnouncement( EscapableDialogComponent ) { - constructor( - public override dialogRef: MatDialogRef, - @Inject(MAT_DIALOG_DATA) public data: DialogData, - public liveAnnouncer: LiveAnnouncer, - public override focusService: FocusManagerService - ) { + override dialogRef: MatDialogRef; + data: DialogData; + liveAnnouncer: LiveAnnouncer; + override focusService: FocusManagerService; + + constructor() { + const dialogRef = inject>(MatDialogRef); + const data = inject(MAT_DIALOG_DATA); + const liveAnnouncer = inject(LiveAnnouncer); + const focusService = inject(FocusManagerService); + // @ts-expect-error ComponentWithAnnouncement should have 4 arguments super(dialogRef, data.title, liveAnnouncer, focusService); + this.dialogRef = dialogRef; + this.data = data; + this.liveAnnouncer = liveAnnouncer; + this.focusService = focusService; } confirm() { diff --git a/modules/ui/src/app/components/snack-bar/snack-bar.component.scss b/modules/ui/src/app/components/snack-bar/snack-bar.component.scss index c3772e863..cc4ad80a9 100644 --- a/modules/ui/src/app/components/snack-bar/snack-bar.component.scss +++ b/modules/ui/src/app/components/snack-bar/snack-bar.component.scss @@ -13,7 +13,8 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -@import '../../../theming/colors'; +@use 'colors'; +@use 'variables'; .snack-bar-container { display: flex; @@ -22,8 +23,9 @@ margin: 0; } - .snack-bar-actions button.action-btn { - color: $blue-300; + .snack-bar-actions button.action-btn.stop { + font-family: variables.$font-text; + color: colors.$inverse-primary; font-weight: 500; line-height: 20px; letter-spacing: 0.25px; diff --git a/modules/ui/src/app/components/snack-bar/snack-bar.component.ts b/modules/ui/src/app/components/snack-bar/snack-bar.component.ts index c1c4f4242..696a7a46e 100644 --- a/modules/ui/src/app/components/snack-bar/snack-bar.component.ts +++ b/modules/ui/src/app/components/snack-bar/snack-bar.component.ts @@ -27,7 +27,7 @@ import { setIsOpenWaitSnackBar, setIsStopTestrun } from '../../store/actions'; @Component({ selector: 'app-snack-bar', - standalone: true, + imports: [ MatButtonModule, MatSnackBarLabel, @@ -39,8 +39,9 @@ import { setIsOpenWaitSnackBar, setIsStopTestrun } from '../../store/actions'; changeDetection: ChangeDetectionStrategy.OnPush, }) export class SnackBarComponent { + private store = inject>(Store); + snackBarRef = inject(MatSnackBarRef); - constructor(private store: Store) {} wait(): void { this.snackBarRef.dismiss(); diff --git a/modules/ui/src/app/components/spinner/spinner.component.scss b/modules/ui/src/app/components/spinner/spinner.component.scss index 42b5a6264..8d582b0dd 100644 --- a/modules/ui/src/app/components/spinner/spinner.component.scss +++ b/modules/ui/src/app/components/spinner/spinner.component.scss @@ -1,5 +1,6 @@ @use '@angular/material' as mat; -@import '../../../theming/colors'; +@use 'm3-theme' as *; +@use 'colors'; .spinner-container { position: absolute; @@ -15,22 +16,24 @@ justify-content: center; } -.loader { - width: 36px; - height: 36px; - border: 4px solid mat.m2-get-color-from-palette($color-primary, 500); - border-bottom-color: transparent; - border-radius: 50%; - display: inline-block; - box-sizing: border-box; - animation: rotation 1s linear infinite; -} - -@keyframes rotation { - 0% { - transform: rotate(0deg); +::ng-deep { + .loader { + width: 36px; + height: 36px; + border: 4px solid mat.get-theme-color($light-theme, primary, 40); + border-bottom-color: transparent; + border-radius: 50%; + display: inline-block; + box-sizing: border-box; + animation: rotation 1s linear infinite; } - 100% { - transform: rotate(360deg); + + @keyframes rotation { + 0% { + transform: rotate(0deg); + } + 100% { + transform: rotate(360deg); + } } } diff --git a/modules/ui/src/app/components/spinner/spinner.component.ts b/modules/ui/src/app/components/spinner/spinner.component.ts index de45b59e9..874b47c6f 100644 --- a/modules/ui/src/app/components/spinner/spinner.component.ts +++ b/modules/ui/src/app/components/spinner/spinner.component.ts @@ -1,4 +1,4 @@ -import { Component, OnInit } from '@angular/core'; +import { Component, OnInit, inject } from '@angular/core'; import { LoaderService } from '../../services/loader.service'; import { CommonModule } from '@angular/common'; import { Observable } from 'rxjs/internal/Observable'; @@ -7,13 +7,13 @@ import { Observable } from 'rxjs/internal/Observable'; selector: 'app-spinner', templateUrl: './spinner.component.html', styleUrls: ['./spinner.component.scss'], - standalone: true, + imports: [CommonModule], }) export class SpinnerComponent implements OnInit { - loader$!: Observable; + loaderService = inject(LoaderService); - constructor(public loaderService: LoaderService) {} + loader$!: Observable; ngOnInit() { this.loader$ = this.loaderService.getLoading(); diff --git a/modules/ui/src/app/components/stepper/stepper.component.scss b/modules/ui/src/app/components/stepper/stepper.component.scss index d5be19c55..c8ed44e65 100644 --- a/modules/ui/src/app/components/stepper/stepper.component.scss +++ b/modules/ui/src/app/components/stepper/stepper.component.scss @@ -13,8 +13,8 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -@import '../../../theming/colors'; -@import '../../../theming/variables'; +@use 'colors'; +@use 'variables'; .form-container { height: 100%; @@ -50,15 +50,15 @@ } .form-step { - border: 2px solid $lighter-grey; + border: 2px solid colors.$lighter-grey; width: 4px; height: 4px; display: inline-block; border-radius: 100%; margin: 0 8px; &.step-active { - border-color: $secondary; - background: $secondary; + border-color: colors.$secondary; + background: colors.$secondary; } } @@ -72,16 +72,16 @@ .form-button-back, .form-button-forward { - height: $icon-size; - width: $icon-size; - min-width: $icon-size; + height: variables.$icon-size; + width: variables.$icon-size; + min-width: variables.$icon-size; margin: 0; padding: 0; & mat-icon { - color: $secondary; - width: $icon-size; - height: $icon-size; - font-size: $icon-size; + color: colors.$secondary; + width: variables.$icon-size; + height: variables.$icon-size; + font-size: variables.$icon-size; margin: 0; } diff --git a/modules/ui/src/app/components/stepper/stepper.component.spec.ts b/modules/ui/src/app/components/stepper/stepper.component.spec.ts index 25b5f9740..ed35db415 100644 --- a/modules/ui/src/app/components/stepper/stepper.component.spec.ts +++ b/modules/ui/src/app/components/stepper/stepper.component.spec.ts @@ -16,7 +16,7 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; import { StepperComponent } from './stepper.component'; -import { Component, ViewChild, ViewEncapsulation } from '@angular/core'; +import { Component, ViewEncapsulation, viewChild, inject } from '@angular/core'; import { FormBuilder, ReactiveFormsModule, Validators } from '@angular/forms'; import { CdkStep } from '@angular/cdk/stepper'; import { MatFormField, MatFormFieldModule } from '@angular/material/form-field'; @@ -26,7 +26,7 @@ import { NoopAnimationsModule } from '@angular/platform-browser/animations'; @Component({ selector: 'app-stepper-bypass', - standalone: true, + imports: [ CdkStep, StepperComponent, @@ -39,11 +39,13 @@ import { NoopAnimationsModule } from '@angular/platform-browser/animations'; templateUrl: './stepper-test.component.html', }) class TestStepperComponent { - @ViewChild('stepper') public stepper!: StepperComponent; + private fb = inject(FormBuilder); + + readonly stepper = viewChild.required('stepper'); testForm; firstStep; secondStep; - constructor(private fb: FormBuilder) { + constructor() { this.firstStep = this.fb.group({ firstControl: ['', [Validators.required]], }); @@ -85,7 +87,7 @@ describe('StepperComponent', () => { }); it('should not mark selected step touched if not interacted', () => { - component.stepper.nextClick(); + component.stepper().nextClick(); expect(component.firstStep.touched).toBeFalse(); }); diff --git a/modules/ui/src/app/components/stepper/stepper.component.ts b/modules/ui/src/app/components/stepper/stepper.component.ts index ee7f1c5ab..22321b71b 100644 --- a/modules/ui/src/app/components/stepper/stepper.component.ts +++ b/modules/ui/src/app/components/stepper/stepper.component.ts @@ -17,20 +17,18 @@ import { Component, Input, TemplateRef } from '@angular/core'; import { CdkStepper, CdkStepperModule } from '@angular/cdk/stepper'; import { NgForOf, NgIf, NgTemplateOutlet } from '@angular/common'; import { MatIcon } from '@angular/material/icon'; -import { MatButton, MatIconButton } from '@angular/material/button'; +import { MatButton } from '@angular/material/button'; import { FormGroup } from '@angular/forms'; import { MatTooltipModule } from '@angular/material/tooltip'; @Component({ selector: 'app-stepper', - standalone: true, imports: [ NgForOf, NgTemplateOutlet, CdkStepperModule, NgIf, MatIcon, - MatIconButton, MatButton, MatTooltipModule, ], diff --git a/modules/ui/src/app/components/testing-complete/testing-complete.component.spec.ts b/modules/ui/src/app/components/testing-complete/testing-complete.component.spec.ts index 494b58823..087758dc3 100644 --- a/modules/ui/src/app/components/testing-complete/testing-complete.component.spec.ts +++ b/modules/ui/src/app/components/testing-complete/testing-complete.component.spec.ts @@ -65,7 +65,8 @@ describe('TestingCompleteComponent', () => { profiles: [], testrunStatus: MOCK_PROGRESS_DATA_COMPLIANT, isTestingComplete: true, - url: 'https://api.testrun.io/report.pdf', + report: 'https://api.testrun.io/report.pdf', + export: '', isPilot: false, }, autoFocus: 'first-tabbable', diff --git a/modules/ui/src/app/components/testing-complete/testing-complete.component.ts b/modules/ui/src/app/components/testing-complete/testing-complete.component.ts index 79fc46c79..e8d04d149 100644 --- a/modules/ui/src/app/components/testing-complete/testing-complete.component.ts +++ b/modules/ui/src/app/components/testing-complete/testing-complete.component.ts @@ -4,6 +4,7 @@ import { Input, OnDestroy, OnInit, + inject, } from '@angular/core'; import { Subject, takeUntil, timer } from 'rxjs'; import { MatDialog } from '@angular/material/dialog'; @@ -19,21 +20,18 @@ import { TestingType } from '../../model/device'; @Component({ selector: 'app-testing-complete', - standalone: true, imports: [], template: '', changeDetection: ChangeDetectionStrategy.OnPush, }) export class TestingCompleteComponent implements OnDestroy, OnInit { + dialog = inject(MatDialog); + private focusManagerService = inject(FocusManagerService); + @Input() profiles: Profile[] = []; @Input() data!: TestrunStatus | null; private destroy$: Subject = new Subject(); - constructor( - public dialog: MatDialog, - private focusManagerService: FocusManagerService - ) {} - ngOnInit() { timer(1000) .pipe(takeUntil(this.destroy$)) @@ -53,7 +51,8 @@ export class TestingCompleteComponent implements OnDestroy, OnInit { profiles: this.profiles, testrunStatus: this.data, isTestingComplete: true, - url: this.data?.report, + report: this.data?.report, + export: this.data?.export, isPilot: this.data?.device.test_pack === TestingType.Pilot, }, autoFocus: 'first-tabbable', diff --git a/modules/ui/src/app/components/version/consent-dialog/consent-dialog.component.html b/modules/ui/src/app/components/version/consent-dialog/consent-dialog.component.html index 6867dd445..244c77e88 100644 --- a/modules/ui/src/app/components/version/consent-dialog/consent-dialog.component.html +++ b/modules/ui/src/app/components/version/consent-dialog/consent-dialog.component.html @@ -81,16 +81,7 @@

Welcome to Testrun!

-
- - Pilot Assessment -

- Pilot project support is now offered through Testrun. Follow the - instructions set out to get your pilot recommendation. -

-
-
-
+
Testrun uses Google Analytics to learn about how our users use the application. By installing and running Testrun, you understand and accept @@ -114,7 +105,7 @@

Welcome to Testrun!

(click)="confirm(optOut)" class="confirm-button" color="primary" - mat-raised-button + mat-flat-button aria-label="OK and Proceed to Testrun" type="button"> OK diff --git a/modules/ui/src/app/components/version/consent-dialog/consent-dialog.component.scss b/modules/ui/src/app/components/version/consent-dialog/consent-dialog.component.scss index c32f47a41..cd9feedb6 100644 --- a/modules/ui/src/app/components/version/consent-dialog/consent-dialog.component.scss +++ b/modules/ui/src/app/components/version/consent-dialog/consent-dialog.component.scss @@ -13,12 +13,16 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -@import '../../../../theming/colors'; +@use 'colors'; +@use 'mixins'; +@use 'variables'; + +::ng-deep :root { + --mat-dialog-container-max-width: 570px; +} :host { - display: grid; - overflow: hidden; - width: 570px; + @include mixins.dialog; padding: 16px; gap: 16px; } @@ -32,7 +36,7 @@ align-items: start !important; &.check_circle, - &.warning_amber { + &.warning { padding-top: 16px; } } @@ -54,30 +58,66 @@ } .consent-main-content { - padding: 0 66px 16px 66px; - font-family: Roboto, sans-serif; + padding: 0 34px 16px 54px; + font-family: variables.$font-text; font-size: 14px; line-height: 20px; - letter-spacing: 0.2px; - color: $grey-800; + letter-spacing: 0; + color: colors.$on-surface; h2 { - font-size: 14px; + font-size: 16px; + font-weight: 500; + line-height: 24px; } ul { padding-inline-start: 24px; margin-bottom: 0; + + li::marker { + color: colors.$primary; + } } } +::ng-deep .message-link { + color: colors.$primary; + text-decoration: underline; +} + .section-container { + ::ng-deep .callout-context { + padding: 0 0 4px; + } + .section-title { - font-size: 18px; + font-size: 16px; + line-height: 24px; + letter-spacing: 0; + font-weight: 500; + font-family: variables.$font-text; } + + .section-content { + margin: 0; + font-family: variables.$font-text; + } + .section-action-container { text-align: end; - margin-bottom: 0; + margin: 12px 0 0; + } + + .download-link { + color: colors.$orange-40; + font-family: variables.$font-text; + font-size: 14px; + margin-right: -2px; + + ::ng-deep .mat-focus-indicator { + display: none; + } } } @@ -92,14 +132,47 @@ } } +.section-container-info { + ::ng-deep .callout-container { + padding: 10px 16px 14px 16px; + } + + ::ng-deep .callout-icon { + padding: 6px 0; + } +} + .consent-actions { - border-top: 1px solid $lighter-grey; - margin: 0 -16px; - padding: 16px 16px 0 16px; + border-top: 1px solid colors.$outline-variant; + padding: 16px 0 0; + margin: 0; min-height: 30px; justify-content: space-between; } -.consent-actions-opt-out ::ng-deep label { - font-weight: 500; +.consent-actions-opt-out { + ::ng-deep label { + font-family: variables.$font-text; + } + + ::ng-deep .mdc-checkbox__native-control:focus ~ .mat-focus-indicator::before { + content: none; + } + + ::ng-deep + .mdc-checkbox__native-control:focus-visible + ~ .mat-focus-indicator::before { + content: ''; + } +} + +.confirm-button { + border-radius: 12px; + padding: 0 6px; + min-width: 54px; + margin-right: 24px; + + ::ng-deep .mat-focus-indicator { + display: none; + } } diff --git a/modules/ui/src/app/components/version/consent-dialog/consent-dialog.component.spec.ts b/modules/ui/src/app/components/version/consent-dialog/consent-dialog.component.spec.ts index d6925c131..9a12c04e1 100644 --- a/modules/ui/src/app/components/version/consent-dialog/consent-dialog.component.spec.ts +++ b/modules/ui/src/app/components/version/consent-dialog/consent-dialog.component.spec.ts @@ -30,20 +30,13 @@ import { MatButtonModule } from '@angular/material/button'; import { of } from 'rxjs'; import { NEW_VERSION, VERSION } from '../../../mocks/version.mock'; import { MatIconTestingModule } from '@angular/material/icon/testing'; -import { FocusManagerService } from '../../../services/focus-manager.service'; -import SpyObj = jasmine.SpyObj; describe('ConsentDialogComponent', () => { let component: ConsentDialogComponent; let fixture: ComponentFixture; let compiled: HTMLElement; - let mockFocusManagerService: SpyObj; beforeEach(() => { - mockFocusManagerService = jasmine.createSpyObj('mockFocusManagerService', [ - 'focusFirstElementInContainer', - ]); - TestBed.configureTestingModule({ imports: [ ConsentDialogComponent, @@ -60,12 +53,11 @@ describe('ConsentDialogComponent', () => { }, }, { provide: MAT_DIALOG_DATA, useValue: {} }, - { provide: FocusManagerService, useValue: mockFocusManagerService }, ], }); fixture = TestBed.createComponent(ConsentDialogComponent); component = fixture.componentInstance; - component.data = { version: NEW_VERSION }; + component.data = { version: NEW_VERSION, optOut: false }; component.optOut = false; fixture.detectChanges(); compiled = fixture.nativeElement as HTMLElement; @@ -117,17 +109,24 @@ describe('ConsentDialogComponent', () => { }); it('should set focus to first focusable elem when close dialog', fakeAsync(() => { + const button = document.createElement('BUTTON'); + button.classList.add('version-content'); + document.querySelector('body')?.appendChild(button); + + const versionButton = window.document.querySelector( + '.version-content' + ) as HTMLButtonElement; + const buttonFocusSpy = spyOn(versionButton, 'focus'); + component.confirm(true); tick(100); - expect( - mockFocusManagerService.focusFirstElementInContainer - ).toHaveBeenCalled(); + expect(buttonFocusSpy).toHaveBeenCalled(); })); describe('with new version available', () => { beforeEach(() => { - component.data = { version: NEW_VERSION }; + component.data = { version: NEW_VERSION, optOut: false }; fixture.detectChanges(); }); @@ -150,7 +149,7 @@ describe('ConsentDialogComponent', () => { describe('with no new version available', () => { beforeEach(() => { - component.data = { version: VERSION }; + component.data = { version: VERSION, optOut: false }; fixture.detectChanges(); }); diff --git a/modules/ui/src/app/components/version/consent-dialog/consent-dialog.component.ts b/modules/ui/src/app/components/version/consent-dialog/consent-dialog.component.ts index 3becacf89..63f9cecae 100644 --- a/modules/ui/src/app/components/version/consent-dialog/consent-dialog.component.ts +++ b/modules/ui/src/app/components/version/consent-dialog/consent-dialog.component.ts @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { Component, Inject } from '@angular/core'; +import { Component, inject } from '@angular/core'; import { MAT_DIALOG_DATA, MatDialogModule, @@ -26,16 +26,16 @@ import { CalloutType } from '../../../model/callout-type'; import { NgIf } from '@angular/common'; import { MatCheckbox } from '@angular/material/checkbox'; import { FormsModule } from '@angular/forms'; -import { FocusManagerService } from '../../../services/focus-manager.service'; import { timer } from 'rxjs'; type DialogData = { version: Version; + optOut: boolean; }; @Component({ selector: 'app-consent-dialog', - standalone: true, + imports: [ MatDialogModule, MatButtonModule, @@ -48,13 +48,11 @@ type DialogData = { styleUrl: './consent-dialog.component.scss', }) export class ConsentDialogComponent { + dialogRef = inject>(MatDialogRef); + data = inject(MAT_DIALOG_DATA); + public readonly CalloutType = CalloutType; - optOut = false; - constructor( - private readonly focusManagerService: FocusManagerService, - public dialogRef: MatDialogRef, - @Inject(MAT_DIALOG_DATA) public data: DialogData - ) {} + optOut = this.data.optOut; confirm(optOut: boolean) { // dialog should be closed with opposite value to grant or deny access to GA @@ -63,7 +61,12 @@ export class ConsentDialogComponent { }; this.dialogRef.close(dialogResult); timer(100).subscribe(() => { - this.focusManagerService.focusFirstElementInContainer(); + const versionButton = window.document.querySelector( + '.version-content' + ) as HTMLButtonElement; + if (versionButton) { + versionButton.focus(); + } }); } } diff --git a/modules/ui/src/app/components/version/version.component.scss b/modules/ui/src/app/components/version/version.component.scss index c506e4cfa..7650f7339 100644 --- a/modules/ui/src/app/components/version/version.component.scss +++ b/modules/ui/src/app/components/version/version.component.scss @@ -13,7 +13,8 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -@import '../../../theming/colors'; +@use 'colors'; +@use 'variables'; :host { position: relative; @@ -22,11 +23,11 @@ .version-content, .version-content-update { min-width: 48px; - height: 34px; + height: variables.$nav-button-height; max-width: 100%; - background: $color-background-grey; border-radius: 20px; cursor: pointer; + color: colors.$on-surface-variant; & ::ng-deep .mdc-button__label { text-overflow: ellipsis; overflow: hidden; @@ -39,7 +40,7 @@ width: 8px; height: 8px; border-radius: 100%; - background: $red-800; + background: colors.$red-700; top: 3px; right: 3px; } diff --git a/modules/ui/src/app/components/version/version.component.ts b/modules/ui/src/app/components/version/version.component.ts index ad8fcafa0..21a6faaed 100644 --- a/modules/ui/src/app/components/version/version.component.ts +++ b/modules/ui/src/app/components/version/version.component.ts @@ -20,6 +20,7 @@ import { OnDestroy, OnInit, Output, + inject, } from '@angular/core'; import { CommonModule } from '@angular/common'; import { @@ -36,27 +37,27 @@ import { Subject } from 'rxjs/internal/Subject'; import { takeUntil } from 'rxjs/internal/operators/takeUntil'; import { filter, timer } from 'rxjs'; import { ConsentDialogComponent } from './consent-dialog/consent-dialog.component'; +import { LocalStorageService } from '../../services/local-storage.service'; // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type declare const gtag: Function; @Component({ selector: 'app-version', - standalone: true, + imports: [CommonModule, MatButtonModule, MatDialogModule], templateUrl: './version.component.html', styleUrls: ['./version.component.scss'], }) export class VersionComponent implements OnInit, OnDestroy { + private testRunService = inject(TestRunService); + private localStorageService = inject(LocalStorageService); + dialog = inject(MatDialog); + @Input() consentShown!: boolean; @Output() consentShownEvent = new EventEmitter(); version$!: Observable; private destroy$: Subject = new Subject(); - constructor( - private testRunService: TestRunService, - public dialog: MatDialog - ) {} - ngOnInit() { this.testRunService.fetchVersion(); @@ -92,7 +93,10 @@ export class VersionComponent implements OnInit, OnDestroy { } openConsentDialog(version: Version) { - const dialogData = { version }; + const dialogData = { + version, + optOut: !this.localStorageService.getGAConsent(), + }; const dialogRef = this.dialog.open(ConsentDialogComponent, { ariaLabel: 'Welcome to Testrun modal window', data: dialogData, @@ -112,6 +116,7 @@ export class VersionComponent implements OnInit, OnDestroy { gtag('consent', 'update', { analytics_storage: dialogResult.grant ? 'granted' : 'denied', }); + this.localStorageService.setGAConsent(dialogResult.grant); }); } diff --git a/modules/ui/src/app/components/wifi/wifi.component.html b/modules/ui/src/app/components/wifi/wifi.component.html index c93d05f7e..a115edfaa 100644 --- a/modules/ui/src/app/components/wifi/wifi.component.html +++ b/modules/ui/src/app/components/wifi/wifi.component.html @@ -15,7 +15,7 @@ --> -
diff --git a/modules/ui/src/app/pages/certificates/certificate-item/certificate-item.component.scss b/modules/ui/src/app/pages/certificates/certificate-item/certificate-item.component.scss deleted file mode 100644 index 394ddec83..000000000 --- a/modules/ui/src/app/pages/certificates/certificate-item/certificate-item.component.scss +++ /dev/null @@ -1,94 +0,0 @@ -/** - * Copyright 2023 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -@import 'src/theming/colors'; -@import 'src/theming/variables'; - -:host { - ::ng-deep .mat-mdc-progress-bar { - --mdc-linear-progress-active-indicator-color: #1967d2; - } -} - -:host:first-child .certificate-item-container { - border-top: 1px solid $lighter-grey; -} - -.certificate-item-container { - display: grid; - grid-template-columns: 24px minmax(200px, 1fr) 24px; - gap: 16px; - box-sizing: border-box; - padding: 12px 0; - border-bottom: 1px solid $lighter-grey; -} - -.certificate-item-icon { - color: $grey-700; -} - -.certificate-item-delete { - padding: 0; - height: 24px; - width: 24px; - border-radius: 4px; - color: $grey-700; - display: flex; - align-items: flex-start; - justify-content: center; - & ::ng-deep .mat-mdc-button-persistent-ripple { - border-radius: 4px; - } - &:disabled { - pointer-events: none; - opacity: 0.6; - } -} - -.certificate-item-information { - overflow: hidden; - p { - font-family: $font-secondary, sans-serif; - margin: 0; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - } - .certificate-item-name { - font-size: 16px; - color: $grey-800; - min-height: 24px; - } - .certificate-item-organisation, - .certificate-item-expires { - font-size: 14px; - color: $grey-700; - min-height: 20px; - } -} - -.certificate-expired { - .certificate-item-icon { - color: $red-700; - } - .certificate-item-name { - color: $red-800; - } - - .certificate-item-organisation, - .certificate-item-expires { - color: $red-700; - } -} diff --git a/modules/ui/src/app/pages/certificates/certificate-item/certificate-item.component.ts b/modules/ui/src/app/pages/certificates/certificate-item/certificate-item.component.ts deleted file mode 100644 index 0eea2f7ec..000000000 --- a/modules/ui/src/app/pages/certificates/certificate-item/certificate-item.component.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { Component, EventEmitter, Input, Output } from '@angular/core'; -import { Certificate, CertificateStatus } from '../../../model/certificate'; -import { MatIcon } from '@angular/material/icon'; -import { CommonModule } from '@angular/common'; -import { MatButtonModule } from '@angular/material/button'; -import { MatProgressBarModule } from '@angular/material/progress-bar'; -import { provideAnimations } from '@angular/platform-browser/animations'; -import { MatError } from '@angular/material/form-field'; - -@Component({ - selector: 'app-certificate-item', - standalone: true, - imports: [ - MatIcon, - MatButtonModule, - MatProgressBarModule, - CommonModule, - MatError, - ], - providers: [provideAnimations()], - templateUrl: './certificate-item.component.html', - styleUrl: './certificate-item.component.scss', -}) -export class CertificateItemComponent { - @Input() certificate!: Certificate; - @Output() deleteButtonClicked = new EventEmitter(); - - CertificateStatus = CertificateStatus; -} diff --git a/modules/ui/src/app/pages/certificates/certificate-upload-button/certificate-upload-button.component.scss b/modules/ui/src/app/pages/certificates/certificate-upload-button/certificate-upload-button.component.scss deleted file mode 100644 index e48f64ceb..000000000 --- a/modules/ui/src/app/pages/certificates/certificate-upload-button/certificate-upload-button.component.scss +++ /dev/null @@ -1,14 +0,0 @@ -.browse-files-button { - margin: 18px 16px; - padding: 8px 24px; - font-size: 14px; - font-weight: 500; - line-height: 20px; - letter-spacing: 0.25px; - height: auto; - min-height: 36px; -} - -#default-file-input { - display: none; -} diff --git a/modules/ui/src/app/pages/certificates/certificates.component.html b/modules/ui/src/app/pages/certificates/certificates.component.html index bcc5173b6..e13bbffcb 100644 --- a/modules/ui/src/app/pages/certificates/certificates.component.html +++ b/modules/ui/src/app/pages/certificates/certificates.component.html @@ -13,31 +13,16 @@ See the License for the specific language governing permissions and limitations under the License. --> - -
-

Certificates

- -
-
- -
- -
-
-
+
+ +
+
+ + +
diff --git a/modules/ui/src/app/pages/certificates/certificates.component.scss b/modules/ui/src/app/pages/certificates/certificates.component.scss index 5ee4e9847..ee891a214 100644 --- a/modules/ui/src/app/pages/certificates/certificates.component.scss +++ b/modules/ui/src/app/pages/certificates/certificates.component.scss @@ -14,77 +14,21 @@ * limitations under the License. */ @use '@angular/material' as mat; -@import '../../../theming/colors'; -@import '../../../theming/variables'; +@use 'colors'; +@use 'variables'; :host { display: flex; flex-direction: column; - height: 100%; flex: 1 0 auto; } -.certificates-drawer-header { - display: flex; - justify-content: space-between; - align-items: center; - padding: 12px 12px 16px 24px; - - &-title { - margin: 0; - font-size: 22px; - font-style: normal; - font-weight: 400; - line-height: 28px; - color: $dark-grey; - } - - &-button { - min-width: 24px; - width: 24px; - height: 24px; - margin: 4px; - padding: 8px !important; - box-sizing: content-box; - line-height: normal !important; - - .close-button-icon { - width: 24px; - height: 24px; - margin: 0; - } - - ::ng-deep * { - line-height: inherit !important; - } - } -} - -.certificates-drawer-content { - overflow: hidden; - flex: 1; - display: grid; - grid-template-rows: auto 1fr auto; -} - .content-certificates { - padding: 0 16px; - border-bottom: 1px solid $lighter-grey; - overflow-y: scroll; + margin: 2px 18px 0; + height: max-content; + padding: 0 6px 6px; } -.certificates-drawer-footer { - padding: 16px 24px 8px 16px; - margin-top: auto; - display: flex; - flex-shrink: 0; - justify-content: flex-end; - - .close-button { - padding: 0 24px; - font-size: 14px; - font-weight: 500; - line-height: 20px; - letter-spacing: 0.25px; - } +.certificates-button-container { + margin: 24px; } diff --git a/modules/ui/src/app/pages/certificates/certificates.component.spec.ts b/modules/ui/src/app/pages/certificates/certificates.component.spec.ts index 1deda3d21..139c2605d 100644 --- a/modules/ui/src/app/pages/certificates/certificates.component.spec.ts +++ b/modules/ui/src/app/pages/certificates/certificates.component.spec.ts @@ -75,20 +75,6 @@ describe('CertificatesComponent', () => { }); describe('DOM tests', () => { - it('should emit closeSettingEvent when header button clicked', () => { - const headerCloseButton = fixture.nativeElement.querySelector( - '.certificates-drawer-header-button' - ) as HTMLButtonElement; - spyOn(component.closeCertificatedEvent, 'emit'); - - headerCloseButton.click(); - - expect(mockLiveAnnouncer.announce).toHaveBeenCalledWith( - 'The certificates panel is closed.' - ); - expect(component.closeCertificatedEvent.emit).toHaveBeenCalled(); - }); - it('should have upload file button', () => { const uploadCertificatesButton = fixture.nativeElement.querySelector( '.browse-files-button' @@ -99,9 +85,8 @@ describe('CertificatesComponent', () => { describe('with certificates', () => { it('should have certificates list', () => { - const certificateList = fixture.nativeElement.querySelectorAll( - 'app-certificate-item' - ); + const certificateList = + fixture.nativeElement.querySelectorAll('.cdk-row'); expect(certificateList.length).toEqual(2); }); @@ -128,7 +113,7 @@ describe('CertificatesComponent', () => { autoFocus: true, hasBackdrop: true, disableClose: true, - panelClass: 'simple-dialog', + panelClass: ['simple-dialog', 'delete-certificate'], }); openSpy.calls.reset(); @@ -137,12 +122,10 @@ describe('CertificatesComponent', () => { describe('#focusNextButton', () => { it('should focus next active element if exist', fakeAsync(() => { - const row = window.document.querySelector( - 'app-certificate-item' - ) as HTMLElement; + const row = window.document.querySelector('.cdk-row') as HTMLElement; row.classList.add('certificate-selected'); const nextButton = window.document.querySelector( - '.certificate-selected + app-certificate-item .certificate-item-delete' + '.certificate-selected + .cdk-row .certificate-item-delete' ) as HTMLButtonElement; const buttonFocusSpy = spyOn(nextButton, 'focus'); @@ -152,9 +135,9 @@ describe('CertificatesComponent', () => { flush(); })); - it('should focus navigation close button if next active element does not exist', fakeAsync(() => { + it('should focus upload button if next active element does not exist', fakeAsync(() => { const nextButton = window.document.querySelector( - '.certificates-drawer-header .certificates-drawer-header-button' + '.browse-files-button' ) as HTMLButtonElement; const buttonFocusSpy = spyOn(nextButton, 'focus'); diff --git a/modules/ui/src/app/pages/certificates/certificates.component.ts b/modules/ui/src/app/pages/certificates/certificates.component.ts index 3b8cccccc..b3c6065d5 100644 --- a/modules/ui/src/app/pages/certificates/certificates.component.ts +++ b/modules/ui/src/app/pages/certificates/certificates.component.ts @@ -13,57 +13,47 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { Component, EventEmitter, OnDestroy, Output } from '@angular/core'; -import { MatIcon } from '@angular/material/icon'; -import { CertificateItemComponent } from './certificate-item/certificate-item.component'; +import { + Component, + EventEmitter, + OnDestroy, + Output, + inject, +} from '@angular/core'; import { CommonModule, DatePipe } from '@angular/common'; import { MatButtonModule } from '@angular/material/button'; -import { CdkTrapFocus, LiveAnnouncer } from '@angular/cdk/a11y'; -import { CertificateUploadButtonComponent } from './certificate-upload-button/certificate-upload-button.component'; import { CertificatesStore } from './certificates.store'; import { SimpleDialogComponent } from '../../components/simple-dialog/simple-dialog.component'; import { Subject, takeUntil } from 'rxjs'; import { MatDialog } from '@angular/material/dialog'; +import { CertificatesTableComponent } from './components/certificates-table/certificates-table.component'; +import { CertificateUploadButtonComponent } from './components/certificate-upload-button/certificate-upload-button.component'; @Component({ selector: 'app-certificates', - standalone: true, imports: [ - MatIcon, - CertificateItemComponent, MatButtonModule, CertificateUploadButtonComponent, CommonModule, + CertificatesTableComponent, ], providers: [CertificatesStore, DatePipe], - hostDirectives: [CdkTrapFocus], templateUrl: './certificates.component.html', styleUrl: './certificates.component.scss', }) export class CertificatesComponent implements OnDestroy { - viewModel$ = this.store.viewModel$; + store = inject(CertificatesStore); + dialog = inject(MatDialog); + @Output() closeCertificatedEvent = new EventEmitter(); private destroy$: Subject = new Subject(); - constructor( - private liveAnnouncer: LiveAnnouncer, - private store: CertificatesStore, - public dialog: MatDialog - ) { - this.store.getCertificates(); - } - ngOnDestroy() { this.destroy$.next(true); this.destroy$.unsubscribe(); } - closeCertificates() { - this.liveAnnouncer.announce('The certificates panel is closed.'); - this.closeCertificatedEvent.emit(); - } - uploadFile(file: File) { this.store.uploadCertificate(file); } @@ -80,7 +70,7 @@ export class CertificatesComponent implements OnDestroy { autoFocus: true, hasBackdrop: true, disableClose: true, - panelClass: 'simple-dialog', + panelClass: ['simple-dialog', 'delete-certificate'], }); dialogRef @@ -97,16 +87,16 @@ export class CertificatesComponent implements OnDestroy { focusNextButton() { // Try to focus next interactive element, if exists const next = window.document.querySelector( - '.certificate-selected + app-certificate-item .certificate-item-delete' + '.certificate-selected + .cdk-row .certificate-item-delete' ) as HTMLButtonElement; if (next) { next.focus(); } else { - // If next interactive element doest not exist, close button will be focused - const menuButton = window.document.querySelector( - '.certificates-drawer-header .certificates-drawer-header-button' + // If next interactive element doest not exist, upload button will be focused + const uploadButton = window.document.querySelector( + '.browse-files-button' ) as HTMLButtonElement; - menuButton?.focus(); + uploadButton?.focus(); } } } diff --git a/modules/ui/src/app/pages/certificates/certificates.store.spec.ts b/modules/ui/src/app/pages/certificates/certificates.store.spec.ts index 5e66104e6..66d01fde6 100644 --- a/modules/ui/src/app/pages/certificates/certificates.store.spec.ts +++ b/modules/ui/src/app/pages/certificates/certificates.store.spec.ts @@ -1,225 +1,98 @@ -/* - * Copyright 2023 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ import { TestBed } from '@angular/core/testing'; -import { of, skip, take } from 'rxjs'; -import { provideMockStore } from '@ngrx/store/testing'; -import { TestRunService } from '../../services/test-run.service'; -import SpyObj = jasmine.SpyObj; -import { - certificate, - certificate2, - certificate_uploading, - FILE, - INVALID_FILE, -} from '../../mocks/certificate.mock'; import { CertificatesStore } from './certificates.store'; -import { NoopAnimationsModule } from '@angular/platform-browser/animations'; -import { DatePipe } from '@angular/common'; +import { TestRunService } from '../../services/test-run.service'; import { NotificationService } from '../../services/notification.service'; +import { DatePipe } from '@angular/common'; +import { of, throwError } from 'rxjs'; +import { Certificate } from '../../model/certificate'; describe('CertificatesStore', () => { - let certificateStore: CertificatesStore; - let mockService: SpyObj; - const notificationServiceMock: jasmine.SpyObj = - jasmine.createSpyObj(['notify']); - + // @ts-expect-error certificatesStore is a ReturnType of CertificatesStore + let certificatesStore: ReturnType; + const mockCertificates: Certificate[] = [ + { name: 'Cert1', uploading: false }, + { name: 'Cert2', uploading: false }, + ]; + const testRunServiceMock = jasmine.createSpyObj('TestRunService', [ + 'fetchCertificates', + 'deleteCertificate', + 'uploadCertificate', + ]); + const notificationServiceMock = jasmine.createSpyObj('NotificationService', [ + 'notify', + ]); beforeEach(() => { - mockService = jasmine.createSpyObj([ - 'fetchCertificates', - 'uploadCertificate', - 'deleteCertificate', - ]); - // @ts-expect-error data layer should be defined - window.dataLayer = window.dataLayer || []; - TestBed.configureTestingModule({ - imports: [NoopAnimationsModule], providers: [ - CertificatesStore, - provideMockStore({}), - { provide: TestRunService, useValue: mockService }, + { provide: TestRunService, useValue: testRunServiceMock }, { provide: NotificationService, useValue: notificationServiceMock }, DatePipe, + CertificatesStore, ], }); + testRunServiceMock.fetchCertificates.and.returnValue(of(mockCertificates)); - certificateStore = TestBed.inject(CertificatesStore); + certificatesStore = TestBed.inject(CertificatesStore); }); - it('should be created', () => { - expect(certificateStore).toBeTruthy(); - }); - - describe('updaters', () => { - it('should update certificates', (done: DoneFn) => { - certificateStore.viewModel$.pipe(skip(1), take(1)).subscribe(store => { - expect(store.certificates).toEqual([certificate]); - done(); - }); + it('should initialize with certificates fetched from the service', () => { + certificatesStore.getCertificates(); - certificateStore.updateCertificates([certificate]); - }); + expect(testRunServiceMock.fetchCertificates).toHaveBeenCalled(); + expect(certificatesStore.certificates()).toEqual(mockCertificates); + }); - it('should update selectedCertificate', (done: DoneFn) => { - const certificate = 'test'; - certificateStore.viewModel$.pipe(skip(1), take(1)).subscribe(store => { - expect(store.selectedCertificate).toEqual(certificate); - done(); - }); + it('should handle errors when fetching certificates', () => { + testRunServiceMock.fetchCertificates.and.returnValue(throwError('Error')); - certificateStore.selectCertificate(certificate); - }); - }); + certificatesStore.getCertificates(); - describe('selectors', () => { - it('should select state', done => { - certificateStore.viewModel$.pipe(take(1)).subscribe(store => { - expect(store).toEqual({ - certificates: [], - selectedCertificate: '', - }); - done(); - }); - }); + expect(certificatesStore.certificates()).toEqual([]); }); - describe('effects', () => { - describe('fetchCertificates', () => { - const certificates = [certificate]; + it('should delete a certificate and update the store', () => { + testRunServiceMock.deleteCertificate.and.returnValue(of(true)); - beforeEach(() => { - mockService.fetchCertificates.and.returnValue(of(certificates)); - }); + certificatesStore.deleteCertificate('Cert1'); - it('should update certificates', done => { - certificateStore.viewModel$.pipe(skip(1), take(1)).subscribe(store => { - expect(store.certificates).toEqual(certificates); - done(); - }); + expect(testRunServiceMock.deleteCertificate).toHaveBeenCalledWith('Cert1'); + expect(certificatesStore.certificates()).toEqual([ + { name: 'Cert2', uploading: false }, + ]); + }); - certificateStore.getCertificates(); - }); - }); + it('should handle errors when deleting a certificate', () => { + testRunServiceMock.deleteCertificate.and.returnValue(throwError('Error')); - describe('uploadCertificate', () => { - beforeEach(() => { - mockService.uploadCertificate.and.returnValue(of(true)); - mockService.fetchCertificates.and.returnValue(of([certificate])); - }); - - describe('with valid certificate file', () => { - it('should update certificates', done => { - const uploadingCertificate = certificate_uploading; - - certificateStore.viewModel$ - .pipe(skip(1), take(1)) - .subscribe(store => { - expect(store.certificates).toContain(uploadingCertificate); - }); - - certificateStore.viewModel$ - .pipe(skip(2), take(1)) - .subscribe(store => { - expect(store.certificates).toEqual([certificate]); - done(); - }); - - certificateStore.uploadCertificate(FILE); - }); - - it('should notify', () => { - const container = document.createElement('DIV'); - container.classList.add('certificates-drawer-content'); - document.querySelector('body')?.appendChild(container); - certificateStore.uploadCertificate(FILE); - - expect(notificationServiceMock.notify).toHaveBeenCalledWith( - 'Certificate successfully added.\niot.bms.google.com by Google, Inc. valid until 01 Sep 2024', - 0, - 'certificate-notification', - 10000, - container - ); - }); - - it('should send GA event "successful_saving_certificate"', () => { - const container = document.createElement('DIV'); - container.classList.add('certificates-drawer-content'); - document.querySelector('body')?.appendChild(container); - certificateStore.uploadCertificate(FILE); - - expect( - // @ts-expect-error data layer should be defined - window.dataLayer.some( - (item: { event: string }) => - item.event === 'successful_saving_certificate' - ) - ).toBeTruthy(); - }); - }); - - describe('with invalid certificate file', () => { - it('should notify about errors', () => { - const container = document.createElement('DIV'); - container.classList.add('certificates-drawer-content'); - document.querySelector('body')?.appendChild(container); - certificateStore.uploadCertificate(INVALID_FILE); - - expect(notificationServiceMock.notify).toHaveBeenCalledWith( - 'File "some very long strange n..." is not added.\nThe file name should be alphanumeric, symbols -_. are allowed.\nFile extension must be .cert, .crt, .pem, .cer.\nMax name length is 24 characters.\nFile size should be a max of 4KB', - 0, - 'certificate-notification', - 24000, - container - ); - }); - }); - - it('should not upload certificates if error happens', done => { - mockService.uploadCertificate.and.returnValue(of(false)); - mockService.fetchCertificates.and.returnValue(of([])); - - const uploadingCertificate = certificate_uploading; - - certificateStore.viewModel$.pipe(skip(1), take(1)).subscribe(store => { - expect(store.certificates).toContain(uploadingCertificate); - }); - - certificateStore.viewModel$.pipe(skip(2), take(1)).subscribe(store => { - expect(store.certificates).not.toContain(certificate); - done(); - }); - - certificateStore.uploadCertificate(FILE); - }); - }); + certificatesStore.deleteCertificate('Cert1'); + expect(certificatesStore.certificates()).toEqual(mockCertificates); + }); - describe('deleteCertificate', () => { - it('should update store', done => { - mockService.deleteCertificate.and.returnValue(of(true)); + it('should upload a certificate and update the store', () => { + const mockFile = new File(['content'], 'Cert1.crt'); + const uploadedCertificates: Certificate[] = [ + { name: 'Cert1', uploading: false }, + ]; + testRunServiceMock.uploadCertificate.and.returnValue(of(true)); + testRunServiceMock.fetchCertificates.and.returnValue( + of(uploadedCertificates) + ); - certificateStore.updateCertificates([certificate, certificate2]); + certificatesStore.uploadCertificate(mockFile); - certificateStore.viewModel$.pipe(skip(1), take(1)).subscribe(store => { - expect(store.certificates).toEqual([certificate2]); - done(); - }); + expect(testRunServiceMock.uploadCertificate).toHaveBeenCalledWith(mockFile); + expect(certificatesStore.certificates()).toEqual(uploadedCertificates); + }); - certificateStore.deleteCertificate(certificate.name); - }); + it('should notify and revert on upload error', () => { + const mockFile = new File(['content'], 'Cert1.pdf', { + type: 'application/pdf', }); + testRunServiceMock.uploadCertificate.and.returnValue(throwError('Error')); + + certificatesStore.uploadCertificate(mockFile); + + expect(notificationServiceMock.notify).toHaveBeenCalled(); + expect(certificatesStore.certificates()).toEqual(mockCertificates); }); }); diff --git a/modules/ui/src/app/pages/certificates/certificates.store.ts b/modules/ui/src/app/pages/certificates/certificates.store.ts index 610daeffb..9d14a014b 100644 --- a/modules/ui/src/app/pages/certificates/certificates.store.ts +++ b/modules/ui/src/app/pages/certificates/certificates.store.ts @@ -14,160 +14,152 @@ * limitations under the License. */ -import { Injectable } from '@angular/core'; -import { ComponentStore } from '@ngrx/component-store'; -import { switchMap, tap, withLatestFrom } from 'rxjs/operators'; -import { catchError, EMPTY, exhaustMap, of, throwError } from 'rxjs'; +import { computed, inject } from '@angular/core'; +import { signalStore } from '@ngrx/signals'; +import { switchMap, tap } from 'rxjs/operators'; +import { catchError, EMPTY, exhaustMap, throwError } from 'rxjs'; import { Certificate } from '../../model/certificate'; import { TestRunService } from '../../services/test-run.service'; import { NotificationService } from '../../services/notification.service'; import { DatePipe } from '@angular/common'; import { FILE_NAME_LENGTH, getValidationErrors } from './certificate.validator'; - -export interface AppComponentState { - certificates: Certificate[]; - selectedCertificate: string; -} +import { + withState, + withHooks, + withMethods, + patchState, + withComputed, +} from '@ngrx/signals'; +import { tapResponse } from '@ngrx/operators'; +import { rxMethod } from '@ngrx/signals/rxjs-interop'; +import { MatTableDataSource } from '@angular/material/table'; const SYMBOLS_PER_SECOND = 9.5; -@Injectable() -export class CertificatesStore extends ComponentStore { - private certificates$ = this.select(state => state.certificates); - private selectedCertificate$ = this.select( - state => state.selectedCertificate - ); - - viewModel$ = this.select({ - certificates: this.certificates$, - selectedCertificate: this.selectedCertificate$, - }); - - updateCertificates = this.updater((state, certificates: Certificate[]) => ({ - ...state, - certificates, - })); - - selectCertificate = this.updater((state, selectedCertificate: string) => ({ - ...state, - selectedCertificate, - })); - getCertificates = this.effect(trigger$ => { - return trigger$.pipe( - exhaustMap(() => { - return this.testRunService.fetchCertificates().pipe( - tap((certificates: Certificate[]) => { - this.updateCertificates(certificates); - }) +export const CertificatesStore = signalStore( + withState({ + certificates: [] as Certificate[], + selectedCertificate: '', + displayedColumns: ['name', 'organisation', 'expires', 'status', 'actions'], + dataLoaded: false, + }), + withComputed(({ certificates }) => ({ + dataSource: computed(() => new MatTableDataSource(certificates())), + })), + withMethods( + ( + store, + testRunService = inject(TestRunService), + notificationService = inject(NotificationService), + datePipe = inject(DatePipe) + ) => { + function removeCertificate(name: string) { + patchState(store, { + certificates: store + .certificates() + .filter(certificate => certificate.name !== name), + }); + } + function addCertificate(name: string, certificates: Certificate[]) { + const certificate = { name, uploading: true } as Certificate; + patchState(store, { + certificates: [certificate, ...certificates], + }); + } + function notify(message: string) { + notificationService.notify( + message, + 0, + 'certificate-notification', + Math.ceil(message.length / SYMBOLS_PER_SECOND) * 1000, + window.document.querySelector('.certificates-drawer-content') ); - }) - ); - }); - - uploadCertificate = this.effect(trigger$ => { - return trigger$.pipe( - withLatestFrom(this.certificates$), - switchMap(res => { - const [file] = res; - const errors = getValidationErrors(file); - if (errors.length > 0) { - errors.unshift( - `File "${this.getShortCertificateName(file.name)}" is not added.` - ); - this.notify(errors.join('\n')); - return EMPTY; - } - return of(res); - }), - tap(res => { - const [file, certificates] = res; - this.addCertificate(file.name, certificates); - }), - exhaustMap(([file, certificates]) => { - return this.testRunService.uploadCertificate(file).pipe( - exhaustMap(uploaded => { - if (uploaded) { - return this.testRunService.fetchCertificates(); + } + function getShortCertificateName(name: string) { + return name.length <= FILE_NAME_LENGTH + ? name + : `${name.substring(0, FILE_NAME_LENGTH)}...`; + } + return { + getShortCertificateName, + selectCertificate: (certificate: string) => { + patchState(store, { + selectedCertificate: certificate, + }); + }, + getCertificates: rxMethod( + switchMap(() => + testRunService.fetchCertificates().pipe( + tapResponse({ + next: certificates => + patchState(store, { certificates, dataLoaded: true }), + error: () => patchState(store, { certificates: [] }), + }) + ) + ) + ), + deleteCertificate: rxMethod( + switchMap((certificate: string) => + testRunService.deleteCertificate(certificate).pipe( + tapResponse({ + next: remove => { + if (remove) { + removeCertificate(certificate); + } + }, + error: () => + patchState(store, { certificates: store.certificates() }), + }) + ) + ) + ), + uploadCertificate: rxMethod( + switchMap((file: File) => { + const errors = getValidationErrors(file); + if (errors.length > 0) { + errors.unshift( + `File "${getShortCertificateName(file.name)}" is not added.` + ); + notify(errors.join('\n')); + return EMPTY; } - return throwError('Failed to upload certificate'); - }), - tap(newCertificates => { - const uploadedCertificate = newCertificates.filter( - certificate => - !certificates.some(cert => cert.name === certificate.name) - )[0]; - this.updateCertificates(newCertificates); - // @ts-expect-error data layer is not null - window.dataLayer.push({ - event: 'successful_saving_certificate', - }); - this.notify( - `Certificate successfully added.\n${uploadedCertificate.name} by ${uploadedCertificate.organisation} valid until ${this.datePipe.transform(uploadedCertificate.expires, 'dd MMM yyyy')}` + addCertificate(file.name, store.certificates()); + return testRunService.uploadCertificate(file).pipe( + exhaustMap(uploaded => { + if (uploaded) { + return testRunService.fetchCertificates(); + } + return throwError('Failed to upload certificate'); + }), + tap(newCertificates => { + const uploadedCertificate = newCertificates.filter( + certificate => + !store + .certificates() + .some(cert => cert.name === certificate.name) + )[0]; + patchState(store, { certificates: newCertificates }); + // @ts-expect-error data layer is not null + window.dataLayer.push({ + event: 'successful_saving_certificate', + }); + notify( + `Certificate successfully added.\n${uploadedCertificate.name} by ${uploadedCertificate.organisation} valid until ${datePipe.transform(uploadedCertificate.expires, 'dd MMM yyyy')}` + ); + }), + catchError(() => { + removeCertificate(file.name); + return EMPTY; + }) ); - }), - catchError(() => { - this.removeCertificate(file.name, certificates); - return EMPTY; - }) - ); - }) - ); - }); - - addCertificate(name: string, certificates: Certificate[]) { - const certificate = { name, uploading: true } as Certificate; - this.updateCertificates([certificate, ...certificates]); - } - - deleteCertificate = this.effect(trigger$ => { - return trigger$.pipe( - withLatestFrom(this.certificates$), - exhaustMap(([certificate, current]) => { - return this.testRunService.deleteCertificate(certificate).pipe( - tap(remove => { - if (remove) { - this.removeCertificate(certificate, current); - } - }), - catchError(() => { - return EMPTY; }) - ); - }) - ); - }); - - getShortCertificateName(name: string) { - return name.length <= FILE_NAME_LENGTH - ? name - : `${name.substring(0, FILE_NAME_LENGTH)}...`; - } - - private notify(message: string) { - this.notificationService.notify( - message, - 0, - 'certificate-notification', - Math.ceil(message.length / SYMBOLS_PER_SECOND) * 1000, - window.document.querySelector('.certificates-drawer-content') - ); - } - - private removeCertificate(name: string, current: Certificate[]) { - const certificates = current.filter( - certificate => certificate.name !== name - ); - this.updateCertificates(certificates); - } - - constructor( - private testRunService: TestRunService, - private notificationService: NotificationService, - private datePipe: DatePipe - ) { - super({ - certificates: [], - selectedCertificate: '', - }); - } -} + ), + }; + } + ), + withHooks({ + onInit({ getCertificates }) { + getCertificates(); + }, + }) +); diff --git a/modules/ui/src/app/pages/certificates/certificate-upload-button/certificate-upload-button.component.html b/modules/ui/src/app/pages/certificates/components/certificate-upload-button/certificate-upload-button.component.html similarity index 86% rename from modules/ui/src/app/pages/certificates/certificate-upload-button/certificate-upload-button.component.html rename to modules/ui/src/app/pages/certificates/components/certificate-upload-button/certificate-upload-button.component.html index 0c1724e55..5b9cadeb0 100644 --- a/modules/ui/src/app/pages/certificates/certificate-upload-button/certificate-upload-button.component.html +++ b/modules/ui/src/app/pages/certificates/components/certificate-upload-button/certificate-upload-button.component.html @@ -1,7 +1,6 @@ diff --git a/modules/ui/src/app/pages/certificates/components/certificate-upload-button/certificate-upload-button.component.scss b/modules/ui/src/app/pages/certificates/components/certificate-upload-button/certificate-upload-button.component.scss new file mode 100644 index 000000000..f89d7fd6b --- /dev/null +++ b/modules/ui/src/app/pages/certificates/components/certificate-upload-button/certificate-upload-button.component.scss @@ -0,0 +1,19 @@ +@use 'colors'; +@use 'variables'; + +.browse-files-button { + border-radius: 16px; + padding: 16px 24px; + font-family: variables.$font-text; + font-size: 16px; + font-weight: 500; + line-height: 24px; + letter-spacing: 0.25px; + height: auto; + background: colors.$secondary-container; + color: colors.$on-secondary-container; +} + +#default-file-input { + display: none; +} diff --git a/modules/ui/src/app/pages/certificates/certificate-upload-button/certificate-upload-button.component.spec.ts b/modules/ui/src/app/pages/certificates/components/certificate-upload-button/certificate-upload-button.component.spec.ts similarity index 100% rename from modules/ui/src/app/pages/certificates/certificate-upload-button/certificate-upload-button.component.spec.ts rename to modules/ui/src/app/pages/certificates/components/certificate-upload-button/certificate-upload-button.component.spec.ts diff --git a/modules/ui/src/app/pages/certificates/certificate-upload-button/certificate-upload-button.component.ts b/modules/ui/src/app/pages/certificates/components/certificate-upload-button/certificate-upload-button.component.ts similarity index 97% rename from modules/ui/src/app/pages/certificates/certificate-upload-button/certificate-upload-button.component.ts rename to modules/ui/src/app/pages/certificates/components/certificate-upload-button/certificate-upload-button.component.ts index 88cf522b4..dc481bad9 100644 --- a/modules/ui/src/app/pages/certificates/certificate-upload-button/certificate-upload-button.component.ts +++ b/modules/ui/src/app/pages/certificates/components/certificate-upload-button/certificate-upload-button.component.ts @@ -3,7 +3,7 @@ import { MatButtonModule } from '@angular/material/button'; @Component({ selector: 'app-certificate-upload-button', - standalone: true, + imports: [MatButtonModule], templateUrl: './certificate-upload-button.component.html', styleUrl: './certificate-upload-button.component.scss', diff --git a/modules/ui/src/app/pages/certificates/components/certificates-table/certificates-table.component.html b/modules/ui/src/app/pages/certificates/components/certificates-table/certificates-table.component.html new file mode 100644 index 000000000..c2bb9bb5a --- /dev/null +++ b/modules/ui/src/app/pages/certificates/components/certificates-table/certificates-table.component.html @@ -0,0 +1,127 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Certificate Name + {{ data.name }} + + Organisation + + {{ data.organisation }} + + Expires + + {{ data.expires | date: 'dd MMM yyyy' }} + + Status + + + {{ data.status }} + + + +
+
+ + CA certificates must be uploaded to complete TLS testing + +
+
+ +
+
+
+ + + + + diff --git a/modules/ui/src/app/pages/certificates/components/certificates-table/certificates-table.component.scss b/modules/ui/src/app/pages/certificates/components/certificates-table/certificates-table.component.scss new file mode 100644 index 000000000..92dd71355 --- /dev/null +++ b/modules/ui/src/app/pages/certificates/components/certificates-table/certificates-table.component.scss @@ -0,0 +1,127 @@ +/** + * Copyright 2023 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +@use '@angular/material' as mat; +@use 'variables'; +@use 'colors'; + +:host { + @include mat.table-overrides( + ( + background-color: colors.$surface, + header-headline-color: colors.$on-surface-variant, + row-item-label-text-color: colors.$on-surface-variant, + header-headline-font: variables.$font-text, + row-item-label-text-font: variables.$font-text, + footer-supporting-text-font: variables.$font-text, + row-item-outline-color: colors.$outline-variant, + ) + ); + + ::ng-deep .mdc-data-table__row:last-child .mat-mdc-cell { + border-bottom: 1px solid colors.$outline-variant; + } +} + +::ng-deep .delete-certificate app-simple-dialog { + width: 329px; +} + +.table-cell-actions { + text-align: right; +} + +.cell-result { + font-family: #{variables.$font-text}; + font-weight: 500; + margin: 0; + padding: 6px 12px; + border-radius: 8px; + font-size: 12px; + line-height: 16px; + letter-spacing: 0.3px; + white-space: nowrap; + &.valid { + background: colors.$tertiary-container; + color: colors.$on-tertiary-container; + } + &.expired { + background: colors.$error-container; + color: colors.$on-error-container; + } +} + +.uploading { + background: rgba(196, 199, 197, 0.16); + font-style: italic; +} + +.results-content-empty-message { + display: flex; + flex-direction: column; + align-items: center; + gap: 16px; +} + +.results-content-empty-message-header { + font-weight: 400; + line-height: 28px; + font-size: 22px; + color: colors.$on-surface; +} + +.results-content-empty-message-main { + font-family: variables.$font-secondary; + font-weight: 400; + font-size: 16px; + line-height: 24px; + letter-spacing: 0.1px; + color: colors.$on-surface-variant; +} + +.results-content-empty-message-img { + width: 293px; + height: 154px; + background-image: url(/assets/icons/desktop-new.svg); +} + +.certificates-content-empty { + min-height: 500px; +} + +.empty-data-cell { + position: relative; +} + +.callout-container { + display: flex; + position: absolute; + top: 0; + width: 100%; + + ::ng-deep .callout-container { + margin: 6px 0; + padding: 14px 16px; + } + + ::ng-deep .callout-context { + font-family: variables.$font-text; + letter-spacing: 0; + } +} + +.results-content-filter-empty { + margin-top: 60px; +} diff --git a/modules/ui/src/app/pages/certificates/certificate-item/certificate-item.component.spec.ts b/modules/ui/src/app/pages/certificates/components/certificates-table/certificates-table.component.spec.ts similarity index 65% rename from modules/ui/src/app/pages/certificates/certificate-item/certificate-item.component.spec.ts rename to modules/ui/src/app/pages/certificates/components/certificates-table/certificates-table.component.spec.ts index 5840aa2bb..bcd275ed9 100644 --- a/modules/ui/src/app/pages/certificates/certificate-item/certificate-item.component.spec.ts +++ b/modules/ui/src/app/pages/certificates/components/certificates-table/certificates-table.component.spec.ts @@ -1,25 +1,38 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; -import { CertificateItemComponent } from './certificate-item.component'; +import { CertificatesTableComponent } from './certificates-table.component'; +import { MatTableDataSource } from '@angular/material/table'; import { certificate, certificate_uploading, -} from '../../../mocks/certificate.mock'; +} from '../../../../mocks/certificate.mock'; -describe('CertificateItemComponent', () => { - let component: CertificateItemComponent; - let fixture: ComponentFixture; +describe('CertificatesTableComponent', () => { let compiled: HTMLElement; + let component: CertificatesTableComponent; + let fixture: ComponentFixture; beforeEach(async () => { await TestBed.configureTestingModule({ - imports: [CertificateItemComponent], + imports: [CertificatesTableComponent], }).compileComponents(); - fixture = TestBed.createComponent(CertificateItemComponent); - compiled = fixture.nativeElement as HTMLElement; + fixture = TestBed.createComponent(CertificatesTableComponent); component = fixture.componentInstance; - component.certificate = certificate; + fixture.componentRef.setInput( + 'dataSource', + new MatTableDataSource([certificate]) + ); + fixture.componentRef.setInput('selectedCertificate', ''); + fixture.componentRef.setInput('dataLoaded', true); + fixture.componentRef.setInput('displayedColumns', [ + 'name', + 'organisation', + 'expires', + 'status', + 'actions', + ]); + compiled = fixture.nativeElement as HTMLElement; fixture.detectChanges(); }); @@ -29,7 +42,7 @@ describe('CertificateItemComponent', () => { describe('DOM tests', () => { it('should have certificate name', () => { - const name = compiled.querySelector('.certificate-item-name'); + const name = compiled.querySelector('.cdk-row .mat-column-name'); expect(name?.textContent?.trim()).toEqual('iot.bms.google.com'); }); @@ -37,14 +50,14 @@ describe('CertificateItemComponent', () => { describe('uploaded certificate', () => { it('should have certificate organization', () => { const organization = compiled.querySelector( - '.certificate-item-organisation' + '.cdk-row .mat-column-organisation' ); expect(organization?.textContent?.trim()).toEqual('Google, Inc.'); }); it('should have certificate expire date', () => { - const date = compiled.querySelector('.certificate-item-expires'); + const date = compiled.querySelector('.cdk-row .mat-column-expires'); expect(date?.textContent?.trim()).toEqual('01 Sep 2024'); }); @@ -76,16 +89,13 @@ describe('CertificateItemComponent', () => { describe('uploading certificate', () => { beforeEach(() => { - component.certificate = certificate_uploading; + fixture.componentRef.setInput( + 'dataSource', + new MatTableDataSource([certificate_uploading]) + ); fixture.detectChanges(); }); - it('should have loader', () => { - const loader = compiled.querySelector('mat-progress-bar'); - - expect(loader).not.toBeNull(); - }); - it('should have disabled delete button', () => { const deleteButton = fixture.nativeElement.querySelector( '.certificate-item-delete' diff --git a/modules/ui/src/app/pages/certificates/components/certificates-table/certificates-table.component.ts b/modules/ui/src/app/pages/certificates/components/certificates-table/certificates-table.component.ts new file mode 100644 index 000000000..a03a49135 --- /dev/null +++ b/modules/ui/src/app/pages/certificates/components/certificates-table/certificates-table.component.ts @@ -0,0 +1,34 @@ +import { Component, input, output } from '@angular/core'; +import { Certificate } from '../../../../model/certificate'; +import { MatTableDataSource, MatTableModule } from '@angular/material/table'; +import { MatIconModule } from '@angular/material/icon'; +import { MatButtonModule } from '@angular/material/button'; +import { CommonModule } from '@angular/common'; +import { CalloutType } from '../../../../model/callout-type'; +import { CalloutComponent } from '../../../../components/callout/callout.component'; +import { EmptyMessageComponent } from '../../../../components/empty-message/empty-message.component'; + +@Component({ + selector: 'app-certificates-table', + imports: [ + MatIconModule, + MatTableModule, + MatButtonModule, + CommonModule, + CalloutComponent, + EmptyMessageComponent, + ], + templateUrl: './certificates-table.component.html', + styleUrl: './certificates-table.component.scss', +}) +export class CertificatesTableComponent { + dataSource = input.required>(); + readonly CalloutType = CalloutType; + selectedCertificate = input(); + dataLoaded = input(false); + displayedColumns = input([]); + deleteButtonClicked = output(); + trackByName(index: number, item: Certificate) { + return item.name; + } +} diff --git a/modules/ui/src/app/pages/devices/components/device-form/device.validators.ts b/modules/ui/src/app/pages/devices/components/device-form/device.validators.ts index 3ffa45802..8ef530bad 100644 --- a/modules/ui/src/app/pages/devices/components/device-form/device.validators.ts +++ b/modules/ui/src/app/pages/devices/components/device-form/device.validators.ts @@ -64,7 +64,10 @@ export class DeviceValidators { }; } - public differentMACAddress(devices: Device[], device?: Device): ValidatorFn { + public differentMACAddress( + devices: Device[], + device: Device | null + ): ValidatorFn { return (control: AbstractControl): ValidationErrors | null => { const value = control.value?.trim(); if (value && (!device || device?.mac_addr !== value)) { diff --git a/modules/ui/src/app/pages/devices/components/device-qualification-from/device-qualification-from.component.html b/modules/ui/src/app/pages/devices/components/device-qualification-from/device-qualification-from.component.html index 4bf0e13a5..f2eb9edc8 100644 --- a/modules/ui/src/app/pages/devices/components/device-qualification-from/device-qualification-from.component.html +++ b/modules/ui/src/app/pages/devices/components/device-qualification-from/device-qualification-from.component.html @@ -14,334 +14,170 @@ limitations under the License. -->
- -
- -

{{ data.title }}

-
-
- - - -

- {{ data.title }} dialogue step 1 -

-
- - Device Manufacturer - - Please enter device manufacturer name - - Please, check. The manufacturer name must be a maximum of 28 - characters. Only letters, numbers, and accented letters are - permitted. - - - Device Manufacturer is required - - - - Device Model - - Please enter device name - - Please, check. The device model name must be a maximum of 28 - characters. Only letters, numbers, and accented letters are - permitted. - - - Device Model is required - - - - MAC address - - Please enter MAC address - - MAC address is required - - - Please, check. A MAC address consists of 12 hexadecimal digits (0 - to 9, a to f, or A to F). - - - This MAC address is already used for another device in the - repository. - - - - Please, select the testing journey for device + + + + + Please, check. The manufacturer name must be a maximum of 28 + characters. Only letters, numbers, and accented letters are + permitted. - - - - - - Device Qualification - - - - - Pilot Assessment - - - - - - - At least one test has to be selected to save a Device. - -
-
- - - -

- {{ data.title }} dialogue step {{ step.step + 1 }} -

-
-

- {{ step.title }} -

-

- {{ step.description }} -

- -
-
-
+ + + Device Manufacturer is required + + + + + + + Please, check. The device model name must be a maximum of 28 + characters. Only letters, numbers, and accented letters are + permitted. + + + Device Model is required + + + + + + + MAC address is required + + + Please, check. A MAC address consists of 12 hexadecimal digits (0 to + 9, a to f, or A to F). + + + This MAC address is already used for another device in the + repository. + + - -

- {{ data.title }} dialogue last step -

-

- {{ data.title }} dialogue step 4 -

-
-
-

Summary

-

- - The device has been configured. Please check the setup. - - - No changes were made to the device configuration. - - The device cannot be configured -

-
-
- -
-

- Device type - {{ device?.type }} -

-

- Technology - {{ device?.technology }} -

-
-
-
- Select Save to create your new device. You will then be able to - carry on your device testing journey: -
    -
  • - Run Testrun against your device until you achieve a compliant - result -
  • -
  • Export the Testrun report and output files
  • -
  • Send the testing results to the lab for validation
  • -
-
+ Please, select the testing journey for device + -
-
-

- - error - - Unable to create the device -

-
-
-

- Validation error! -

-

- Please go back and correct the errors on - - , - - - and - - Step {{ step + 1 }}. -

+ + + + Device Qualification + + + + + Pilot Assessment + + + + + + + At least one test has to be selected to save a Device. + -

- All existing fields must be filled in. -

-
-
-
-
-
- - - -
-
-
-
+ +
+
+
+ + +
+ +
diff --git a/modules/ui/src/app/pages/devices/components/device-qualification-from/device-qualification-from.component.scss b/modules/ui/src/app/pages/devices/components/device-qualification-from/device-qualification-from.component.scss index 902986f90..86852ea68 100644 --- a/modules/ui/src/app/pages/devices/components/device-qualification-from/device-qualification-from.component.scss +++ b/modules/ui/src/app/pages/devices/components/device-qualification-from/device-qualification-from.component.scss @@ -14,99 +14,62 @@ * limitations under the License. */ @use '@angular/material' as mat; -@import 'src/theming/colors'; -@import 'src/theming/variables'; +@use 'm3-theme' as *; +@use 'colors'; +@use 'variables'; +@use 'mixins'; -$form-min-width: 732px; +$form-max-width: var(--mat-dialog-container-max-width); +$form-min-width: 285px; :host { container-type: size; container-name: qualification-form; display: grid; - grid-template-rows: 1fr; - overflow: auto; - grid-template-columns: minmax(285px, $form-max-width); - height: 100vh; - max-height: 978px; + height: 100%; + background: colors.$surface; + border-radius: 8px; + box-shadow: + 0px 4px 8px 3px rgba(60, 64, 67, 0.15), + 0px 1px 3px 0px rgba(60, 64, 67, 0.3); + box-sizing: border-box; } .device-qualification-form { - overflow: hidden; + overflow-y: scroll; } ::ng-deep .device-form-test-modules { overflow: auto; min-height: 78px; display: grid; - grid-template-columns: repeat(2, 1fr); - grid-template-rows: repeat(4, 1fr); + grid-template-columns: repeat(3, 1fr); + grid-template-rows: repeat(2, 1fr); grid-auto-flow: column; padding-top: 16px; + padding-left: 10px; p { margin: 8px 0; } } -.close-button { - color: $primary; -} - .hidden { display: none; } -.device-qualification-form-header { - position: relative; - padding-top: 24px; - &-title { - margin: 0; - font-size: 32px; - font-style: normal; - font-weight: 400; - line-height: 40px; - color: $grey-800; - text-align: center; - padding: 38px 0; - background-image: url(/assets/icons/create_device_header.svg); - } - - &-close-button { - position: absolute; - right: 0; - top: 0; - min-width: 24px; - width: 24px; - height: 24px; - box-sizing: content-box; - line-height: normal !important; - padding: 0; - margin: 0; - - .close-button-icon { - width: 24px; - height: 24px; - margin: 0; - } - - ::ng-deep * { - line-height: inherit !important; - } - } -} - .device-qualification-form-journey-label { - font-family: $font-secondary; + font-family: variables.$font-text; font-style: normal; - font-weight: 400; + font-weight: 500; font-size: 16px; line-height: 24px; letter-spacing: 0.1px; - color: $grey-800; - margin: 24px 16px 0 16px; + color: colors.$on-surface-variant; + padding: 20px 20px 8px 16px; } .device-qualification-form-journey-button { - padding: 0 18px 0 24px; + padding: 0 18px; } .device-qualification-form-journey-button-info { @@ -114,190 +77,48 @@ $form-min-width: 732px; } .device-qualification-form-journey-button-label { - font-family: $font-secondary; + font-family: variables.$font-text; font-style: normal; - font-weight: 500; - font-size: 14px; - line-height: 20px; + font-weight: 400; + font-size: 16px; + line-height: 24px; letter-spacing: 0.2px; - color: $grey-800; -} - -.device-qualification-form-test-modules-container { - padding: 0 24px; -} - -.device-qualification-form-step-title { - margin: 0; - font-style: normal; - font-weight: 500; - font-size: 22px; - line-height: 28px; - text-align: center; - color: $grey-900; - padding: 0 24px; - display: inline-block; - height: 28px; -} - -.device-qualification-form-step-description { - font-family: $font-secondary; - text-align: center; - color: $grey-800; - margin: 0; - padding: 8px 16px 0 16px; -} - -.step-link { - color: $primary; - text-decoration: underline; - cursor: pointer; -} - -.device-qualification-form-step-content { - padding: 0 16px; - overflow: scroll; } .device-qualification-form-page { - padding-top: 10px; - margin-top: -10px; display: grid; - gap: 8px; - height: 100%; - overflow: hidden; align-content: start; - &:has(.device-qualification-form-summary-container) { - grid-template-rows: min-content min-content 1fr min-content; - } + padding: 24px 60px 0 60px; } -.device-qualification-form-summary-container { +.device-qualification-form-journey { display: grid; - align-items: center; - justify-content: center; - overflow: scroll; - ::ng-deep { - .device-item, - .device-qualification-form-summary-info { - width: $device-item-width; - } - } -} - -.device-qualification-form-summary { - border-radius: 12px; - background: mat.m2-get-color-from-palette($color-primary, 50); - padding: 24px; - width: max-content; - margin-left: auto; - margin-right: auto; - margin-top: 24px; - &-error { - background: $red-50; - } + grid-template-columns: repeat(2, 1fr); } .device-qualification-form-actions { - width: $device-item-width; - text-align: center; - padding: 8px 24px; - justify-self: center; - align-self: end; - &:has(.delete-button) { - text-align: right; - } - .close-button, - .delete-button { - border: 1px solid $lighter-grey; - } - .delete-button { - color: $primary; - float: left; + @include mixins.form-actions; + + div { + display: flex; + gap: 12px; } + .close-button { padding: 0 16px; } - .save-button { - margin-left: 16px; - } -} - -.device-qualification-form-summary-info { - margin-top: 16px; - border-radius: 12px; - padding: 16px 24px; - background: #fff; - box-sizing: border-box; - &-title, - &-title-error { - font-size: 18px; - font-weight: 400; - line-height: 24px; - text-align: center; - color: $grey-900; - } - &-description { - font-family: $font-secondary; - font-size: 16px; - font-weight: 400; - line-height: 24px; - text-align: center; - color: $grey-800; - } - .info-label { - display: block; - font-family: $font-secondary; - font-size: 14px; - font-weight: 400; - line-height: 20px; - text-align: left; - color: $secondary; - } - .info-value { - display: block; - color: $grey-800; - font-family: $font-secondary; - font-size: 16px; - font-weight: 400; - line-height: 24px; - text-align: left; - } -} -.device-qualification-form-summary-info-title-error { - display: flex; - align-items: center; - height: 48px; - margin: auto; - justify-content: center; - color: $red-800; - gap: 14px; - ::ng-deep mat-icon { - color: $red-800; + .delete-button:not(.mat-mdc-button-disabled) { + @include mixins.delete-red-button; } -} -.device-qualification-form-instructions { - margin-top: auto; - padding-top: 8px; - color: $grey-800; - text-align: center; - font-family: $font-secondary; - font-size: 16px; - width: 510px; - ul { - margin-bottom: 0; - text-align: left; - padding-left: 26px; - } - li { - line-height: 24px; + .close-button:not(.mat-mdc-button-disabled) { + @include mixins.secondary-button; } } ::ng-deep mat-error { - background: $white; + background: colors.$white; } :host mat-form-field { @@ -307,23 +128,30 @@ $form-min-width: 732px; } } +::ng-deep .device-tests-description { + padding: 0 20px; +} + +::ng-deep .device-tests-title { + font-family: variables.$font-text; + font-style: normal; + font-weight: 500; + font-size: 16px !important; + line-height: 24px !important; + letter-spacing: 0.1px; + padding: 20px 20px 8px 16px; +} + .device-qualification-form-test-modules-container-error ::ng-deep .device-tests-title { - color: $red-800; + color: colors.$red-800; } .device-qualification-form-test-modules-error { padding: 0 24px; } -.form-content-summary { - display: grid; - grid-template-rows: 1fr auto; - grid-row-gap: 16px; - overflow: hidden; -} - @container qualification-form (height < 870px) { .device-qualification-form-page { overflow: scroll; @@ -336,8 +164,7 @@ $form-min-width: 732px; @container qualification-form (height < 580px) { .device-qualification-form-page { overflow: scroll; - .device-qualification-form-step-content, - .device-qualification-form-summary-container { + .device-qualification-form-step-content { overflow: visible; } } diff --git a/modules/ui/src/app/pages/devices/components/device-qualification-from/device-qualification-from.component.spec.ts b/modules/ui/src/app/pages/devices/components/device-qualification-from/device-qualification-from.component.spec.ts index bcc34b35d..c366aec23 100644 --- a/modules/ui/src/app/pages/devices/components/device-qualification-from/device-qualification-from.component.spec.ts +++ b/modules/ui/src/app/pages/devices/components/device-qualification-from/device-qualification-from.component.spec.ts @@ -17,7 +17,6 @@ import { ComponentFixture, discardPeriodicTasks, fakeAsync, - flush, TestBed, tick, } from '@angular/core/testing'; @@ -31,7 +30,7 @@ import { import { of } from 'rxjs'; import { NgxMaskDirective, NgxMaskPipe, provideNgxMask } from 'ngx-mask'; import { MatButtonModule } from '@angular/material/button'; -import { FormArray, ReactiveFormsModule } from '@angular/forms'; +import { ReactiveFormsModule } from '@angular/forms'; import { MatCheckboxModule } from '@angular/material/checkbox'; import { MatInputModule } from '@angular/material/input'; import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; @@ -46,12 +45,12 @@ import { MatIconTestingModule } from '@angular/material/icon/testing'; import { TestRunService } from '../../../../services/test-run.service'; import { DevicesStore } from '../../devices.store'; import { provideMockStore } from '@ngrx/store/testing'; -import { FormAction } from '../../devices.component'; import { DeviceStatus, TestingType } from '../../../../model/device'; import { Component, Input } from '@angular/core'; import { QuestionFormat } from '../../../../model/question'; import { BehaviorSubject } from 'rxjs/internal/BehaviorSubject'; import { selectDevices } from '../../../../store/selectors'; +import { SimpleDialogComponent } from '../../../../components/simple-dialog/simple-dialog.component'; describe('DeviceQualificationFromComponent', () => { let component: DeviceQualificationFromComponent; @@ -68,9 +67,9 @@ describe('DeviceQualificationFromComponent', () => { const MOCK_DEVICE = { status: DeviceStatus.VALID, - manufacturer: '', - model: '', - mac_addr: '', + manufacturer: 'manufacturer', + model: 'model', + mac_addr: '01:01:01:01:01:01', test_pack: TestingType.Qualification, type: '', technology: '', @@ -134,12 +133,10 @@ describe('DeviceQualificationFromComponent', () => { component = fixture.componentInstance; compiled = fixture.nativeElement as HTMLElement; - component.data = { - testModules: MOCK_TEST_MODULES, - devices: [], - index: 0, - isCreate: true, - }; + fixture.componentRef.setInput('testModules', MOCK_TEST_MODULES); + fixture.componentRef.setInput('devices', []); + fixture.componentRef.setInput('isCreate', true); + testrunServiceMock.fetchQuestionnaireFormat.and.returnValue( of(DEVICES_FORM) ); @@ -160,180 +157,15 @@ describe('DeviceQualificationFromComponent', () => { }); it('should fetch devices format', () => { - fixture.detectChanges(); const getQuestionnaireFormatSpy = spyOn( component.devicesStore, 'getQuestionnaireFormat' ); - component.ngOnInit(); fixture.detectChanges(); expect(getQuestionnaireFormatSpy).toHaveBeenCalled(); }); - it('should close dialog on "cancel" click with do data if form has no changes', () => { - fixture.detectChanges(); - const closeSpy = spyOn(component.dialogRef, 'close'); - const closeButton = compiled.querySelector( - '.device-qualification-form-header-close-button' - ) as HTMLButtonElement; - - closeButton?.click(); - - expect(closeSpy).toHaveBeenCalledWith(); - - closeSpy.calls.reset(); - }); - - it('should close dialog on escape', fakeAsync(() => { - const closeSpy = spyOn(component.dialogRef, 'close'); - fixture.detectChanges(); - - keyboardEvent.next(new KeyboardEvent('keydown', { code: 'Escape' })); - - tick(); - - expect(closeSpy).toHaveBeenCalledWith(); - - closeSpy.calls.reset(); - })); - - it('should close dialog on submit with "Save" action', fakeAsync(() => { - component.device = MOCK_DEVICE; - const closeSpy = spyOn(component.dialogRef, 'close'); - fixture.detectChanges(); - - component.submit(); - tick(); - flush(); - - expect(closeSpy).toHaveBeenCalledWith({ - action: 'Save', - device: MOCK_DEVICE, - }); - - closeSpy.calls.reset(); - })); - - it('should close dialog on delete with "Delete" action', fakeAsync(() => { - const closeSpy = spyOn(component.dialogRef, 'close'); - fixture.detectChanges(); - - component.delete(); - tick(); - - expect(closeSpy).toHaveBeenCalledWith({ - action: 'Delete', - device: MOCK_DEVICE, - index: 0, - }); - - closeSpy.calls.reset(); - })); - - describe('#deviceHasNoChanges', () => { - const deviceProps = [ - { manufacturer: 'test' }, - { model: 'test' }, - { mac_addr: 'test' }, - { test_pack: TestingType.Pilot }, - { type: 'test' }, - { technology: 'test' }, - { - test_modules: { - udmi: { - enabled: false, - }, - }, - }, - { additional_info: undefined }, - { - additional_info: [ - { question: 'What type of device is this?', answer: 'test' }, - ], - }, - ]; - it('should return true if devices the same', () => { - const result = component.deviceHasNoChanges(MOCK_DEVICE, MOCK_DEVICE); - - expect(result).toBeTrue(); - }); - - deviceProps.forEach(item => { - it(`should return false if devices have different props`, () => { - const MOCK_DEVICE_1 = { ...MOCK_DEVICE, ...item }; - const result = component.deviceHasNoChanges(MOCK_DEVICE_1, MOCK_DEVICE); - - expect(result).toBeFalse(); - }); - }); - }); - - it('should trigger onResize method when window is resized ', () => { - fixture.detectChanges(); - const spyOnResize = spyOn(component, 'onResize'); - window.dispatchEvent(new Event('resize')); - fixture.detectChanges(); - expect(spyOnResize).toHaveBeenCalled(); - }); - - it('#goToStep should set selected index', () => { - fixture.detectChanges(); - component.goToStep(0); - - expect(component.stepper.selectedIndex).toBe(0); - }); - - it('should close dialog on "cancel" click', () => { - fixture.detectChanges(); - component.manufacturer.setValue('test'); - ( - component.deviceQualificationForm.get('steps') as FormArray - ).controls.forEach(control => control.markAsDirty()); - fixture.detectChanges(); - const closeSpy = spyOn(component.dialogRef, 'close'); - const closeButton = compiled.querySelector( - '.device-qualification-form-header-close-button' - ) as HTMLButtonElement; - - closeButton?.click(); - - expect(closeSpy).toHaveBeenCalledWith({ - action: FormAction.Close, - index: 0, - device: { - status: DeviceStatus.VALID, - manufacturer: 'test', - model: '', - mac_addr: '', - test_pack: 'Device Qualification', - type: '', - technology: '', - test_modules: { - udmi: { - enabled: true, - }, - connection: { - enabled: true, - }, - }, - additional_info: [ - { question: 'What type of device is this?', answer: '' }, - { - question: 'Does your device process any sensitive information? ', - answer: '', - }, - { - question: 'Please select the technology this device falls into', - answer: '', - }, - ], - }, - }); - - closeSpy.calls.reset(); - }); - describe('test modules', () => { beforeEach(() => { fixture.detectChanges(); @@ -463,15 +295,13 @@ describe('DeviceQualificationFromComponent', () => { }); describe('mac address', () => { - beforeEach(() => { - fixture.detectChanges(); - }); - it('should not be disabled', () => { + fixture.detectChanges(); expect(component.mac_addr.disabled).toBeFalse(); }); it('should not contain errors when input is correct', () => { + fixture.detectChanges(); const macAddress: HTMLInputElement = compiled.querySelector( '.device-qualification-form-mac-address' ) as HTMLInputElement; @@ -488,6 +318,7 @@ describe('DeviceQualificationFromComponent', () => { }); it('should have "pattern" error when field does not satisfy pattern', () => { + fixture.detectChanges(); ['value', 'q01e423573c4'].forEach(value => { const macAddress: HTMLInputElement = compiled.querySelector( '.device-qualification-form-mac-address' @@ -508,13 +339,10 @@ describe('DeviceQualificationFromComponent', () => { }); it('should have "has_same_mac_address" error when MAC address is already used', () => { - component.data = { - testModules: MOCK_TEST_MODULES, - devices: [device], - index: 0, - isCreate: true, - }; - component.ngOnInit(); + fixture.componentRef.setInput('testModules', MOCK_TEST_MODULES); + fixture.componentRef.setInput('devices', [device]); + fixture.componentRef.setInput('isCreate', true); + fixture.detectChanges(); const macAddress: HTMLInputElement = compiled.querySelector( @@ -537,23 +365,20 @@ describe('DeviceQualificationFromComponent', () => { describe('when device is present', () => { beforeEach(() => { - component.data = { - devices: [device], - testModules: MOCK_TEST_MODULES, - device: { - status: DeviceStatus.VALID, - manufacturer: 'Delta', - model: 'O3-DIN-CPU', - mac_addr: '00:1e:42:35:73:c4', - test_modules: { - udmi: { - enabled: true, - }, + fixture.componentRef.setInput('testModules', MOCK_TEST_MODULES); + fixture.componentRef.setInput('devices', [device]); + fixture.componentRef.setInput('isCreate', false); + fixture.componentRef.setInput('initialDevice', { + status: DeviceStatus.VALID, + manufacturer: 'Delta', + model: 'O3-DIN-CPU', + mac_addr: '00:1e:42:35:73:c4', + test_modules: { + udmi: { + enabled: true, }, }, - isCreate: false, - index: 0, - }; + }); }); it('should fill form values with device values', fakeAsync(() => { @@ -577,299 +402,93 @@ describe('DeviceQualificationFromComponent', () => { discardPeriodicTasks(); })); - }); - describe('steps', () => { - beforeEach(() => { + it('should have enabled delete button', () => { fixture.detectChanges(); - }); - - describe('with questionnaire', () => { - it('should have steps', () => { - expect( - (component.deviceQualificationForm.get('steps') as FormArray).controls - .length - ).toEqual(3); - }); - }); - - it('should not save data when fields are empty', () => { - const forwardButton = compiled.querySelector( - '.form-button-forward' + const button = compiled.querySelector( + '.delete-button' ) as HTMLButtonElement; - const model: HTMLInputElement = compiled.querySelector( - '.device-qualification-form-model' - ) as HTMLInputElement; - const manufacturer: HTMLInputElement = compiled.querySelector( - '.device-qualification-form-manufacturer' - ) as HTMLInputElement; - const macAddress: HTMLInputElement = compiled.querySelector( - '.device-qualification-form-mac-address' - ) as HTMLInputElement; - ['', ' '].forEach(value => { - model.value = value; - model.dispatchEvent(new Event('input')); - manufacturer.value = value; - manufacturer.dispatchEvent(new Event('input')); - macAddress.value = value; - macAddress.dispatchEvent(new Event('input')); - forwardButton?.click(); - fixture.detectChanges(); + expect(button.disabled).toBeFalse(); + }); - const requiredErrors = compiled.querySelectorAll('mat-error'); - expect(requiredErrors?.length).toEqual(3); + it('should open cancel dialog when device is changed', () => { + const openSpy = spyOn(component.dialog, 'open').and.returnValue({ + beforeClosed: () => of(true), + } as MatDialogRef); + fixture.detectChanges(); + fixture.componentRef.setInput('initialDevice', { + status: DeviceStatus.VALID, + manufacturer: 'Alpha', + model: 'O3-DIN-CPU', + mac_addr: '00:22:42:35:73:c4', + test_modules: { + udmi: { + enabled: true, + }, + }, + }); + fixture.detectChanges(); - requiredErrors.forEach(error => { - expect(error?.innerHTML).toContain('required'); - }); + expect(openSpy).toHaveBeenCalledWith(SimpleDialogComponent, { + ariaLabel: 'Discard the Device changes', + data: { + title: 'Discard changes?', + content: `You have unsaved changes that would be permanently lost.`, + confirmName: 'Discard', + }, + autoFocus: true, + hasBackdrop: true, + disableClose: true, + panelClass: ['simple-dialog', 'close-device'], }); + + openSpy.calls.reset(); }); + }); - describe('happy flow', () => { - beforeEach(() => { - component.model.setValue('model'); - component.manufacturer.setValue('manufacturer'); - component.mac_addr.setValue('07:07:07:07:07:07'); - component.test_modules.setValue([true, true]); - }); + describe('when device is null', () => { + beforeEach(() => { + fixture.componentRef.setInput('testModules', MOCK_TEST_MODULES); + fixture.componentRef.setInput('devices', [device]); + fixture.componentRef.setInput('isCreate', true); + fixture.componentRef.setInput('initialDevice', null); + }); - it('should save device when step is changed', () => { - const forwardButton = compiled.querySelector( - '.form-button-forward' - ) as HTMLButtonElement; - forwardButton.click(); - - expect(component.device).toEqual({ - status: DeviceStatus.VALID, - manufacturer: 'manufacturer', - model: 'model', - mac_addr: '07:07:07:07:07:07', - test_pack: TestingType.Qualification, - type: '', - technology: '', - test_modules: { - udmi: { - enabled: true, - }, - connection: { - enabled: true, - }, - }, - additional_info: [ - { question: 'What type of device is this?', answer: '' }, - { - question: 'Does your device process any sensitive information? ', - answer: '', - }, - { - question: 'Please select the technology this device falls into', - answer: '', - }, - ], - }); - }); + it('should have disabled delete button', () => { + fixture.detectChanges(); + const button = compiled.querySelector( + '.delete-button' + ) as HTMLButtonElement; - describe('summary', () => { - beforeEach(() => { - const forwardButton = compiled.querySelector( - '.form-button-forward' - ) as HTMLButtonElement; - forwardButton.click(); // will redirect to 2 step - fixture.detectChanges(); - - const nextForwardButton = compiled.querySelector( - '.form-button-forward' - ) as HTMLButtonElement; - nextForwardButton.click(); //will redirect to summary - - fixture.detectChanges(); - }); - - it('should have device item', () => { - const item = compiled.querySelector('app-device-item'); - expect(item).toBeTruthy(); - }); - - it('should have instructions', () => { - const instructions = compiled.querySelector( - '.device-qualification-form-instructions' - ); - expect(instructions).toBeTruthy(); - }); - - it('should not have instructions when device is editing', () => { - component.data = { - devices: [device], - testModules: MOCK_TEST_MODULES, - device: { - status: DeviceStatus.VALID, - manufacturer: 'Delta', - model: 'O3-DIN-CPU', - mac_addr: '00:1e:42:35:73:c4', - test_modules: { - udmi: { - enabled: true, - }, - }, - }, - isCreate: false, - index: 0, - }; - fixture.detectChanges(); - - const instructions = compiled.querySelector( - '.device-qualification-form-instructions' - ); - expect(instructions).toBeNull(); - }); - - it('should save device', () => { - const saveSpy = spyOn(component.devicesStore, 'saveDevice'); - - component.submit(); - - const args = saveSpy.calls.argsFor(0); - // @ts-expect-error config is in object - expect(args[0].device).toEqual({ - status: DeviceStatus.VALID, - manufacturer: 'manufacturer', - model: 'model', - mac_addr: '07:07:07:07:07:07', - test_pack: 'Device Qualification', - type: '', - technology: '', - test_modules: { - connection: { - enabled: true, - }, - udmi: { - enabled: true, - }, - }, - additional_info: [ - { question: 'What type of device is this?', answer: '' }, - { - question: - 'Does your device process any sensitive information? ', - answer: '', - }, - { - question: 'Please select the technology this device falls into', - answer: '', - }, - ], - }); - expect(saveSpy).toHaveBeenCalled(); - }); - - it('should edit device', () => { - component.data = { - devices: [device], - testModules: MOCK_TEST_MODULES, - device: { - status: DeviceStatus.VALID, - manufacturer: 'Delta', - model: 'O3-DIN-CPU', - mac_addr: '00:1e:42:35:73:c4', - test_modules: { - udmi: { - enabled: true, - }, - }, - }, - isCreate: false, - index: 0, - }; - fixture.detectChanges(); - const editSpy = spyOn(component.devicesStore, 'editDevice'); - - component.submit(); - - const args = editSpy.calls.argsFor(0); - // @ts-expect-error config is in object - expect(args[0].device).toEqual({ - status: DeviceStatus.VALID, - manufacturer: 'manufacturer', - model: 'model', - mac_addr: '07:07:07:07:07:07', - test_pack: 'Device Qualification', - type: '', - technology: '', - test_modules: { - connection: { - enabled: true, - }, - udmi: { - enabled: true, - }, - }, - additional_info: [ - { question: 'What type of device is this?', answer: '' }, - { - question: - 'Does your device process any sensitive information? ', - answer: '', - }, - { - question: 'Please select the technology this device falls into', - answer: '', - }, - ], - }); - expect(editSpy).toHaveBeenCalled(); - }); - }); + expect(button.disabled).toBeTrue(); }); + }); - describe('with errors', () => { - beforeEach(() => { - component.data = { - devices: [device], - testModules: MOCK_TEST_MODULES, - device: { - status: DeviceStatus.VALID, - manufacturer: 'Delta', - model: 'O3-DIN-CPU', - mac_addr: '00:1e:42:35:73:c4', - test_modules: { - udmi: { - enabled: true, - }, - }, - }, - isCreate: false, - index: 0, - }; - component.model.setValue(''); + describe('with changes', () => { + it('should have enabled cancel button', () => { + fixture.detectChanges(); + component.model.setValue('new value'); + fixture.detectChanges(); + const button = compiled.querySelector( + '.close-button' + ) as HTMLButtonElement; - fixture.detectChanges(); - }); + expect(button.disabled).toBeFalse(); + }); + }); - describe('summary', () => { - beforeEach(() => { - const forwardButton = compiled.querySelector( - '.form-button-forward' - ) as HTMLButtonElement; - forwardButton.click(); // will redirect to 2 step - fixture.detectChanges(); - - const nextForwardButton = compiled.querySelector( - '.form-button-forward' - ) as HTMLButtonElement; - nextForwardButton.click(); //will redirect to summary - fixture.detectChanges(); - }); - - it('should have error message', () => { - const error = compiled.querySelector( - '.device-qualification-form-summary-info-description' - ); - expect(error?.textContent?.trim()).toEqual( - 'Please go back and correct the errors on Step 1.' - ); - }); - }); + describe('onSaveClicked', () => { + it('should emit device', () => { + fixture.detectChanges(); + const saveSpy = spyOn(component.save, 'emit'); + component.manufacturer.setValue('manufacturer'); + component.model.setValue('model'); + component.mac_addr.setValue('01:01:01:01:01:01'); + component.deviceQualificationForm.markAsDirty(); + + component.onSaveClicked(); + expect(saveSpy).toHaveBeenCalledWith(MOCK_DEVICE); }); }); }); @@ -877,6 +496,7 @@ describe('DeviceQualificationFromComponent', () => { @Component({ selector: 'app-dynamic-form', template: '
', + standalone: false, }) class FakeDynamicFormComponent { @Input() format: QuestionFormat[] = []; diff --git a/modules/ui/src/app/pages/devices/components/device-qualification-from/device-qualification-from.component.ts b/modules/ui/src/app/pages/devices/components/device-qualification-from/device-qualification-from.component.ts index 09edf8cdc..49e655492 100644 --- a/modules/ui/src/app/pages/devices/components/device-qualification-from/device-qualification-from.component.ts +++ b/modules/ui/src/app/pages/devices/components/device-qualification-from/device-qualification-from.component.ts @@ -14,14 +14,14 @@ * limitations under the License. */ import { - AfterViewInit, Component, - ElementRef, - HostListener, - Inject, - OnDestroy, OnInit, - ViewChild, + inject, + input, + effect, + output, + ChangeDetectorRef, + AfterViewInit, } from '@angular/core'; import { AbstractControl, @@ -29,21 +29,18 @@ import { FormBuilder, FormGroup, ReactiveFormsModule, + ValidatorFn, Validators, } from '@angular/forms'; import { DeviceValidators } from '../device-form/device.validators'; import { Device, - DeviceQuestionnaireSection, DeviceStatus, DeviceView, TestingType, TestModule, } from '../../../../model/device'; -import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; import { CommonModule } from '@angular/common'; -import { CdkStep, StepperSelectionEvent } from '@angular/cdk/stepper'; -import { StepperComponent } from '../../../../components/stepper/stepper.component'; import { MatError, MatFormField, @@ -55,40 +52,28 @@ import { MatInputModule } from '@angular/material/input'; import { MatSelectModule } from '@angular/material/select'; import { MatCheckboxModule } from '@angular/material/checkbox'; import { TextFieldModule } from '@angular/cdk/text-field'; -import { NgxMaskDirective, NgxMaskPipe, provideNgxMask } from 'ngx-mask'; -import { MatIcon } from '@angular/material/icon'; +import { NgxMaskDirective, provideNgxMask } from 'ngx-mask'; import { MatRadioButton, MatRadioGroup } from '@angular/material/radio'; import { ProfileValidators } from '../../../risk-assessment/profile-form/profile.validators'; import { DevicesStore } from '../../devices.store'; import { DynamicFormComponent } from '../../../../components/dynamic-form/dynamic-form.component'; -import { filter, skip, Subject, takeUntil, timer } from 'rxjs'; -import { FormAction, FormResponse } from '../../devices.component'; -import { DeviceItemComponent } from '../../../../components/device-item/device-item.component'; -import { ProgramTypeIconComponent } from '../../../../components/program-type-icon/program-type-icon.component'; +import { skip, timer } from 'rxjs'; import { Question } from '../../../../model/profile'; -import { FormControlType } from '../../../../model/question'; -import { ProgramType } from '../../../../model/program-type'; -import { FocusManagerService } from '../../../../services/focus-manager.service'; +import { FormControlType, QuestionFormat } from '../../../../model/question'; +import { BehaviorSubject } from 'rxjs/internal/BehaviorSubject'; +import { SimpleDialogComponent } from '../../../../components/simple-dialog/simple-dialog.component'; +import { MatDialog } from '@angular/material/dialog'; +import { of } from 'rxjs/internal/observable/of'; +import { Observable } from 'rxjs/internal/Observable'; +import { map } from 'rxjs/internal/operators/map'; const MAC_ADDRESS_PATTERN = '^[\\s]*[a-fA-F0-9]{2}(?:[:][a-fA-F0-9]{2}){5}[\\s]*$'; -interface DialogData { - title?: string; - device?: Device; - initialDevice?: Device; - devices: Device[]; - testModules: TestModule[]; - index: number; - isCreate: boolean; -} - @Component({ selector: 'app-device-qualification-from', - standalone: true, + imports: [ - CdkStep, - StepperComponent, MatFormField, DeviceTestsComponent, MatButtonModule, @@ -101,285 +86,151 @@ interface DialogData { MatCheckboxModule, TextFieldModule, NgxMaskDirective, - NgxMaskPipe, - MatIcon, MatRadioGroup, MatRadioButton, DynamicFormComponent, - DeviceItemComponent, - ProgramTypeIconComponent, ], - providers: [provideNgxMask(), DevicesStore], + providers: [provideNgxMask()], templateUrl: './device-qualification-from.component.html', styleUrl: './device-qualification-from.component.scss', }) -export class DeviceQualificationFromComponent - implements OnInit, AfterViewInit, OnDestroy -{ - readonly FORM_HEIGHT = 993; +export class DeviceQualificationFromComponent implements OnInit, AfterViewInit { readonly TestingType = TestingType; readonly DeviceView = DeviceView; - readonly ProgramType = ProgramType; - @ViewChild('stepper') public stepper!: StepperComponent; - testModules: TestModule[] = []; + + private fb = inject(FormBuilder); + private cdr = inject(ChangeDetectorRef); + private deviceValidators = inject(DeviceValidators); + private profileValidators = inject(ProfileValidators); + private changeDevice = false; + private macAddressValidator!: ValidatorFn; + + dialog = inject(MatDialog); + formIsLoaded$ = new BehaviorSubject(false); + devicesStore = inject(DevicesStore); deviceQualificationForm: FormGroup = this.fb.group({}); - device: Device | undefined; - format: DeviceQuestionnaireSection[] = []; - selectedIndex: number = 0; - typeStep = 1; + format: QuestionFormat[] = []; typeQuestion = 0; - technologyStep = 1; technologyQuestion = 2; + device: Device | null = null; + + initialDevice = input(null); - private destroy$: Subject = new Subject(); + initialDeviceEffect = effect(() => { + if ( + this.changeDevice || + this.deviceHasNoChanges(this.device, this.createDeviceFromForm()) + ) { + this.changeDevice = false; + this.device = this.initialDevice(); + if (this.device && this.device.mac_addr) { + this.fillDeviceForm(this.format, this.device); + } else { + this.resetForm(); + } + this.updateMacAddressValidator(); + } else if (this.device != this.initialDevice()) { + // prevent select new device before user confirmation + this.devicesStore.selectDevice(this.device); + this.openCloseDialogToChangeDevice(this.initialDevice()); + } + }); + + devices = input([]); + testModules = input([]); + isCreate = input(true); + + save = output(); + delete = output(); + cancel = output(); get model() { - return this.getStep(0).get('model') as AbstractControl; + return this.deviceQualificationForm.get('model') as AbstractControl; } get manufacturer() { - return this.getStep(0).get('manufacturer') as AbstractControl; + return this.deviceQualificationForm.get('manufacturer') as AbstractControl; } get mac_addr() { - return this.getStep(0).get('mac_addr') as AbstractControl; + return this.deviceQualificationForm.get('mac_addr') as AbstractControl; } get test_pack() { - return this.getStep(0).get('test_pack') as AbstractControl; + return this.deviceQualificationForm.get('test_pack') as AbstractControl; } get type() { - return this.getStep(this.typeStep)?.get( + return this.deviceQualificationForm.get( this.typeQuestion.toString() ) as AbstractControl; } get technology() { - return this.getStep(this.technologyStep)?.get( + return this.deviceQualificationForm.get( this.technologyQuestion.toString() ) as AbstractControl; } get test_modules() { - return this.getStep(0).controls['test_modules'] as FormArray; - } - - get formValid() { - return ( - this.deviceQualificationForm.get('steps') as FormArray - ).controls.every(control => (control as FormGroup).valid); - } - - deviceHasNoChanges(device1: Device | undefined, device2: Device | undefined) { - return device1 && device2 && this.compareDevices(device1, device2); - } - - @HostListener('window:resize', ['$event']) - onResize() { - this.setDialogHeight(); - } - - constructor( - private fb: FormBuilder, - private deviceValidators: DeviceValidators, - private profileValidators: ProfileValidators, - public dialogRef: MatDialogRef, - @Inject(MAT_DIALOG_DATA) public data: DialogData, - public devicesStore: DevicesStore, - private element: ElementRef, - private focusService: FocusManagerService - ) { - this.device = data.device; + return this.deviceQualificationForm.controls['test_modules'] as FormArray; } ngOnInit(): void { - this.createBasicStep(); - this.testModules = this.data.testModules; + this.createDeviceForm(); this.devicesStore.questionnaireFormat$.pipe(skip(1)).subscribe(format => { - this.createDeviceForm(format); this.format = format; - format.forEach(step => { - step.questions.forEach((question, index) => { - // need to define the step and index of type and technology - if (question.question.toLowerCase().includes('type')) { - this.typeStep = step.step; - this.typeQuestion = index; - } else if (question.question.toLowerCase().includes('technology')) { - this.technologyStep = step.step; - this.technologyQuestion = index; - } - }); + format.forEach((question, index) => { + // need to define the step and index of type and technology + if (question.question.toLowerCase().includes('type')) { + this.typeQuestion = index; + } else if (question.question.toLowerCase().includes('technology')) { + this.technologyQuestion = index; + } }); - timer(0) - .pipe(takeUntil(this.destroy$)) - .subscribe(() => { - if (this.data.device) { - this.fillDeviceForm(this.format, this.data.device!); - } - if (this.data.index) { - // previous steps should be marked as interacted - for (let i = 0; i <= this.data.index; i++) { - this.goToStep(i); - } - } - this.dialogRef - .keydownEvents() - .pipe(filter((e: KeyboardEvent) => e.code === 'Escape')) - .subscribe(() => { - this.closeForm(); - }); - }); + timer(0).subscribe(() => { + if (this.initialDevice()) { + this.fillDeviceForm(this.format, this.initialDevice()!); + } + this.formIsLoaded$.next(true); + }); }); this.devicesStore.getQuestionnaireFormat(); } ngAfterViewInit() { - //set static height for better UX - this.element.nativeElement.style.height = - this.element.nativeElement.offsetHeight + 'px'; - } - - ngOnDestroy() { - this.destroy$.next(true); - this.destroy$.unsubscribe(); - } - - submit(): void { - this.updateDevice(this.device!, () => { - this.dialogRef.close({ - action: FormAction.Save, - device: this.device, - } as FormResponse); - }); - } - - delete(): void { - this.dialogRef.close({ - action: FormAction.Delete, - device: this.createDeviceFromForm(), - index: this.stepper.selectedIndex, - } as FormResponse); - } - - closeForm(): void { - const device1 = this.data.initialDevice; - const device2 = this.createDeviceFromForm(); - if ( - (device1 && device2 && this.compareDevices(device1, device2)) || - (!device1 && this.deviceIsEmpty(device2)) - ) { - this.dialogRef.close(); - } else { - this.dialogRef.close({ - action: FormAction.Close, - device: this.createDeviceFromForm(), - index: this.stepper.selectedIndex, - } as FormResponse); - } - } - - getStep(step: number) { - return (this.deviceQualificationForm.get('steps') as FormArray).controls[ - step - ] as FormGroup; + this.cdr.detectChanges(); } - onStepChange(event: StepperSelectionEvent) { - this.focusService.focusFirstElementInContainer(); - if (event.previouslySelectedStep.completed) { - this.device = this.createDeviceFromForm(); - } + onSaveClicked(): void { + this.save.emit(this.createDeviceFromForm()); + this.deviceQualificationForm.markAsPristine(); + this.changeDevice = true; } - getErrorSteps(): number[] { - const steps: number[] = []; - (this.deviceQualificationForm.get('steps') as FormArray).controls.forEach( - (control, index) => { - if (!control.valid) steps.push(index); - } - ); - return steps; + onCancelClicked(): void { + this.cancel.emit(); } - goToStep(index: number, event?: Event) { - event?.preventDefault(); - this.stepper.selectedIndex = index; + onDeleteClick(): void { + this.delete.emit(this.initialDevice()!); } - private fillDeviceForm( - format: DeviceQuestionnaireSection[], - device: Device - ): void { - format.forEach(step => { - step.questions.forEach((question, index) => { - const answer = device.additional_info?.find( - answers => answers.question === question.question - )?.answer; - if (answer !== undefined && answer !== null && answer !== '') { - if (question.type === FormControlType.SELECT_MULTIPLE) { - question.options?.forEach((item, idx) => { - if ((answer as number[])?.includes(idx)) { - ( - this.getStep(step.step).get(index.toString()) as FormGroup - ).controls[idx].setValue(true); - } else { - ( - this.getStep(step.step).get(index.toString()) as FormGroup - ).controls[idx].setValue(false); - } - }); - } else { - ( - this.getStep(step.step).get(index.toString()) as AbstractControl - ).setValue(answer || ''); - } - } else { - ( - this.getStep(step.step)?.get(index.toString()) as AbstractControl - )?.markAsTouched(); - } - }); + resetForm() { + this.deviceQualificationForm.reset({ + test_pack: TestingType.Qualification, }); - this.model.setValue(device.model); - this.manufacturer.setValue(device.manufacturer); - this.mac_addr.setValue(device.mac_addr); - - if ( - device.test_pack && - (device.test_pack === TestingType.Qualification || - device.test_pack === TestingType.Pilot) - ) { - this.test_pack.setValue(device.test_pack); - } else { - this.test_pack.setValue(TestingType.Qualification); - } - - this.type?.setValue(device.type); - this.technology?.setValue(device.technology); - } - - private updateDevice(device: Device, callback: () => void) { - if (!this.data.isCreate && this.data.device) { - this.devicesStore.editDevice({ - device, - mac_addr: this.data.device.mac_addr, - onSuccess: callback, - }); - } else { - this.devicesStore.saveDevice({ device, onSuccess: callback }); - } } - private createDeviceFromForm(): Device { + createDeviceFromForm(): Device { const testModules: { [key: string]: { enabled: boolean } } = {}; - this.getStep(0).value.test_modules.forEach( + this.deviceQualificationForm.value.test_modules.forEach( (enabled: boolean, i: number) => { - testModules[this.testModules[i]?.name] = { + testModules[this.testModules()[i]?.name] = { enabled: enabled, }; } @@ -387,133 +238,84 @@ export class DeviceQualificationFromComponent const additionalInfo: Question[] = []; - this.format.forEach(step => { - step.questions.forEach((question, index) => { - const response: Question = {}; - response.question = question.question; + this.format.forEach((question, index) => { + const response: Question = {}; + response.question = question.question; - if (question.type === FormControlType.SELECT_MULTIPLE) { - const answer: number[] = []; - question.options?.forEach((_, idx) => { - const value = this.getStep(step.step).value[index][idx]; - if (value) { - answer.push(idx); - } - }); - response.answer = answer; - } else { - response.answer = this.getStep(step.step).value[index]?.trim(); - } - additionalInfo.push(response); - }); + if (question.type === FormControlType.SELECT_MULTIPLE) { + const answer: number[] = []; + question.options?.forEach((_, idx) => { + const value = this.deviceQualificationForm.value[index][idx]; + if (value) { + answer.push(idx); + } + }); + response.answer = answer; + } else { + response.answer = this.deviceQualificationForm.value[index]?.trim(); + } + additionalInfo.push(response); }); return { status: DeviceStatus.VALID, - model: this.model.value.trim(), - manufacturer: this.manufacturer.value.trim(), - mac_addr: this.mac_addr.value.trim(), - test_pack: this.test_pack.value, + model: this.model?.value?.trim(), + manufacturer: this.manufacturer?.value?.trim(), + mac_addr: this.mac_addr?.value?.trim(), + test_pack: this.test_pack?.value, test_modules: testModules, - type: this.type.value, - technology: this.technology.value, + type: this.type?.value, + technology: this.technology?.value, additional_info: additionalInfo, } as Device; } - private createBasicStep() { - const firstStep = this.fb.group({ - model: [ - '', - [ - this.profileValidators.textRequired(), - this.deviceValidators.deviceStringFormat(), - ], - ], - manufacturer: [ - '', - [ - this.profileValidators.textRequired(), - this.deviceValidators.deviceStringFormat(), - ], - ], - mac_addr: [ - '', - [ - this.profileValidators.textRequired(), - Validators.pattern(MAC_ADDRESS_PATTERN), - this.deviceValidators.differentMACAddress( - this.data.devices, - this.data.device - ), - ], - ], - test_modules: new FormArray( - [], - this.deviceValidators.testModulesRequired() - ), - test_pack: [TestingType.Qualification], - }); - - this.deviceQualificationForm = this.fb.group({ - steps: this.fb.array([firstStep]), - }); - } - - private createDeviceForm(format: DeviceQuestionnaireSection[]) { - format.forEach(() => { - (this.deviceQualificationForm.get('steps') as FormArray).controls.push( - this.createStep() - ); - }); - - // summary step - (this.deviceQualificationForm.get('steps') as FormArray).controls.push( - this.fb.group({}) + deviceHasNoChanges(device1: Device | null, device2: Device) { + return ( + (device1 === null && this.deviceIsEmpty(device2)) || + (device1 && this.compareDevices(device1, device2)) ); } - private createStep() { - return new FormGroup({}); + close(): Observable { + if ( + this.deviceHasNoChanges(this.initialDevice(), this.createDeviceFromForm()) + ) { + return of(true); + } + return this.openCloseDialog().pipe(map(res => !!res)); } - private compareDevices(device1: Device, device2: Device) { - if (device1.manufacturer !== device2.manufacturer) { + private deviceIsEmpty(device: Device) { + if (device.manufacturer && device.manufacturer !== '') { return false; } - if (device1.model !== device2.model) { + if (device.model && device.model !== '') { return false; } - if (device1.mac_addr !== device2.mac_addr) { + if (device.mac_addr && device.mac_addr !== '') { return false; } - if (device1.type !== device2.type) { + if (device.type && device.type !== '') { return false; } - if (device1.technology !== device2.technology) { + if (device.technology && device.technology !== '') { return false; } - if (device1.test_pack !== device2.test_pack) { + if (device.test_pack !== TestingType.Qualification) { return false; } - const keys1 = Object.keys(device1.test_modules!); + const keys1 = Object.keys(device.test_modules!); for (const key of keys1) { - const val1 = device1.test_modules![key]; - const val2 = device2.test_modules![key]; - if (val1?.enabled !== val2?.enabled) { + const val1 = device.test_modules![key]; + if (!val1.enabled) { return false; } } - - if (device1.additional_info) { - for (const question of device1.additional_info) { - if ( - question.answer !== - device2.additional_info?.find( - question2 => question2.question === question.question - )?.answer - ) { + if (device.additional_info) { + for (const question of device.additional_info) { + if (question.answer && question.answer !== '') { return false; } } @@ -523,37 +325,43 @@ export class DeviceQualificationFromComponent return true; } - private deviceIsEmpty(device: Device) { - if (device.manufacturer !== '') { + private compareDevices(device1: Device, device2: Device) { + if (device1.manufacturer !== device2.manufacturer) { return false; } - if (device.model !== '') { + if (device1.model !== device2.model) { return false; } - if (device.mac_addr !== '') { + if (device1.mac_addr !== device2.mac_addr) { return false; } - if (device.type !== '') { + if (device1.type !== device2.type) { return false; } - if (device.technology !== '') { + if (device1.technology !== device2.technology) { return false; } - if (device.test_pack !== TestingType.Qualification) { + if (device1.test_pack !== device2.test_pack) { return false; } - const keys1 = Object.keys(device.test_modules!); + const keys1 = Object.keys(device1.test_modules!); for (const key of keys1) { - const val1 = device.test_modules![key]; - if (!val1.enabled) { + const val1 = device1.test_modules![key]; + const val2 = device2.test_modules![key]; + if (val1?.enabled !== val2?.enabled) { return false; } } - if (device.additional_info) { - for (const question of device.additional_info) { - if (question.answer !== '') { + if (device1.additional_info) { + for (const question of device1.additional_info) { + if ( + question.answer !== + device2.additional_info?.find( + question2 => question2.question === question.question + )?.answer + ) { return false; } } @@ -563,12 +371,126 @@ export class DeviceQualificationFromComponent return true; } - private setDialogHeight(): void { - const windowHeight = window.innerHeight; - if (windowHeight < this.FORM_HEIGHT) { - this.element.nativeElement.style.height = '100vh'; + private fillDeviceForm(format: QuestionFormat[], device: Device): void { + format.forEach((question, index) => { + const answer = device.additional_info?.find( + answers => answers.question === question.question + )?.answer; + if (answer !== undefined && answer !== null && answer !== '') { + if (question.type === FormControlType.SELECT_MULTIPLE) { + question.options?.forEach((item, idx) => { + if ((answer as number[])?.includes(idx)) { + ( + this.deviceQualificationForm.get(index.toString()) as FormGroup + ).controls[idx].setValue(true); + } else { + ( + this.deviceQualificationForm.get(index.toString()) as FormGroup + ).controls[idx].setValue(false); + } + }); + } else { + ( + this.deviceQualificationForm.get( + index.toString() + ) as AbstractControl + ).setValue(answer || ''); + } + } else { + ( + this.deviceQualificationForm.get(index.toString()) as AbstractControl + )?.markAsTouched(); + } + }); + this.model.setValue(device.model); + this.manufacturer.setValue(device.manufacturer); + this.mac_addr.setValue(device.mac_addr); + + if ( + device.test_pack && + (device.test_pack === TestingType.Qualification || + device.test_pack === TestingType.Pilot) + ) { + this.test_pack.setValue(device.test_pack); } else { - this.element.nativeElement.style.height = this.FORM_HEIGHT + 'px'; + this.test_pack.setValue(TestingType.Qualification); + } + + this.type?.setValue(device.type); + this.technology?.setValue(device.technology); + } + + private createDeviceForm() { + this.macAddressValidator = this.deviceValidators.differentMACAddress( + this.devices(), + this.initialDevice() + ); + + this.deviceQualificationForm = this.fb.group({ + model: [ + '', + [ + this.profileValidators.textRequired(), + this.deviceValidators.deviceStringFormat(), + ], + ], + manufacturer: [ + '', + [ + this.profileValidators.textRequired(), + this.deviceValidators.deviceStringFormat(), + ], + ], + mac_addr: [ + '', + [ + this.profileValidators.textRequired(), + Validators.pattern(MAC_ADDRESS_PATTERN), + this.macAddressValidator, + ], + ], + test_modules: new FormArray( + [], + this.deviceValidators.testModulesRequired() + ), + test_pack: [TestingType.Qualification], + }); + } + + private openCloseDialogToChangeDevice(device: Device | null) { + this.openCloseDialog().subscribe(close => { + if (close) { + this.changeDevice = true; + this.devicesStore.selectDevice(device); + } + }); + } + private openCloseDialog() { + const dialogRef = this.dialog.open(SimpleDialogComponent, { + ariaLabel: 'Discard the Device changes', + data: { + title: 'Discard changes?', + content: `You have unsaved changes that would be permanently lost.`, + confirmName: 'Discard', + }, + autoFocus: true, + hasBackdrop: true, + disableClose: true, + panelClass: ['simple-dialog', 'close-device'], + }); + + return dialogRef?.beforeClosed(); + } + + private updateMacAddressValidator() { + if (this.mac_addr) { + this.mac_addr.removeValidators([this.macAddressValidator]); + this.macAddressValidator = this.deviceValidators.differentMACAddress( + this.devices(), + this.initialDevice() + ); + this.mac_addr.addValidators(this.macAddressValidator); + this.mac_addr.updateValueAndValidity(); } } } diff --git a/modules/ui/src/app/pages/devices/devices.component.html b/modules/ui/src/app/pages/devices/devices.component.html index f6567b49e..c04c2cc3c 100644 --- a/modules/ui/src/app/pages/devices/devices.component.html +++ b/modules/ui/src/app/pages/devices/devices.component.html @@ -14,43 +14,67 @@ limitations under the License. --> - - -

Devices

- -
-
- - - -
-
+ + + + + + + + + -
+ -
+
+ + + +
diff --git a/modules/ui/src/app/pages/devices/devices.component.scss b/modules/ui/src/app/pages/devices/devices.component.scss index 7d639d509..a031674bd 100644 --- a/modules/ui/src/app/pages/devices/devices.component.scss +++ b/modules/ui/src/app/pages/devices/devices.component.scss @@ -13,37 +13,8 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -@import 'src/theming/colors'; -@import 'src/theming/variables'; +@use 'mixins'; -:host { - overflow: hidden; - flex-direction: column; - display: flex; -} - -.device-repository-content-empty { - position: absolute; - top: 0; - width: 100%; - height: 100%; - display: flex; - align-items: center; - justify-content: center; -} - -.device-repository-toolbar { - gap: 16px; - background: $white; - height: 74px; - padding: 24px 0 8px 32px; -} - -.device-repository-content { - align-content: start; - padding: 24px 32px; - display: grid; - grid-template-columns: repeat(auto-fit, $device-item-width); - gap: 16px; - overflow-y: auto; +.device-add-button { + @include mixins.add-button; } diff --git a/modules/ui/src/app/pages/devices/devices.component.spec.ts b/modules/ui/src/app/pages/devices/devices.component.spec.ts index d5802c382..f693d8103 100644 --- a/modules/ui/src/app/pages/devices/devices.component.spec.ts +++ b/modules/ui/src/app/pages/devices/devices.component.spec.ts @@ -20,10 +20,9 @@ import { tick, } from '@angular/core/testing'; import { of } from 'rxjs'; -import { Device } from '../../model/device'; +import { Device, DeviceAction, TestModule } from '../../model/device'; -import { DevicesComponent, FormAction } from './devices.component'; -import { DevicesModule } from './devices.module'; +import { DevicesComponent } from './devices.component'; import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; import { MatDialogRef } from '@angular/material/dialog'; import { device, MOCK_TEST_MODULES } from '../../mocks/device.mock'; @@ -36,7 +35,7 @@ import { TestrunInitiateFormComponent } from '../testrun/components/testrun-init import { Routes } from '../../model/routes'; import { Router } from '@angular/router'; import { RouterTestingModule } from '@angular/router/testing'; -import { Component } from '@angular/core'; +import { Component, input, output } from '@angular/core'; import { MOCK_PROGRESS_DATA_IN_PROGRESS } from '../../mocks/testrun.mock'; import { DeviceQualificationFromComponent } from './components/device-qualification-from/device-qualification-from.component'; @@ -60,22 +59,33 @@ describe('DevicesComponent', () => { 'getTestModules', 'deleteDevice', ]); + mockDevicesStore.isOpenAddDevice$ = of(false); await TestBed.configureTestingModule({ imports: [ RouterTestingModule.withRoutes([ { path: 'testing', component: FakeProgressComponent }, ]), - DevicesModule, BrowserAnimationsModule, MatIconTestingModule, + DevicesComponent, + FakeProgressComponent, + FakeDeviceQualificationComponent, ], providers: [ { provide: DevicesStore, useValue: mockDevicesStore }, { provide: FocusManagerService, useValue: stateServiceMock }, ], - declarations: [DevicesComponent, FakeProgressComponent], - }).compileComponents(); + }) + .overrideComponent(DevicesComponent, { + remove: { + imports: [DeviceQualificationFromComponent], + }, + add: { + imports: [FakeDeviceQualificationComponent], + }, + }) + .compileComponents(); TestBed.overrideProvider(DevicesStore, { useValue: mockDevicesStore }); @@ -96,27 +106,30 @@ describe('DevicesComponent', () => { selectedDevice: null, deviceInProgress: null, testModules: [], + actions: [ + { + action: DeviceAction.StartNewTestrun, + svgIcon: 'testrun_logo_small', + }, + { action: DeviceAction.Delete, icon: 'delete' }, + ], }); mockDevicesStore.devices$ = of([]); mockDevicesStore.testModules$ = of([]); - mockDevicesStore.isOpenAddDevice$ = of(true); fixture.detectChanges(); }); it('should show only add device button if no device added', () => { - const button = compiled.querySelector( - '.device-repository-content-empty button' - ); + const button = compiled.querySelector('app-empty-page button'); expect(button).toBeTruthy(); }); - it('should open the modal if isOpenAddDevice$ as true', () => { - const openDialogSpy = spyOn(component, 'openDialog'); - + it('should open form if isOpenAddDevice$ as true', () => { + mockDevicesStore.isOpenAddDevice$ = of(true); component.ngOnInit(); - expect(openDialogSpy).toHaveBeenCalled(); + expect(component.isOpenDeviceForm).toBeTrue(); }); }); @@ -127,6 +140,13 @@ describe('DevicesComponent', () => { selectedDevice: device, deviceInProgress: device, testModules: [], + actions: [ + { + action: DeviceAction.StartNewTestrun, + svgIcon: 'testrun_logo_small', + }, + { action: DeviceAction.Delete, icon: 'delete' }, + ], }); fixture.detectChanges(); }); @@ -137,68 +157,15 @@ describe('DevicesComponent', () => { expect(item.length).toEqual(3); })); - it('should open device dialog on "add device button" click', () => { - const openSpy = spyOn(component.dialog, 'open').and.returnValue({ - afterClosed: () => of(true), - beforeClosed: () => of(true), - } as MatDialogRef); + it('should open form on "add device button" click', () => { fixture.detectChanges(); const button = compiled.querySelector( - '.device-add-button' + '.add-entity-button' ) as HTMLButtonElement; button?.click(); expect(button).toBeTruthy(); - expect(openSpy).toHaveBeenCalled(); - expect(openSpy).toHaveBeenCalledWith(DeviceQualificationFromComponent, { - ariaLabel: 'Create Device', - data: { - device: null, - initialDevice: undefined, - title: 'Create Device', - testModules: [], - devices: [device, device, device], - index: 0, - isCreate: true, - }, - autoFocus: 'first-tabbable', - hasBackdrop: true, - disableClose: true, - panelClass: 'device-form-dialog', - }); - - openSpy.calls.reset(); - }); - - describe('#openDialog', () => { - it('should open device dialog on item click', () => { - const openSpy = spyOn(component.dialog, 'open').and.returnValue({ - beforeClosed: () => of(true), - } as MatDialogRef); - fixture.detectChanges(); - - component.openDialog([device], MOCK_TEST_MODULES, device, device, true); - - expect(openSpy).toHaveBeenCalled(); - expect(openSpy).toHaveBeenCalledWith(DeviceQualificationFromComponent, { - ariaLabel: 'Edit device', - data: { - device: device, - initialDevice: device, - title: 'Edit device', - devices: [device], - testModules: MOCK_TEST_MODULES, - index: 0, - isCreate: false, - }, - autoFocus: 'first-tabbable', - hasBackdrop: true, - disableClose: true, - panelClass: 'device-form-dialog', - }); - - openSpy.calls.reset(); - }); + expect(component.isOpenDeviceForm).toBeTrue(); }); it('should disable device if deviceInProgress is exist', () => { @@ -208,16 +175,6 @@ describe('DevicesComponent', () => { }); }); - it('should call setIsOpenAddDevice if dialog closes with null', () => { - spyOn(component.dialog, 'open').and.returnValue({ - beforeClosed: () => of(null), - } as MatDialogRef); - - component.openDialog([], MOCK_TEST_MODULES); - - expect(mockDevicesStore.setIsOpenAddDevice).toHaveBeenCalled(); - }); - describe('close dialog', () => { beforeEach(() => { component.viewModel$ = of({ @@ -225,6 +182,13 @@ describe('DevicesComponent', () => { selectedDevice: device, deviceInProgress: null, testModules: [], + actions: [ + { + action: DeviceAction.StartNewTestrun, + svgIcon: 'testrun_logo_small', + }, + { action: DeviceAction.Delete, icon: 'delete' }, + ], }); fixture.detectChanges(); }); @@ -234,47 +198,6 @@ describe('DevicesComponent', () => { expect(item.length).toEqual(3); })); - - it('should open device dialog when dialog return null', () => { - const openDeviceDialogSpy = spyOn(component, 'openDialog'); - spyOn(component.dialog, 'open').and.returnValue({ - beforeClosed: () => of(null), - } as MatDialogRef); - - component.openCloseDialog( - [device], - MOCK_TEST_MODULES, - device, - undefined, - false, - 0, - 0 - ); - - expect(openDeviceDialogSpy).toHaveBeenCalledWith( - [device], - MOCK_TEST_MODULES, - device, - undefined, - false, - 0, - 0 - ); - }); - }); - - it('should delete device if dialog closes with object, action delete and selected device', () => { - spyOn(component.dialog, 'open').and.returnValue({ - beforeClosed: () => - of({ - device, - action: FormAction.Delete, - }), - } as MatDialogRef); - - component.openDialog([device], MOCK_TEST_MODULES, device); - - expect(mockDevicesStore.deleteDevice).toHaveBeenCalled(); }); describe('delete device dialog', () => { @@ -284,6 +207,13 @@ describe('DevicesComponent', () => { selectedDevice: device, deviceInProgress: null, testModules: [], + actions: [ + { + action: DeviceAction.StartNewTestrun, + svgIcon: 'testrun_logo_small', + }, + { action: DeviceAction.Delete, icon: 'delete' }, + ], }); fixture.detectChanges(); }); @@ -293,48 +223,13 @@ describe('DevicesComponent', () => { beforeClosed: () => of(true), } as MatDialogRef); - component.openDeleteDialog( - [device], - MOCK_TEST_MODULES, - device, - device, - false, - 0, - 0 - ); + component.openDeleteDialog(device); const args = mockDevicesStore.deleteDevice.calls.argsFor(0); // @ts-expect-error config is in object expect(args[0].device).toEqual(device); expect(mockDevicesStore.deleteDevice).toHaveBeenCalled(); }); - - it('should open device dialog when dialog return null', () => { - const openDeviceDialogSpy = spyOn(component, 'openDialog'); - spyOn(component.dialog, 'open').and.returnValue({ - beforeClosed: () => of(null), - } as MatDialogRef); - - component.openDeleteDialog( - [device], - MOCK_TEST_MODULES, - device, - device, - false, - 0, - 0 - ); - - expect(openDeviceDialogSpy).toHaveBeenCalledWith( - [device], - MOCK_TEST_MODULES, - device, - device, - false, - 0, - 0 - ); - }); }); describe('#openStartTestrun', () => { @@ -380,3 +275,18 @@ describe('DevicesComponent', () => { template: '', }) class FakeProgressComponent {} + +@Component({ + selector: 'app-device-qualification-from', + template: '
', +}) +class FakeDeviceQualificationComponent { + initialDevice = input(null); + devices = input([]); + testModules = input([]); + isCreate = input(true); + + save = output(); + delete = output(); + cancel = output(); +} diff --git a/modules/ui/src/app/pages/devices/devices.component.ts b/modules/ui/src/app/pages/devices/devices.component.ts index 6bf24431b..73553e9a1 100644 --- a/modules/ui/src/app/pages/devices/devices.component.ts +++ b/modules/ui/src/app/pages/devices/devices.component.ts @@ -19,75 +19,97 @@ import { OnDestroy, OnInit, ChangeDetectorRef, + inject, + viewChild, } from '@angular/core'; -import { MatDialog, MatDialogRef } from '@angular/material/dialog'; +import { MatDialog, MatDialogModule } from '@angular/material/dialog'; import { Device, - DeviceStatus, + DeviceAction, DeviceView, TestModule, } from '../../model/device'; -import { map, Subject, takeUntil, timer } from 'rxjs'; +import { LayoutType } from '../../model/layout-type'; +import { Subject, takeUntil, timer } from 'rxjs'; import { SimpleDialogComponent } from '../../components/simple-dialog/simple-dialog.component'; -import { combineLatest } from 'rxjs/internal/observable/combineLatest'; import { FocusManagerService } from '../../services/focus-manager.service'; import { Routes } from '../../model/routes'; import { Router } from '@angular/router'; import { TestrunInitiateFormComponent } from '../testrun/components/testrun-initiate-form/testrun-initiate-form.component'; import { DevicesStore } from './devices.store'; import { DeviceQualificationFromComponent } from './components/device-qualification-from/device-qualification-from.component'; -import { Observable } from 'rxjs/internal/Observable'; +import { CommonModule } from '@angular/common'; +import { MatToolbarModule } from '@angular/material/toolbar'; +import { MatButtonModule } from '@angular/material/button'; +import { MatIconModule } from '@angular/material/icon'; +import { ScrollingModule } from '@angular/cdk/scrolling'; +import { ReactiveFormsModule } from '@angular/forms'; +import { MatCheckboxModule } from '@angular/material/checkbox'; +import { MatInputModule } from '@angular/material/input'; +import { DeviceItemComponent } from '../../components/device-item/device-item.component'; +import { EmptyPageComponent } from '../../components/empty-page/empty-page.component'; +import { ListLayoutComponent } from '../../components/list-layout/list-layout.component'; +import { EntityActionResult } from '../../model/entity-action'; +import { NoEntitySelectedComponent } from '../../components/no-entity-selected/no-entity-selected.component'; +import { LiveAnnouncer } from '@angular/cdk/a11y'; import { CanComponentDeactivate } from '../../guards/can-deactivate.guard'; - -export enum FormAction { - Delete = 'Delete', - Close = 'Close', - Save = 'Save', -} - -export interface FormResponse { - device?: Device; - action: FormAction; - index: number; -} +import { Observable } from 'rxjs/internal/Observable'; +import { of } from 'rxjs/internal/observable/of'; @Component({ selector: 'app-device-repository', templateUrl: './devices.component.html', styleUrls: ['./devices.component.scss'], + imports: [ + CommonModule, + MatToolbarModule, + MatButtonModule, + MatIconModule, + ScrollingModule, + MatDialogModule, + ReactiveFormsModule, + MatCheckboxModule, + MatInputModule, + DeviceItemComponent, + EmptyPageComponent, + ListLayoutComponent, + NoEntitySelectedComponent, + DeviceQualificationFromComponent, + ], providers: [DevicesStore], }) export class DevicesComponent implements OnInit, OnDestroy, CanComponentDeactivate { readonly DeviceView = DeviceView; + readonly LayoutType = LayoutType; + readonly form = viewChild(DeviceQualificationFromComponent); + private readonly focusManagerService = inject(FocusManagerService); + private readonly changeDetectorRef = inject(ChangeDetectorRef); + private readonly liveAnnouncer = inject(LiveAnnouncer); + private readonly route = inject(Router); + private readonly devicesStore = inject(DevicesStore); + dialog = inject(MatDialog); + private element = inject(ElementRef); private destroy$: Subject = new Subject(); viewModel$ = this.devicesStore.viewModel$; - deviceDialog: MatDialogRef | undefined; + isOpenDeviceForm = false; - constructor( - private readonly focusManagerService: FocusManagerService, - public dialog: MatDialog, - private element: ElementRef, - private readonly changeDetectorRef: ChangeDetectorRef, - private route: Router, - private devicesStore: DevicesStore - ) {} + canDeactivate(): Observable { + const form = this.form(); + if (form) { + return form.close(); + } else { + return of(true); + } + } ngOnInit(): void { - combineLatest([ - this.devicesStore.devices$, - this.devicesStore.isOpenAddDevice$, - this.devicesStore.testModules$, - ]) + this.devicesStore.isOpenAddDevice$ .pipe(takeUntil(this.destroy$)) - .subscribe(([devices, isOpenAddDevice, testModules]) => { - if ( - !devices?.filter(device => device.status === DeviceStatus.VALID) - .length && - isOpenAddDevice - ) { - this.openDialog(devices, testModules); + .subscribe(isOpenAddDevice => { + if (isOpenAddDevice) { + this.openForm(); } }); } @@ -97,13 +119,19 @@ export class DevicesComponent this.destroy$.unsubscribe(); } - canDeactivate(): Observable { - this.deviceDialog?.componentInstance?.closeForm(); - return this.dialog.afterAllClosed.pipe( - map(() => { - return true; - }) - ); + menuItemClicked( + { action, entity }: EntityActionResult, + devices: Device[], + testModules: TestModule[] + ) { + switch (action) { + case DeviceAction.StartNewTestrun: + this.openStartTestrun(entity, devices, testModules); + break; + case DeviceAction.Delete: + this.openDeleteDialog(entity); + break; + } } openStartTestrun( @@ -143,166 +171,122 @@ export class DevicesComponent }); } - openDialog( - devices: Device[] = [], - testModules: TestModule[], - initialDevice?: Device, - selectedDevice?: Device, - isEditDevice = false, - index = 0, - deviceIndex?: number - ): void { - this.deviceDialog = this.dialog.open(DeviceQualificationFromComponent, { - ariaLabel: isEditDevice ? 'Edit device' : 'Create Device', - data: { - device: selectedDevice || null, - initialDevice, - title: isEditDevice ? 'Edit device' : 'Create Device', - testModules: testModules, - devices, - index, - isCreate: !isEditDevice, - }, - autoFocus: 'first-tabbable', - hasBackdrop: true, - disableClose: true, - panelClass: 'device-form-dialog', - }); - this.deviceDialog?.beforeClosed().subscribe((response: FormResponse) => { - if (!response) { - this.devicesStore.setIsOpenAddDevice(false); - return; - } - if (response.action === FormAction.Close) { - this.openCloseDialog( - devices, - testModules, - initialDevice, - response.device, - isEditDevice, - response.index, - deviceIndex - ); - } else if (response.action === FormAction.Save && response.device) { - timer(10) - .pipe(takeUntil(this.destroy$)) - .subscribe(() => { - if (!initialDevice) { - this.focusManagerService.focusFirstElementInContainer(); - } else if (deviceIndex !== undefined) { - this.focusSelectedButton(deviceIndex); - } - }); - } - if (response.action === FormAction.Delete && initialDevice) { - if (response.device) { - this.openDeleteDialog( - devices, - testModules, - initialDevice, - response.device, - isEditDevice, - response.index, - deviceIndex! - ); - } - } - }); + async openForm(device: Device | null = null) { + this.devicesStore.selectDevice(device); + this.isOpenDeviceForm = true; + await this.liveAnnouncer.announce('Device qualification form'); + this.focusManagerService.focusFirstElementInContainer( + window.document.querySelector('app-device-qualification-from') + ); } - openCloseDialog( - devices: Device[], - testModules: TestModule[], - initialDevice?: Device, - device?: Device, - isEditDevice = false, - index = 0, - deviceIndex?: number - ) { - const dialogRef = this.dialog.open(SimpleDialogComponent, { - ariaLabel: 'Close the Device menu', - data: { - title: 'Are you sure?', - content: `By closing the device profile you will loose any new changes you have made to the device.`, - }, - autoFocus: true, - hasBackdrop: true, - disableClose: true, - panelClass: 'simple-dialog', + save(device: Device, initialDevice: Device | null) { + this.updateDevice(device, initialDevice, (index: number) => { + this.devicesStore.selectDevice(device); + this.focusDevice(index); }); + } - dialogRef?.beforeClosed().subscribe(close => { - if (!close) { - this.openDialog( - devices, - testModules, - initialDevice, - device, - isEditDevice, - index, - deviceIndex - ); - } else if (deviceIndex !== undefined) { - this.focusSelectedButton(deviceIndex); - } else { - this.focusManagerService.focusFirstElementInContainer(); - } - }); + discard() { + this.openCloseDialog(); } - openDeleteDialog( - devices: Device[], - testModules: TestModule[], - initialDevice: Device, + delete(device: Device) { + this.openDeleteDialog(device); + } + + private updateDevice( device: Device, - isEditDevice = false, - index = 0, - deviceIndex: number + initialDevice: Device | null = null, + callback: (idx: number) => void ) { + if (initialDevice) { + this.devicesStore.editDevice({ + device, + mac_addr: initialDevice.mac_addr, + onSuccess: callback, + }); + } else { + this.devicesStore.saveDevice({ device, onSuccess: callback }); + } + } + + openDeleteDialog(device: Device) { const dialogRef = this.dialog.open(SimpleDialogComponent, { ariaLabel: 'Delete device', data: { title: 'Delete device?', content: `You are about to delete ${ - initialDevice.manufacturer + ' ' + initialDevice.model + device.manufacturer + ' ' + device.model }. Are you sure?`, - device: device, }, autoFocus: true, hasBackdrop: true, disableClose: true, - panelClass: 'simple-dialog', + panelClass: ['simple-dialog', 'delete-dialog'], }); dialogRef?.beforeClosed().subscribe(deleteDevice => { if (deleteDevice) { this.devicesStore.deleteDevice({ - device: initialDevice, - onDelete: () => { + device: device, + onDelete: (deviceIndex = 0) => { + this.isOpenDeviceForm = false; this.focusNextButton(deviceIndex); }, }); - } else { - this.openDialog( - devices, - testModules, - initialDevice, - device, - isEditDevice, - index, - deviceIndex - ); } }); } - private focusSelectedButton(index: number) { - const selected = this.element.nativeElement.querySelectorAll( - 'app-device-item .button-edit' - )[index]; - if (selected) { - selected.focus(); + deviceIsDisabled(mac_addr?: string) { + return (device: Device) => { + return device.mac_addr === mac_addr; + }; + } + + getDeviceTooltip(mac_addr?: string) { + return (device: Device) => { + if (this.deviceIsDisabled(mac_addr)(device)) { + return 'Device under test'; + } + return ''; + }; + } + + private openCloseDialog() { + const dialogRef = this.dialog.open(SimpleDialogComponent, { + ariaLabel: 'Discard the Device changes', + data: { + title: 'Discard changes?', + content: `You have unsaved changes that would be permanently lost.`, + confirmName: 'Discard', + }, + autoFocus: true, + hasBackdrop: true, + disableClose: true, + panelClass: ['simple-dialog', 'discard-dialog'], + }); + + dialogRef?.beforeClosed().subscribe(close => { + if (close) { + this.isOpenDeviceForm = false; + this.devicesStore.selectDevice(null); + this.focusSelectedButton(); + } + }); + } + + private focusSelectedButton() { + const selectedButton = this.element.nativeElement.querySelector( + 'app-device-item.selected .button-edit' + ); + if (selectedButton) { + selectedButton.focus(); + } else { + this.focusAddButton(); } } + private focusNextButton(index: number) { this.changeDetectorRef.detectChanges(); // Try to focus next device item, if exist @@ -313,9 +297,27 @@ export class DevicesComponent next.focus(); } else { // If next device item doest not exist, add device button should be focused - const addButton = + this.focusAddButton(); + } + } + + private focusDevice(index: number) { + this.changeDetectorRef.detectChanges(); + const device = this.element.nativeElement.querySelectorAll( + 'app-device-item .button-edit' + )[index]; + device?.focus(); + } + + private focusAddButton(): void { + let addButton = + this.element.nativeElement.querySelector('.add-entity-button'); + if (!addButton) { + addButton = this.element.nativeElement.querySelector('.device-add-button'); - addButton?.focus(); } + timer(100).subscribe(() => { + addButton?.focus(); + }); } } diff --git a/modules/ui/src/app/pages/devices/devices.module.ts b/modules/ui/src/app/pages/devices/devices.module.ts deleted file mode 100644 index 32f8a2865..000000000 --- a/modules/ui/src/app/pages/devices/devices.module.ts +++ /dev/null @@ -1,59 +0,0 @@ -/** - * Copyright 2023 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import { ScrollingModule } from '@angular/cdk/scrolling'; -import { CommonModule } from '@angular/common'; -import { HttpClientModule } from '@angular/common/http'; -import { NgModule } from '@angular/core'; -import { ReactiveFormsModule } from '@angular/forms'; -import { MatButtonModule } from '@angular/material/button'; -import { MatCheckboxModule } from '@angular/material/checkbox'; -import { MatDialogModule } from '@angular/material/dialog'; -import { MatIconModule } from '@angular/material/icon'; -import { MatInputModule } from '@angular/material/input'; -import { MatToolbarModule } from '@angular/material/toolbar'; - -import { DevicesRoutingModule } from './devices-routing.module'; -import { DevicesComponent } from './devices.component'; -import { DeviceItemComponent } from '../../components/device-item/device-item.component'; -import { DeviceTestsComponent } from '../../components/device-tests/device-tests.component'; -import { SpinnerComponent } from '../../components/spinner/spinner.component'; -import { SimpleDialogComponent } from '../../components/simple-dialog/simple-dialog.component'; -import { NgxMaskDirective, NgxMaskPipe, provideNgxMask } from 'ngx-mask'; - -@NgModule({ - declarations: [DevicesComponent], - imports: [ - CommonModule, - DevicesRoutingModule, - MatToolbarModule, - MatButtonModule, - MatIconModule, - ScrollingModule, - HttpClientModule, - MatDialogModule, - ReactiveFormsModule, - MatCheckboxModule, - MatInputModule, - DeviceItemComponent, - DeviceTestsComponent, - SpinnerComponent, - SimpleDialogComponent, - NgxMaskDirective, - NgxMaskPipe, - ], - providers: [provideNgxMask()], -}) -export class DevicesModule {} diff --git a/modules/ui/src/app/pages/devices/devices.store.spec.ts b/modules/ui/src/app/pages/devices/devices.store.spec.ts index 7dec601f8..e355963e6 100644 --- a/modules/ui/src/app/pages/devices/devices.store.spec.ts +++ b/modules/ui/src/app/pages/devices/devices.store.spec.ts @@ -33,6 +33,7 @@ import { import { selectDevices, selectIsOpenAddDevice } from '../../store/selectors'; import { DevicesStore } from './devices.store'; import { MOCK_PROGRESS_DATA_IN_PROGRESS } from '../../mocks/testrun.mock'; +import { DeviceAction } from '../../model/device'; describe('DevicesStore', () => { let devicesStore: DevicesStore; @@ -92,6 +93,13 @@ describe('DevicesStore', () => { selectedDevice: null, deviceInProgress: device, testModules: [], + actions: [ + { + action: DeviceAction.StartNewTestrun, + svgIcon: 'testrun_logo_small', + }, + { action: DeviceAction.Delete, icon: 'delete' }, + ], }); done(); }); diff --git a/modules/ui/src/app/pages/devices/devices.store.ts b/modules/ui/src/app/pages/devices/devices.store.ts index c9d5e9523..64e994f1c 100644 --- a/modules/ui/src/app/pages/devices/devices.store.ts +++ b/modules/ui/src/app/pages/devices/devices.store.ts @@ -14,12 +14,12 @@ * limitations under the License. */ -import { Injectable } from '@angular/core'; +import { Injectable, inject } from '@angular/core'; import { ComponentStore } from '@ngrx/component-store'; import { TestRunService } from '../../services/test-run.service'; import { exhaustMap } from 'rxjs'; import { tap, withLatestFrom } from 'rxjs/operators'; -import { Device, TestModule } from '../../model/device'; +import { Device, DeviceAction, TestModule } from '../../model/device'; import { AppState } from '../../store/state'; import { Store } from '@ngrx/store'; import { @@ -34,29 +34,36 @@ import { setIsOpenAddDevice, } from '../../store/actions'; import { TestrunStatus } from '../../model/testrun-status'; -import { DeviceQuestionnaireSection } from '../../model/device'; +import { EntityAction } from '../../model/entity-action'; +import { QuestionFormat } from '../../model/question'; export interface DevicesComponentState { devices: Device[]; selectedDevice: Device | null; testModules: TestModule[]; - questionnaireFormat: DeviceQuestionnaireSection[]; + questionnaireFormat: QuestionFormat[]; + actions: EntityAction[]; } @Injectable() export class DevicesStore extends ComponentStore { + private testRunService = inject(TestRunService); + private store = inject>(Store); + devices$ = this.store.select(selectDevices); isOpenAddDevice$ = this.store.select(selectIsOpenAddDevice); testModules$ = this.store.select(selectTestModules); questionnaireFormat$ = this.select(state => state.questionnaireFormat); private deviceInProgress$ = this.store.select(selectDeviceInProgress); private selectedDevice$ = this.select(state => state.selectedDevice); + private actions$ = this.select(state => state.actions); viewModel$ = this.select({ devices: this.devices$, selectedDevice: this.selectedDevice$, deviceInProgress: this.deviceInProgress$, testModules: this.testModules$, + actions: this.actions$, }); selectDevice = this.updater((state, device: Device | null) => ({ @@ -65,14 +72,14 @@ export class DevicesStore extends ComponentStore { })); updateQuestionnaireFormat = this.updater( - (state, questionnaireFormat: DeviceQuestionnaireSection[]) => ({ + (state, questionnaireFormat: QuestionFormat[]) => ({ ...state, questionnaireFormat, }) ); deleteDevice = this.effect<{ device: Device; - onDelete: () => void; + onDelete: (idx: number) => void; }>(trigger$ => { return trigger$.pipe( exhaustMap(({ device, onDelete }) => { @@ -80,8 +87,11 @@ export class DevicesStore extends ComponentStore { withLatestFrom(this.devices$), tap(([deleted, devices]) => { if (deleted) { + const idx = devices.findIndex( + item => device.mac_addr === item.mac_addr + ); this.removeDevice(device, devices); - onDelete(); + onDelete(idx); } }) ); @@ -89,28 +99,29 @@ export class DevicesStore extends ComponentStore { ); }); - saveDevice = this.effect<{ device: Device; onSuccess: () => void }>( - trigger$ => { - return trigger$.pipe( - exhaustMap(({ device, onSuccess }) => { - return this.testRunService.saveDevice(device).pipe( - withLatestFrom(this.devices$), - tap(([added, devices]) => { - if (added) { - this.addDevice(device, devices); - onSuccess(); - } - }) - ); - }) - ); - } - ); + saveDevice = this.effect<{ + device: Device; + onSuccess: (idx: number) => void; + }>(trigger$ => { + return trigger$.pipe( + exhaustMap(({ device, onSuccess }) => { + return this.testRunService.saveDevice(device).pipe( + withLatestFrom(this.devices$), + tap(([added, devices]) => { + if (added) { + this.addDevice(device, devices); + onSuccess(0); + } + }) + ); + }) + ); + }); editDevice = this.effect<{ device: Device; mac_addr: string; - onSuccess: () => void; + onSuccess: (idx: number) => void; }>(trigger$ => { return trigger$.pipe( exhaustMap(({ device, mac_addr, onSuccess }) => { @@ -118,8 +129,11 @@ export class DevicesStore extends ComponentStore { withLatestFrom(this.devices$), tap(([edited, devices]) => { if (edited) { + const idx = devices.findIndex( + item => device.mac_addr === item.mac_addr + ); this.updateDevice(device, mac_addr, devices); - onSuccess(); + onSuccess(idx); } }) ); @@ -151,7 +165,7 @@ export class DevicesStore extends ComponentStore { return trigger$.pipe( exhaustMap(() => { return this.testRunService.fetchQuestionnaireFormat().pipe( - tap((questionnaireFormat: DeviceQuestionnaireSection[]) => { + tap((questionnaireFormat: QuestionFormat[]) => { this.updateQuestionnaireFormat(questionnaireFormat); }) ); @@ -160,7 +174,7 @@ export class DevicesStore extends ComponentStore { }); private addDevice(device: Device, devices: Device[]): void { - this.updateDevices(devices.concat([device])); + this.updateDevices([device, ...devices]); } private updateDevice( @@ -191,15 +205,16 @@ export class DevicesStore extends ComponentStore { this.store.dispatch(setDevices({ devices })); } - constructor( - private testRunService: TestRunService, - private store: Store - ) { + constructor() { super({ devices: [], selectedDevice: null, testModules: [], questionnaireFormat: [], + actions: [ + { action: DeviceAction.StartNewTestrun, svgIcon: 'testrun_logo_small' }, + { action: DeviceAction.Delete, icon: 'delete' }, + ], }); } } diff --git a/modules/ui/src/app/pages/settings/components/settings-dropdown/settings-dropdown.component.html b/modules/ui/src/app/pages/general-settings/components/settings-dropdown/settings-dropdown.component.html similarity index 73% rename from modules/ui/src/app/pages/settings/components/settings-dropdown/settings-dropdown.component.html rename to modules/ui/src/app/pages/general-settings/components/settings-dropdown/settings-dropdown.component.html index 7157db8d3..ea6194595 100644 --- a/modules/ui/src/app/pages/settings/components/settings-dropdown/settings-dropdown.component.html +++ b/modules/ui/src/app/pages/general-settings/components/settings-dropdown/settings-dropdown.component.html @@ -1,19 +1,22 @@ - -

- {{ description }} -

+
+ +

+ {{ description }} +

+
+ - {{ label }} @@ -35,3 +38,6 @@ +
+ +
diff --git a/modules/ui/src/app/pages/general-settings/components/settings-dropdown/settings-dropdown.component.scss b/modules/ui/src/app/pages/general-settings/components/settings-dropdown/settings-dropdown.component.scss new file mode 100644 index 000000000..e17e02add --- /dev/null +++ b/modules/ui/src/app/pages/general-settings/components/settings-dropdown/settings-dropdown.component.scss @@ -0,0 +1,104 @@ +@use '@angular/material' as mat; +@use 'colors'; +@use 'variables'; + +:host { + display: grid; + grid-template-columns: 1fr 1fr 1.1fr; + gap: 46px; + padding: 8px 0; +} + +.setting-form-label { + font-size: 16px; + line-height: 24px; + font-weight: 500; + color: colors.$on-surface; +} + +:host:has(.two-ports-message) .internet-label { + padding-top: 16px; +} + +.setting-label-description { + margin: 0; + font-size: 14px; + line-height: 20px; + letter-spacing: 0; + color: colors.$on-surface-variant; +} + +.setting-option-value { + padding: 8px 16px; +} + +.option-value { + margin: 0; + font-family: variables.$font-text; + font-size: 16px; + font-style: normal; + font-weight: 400; + line-height: 22px; + letter-spacing: 0; + + &.top { + color: colors.$on-surface-variant; + } + + &.bottom { + color: colors.$schemes-outline; + } +} + +.setting-field { + width: 100%; + padding: 10px 0; + + &.mat-form-field-disabled { + opacity: 0.6; + } + + &::ng-deep.mat-mdc-form-field-subscript-wrapper { + display: none; + } + + ::ng-deep .mat-mdc-form-field-infix { + min-height: 60px; + height: 60px; + display: flex; + align-items: center; + padding-top: 9px; + padding-bottom: 6px; + } + + ::ng-deep .mat-mdc-floating-label { + font-family: Roboto; + font-size: 14px; + font-style: normal; + font-weight: 400; + line-height: 20px; + letter-spacing: 0.2px; + } + + ::ng-deep .mat-mdc-floating-label:not(.mdc-floating-label--float-above) { + top: 28px; + } +} + +.label-column { + display: flex; + flex-direction: column; + justify-content: center; + padding: 0 16px; + color: colors.$on-surface-variant; + font-family: variables.$font-text; +} + +::ng-deep .info-column { + display: flex; + flex-direction: column; + gap: 6px; + width: 260px; + padding: 0 12px; + justify-content: center; +} diff --git a/modules/ui/src/app/pages/settings/components/settings-dropdown/settings-dropdown.component.spec.ts b/modules/ui/src/app/pages/general-settings/components/settings-dropdown/settings-dropdown.component.spec.ts similarity index 88% rename from modules/ui/src/app/pages/settings/components/settings-dropdown/settings-dropdown.component.spec.ts rename to modules/ui/src/app/pages/general-settings/components/settings-dropdown/settings-dropdown.component.spec.ts index b662b0a40..b3a022509 100644 --- a/modules/ui/src/app/pages/settings/components/settings-dropdown/settings-dropdown.component.spec.ts +++ b/modules/ui/src/app/pages/general-settings/components/settings-dropdown/settings-dropdown.component.spec.ts @@ -7,16 +7,19 @@ import { FormsModule, ReactiveFormsModule, } from '@angular/forms'; -import { Component } from '@angular/core'; +import { Component, inject } from '@angular/core'; @Component({ template: '
' + '
', + standalone: false, }) class DummyComponent { + private readonly fb = inject(FormBuilder); + public testForm!: FormGroup; - constructor(private readonly fb: FormBuilder) { + constructor() { this.testForm = this.fb.group({ test: [''], }); diff --git a/modules/ui/src/app/pages/settings/components/settings-dropdown/settings-dropdown.component.ts b/modules/ui/src/app/pages/general-settings/components/settings-dropdown/settings-dropdown.component.ts similarity index 98% rename from modules/ui/src/app/pages/settings/components/settings-dropdown/settings-dropdown.component.ts rename to modules/ui/src/app/pages/general-settings/components/settings-dropdown/settings-dropdown.component.ts index 1fa20d0cb..233f84cbd 100644 --- a/modules/ui/src/app/pages/settings/components/settings-dropdown/settings-dropdown.component.ts +++ b/modules/ui/src/app/pages/general-settings/components/settings-dropdown/settings-dropdown.component.ts @@ -18,7 +18,7 @@ import { KeyValuePipe, NgForOf, NgIf } from '@angular/common'; @Component({ selector: 'app-settings-dropdown', - standalone: true, + imports: [ ReactiveFormsModule, MatFormFieldModule, diff --git a/modules/ui/src/app/pages/general-settings/general-settings.component.html b/modules/ui/src/app/pages/general-settings/general-settings.component.html new file mode 100644 index 000000000..9b95aab53 --- /dev/null +++ b/modules/ui/src/app/pages/general-settings/general-settings.component.html @@ -0,0 +1,210 @@ + +
+ + To change settings, you need to stop testing. + +
+
+ + + + + + + + + + + + + + + + + + + + + + +
+ + Both interfaces must have different values + + +
+ +
+
+
+ + Warning! No ports detected. + +
+
+ +
+
+
+ + +
+
+

{{ label }}

+

+ {{ description }} +

+
+
+ + + + + + +
+
+

+ By installing and running Testrun, you understand and accept the Terms + of Service found + here +

+
+
+
+ +

+ Single port +

+

+ Two ports +

+
+ +

+ + Opt out from Google Analytics + +

+
+ +

+ Internet port is disabled because you selected single port mode +

+
+ + +

This port is required

+ +
+
+ +

+ If a port is missing from this list, you can + + Refresh + + the System settings +

+
+
+ diff --git a/modules/ui/src/app/pages/general-settings/general-settings.component.scss b/modules/ui/src/app/pages/general-settings/general-settings.component.scss new file mode 100644 index 000000000..d476e3b20 --- /dev/null +++ b/modules/ui/src/app/pages/general-settings/general-settings.component.scss @@ -0,0 +1,127 @@ +/** + * Copyright 2023 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +@use '@angular/material' as mat; +@use 'colors'; +@use 'variables'; + +:host { + display: flex; + flex-direction: column; + flex: 1 0 auto; + padding-top: 4px; +} + +.setting-drawer-content { + overflow-y: scroll; + height: max-content; + padding: 8px 32px; + + .setting-drawer-content-form-empty { + grid-template-rows: repeat(2, auto) 1fr; + } + ::ng-deep .callout-container { + margin: 10px 0; + } +} + +.error-message-container { + display: block; + margin-top: auto; + padding-bottom: 8px; + text-align: center; +} + +.message { + margin: 0; + color: colors.$on-surface-variant; + font-family: variables.$font-text; + font-size: 14px; + line-height: 20px; + letter-spacing: 0; +} + +.setting-drawer-footer { + padding: 16px 40px; + margin-top: auto; + display: flex; + flex-shrink: 0; + justify-content: flex-end; + + .save-button { + padding: 0 24px; + font-size: 14px; + font-weight: 500; + line-height: 20px; + letter-spacing: 0.25px; + &::ng-deep .mat-focus-indicator { + display: none; + } + } +} + +.settings-drawer-header-button:not(.mat-mdc-button-disabled), +.close-button:not(.mat-mdc-button-disabled), +.save-button:not(.mat-mdc-button-disabled) { + cursor: pointer; + pointer-events: auto; +} + +.section-item { + min-height: 88px; + display: grid; + grid-template-columns: 1fr 1fr 1.1fr; + gap: 46px; + padding: 8px 0; + + p { + margin: 0; + } + + .label-column { + display: flex; + flex-direction: column; + justify-content: center; + padding: 0 16px; + color: colors.$on-surface-variant; + font-family: variables.$font-text; + } + + .data-column { + display: flex; + } + + .setting-form-label, + .setting-data { + font-size: 16px; + line-height: 24px; + font-weight: 500; + color: colors.$on-surface; + } + + .setting-data { + display: flex; + align-items: center; + font-weight: 400; + } + + .setting-label-description { + margin: 0; + font-size: 14px; + line-height: 20px; + letter-spacing: 0; + color: colors.$on-surface-variant; + } +} diff --git a/modules/ui/src/app/pages/general-settings/general-settings.component.spec.ts b/modules/ui/src/app/pages/general-settings/general-settings.component.spec.ts new file mode 100644 index 000000000..ceef25c51 --- /dev/null +++ b/modules/ui/src/app/pages/general-settings/general-settings.component.spec.ts @@ -0,0 +1,343 @@ +/** + * Copyright 2023 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { GeneralSettingsComponent } from './general-settings.component'; +import { of } from 'rxjs'; +import { MatRadioModule } from '@angular/material/radio'; +import { ReactiveFormsModule } from '@angular/forms'; +import { MatButtonModule } from '@angular/material/button'; +import { MatIcon, MatIconModule } from '@angular/material/icon'; +import { MatIconTestingModule } from '@angular/material/icon/testing'; +import { Component, Input } from '@angular/core'; +import SpyObj = jasmine.SpyObj; +import { MatInputModule } from '@angular/material/input'; +import { MatSelectModule } from '@angular/material/select'; +import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; +import { provideMockStore } from '@ngrx/store/testing'; +import { LoaderService } from '../../services/loader.service'; +import { GeneralSettingsStore } from './general-settings.store'; +import { + MOCK_INTERFACES, + MOCK_SYSTEM_CONFIG_WITH_DATA, +} from '../../mocks/settings.mock'; +import { SettingsDropdownComponent } from './components/settings-dropdown/settings-dropdown.component'; +import { CalloutComponent } from '../../components/callout/callout.component'; +import { SpinnerComponent } from '../../components/spinner/spinner.component'; +import { TestRunService } from '../../services/test-run.service'; +import { MOCK_PROGRESS_DATA_COMPLIANT } from '../../mocks/testrun.mock'; + +describe('GeneralSettingsComponent', () => { + let component: GeneralSettingsComponent; + let fixture: ComponentFixture; + let compiled: HTMLElement; + let mockLoaderService: SpyObj; + let mockTestRunService: SpyObj; + let mockSettingsStore: SpyObj; + + beforeEach(async () => { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (window).gtag = jasmine.createSpy('gtag'); + mockLoaderService = jasmine.createSpyObj('LoaderService', ['setLoading']); + mockTestRunService = jasmine.createSpyObj('TesRunService', [ + 'testrunInProgress', + ]); + mockSettingsStore = jasmine.createSpyObj('SettingsStore', [ + 'getInterfaces', + 'updateSystemConfig', + 'setIsSubmitting', + 'setDefaultFormValues', + 'setFormDisable', + 'setFormEnable', + 'getSystemConfig', + 'viewModel$', + 'systemStatus$', + ]); + mockSettingsStore.systemStatus$ = of(MOCK_PROGRESS_DATA_COMPLIANT); + + await TestBed.configureTestingModule({ + providers: [ + { provide: LoaderService, useValue: mockLoaderService }, + { provide: TestRunService, useValue: mockTestRunService }, + { provide: GeneralSettingsStore, useValue: mockSettingsStore }, + provideMockStore(), + ], + imports: [ + GeneralSettingsComponent, + BrowserAnimationsModule, + MatButtonModule, + MatIconModule, + MatRadioModule, + ReactiveFormsModule, + MatIconTestingModule, + MatIcon, + MatInputModule, + MatSelectModule, + SettingsDropdownComponent, + FakeSpinnerComponent, + FakeCalloutComponent, + ], + }) + .overrideComponent(GeneralSettingsComponent, { + remove: { + imports: [CalloutComponent, SpinnerComponent], + }, + add: { + imports: [FakeSpinnerComponent, FakeCalloutComponent], + }, + }) + .compileComponents(); + + TestBed.overrideProvider(GeneralSettingsStore, { + useValue: mockSettingsStore, + }); + + fixture = TestBed.createComponent(GeneralSettingsComponent); + + component = fixture.componentInstance; + component.viewModel$ = of({ + systemConfig: { network: {} }, + hasConnectionSettings: false, + isSubmitting: false, + isLessThanOneInterface: false, + interfaces: {}, + deviceOptions: {}, + internetOptions: {}, + logLevelOptions: {}, + monitoringPeriodOptions: {}, + }); + fixture.detectChanges(); + compiled = fixture.nativeElement as HTMLElement; + + component.ngOnInit(); + }); + + afterEach(() => { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (window).gtag = undefined; + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); + + it('#reloadSetting should call setLoading in loaderService', () => { + component.reloadSetting(); + + expect(mockLoaderService.setLoading).toHaveBeenCalledWith(true); + }); + + describe('#settingsDisable', () => { + it('should disable setting form when get settingDisable as true ', () => { + component.settingsDisable = true; + + expect(mockSettingsStore.setFormDisable).toHaveBeenCalled(); + }); + + it('should enable setting form when get settingDisable as false ', () => { + component.settingsDisable = false; + + expect(mockSettingsStore.setFormEnable).toHaveBeenCalled(); + }); + + it('should disable "Save" button when get settingDisable as true', () => { + component.settingsDisable = true; + + const saveBtn = compiled.querySelector( + '.save-button' + ) as HTMLButtonElement; + + expect(saveBtn.disabled).toBeTrue(); + }); + }); + + describe('#saveSetting', () => { + beforeEach(() => { + component.ngOnInit(); + }); + + it('should have form error if form has the same value', () => { + const mockSameValue = { key: 'sameValue' }; + component.deviceControl.setValue(mockSameValue); + component.internetControl.setValue(mockSameValue); + + component.saveSetting(); + + expect(component.settingForm.invalid).toBeTrue(); + expect(component.isFormError).toBeTrue(); + expect(mockSettingsStore.setIsSubmitting).toHaveBeenCalledWith(true); + }); + + it('should call createSystemConfig when setting form valid', () => { + const expectedResult = { + network: { + device_intf: 'mockDeviceKey', + internet_intf: '', + }, + log_level: 'INFO', + monitor_period: 600, + }; + + component.deviceControl.setValue({ + key: 'mockDeviceKey', + value: 'mockDeviceValue', + }); + + component.internetControl.setValue({ + key: '', + value: 'defaultValue', + }); + + component.logLevel.setValue({ + key: 'INFO', + value: '', + }); + + component.monitorPeriod.setValue({ + key: '600', + value: '', + }); + + component.saveSetting(); + + const args = mockSettingsStore.updateSystemConfig.calls.argsFor(0); + // @ts-expect-error config is in object + expect(args[0].config).toEqual(expectedResult); + expect(component.settingForm.invalid).toBeFalse(); + expect(mockSettingsStore.updateSystemConfig).toHaveBeenCalled(); + }); + }); + + describe('with no interfaces data', () => { + beforeEach(() => { + component.viewModel$ = of({ + systemConfig: { network: {} }, + hasConnectionSettings: false, + isSubmitting: false, + isLessThanOneInterface: false, + interfaces: {}, + deviceOptions: {}, + internetOptions: {}, + logLevelOptions: {}, + monitoringPeriodOptions: {}, + }); + fixture.detectChanges(); + }); + + it('should have callout component', () => { + const callout = compiled.querySelector('app-callout'); + + expect(callout).toBeTruthy(); + }); + + it('should have disabled "Save" button', () => { + const saveBtn = compiled.querySelector( + '.save-button' + ) as HTMLButtonElement; + + expect(saveBtn.disabled).toBeTrue(); + }); + }); + + describe('with interfaces length less than one', () => { + beforeEach(() => { + component.viewModel$ = of({ + systemConfig: { network: {} }, + hasConnectionSettings: false, + isSubmitting: false, + isLessThanOneInterface: true, + interfaces: {}, + deviceOptions: {}, + internetOptions: {}, + logLevelOptions: {}, + monitoringPeriodOptions: {}, + }); + fixture.detectChanges(); + }); + + it('should have disabled "Save" button', () => { + component.deviceControl.setValue( + MOCK_SYSTEM_CONFIG_WITH_DATA?.network?.device_intf + ); + component.internetControl.setValue( + MOCK_SYSTEM_CONFIG_WITH_DATA?.network?.internet_intf + ); + fixture.detectChanges(); + + const saveBtn = compiled.querySelector( + '.save-button' + ) as HTMLButtonElement; + + expect(saveBtn.disabled).toBeTrue(); + }); + }); + + describe('with interfaces length more then one', () => { + beforeEach(() => { + component.viewModel$ = of({ + systemConfig: { network: {} }, + hasConnectionSettings: false, + isSubmitting: false, + isLessThanOneInterface: false, + interfaces: MOCK_INTERFACES, + deviceOptions: MOCK_INTERFACES, + internetOptions: MOCK_INTERFACES, + logLevelOptions: {}, + monitoringPeriodOptions: {}, + }); + fixture.detectChanges(); + }); + + it('should not have callout component', () => { + const callout = compiled.querySelector('app-callout'); + + expect(callout).toBeFalsy(); + }); + + it('should not have disabled "Save" button', () => { + component.deviceControl.setValue({ + key: MOCK_SYSTEM_CONFIG_WITH_DATA?.network?.device_intf, + value: 'value', + }); + component.internetControl.setValue({ + key: MOCK_SYSTEM_CONFIG_WITH_DATA?.network?.internet_intf, + value: 'value', + }); + component.settingForm.markAsDirty(); + fixture.detectChanges(); + + const saveBtn = compiled.querySelector( + '.save-button' + ) as HTMLButtonElement; + + expect(saveBtn.disabled).toBeFalse(); + }); + }); +}); + +@Component({ + selector: 'app-spinner', + template: '
', +}) +class FakeSpinnerComponent {} + +@Component({ + selector: 'app-callout', + template: '
', +}) +class FakeCalloutComponent { + @Input() type = ''; +} diff --git a/modules/ui/src/app/pages/general-settings/general-settings.component.ts b/modules/ui/src/app/pages/general-settings/general-settings.component.ts new file mode 100644 index 000000000..819f2af13 --- /dev/null +++ b/modules/ui/src/app/pages/general-settings/general-settings.component.ts @@ -0,0 +1,297 @@ +/** + * Copyright 2023 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { + ChangeDetectionStrategy, + Component, + OnDestroy, + OnInit, + inject, +} from '@angular/core'; +import { + FormBuilder, + FormControl, + FormGroup, + FormsModule, + ReactiveFormsModule, +} from '@angular/forms'; +import { Subject, takeUntil, tap, timer } from 'rxjs'; +import { OnlyDifferentValuesValidator } from './only-different-values.validator'; +import { CalloutType } from '../../model/callout-type'; +import { FormKey, SystemConfig } from '../../model/setting'; +import { GeneralSettingsStore } from './general-settings.store'; +import { LoaderService } from '../../services/loader.service'; +import { MatToolbarModule } from '@angular/material/toolbar'; +import { MatFormFieldModule } from '@angular/material/form-field'; +import { MatButtonToggleModule } from '@angular/material/button-toggle'; +import { MatInputModule } from '@angular/material/input'; +import { MatSnackBarModule } from '@angular/material/snack-bar'; +import { SpinnerComponent } from '../../components/spinner/spinner.component'; +import { MatTooltipModule } from '@angular/material/tooltip'; +import { MatButtonModule } from '@angular/material/button'; +import { SettingsDropdownComponent } from './components/settings-dropdown/settings-dropdown.component'; +import { MatSelectModule } from '@angular/material/select'; +import { MatIconModule } from '@angular/material/icon'; +import { MatSidenavModule } from '@angular/material/sidenav'; +import { MatRadioModule } from '@angular/material/radio'; +import { MatDividerModule } from '@angular/material/divider'; +import { CalloutComponent } from '../../components/callout/callout.component'; +import { CommonModule } from '@angular/common'; +import { MatCheckbox } from '@angular/material/checkbox'; +import { TestRunService } from '../../services/test-run.service'; +import { Router } from '@angular/router'; +import { Routes } from '../../model/routes'; +import { FocusManagerService } from '../../services/focus-manager.service'; +import { LocalStorageService } from '../../services/local-storage.service'; + +// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type +declare const gtag: Function; + +@Component({ + selector: 'app-general-settings', + templateUrl: './general-settings.component.html', + styleUrls: ['./general-settings.component.scss'], + imports: [ + MatButtonModule, + MatIconModule, + MatToolbarModule, + MatSidenavModule, + MatButtonToggleModule, + MatRadioModule, + MatInputModule, + MatSelectModule, + MatTooltipModule, + ReactiveFormsModule, + MatFormFieldModule, + MatSnackBarModule, + MatDividerModule, + MatCheckbox, + FormsModule, + SpinnerComponent, + CalloutComponent, + SettingsDropdownComponent, + CommonModule, + ], + providers: [GeneralSettingsStore], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class GeneralSettingsComponent implements OnInit, OnDestroy { + private readonly fb = inject(FormBuilder); + private readonly onlyDifferentValuesValidator = inject( + OnlyDifferentValuesValidator + ); + private settingsStore = inject(GeneralSettingsStore); + private readonly loaderService = inject(LoaderService); + private readonly focusManagerService = inject(FocusManagerService); + private readonly localStorageService = inject(LocalStorageService); + + private isSettingsDisable = false; + get settingsDisable(): boolean { + return this.isSettingsDisable; + } + set settingsDisable(value: boolean) { + this.isSettingsDisable = value; + if (value) { + this.disableSettings(); + } else { + this.enableSettings(); + } + } + public readonly CalloutType = CalloutType; + public readonly FormKey = FormKey; + public settingForm!: FormGroup; + public analyticsForm!: FormGroup; + public readonly Routes = Routes; + viewModel$ = this.settingsStore.viewModel$; + + private destroy$: Subject = new Subject(); + private testRunService = inject(TestRunService); + private route = inject(Router); + + get deviceControl(): FormControl { + return this.settingForm.get(FormKey.DEVICE) as FormControl; + } + + get internetControl(): FormControl { + return this.settingForm.get(FormKey.INTERNET) as FormControl; + } + + get logLevel(): FormControl { + return this.settingForm.get(FormKey.LOG_LEVEL) as FormControl; + } + + get monitorPeriod(): FormControl { + return this.settingForm.get(FormKey.MONITOR_PERIOD) as FormControl; + } + + get isFormValues(): boolean { + return ( + this.deviceControl?.value?.value && + (this.isInternetControlDisabled || this.internetControl?.value?.value) + ); + } + + get isInternetControlDisabled(): boolean { + return this.internetControl?.disabled; + } + + get isFormError(): boolean { + return this.settingForm.hasError('hasSameValues'); + } + + ngOnInit() { + this.createSettingForm(); + this.createAnalyticsForm(); + this.cleanFormErrorMessage(); + this.settingsStore.getInterfaces(); + this.getSystemConfig(); + this.setDefaultFormValues(); + this.settingsStore.systemStatus$ + .pipe(takeUntil(this.destroy$)) + .subscribe(systemStatus => { + if (systemStatus?.status) { + const isTestrunInProgress = this.testRunService.testrunInProgress( + systemStatus.status + ); + if (isTestrunInProgress !== this.isSettingsDisable) { + this.settingsDisable = isTestrunInProgress; + } + } + }); + } + + navigateToRuntime(): void { + this.route.navigate([Routes.Testing]); + } + + reloadSetting(): void { + this.showLoading(); + this.getSystemInterfaces(); + this.getSystemConfig(); + this.setDefaultFormValues(); + } + + saveSetting(): void { + if (this.settingForm.invalid) { + this.settingsStore.setIsSubmitting(true); + this.settingForm.markAllAsTouched(); + } else { + this.createSystemConfig(); + this.settingForm.markAsPristine(); + this.analyticsForm.markAsPristine(); + this.setFocus(); + } + } + + private setFocus(): void { + timer(200).subscribe(() => { + const helpTip = window.document.querySelector( + 'app-help-tip:not(.closed-tip)' + ); + const focusableContainer = helpTip + ? helpTip + : window.document.querySelector('app-settings'); + + this.focusManagerService.focusFirstElementInContainer(focusableContainer); + }); + } + + private disableSettings(): void { + this.settingsStore.setFormDisable(this.settingForm); + } + + private enableSettings(): void { + this.settingsStore.setFormEnable(this.settingForm); + } + + private createSettingForm() { + this.settingForm = this.fb.group( + { + device_intf: [''], + internet_intf: [''], + log_level: [''], + monitor_period: [''], + }, + { + validators: [this.onlyDifferentValuesValidator.onlyDifferentSetting()], + updateOn: 'change', + } + ); + } + + private createAnalyticsForm() { + this.analyticsForm = this.fb.group({ + optOut: new FormControl(!this.localStorageService.getGAConsent()), + }); + } + + private setDefaultFormValues() { + this.settingsStore.setDefaultFormValues(this.settingForm); + } + + private cleanFormErrorMessage(): void { + this.settingForm.valueChanges + .pipe( + takeUntil(this.destroy$), + tap(() => this.settingsStore.setIsSubmitting(false)) + ) + .subscribe(); + } + + private createSystemConfig(): void { + const { device_intf, internet_intf, log_level, monitor_period } = + this.settingForm.value; + const data: SystemConfig = { + network: { + device_intf: device_intf.key, + internet_intf: this.isInternetControlDisabled ? '' : internet_intf.key, + }, + log_level: log_level.key, + monitor_period: Number(monitor_period.key), + }; + this.settingsStore.updateSystemConfig({ + onSystemConfigUpdate: () => { + this.setDefaultFormValues(); + }, + config: data, + }); + gtag('consent', 'update', { + analytics_storage: this.analyticsForm.value.optOut ? 'denied' : 'granted', + }); + this.localStorageService.setGAConsent(!this.analyticsForm.value.optOut); + } + + ngOnDestroy() { + this.destroy$.next(true); + this.destroy$.unsubscribe(); + } + + getSystemInterfaces(): void { + this.settingsStore.getInterfaces(); + this.hideLoading(); + } + + getSystemConfig(): void { + this.settingsStore.getSystemConfig(); + } + + private showLoading() { + this.loaderService.setLoading(true); + } + + private hideLoading() { + this.loaderService.setLoading(false); + } +} diff --git a/modules/ui/src/app/pages/settings/settings.store.spec.ts b/modules/ui/src/app/pages/general-settings/general-settings.store.spec.ts similarity index 70% rename from modules/ui/src/app/pages/settings/settings.store.spec.ts rename to modules/ui/src/app/pages/general-settings/general-settings.store.spec.ts index 969f5cb9f..562a06ee5 100644 --- a/modules/ui/src/app/pages/settings/settings.store.spec.ts +++ b/modules/ui/src/app/pages/general-settings/general-settings.store.spec.ts @@ -13,7 +13,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { LOG_LEVELS, MONITORING_PERIOD, SettingsStore } from './settings.store'; +import { + LOG_LEVELS, + MONITORING_PERIOD, + GeneralSettingsStore, +} from './general-settings.store'; import { TestRunService } from '../../services/test-run.service'; import SpyObj = jasmine.SpyObj; import { TestBed } from '@angular/core/testing'; @@ -23,12 +27,18 @@ import { skip, take } from 'rxjs'; import { selectAdapters, selectHasConnectionSettings, + selectInterfaces, + selectSystemConfig, } from '../../store/selectors'; import { of } from 'rxjs/internal/observable/of'; -import { fetchSystemConfigSuccess } from '../../store/actions'; +import { + fetchInterfaces, + fetchSystemConfig, + fetchSystemConfigSuccess, +} from '../../store/actions'; import { fetchInterfacesSuccess } from '../../store/actions'; import { FormBuilder, FormControl } from '@angular/forms'; -import { FormKey, SystemConfig } from '../../model/setting'; +import { FormKey } from '../../model/setting'; import { MOCK_ADAPTERS, MOCK_DEVICE_VALUE, @@ -41,34 +51,32 @@ import { MOCK_SYSTEM_CONFIG_WITH_SINGLE_PORT, } from '../../mocks/settings.mock'; -describe('SettingsStore', () => { - let settingsStore: SettingsStore; +describe('GeneralSettingsStore', () => { + let settingsStore: GeneralSettingsStore; let mockService: SpyObj; let store: MockStore; let fb: FormBuilder; beforeEach(() => { - mockService = jasmine.createSpyObj([ - 'getSystemInterfaces', - 'createSystemConfig', - 'getSystemConfig', - ]); + mockService = jasmine.createSpyObj(['createSystemConfig']); TestBed.configureTestingModule({ providers: [ - SettingsStore, + GeneralSettingsStore, { provide: TestRunService, useValue: mockService }, provideMockStore({ selectors: [ { selector: selectHasConnectionSettings, value: true }, { selector: selectAdapters, value: {} }, + { selector: selectInterfaces, value: {} }, + { selector: selectSystemConfig, value: { network: {} } }, ], }), FormBuilder, ], }); - settingsStore = TestBed.inject(SettingsStore); + settingsStore = TestBed.inject(GeneralSettingsStore); store = TestBed.inject(MockStore); fb = TestBed.inject(FormBuilder); spyOn(store, 'dispatch').and.callFake(() => {}); @@ -79,28 +87,6 @@ describe('SettingsStore', () => { }); describe('updaters', () => { - describe('setSystemConfig', () => { - it('should update systemConfig', (done: DoneFn) => { - const config = { - network: { - device_intf: 'enx207bd2620617', - internet_intf: 'enx207bd2620618', - }, - log_level: 'INFO', - startup_timeout: 60, - monitor_period: 60, - runtime: 120, - } as SystemConfig; - - settingsStore.viewModel$.pipe(skip(1), take(1)).subscribe(store => { - expect(store.systemConfig).toEqual(config); - done(); - }); - - settingsStore.setSystemConfig(config); - }); - }); - it('should update isSubmitting', (done: DoneFn) => { settingsStore.viewModel$.pipe(skip(1), take(1)).subscribe(store => { expect(store.isSubmitting).toEqual(true); @@ -111,8 +97,7 @@ describe('SettingsStore', () => { }); it('should update interfaces', (done: DoneFn) => { - settingsStore.viewModel$.pipe(skip(3), take(1)).subscribe(store => { - expect(store.interfaces).toEqual(MOCK_INTERFACES); + settingsStore.viewModel$.pipe(skip(2), take(1)).subscribe(store => { expect(store.deviceOptions).toEqual(MOCK_INTERFACES); expect(store.internetOptions).toEqual({ mockDeviceKey: 'mockDeviceValue', @@ -146,52 +131,18 @@ describe('SettingsStore', () => { describe('effects', () => { describe('getSystemConfig', () => { - beforeEach(() => { - mockService.getSystemConfig.and.returnValue(of({ network: {} })); - }); - - it('should dispatch action fetchSystemConfigSuccess', () => { + it('should dispatch action fetchSystemConfig', () => { settingsStore.getSystemConfig(); - expect(store.dispatch).toHaveBeenCalledWith( - fetchSystemConfigSuccess({ systemConfig: { network: {} } }) - ); - }); - - it('should update store', done => { - settingsStore.viewModel$.pipe(skip(1), take(1)).subscribe(store => { - expect(store.systemConfig).toEqual({ network: {} }); - done(); - }); - - settingsStore.getSystemConfig(); + expect(store.dispatch).toHaveBeenCalledWith(fetchSystemConfig()); }); }); describe('getInterfaces', () => { - const interfaces = MOCK_INTERFACES; - - beforeEach(() => { - mockService.getSystemInterfaces.and.returnValue(of(interfaces)); - }); - it('should dispatch action fetchInterfacesSuccess', () => { settingsStore.getInterfaces(); - expect(store.dispatch).toHaveBeenCalledWith( - fetchInterfacesSuccess({ interfaces }) - ); - }); - - it('should update store', done => { - settingsStore.viewModel$.pipe(skip(3), take(1)).subscribe(store => { - expect(store.interfaces).toEqual(interfaces); - expect(store.deviceOptions).toEqual(interfaces); - expect(store.internetOptions).toEqual(interfaces); - done(); - }); - - settingsStore.getInterfaces(); + expect(store.dispatch).toHaveBeenCalledWith(fetchInterfaces()); }); }); @@ -213,20 +164,6 @@ describe('SettingsStore', () => { ); }); - it('should update store', done => { - settingsStore.viewModel$.pipe(skip(1), take(1)).subscribe(store => { - expect(store.systemConfig).toEqual({ network: {} }); - done(); - }); - - settingsStore.updateSystemConfig( - of({ - onSystemConfigUpdate: () => {}, - config: { network: {} }, - }) - ); - }); - it('should call onSystemConfigUpdate', () => { const effectParams = { onSystemConfigUpdate: () => {}, @@ -245,8 +182,12 @@ describe('SettingsStore', () => { describe('setDefaultFormValues', () => { describe('when values are present', () => { beforeEach(() => { - settingsStore.setSystemConfig(MOCK_SYSTEM_CONFIG_WITH_DATA); - settingsStore.setInterfaces(MOCK_INTERFACES); + store.overrideSelector(selectInterfaces, MOCK_INTERFACES); + store.overrideSelector( + selectSystemConfig, + MOCK_SYSTEM_CONFIG_WITH_DATA + ); + store.refreshState(); }); it('should set default form values', () => { @@ -275,8 +216,12 @@ describe('SettingsStore', () => { describe('with single port mode', () => { beforeEach(() => { - settingsStore.setSystemConfig(MOCK_SYSTEM_CONFIG_WITH_SINGLE_PORT); - settingsStore.setInterfaces(MOCK_INTERFACES); + store.overrideSelector(selectInterfaces, MOCK_INTERFACES); + store.overrideSelector( + selectSystemConfig, + MOCK_SYSTEM_CONFIG_WITH_SINGLE_PORT + ); + store.refreshState(); }); it('should disable internet control', () => { @@ -296,8 +241,12 @@ describe('SettingsStore', () => { describe('when values are empty', () => { beforeEach(() => { - settingsStore.setSystemConfig(MOCK_SYSTEM_CONFIG_WITH_NO_DATA); - settingsStore.setInterfaces(MOCK_INTERFACES); + store.overrideSelector(selectInterfaces, MOCK_INTERFACES); + store.overrideSelector( + selectSystemConfig, + MOCK_SYSTEM_CONFIG_WITH_NO_DATA + ); + store.refreshState(); }); it('should set default form values', () => { @@ -343,13 +292,14 @@ describe('SettingsStore', () => { beforeEach(() => { settingsStore.setInterfaces(MOCK_INTERFACES); + store.overrideSelector(selectInterfaces, MOCK_INTERFACES); + store.refreshState(); }); it('should update store', done => { settingsStore.viewModel$ - .pipe(skip(3), take(1)) + .pipe(skip(2), take(1)) .subscribe(storeValue => { - expect(storeValue.interfaces).toEqual(updateInterfaces); expect(storeValue.deviceOptions).toEqual(updateInterfaces); expect(storeValue.internetOptions).toEqual(updateInternetOptions); diff --git a/modules/ui/src/app/pages/settings/settings.store.ts b/modules/ui/src/app/pages/general-settings/general-settings.store.ts similarity index 84% rename from modules/ui/src/app/pages/settings/settings.store.ts rename to modules/ui/src/app/pages/general-settings/general-settings.store.ts index 87071b222..ec2b5c99f 100644 --- a/modules/ui/src/app/pages/settings/settings.store.ts +++ b/modules/ui/src/app/pages/general-settings/general-settings.store.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { Injectable } from '@angular/core'; +import { Injectable, inject } from '@angular/core'; import { ComponentStore } from '@ngrx/component-store'; import { TestRunService } from '../../services/test-run.service'; import { @@ -31,15 +31,17 @@ import { AppState } from '../../store/state'; import { selectAdapters, selectHasConnectionSettings, + selectInterfaces, + selectSystemConfig, + selectSystemStatus, } from '../../store/selectors'; import { FormControl, FormGroup } from '@angular/forms'; +import { fetchInterfaces, fetchSystemConfig } from '../../store/actions'; export interface SettingsComponentState { hasConnectionSettings: boolean; isSubmitting: boolean; - systemConfig: SystemConfig; isLessThanOneInterface: boolean; - interfaces: SystemInterfaces; deviceOptions: SystemInterfaces; internetOptions: SystemInterfaces; logLevelOptions: { [key: string]: string }; @@ -67,20 +69,26 @@ export const MONITORING_PERIOD = { 600: 'Very slow device', }; @Injectable() -export class SettingsStore extends ComponentStore { +export class GeneralSettingsStore extends ComponentStore { + private testRunService = inject(TestRunService); + private store = inject>(Store); + private static readonly DEFAULT_LOG_LEVEL = 'INFO'; private static readonly DEFAULT_MONITORING_PERIOD = '300'; - private systemConfig$ = this.select(state => state.systemConfig); private hasConnectionSettings$ = this.store.select( selectHasConnectionSettings ); private adapters$ = this.store.select(selectAdapters); + systemStatus$ = this.store.select(selectSystemStatus); private isSubmitting$ = this.select(state => state.isSubmitting); private isLessThanOneInterfaces$ = this.select( state => state.isLessThanOneInterface ); - private interfaces$ = this.select(state => state.interfaces); + private interfaces$: Observable = + this.store.select(selectInterfaces); + private systemConfig$: Observable = + this.store.select(selectSystemConfig); private deviceOptions$ = this.select(state => state.deviceOptions); private internetOptions$ = this.select(state => state.internetOptions); private logLevelOptions$ = this.select(state => state.logLevelOptions); @@ -99,11 +107,6 @@ export class SettingsStore extends ComponentStore { monitoringPeriodOptions: this.monitoringPeriodOptions$, }); - setSystemConfig = this.updater((state, systemConfig: SystemConfig) => ({ - ...state, - systemConfig, - })); - setIsSubmitting = this.updater((state, isSubmitting: boolean) => ({ ...state, isSubmitting, @@ -112,36 +115,33 @@ export class SettingsStore extends ComponentStore { setInterfaces = this.updater((state, interfaces: SystemInterfaces) => { return { ...state, - interfaces, deviceOptions: interfaces, internetOptions: interfaces, isLessThanOneInterface: Object.keys(interfaces).length < 1, }; }); + statusLoaded = this.effect(() => { + return this.interfaces$.pipe( + skip(1), + tap(interfaces => { + this.setInterfaces(interfaces); + }) + ); + }); + getInterfaces = this.effect(trigger$ => { return trigger$.pipe( - exhaustMap(() => { - return this.testRunService.getSystemInterfaces().pipe( - tap((interfaces: SystemInterfaces) => { - this.updateInterfaces(interfaces); - }) - ); + tap(() => { + this.store.dispatch(fetchInterfaces()); }) ); }); getSystemConfig = this.effect(trigger$ => { return trigger$.pipe( - exhaustMap(() => { - return this.testRunService.getSystemConfig().pipe( - tap((systemConfig: SystemConfig) => { - this.store.dispatch( - AppActions.fetchSystemConfigSuccess({ systemConfig }) - ); - this.setSystemConfig(systemConfig); - }) - ); + tap(() => { + this.store.dispatch(fetchSystemConfig()); }) ); }); @@ -159,7 +159,6 @@ export class SettingsStore extends ComponentStore { systemConfig: trigger.config, }) ); - this.setSystemConfig(trigger.config); trigger.onSystemConfigUpdate(); }) ); @@ -167,6 +166,26 @@ export class SettingsStore extends ComponentStore { ); }); + setFormDisable = this.effect((formGroup$: Observable) => { + return formGroup$.pipe( + tap(formGroup => { + formGroup.disable(); + }) + ); + }); + + setFormEnable = this.effect((formGroup$: Observable) => { + return formGroup$.pipe( + withLatestFrom(this.systemConfig$), + tap(([formGroup, config]) => { + formGroup.enable(); + if (config.single_intf) { + this.disableInternetInterface(formGroup); + } + }) + ); + }); + setDefaultFormValues = this.effect((formGroup$: Observable) => { return formGroup$.pipe( switchMap(formGroup => @@ -279,7 +298,7 @@ export class SettingsStore extends ComponentStore { ): void { this.setDefaultValue( value, - SettingsStore.DEFAULT_LOG_LEVEL, + GeneralSettingsStore.DEFAULT_LOG_LEVEL, options, formGroup.get(FormKey.LOG_LEVEL) as FormControl ); @@ -292,7 +311,7 @@ export class SettingsStore extends ComponentStore { ): void { this.setDefaultValue( value, - SettingsStore.DEFAULT_MONITORING_PERIOD, + GeneralSettingsStore.DEFAULT_MONITORING_PERIOD, options, formGroup.get(FormKey.MONITOR_PERIOD) as FormControl ); @@ -328,16 +347,11 @@ export class SettingsStore extends ComponentStore { }; } - constructor( - private testRunService: TestRunService, - private store: Store - ) { + constructor() { super({ - systemConfig: { network: {} }, hasConnectionSettings: false, isSubmitting: false, isLessThanOneInterface: false, - interfaces: {}, deviceOptions: {}, internetOptions: {}, logLevelOptions: LOG_LEVELS, diff --git a/modules/ui/src/app/pages/settings/only-different-values.validator.ts b/modules/ui/src/app/pages/general-settings/only-different-values.validator.ts similarity index 90% rename from modules/ui/src/app/pages/settings/only-different-values.validator.ts rename to modules/ui/src/app/pages/general-settings/only-different-values.validator.ts index a153fb0ef..735272742 100644 --- a/modules/ui/src/app/pages/settings/only-different-values.validator.ts +++ b/modules/ui/src/app/pages/general-settings/only-different-values.validator.ts @@ -40,7 +40,11 @@ export class OnlyDifferentValuesValidator { return null; } - if (deviceControlValue.key === internetControlValue.key) { + if ( + deviceControlValue.key === internetControlValue.key && + deviceControlValue.key && + internetControlValue.key + ) { return { hasSameValues: true }; } return null; diff --git a/modules/ui/src/app/pages/reports/components/delete-report/delete-report.component.html b/modules/ui/src/app/pages/reports/components/delete-report/delete-report.component.html index 8897dbc75..1862f1473 100644 --- a/modules/ui/src/app/pages/reports/components/delete-report/delete-report.component.html +++ b/modules/ui/src/app/pages/reports/components/delete-report/delete-report.component.html @@ -18,6 +18,7 @@ class="delete-report-button" href="#" matTooltip="Delete report for Testrun # {{ getTestRunId(data) }}" + [attr.aria-label]="'Delete report for Testrun # {{ getTestRunId(data) }}'" (click)="deleteReport($event)" (keydown.enter)="deleteReport($event)" (keydown.space)="deleteReport($event)"> diff --git a/modules/ui/src/app/pages/reports/components/delete-report/delete-report.component.scss b/modules/ui/src/app/pages/reports/components/delete-report/delete-report.component.scss index 487186787..eac5d91fa 100644 --- a/modules/ui/src/app/pages/reports/components/delete-report/delete-report.component.scss +++ b/modules/ui/src/app/pages/reports/components/delete-report/delete-report.component.scss @@ -13,19 +13,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +@use 'mixins'; + :host { display: inline-block; } .delete-report-button { - cursor: pointer; - display: flex; - align-items: center; - justify-content: center; - text-align: center; - padding: 4px 0; - margin: 0 4px; - & ::ng-deep .mdc-icon-button__ripple { - display: none; - } + @include mixins.report-action; } diff --git a/modules/ui/src/app/pages/reports/components/delete-report/delete-report.component.spec.ts b/modules/ui/src/app/pages/reports/components/delete-report/delete-report.component.spec.ts index f7271c377..46dae86c7 100644 --- a/modules/ui/src/app/pages/reports/components/delete-report/delete-report.component.spec.ts +++ b/modules/ui/src/app/pages/reports/components/delete-report/delete-report.component.spec.ts @@ -69,7 +69,7 @@ describe('DeleteReportComponent', () => { autoFocus: true, hasBackdrop: true, disableClose: true, - panelClass: 'simple-dialog', + panelClass: ['simple-dialog', 'delete-dialog'], }); openSpy.calls.reset(); }); diff --git a/modules/ui/src/app/pages/reports/components/delete-report/delete-report.component.ts b/modules/ui/src/app/pages/reports/components/delete-report/delete-report.component.ts index 96e178443..3571a8fc4 100644 --- a/modules/ui/src/app/pages/reports/components/delete-report/delete-report.component.ts +++ b/modules/ui/src/app/pages/reports/components/delete-report/delete-report.component.ts @@ -19,6 +19,7 @@ import { EventEmitter, OnDestroy, Output, + inject, } from '@angular/core'; import { CommonModule, DatePipe } from '@angular/common'; import { MatButtonModule } from '@angular/material/button'; @@ -31,7 +32,7 @@ import { MatTooltipModule } from '@angular/material/tooltip'; @Component({ selector: 'app-delete-report', - standalone: true, + imports: [CommonModule, MatButtonModule, MatTooltipModule], templateUrl: './delete-report.component.html', styleUrls: ['./delete-report.component.scss'], @@ -42,13 +43,12 @@ export class DeleteReportComponent extends ReportActionComponent implements OnDestroy { + dialog = inject(MatDialog); + @Output() removeDevice = new EventEmitter(); private destroy$: Subject = new Subject(); - constructor( - public dialog: MatDialog, - datePipe: DatePipe - ) { - super(datePipe); + constructor() { + super(); } ngOnDestroy() { @@ -67,7 +67,7 @@ export class DeleteReportComponent autoFocus: true, hasBackdrop: true, disableClose: true, - panelClass: 'simple-dialog', + panelClass: ['simple-dialog', 'delete-dialog'], }); dialogRef diff --git a/modules/ui/src/app/pages/reports/components/filter-chips/filter-chips.component.html b/modules/ui/src/app/pages/reports/components/filter-chips/filter-chips.component.html index 07cf43aa0..e7eed0296 100644 --- a/modules/ui/src/app/pages/reports/components/filter-chips/filter-chips.component.html +++ b/modules/ui/src/app/pages/reports/components/filter-chips/filter-chips.component.html @@ -39,16 +39,15 @@ - - Clear all filters - +
+
Clear all filters
+
diff --git a/modules/ui/src/app/pages/reports/components/filter-chips/filter-chips.component.scss b/modules/ui/src/app/pages/reports/components/filter-chips/filter-chips.component.scss index ec7ef97e0..541540fd5 100644 --- a/modules/ui/src/app/pages/reports/components/filter-chips/filter-chips.component.scss +++ b/modules/ui/src/app/pages/reports/components/filter-chips/filter-chips.component.scss @@ -13,45 +13,77 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -@import 'src/theming/colors'; +@use 'colors'; :host { display: flex; } -.clear-all { - display: flex; - align-items: center; - justify-content: center; - padding: 0 8px; - cursor: pointer; - color: $primary; - font-weight: 400; - height: 32px; - margin: 4px 0 4px 8px; - border-radius: 16px; - flex-shrink: 0; - background: $white; - font-family: Roboto, sans-serif; -} - .filter-chip.mat-mdc-chip { - background: $white; - border: 1px solid $primary; + background: colors.$light-grey; + &::ng-deep .mat-mdc-chip-primary-focus-indicator { + display: none; + } } .filter-chip.mat-mdc-chip ::ng-deep .mat-mdc-chip-action-label { - color: $primary; + color: colors.$on-secondary-container; font-weight: 500; } .filter-chip.mat-mdc-chip ::ng-deep .mat-mdc-chip-remove { - color: $primary; + color: colors.$on-secondary-container; } .filter-chip.cdk-keyboard-focused, .filter-chip-remove:focus-visible { - outline: $black solid 2px; + outline: colors.$black solid 2px; + &:before { + content: none; + } +} + +.filter-chip-remove:focus { + &:before { + content: none; + } +} + +.clear-button:has(.clear-button-label:focus), +.clear-button.cdk-keyboard-focused { + outline: colors.$black solid 2px; +} + +.clear-button-label:focus { + border: none; +} + +.clear-button { + height: var(--mdc-text-button-container-height); + border-radius: var(--mdc-text-button-container-shape); + margin: 0 0 0 8px !important; + border: none; + display: flex; + align-items: center; + justify-content: center; +} + +.clear-button .clear-button-label { + color: colors.$primary !important; + border: none; + outline: none; +} + +.clear-button ::ng-deep .mat-focus-indicator { + display: none; +} + +.clear-button + ::ng-deep + .mdc-evolution-chip__action--primary:not( + .mdc-evolution-chip__action--presentational + ):not(.mdc-ripple-upgraded):focus::before { + border: none; } .filter-chip .filter-chip-remove { @@ -64,7 +96,7 @@ .mat-mdc-standard-chip:not( .mdc-evolution-chip--disabled ).filter-chip-clear-all { - background-color: $white; + background-color: colors.$white; & ::ng-deep .mat-mdc-chip-action { padding: 0; diff --git a/modules/ui/src/app/pages/reports/components/filter-chips/filter-chips.component.spec.ts b/modules/ui/src/app/pages/reports/components/filter-chips/filter-chips.component.spec.ts index 508bb7c54..ec48d528a 100644 --- a/modules/ui/src/app/pages/reports/components/filter-chips/filter-chips.component.spec.ts +++ b/modules/ui/src/app/pages/reports/components/filter-chips/filter-chips.component.spec.ts @@ -73,7 +73,7 @@ describe('FilterChipsComponent', () => { describe('DOM tests', () => { describe('"Clear all filters" button', () => { it('should exist', () => { - const button = compiled.querySelector('.clear-all'); + const button = compiled.querySelector('.clear-button'); expect(button).toBeTruthy(); }); @@ -81,7 +81,7 @@ describe('FilterChipsComponent', () => { it('should clear all filters on click', () => { const clearAllFiltersSpy = spyOn(component, 'clearAllFilters'); const button = compiled.querySelector( - '.clear-all' + '.clear-button' ) as HTMLButtonElement; button.click(); diff --git a/modules/ui/src/app/pages/reports/components/filter-chips/filter-chips.component.ts b/modules/ui/src/app/pages/reports/components/filter-chips/filter-chips.component.ts index 45e4cce62..f2d64a93d 100644 --- a/modules/ui/src/app/pages/reports/components/filter-chips/filter-chips.component.ts +++ b/modules/ui/src/app/pages/reports/components/filter-chips/filter-chips.component.ts @@ -18,13 +18,20 @@ import { MatIconModule } from '@angular/material/icon'; import { MatChipsModule } from '@angular/material/chips'; import { CommonModule, KeyValuePipe } from '@angular/common'; import { DateRange, FilterName, Filters } from '../../../../model/filters'; +import { MatButtonModule } from '@angular/material/button'; @Component({ selector: 'app-filter-chips', templateUrl: './filter-chips.component.html', styleUrls: ['./filter-chips.component.scss'], - standalone: true, - imports: [MatIconModule, MatChipsModule, KeyValuePipe, CommonModule], + + imports: [ + MatIconModule, + MatChipsModule, + MatButtonModule, + KeyValuePipe, + CommonModule, + ], }) export class FilterChipsComponent { @Input() filters!: Filters; diff --git a/modules/ui/src/app/pages/reports/components/filter-dialog/filter-dialog.component.html b/modules/ui/src/app/pages/reports/components/filter-dialog/filter-dialog.component.html index 5f936e1ce..17c4a7915 100644 --- a/modules/ui/src/app/pages/reports/components/filter-dialog/filter-dialog.component.html +++ b/modules/ui/src/app/pages/reports/components/filter-dialog/filter-dialog.component.html @@ -13,10 +13,14 @@ See the License for the specific language governing permissions and limitations under the License. --> +

{{ data.title }}

+ -
+ [ngStyle]="{ + 'max-height': 'calc(100vh - ' + (topPosition + dialogTitle) + 'px)', + }"> + - Please enter device model name - Please enter firmware name -
+

@@ -73,30 +78,28 @@ - Started date + Dates - MM/DD/YYYY – MM/DD/YYYY - Please, select the correct date range in MM/DD/YYYY format. + Please, select the correct date range in mm/dd/yyyy format. @@ -109,8 +112,6 @@ - - + + diff --git a/modules/ui/src/app/pages/reports/components/filter-dialog/filter-dialog.component.scss b/modules/ui/src/app/pages/reports/components/filter-dialog/filter-dialog.component.scss index 3041ab183..a6da7dd55 100644 --- a/modules/ui/src/app/pages/reports/components/filter-dialog/filter-dialog.component.scss +++ b/modules/ui/src/app/pages/reports/components/filter-dialog/filter-dialog.component.scss @@ -14,12 +14,35 @@ * limitations under the License. */ @use 'node_modules/@angular/material/index' as mat; -@import 'src/theming/colors'; +@use 'm3-theme' as *; +@use 'colors'; +@use 'variables'; + +.filter-dialog-title { + padding: 24px 12px 16px 24px; + display: flex; + border-bottom: 1px solid colors.$outline-variant; + &:before { + height: 0; + } +} .filter-dialog-content { display: flex; flex-direction: column; - padding: 16px 16px 0 16px; + padding: 0 4px; + + &:has(.text-field) { + padding: 0 24px 16px; + } + + &:has(.filter-result-item) { + padding: 0 16px; + } +} + +.filter-form { + padding-top: 16px; } .date-field { @@ -28,51 +51,100 @@ .date-calendar { flex-grow: 1; - overflow: auto; min-height: 2em; + + &::ng-deep .mat-calendar-body-label { + visibility: hidden; + } + &::ng-deep .mat-calendar-body-label[colspan='7'] { + display: none; + } + + &::ng-deep mat-year-view .mat-calendar-body-label[colspan='4'] { + display: none; + } + + &::ng-deep .mat-calendar-header { + padding-top: 0; + } + + ::ng-deep .mat-calendar-header button .mat-focus-indicator { + display: none; + } + + ::ng-deep.mat-calendar-body-cell:focus .mat-focus-indicator::before { + content: none; + } + + ::ng-deep.mat-calendar-body-cell:focus-visible .mat-focus-indicator::before { + content: ''; + } } .filter-dialog-actions { - border-top: 1px solid $lighter-grey; - gap: 16px; + padding: 8px 12px 24px; + font-family: variables.$font-text; + gap: 8px; button { min-width: 38px; margin: 0; - padding: 0 8px; - color: mat.m2-get-color-from-palette($color-primary, 600); + padding: 0 16px; font-weight: 500; line-height: 20px; - letter-spacing: 0.25px; + + ::ng-deep .mat-focus-indicator { + display: none; + } } } -.text-field, -.date-field { +.text-field { width: 100%; } +.date-field { + margin: 0 12px; +} + +.filter-result { + display: flex; + flex-direction: column; + gap: 10px; + margin-bottom: 16px; + + ::ng-deep .mdc-checkbox__native-control:focus ~ .mat-focus-indicator::before { + content: none; + } + + ::ng-deep + .mdc-checkbox__native-control:focus-visible + ~ .mat-focus-indicator::before { + content: ''; + } +} + .filter-result-item { - padding: 4px 0; + padding: 8px 0; margin: 0; +} - &:first-child { - padding-top: 0; +.date-field, +.text-field { + &::ng-deep.mat-mdc-form-field-subscript-wrapper { + display: none; } - &:last-child { - padding-bottom: 20px; + &::ng-deep.mat-mdc-form-field-subscript-wrapper:has(mat-error) { + display: block; } } .text-field { - padding-bottom: 10px; - &::ng-deep.mat-mdc-form-field-subscript-wrapper:has(mat-error) { height: 60px; } - &::ng-deep.mat-mdc-form-field-hint-wrapper, &::ng-deep.mat-mdc-form-field-error-wrapper { padding: 0 10px; } diff --git a/modules/ui/src/app/pages/reports/components/filter-dialog/filter-dialog.component.spec.ts b/modules/ui/src/app/pages/reports/components/filter-dialog/filter-dialog.component.spec.ts index dfa48e8f0..d866a1f37 100644 --- a/modules/ui/src/app/pages/reports/components/filter-dialog/filter-dialog.component.spec.ts +++ b/modules/ui/src/app/pages/reports/components/filter-dialog/filter-dialog.component.spec.ts @@ -33,7 +33,7 @@ import { MatDatepickerModule, } from '@angular/material/datepicker'; import { MatNativeDateModule } from '@angular/material/core'; -import { DateRange, FilterName } from '../../../../model/filters'; +import { DateRange, FilterName, FilterTitle } from '../../../../model/filters'; import { NoopAnimationsModule } from '@angular/platform-browser/animations'; import { of } from 'rxjs'; @@ -84,6 +84,7 @@ describe('FilterDialogComponent', () => { component.data = { trigger: mockClientRest, filter: FilterName.DeviceInfo, + title: FilterTitle.DeviceInfo, }; component.data.trigger.nativeElement = { getBoundingClientRect: () => mockData, @@ -152,6 +153,7 @@ describe('FilterDialogComponent', () => { component.data = { trigger: mockClientRest, filter: FilterName.DeviceFirmware, + title: FilterTitle.DeviceFirmware, }; fixture.detectChanges(); @@ -178,6 +180,7 @@ describe('FilterDialogComponent', () => { component.data = { trigger: mockClientRest, filter: FilterName.Started, + title: FilterTitle.Started, }; fixture.detectChanges(); }); @@ -246,7 +249,9 @@ describe('FilterDialogComponent', () => { }); it('should max date as today', () => { - expect(component.calendar.maxDate?.getDate()).toBe(new Date().getDate()); + expect(component.calendar()?.maxDate?.getDate()).toBe( + new Date().getDate() + ); }); }); }); diff --git a/modules/ui/src/app/pages/reports/components/filter-dialog/filter-dialog.component.ts b/modules/ui/src/app/pages/reports/components/filter-dialog/filter-dialog.component.ts index 6dd78f401..26bce3260 100644 --- a/modules/ui/src/app/pages/reports/components/filter-dialog/filter-dialog.component.ts +++ b/modules/ui/src/app/pages/reports/components/filter-dialog/filter-dialog.component.ts @@ -18,9 +18,9 @@ import { Component, ElementRef, HostListener, - Inject, OnInit, - ViewChild, + viewChild, + inject, } from '@angular/core'; import { CommonModule } from '@angular/common'; import { @@ -36,7 +36,9 @@ import { FormBuilder, FormControl, FormGroup, + FormGroupDirective, FormsModule, + NgForm, NgModel, ReactiveFormsModule, } from '@angular/forms'; @@ -52,7 +54,7 @@ import { MatDatepickerInputEvent, MatDatepickerModule, } from '@angular/material/datepicker'; -import { MatNativeDateModule } from '@angular/material/core'; +import { ErrorStateMatcher, MatNativeDateModule } from '@angular/material/core'; import { FilterName, DateRange as LocalDateRange, @@ -64,14 +66,31 @@ import { } from '../../../../model/testrun-status'; import { DeviceValidators } from '../../../devices/components/device-form/device.validators'; +class DateErrorStateMatcher implements ErrorStateMatcher { + isErrorState( + control: FormControl | null, + form: FormGroupDirective | NgForm | null + ): boolean { + const isSubmitted = form && form.submitted; + return !!( + control && + control.errors && + control.errors['matDatepickerParse'].text.trim().length > 0 && + control.invalid && + (control.touched || isSubmitted) + ); + } +} + interface DialogData { trigger: ElementRef; filter: string; + title: string; } @Component({ selector: 'app-filter-dialog', - standalone: true, + imports: [ CommonModule, MatDialogModule, @@ -99,6 +118,11 @@ export class FilterDialogComponent extends EscapableDialogComponent implements OnInit { + override dialogRef: MatDialogRef; + private deviceValidators = inject(DeviceValidators); + data = inject(MAT_DIALOG_DATA); + private fb = inject(FormBuilder); + resultList = [ { value: ResultOfTestrun.Compliant, enabled: false }, { value: ResultOfTestrun.NonCompliant, enabled: false }, @@ -113,6 +137,7 @@ export class FilterDialogComponent range: LocalDateRange = new LocalDateRange(); topPosition = 0; + dialogTitle = 110; today = new Date(); @@ -123,17 +148,15 @@ export class FilterDialogComponent this.setDialogView(); } - @ViewChild(MatCalendar) calendar!: MatCalendar; - @ViewChild('startDate') startDate!: NgModel; - @ViewChild('endDate') endDate!: NgModel; - - constructor( - public override dialogRef: MatDialogRef, - private deviceValidators: DeviceValidators, - @Inject(MAT_DIALOG_DATA) public data: DialogData, - private fb: FormBuilder - ) { - super(dialogRef); + readonly calendar = viewChild(MatCalendar); + readonly startDate = viewChild('startDate'); + readonly endDate = viewChild('endDate'); + + constructor() { + const dialogRef = inject>(MatDialogRef); + + super(); + this.dialogRef = dialogRef; } get deviceInfo() { @@ -154,12 +177,21 @@ export class FilterDialogComponent const rect = this.data.trigger?.nativeElement.getBoundingClientRect(); matDialogConfig.position = { - left: `${rect.left - 80}px`, - top: `${rect.bottom + 0}px`, + left: + this.data.filter === FilterName.Results + ? `${rect.left - 240}px` + : `${rect.left}px`, + top: `${rect.bottom + 14}px`, }; this.topPosition = rect.bottom + this.dialog_actions_height; - matDialogConfig.width = this.data.filter === 'results' ? '240px' : '328px'; + if (this.data.filter === FilterName.Started) { + matDialogConfig.width = '360px'; + } else if (this.data.filter === FilterName.Results) { + matDialogConfig.width = '240px'; + } else { + matDialogConfig.width = '328px'; + } this.dialogRef.updateSize(matDialogConfig.width); this.dialogRef.updatePosition(matDialogConfig.position); @@ -196,8 +228,8 @@ export class FilterDialogComponent confirm(): void { if ( this.filterForm?.invalid || - this.startDate?.invalid || - this.endDate?.invalid + this.startDate()?.invalid || + this.endDate()?.invalid ) { return; } @@ -230,6 +262,8 @@ export class FilterDialogComponent this.dialogRef.close(); } + dateMatcher = new DateErrorStateMatcher(); + startDateChanged(event: MatDatepickerInputEvent) { const date = event.value; if (date && date.getFullYear() > this.today.getFullYear()) { @@ -241,8 +275,11 @@ export class FilterDialogComponent this.selectedRangeValue?.end || null ); if (this.selectedRangeValue.start) { - this.calendar.activeDate = this.selectedRangeValue.start; - this.calendar.updateTodaysDate(); + const calendar = this.calendar(); + if (calendar) { + calendar.activeDate = this.selectedRangeValue.start; + calendar.updateTodaysDate(); + } } } @@ -257,8 +294,11 @@ export class FilterDialogComponent event.value ); if (this.selectedRangeValue?.end) { - this.calendar.activeDate = this.selectedRangeValue.end; - this.calendar.updateTodaysDate(); + const calendar = this.calendar(); + if (calendar) { + calendar.activeDate = this.selectedRangeValue.end; + calendar.updateTodaysDate(); + } } } } diff --git a/modules/ui/src/app/pages/reports/components/filter-header/filter-header.component.html b/modules/ui/src/app/pages/reports/components/filter-header/filter-header.component.html new file mode 100644 index 000000000..21f94113d --- /dev/null +++ b/modules/ui/src/app/pages/reports/components/filter-header/filter-header.component.html @@ -0,0 +1,38 @@ + + {{ headerText }} + + + + + + + diff --git a/modules/ui/src/app/pages/reports/components/filter-header/filter-header.component.scss b/modules/ui/src/app/pages/reports/components/filter-header/filter-header.component.scss new file mode 100644 index 000000000..a965cb6b1 --- /dev/null +++ b/modules/ui/src/app/pages/reports/components/filter-header/filter-header.component.scss @@ -0,0 +1,43 @@ +@use 'node_modules/@angular/material/index' as mat; +@use 'src/theming/m3-theme' as *; +@use 'colors'; +@use 'variables'; + +:host { + display: contents; +} + +th { + height: var(--mat-table-header-container-height); + vertical-align: middle; +} + +.filter-button { + display: flex; + width: variables.$reports-table-header-size; + height: variables.$reports-table-header-size; + justify-content: center; + align-items: center; + flex-shrink: 0; + margin-left: 8px; + padding: 0; + border: none; + background: colors.$white; + cursor: pointer; + border-radius: 50%; + &:hover { + background-color: rgba(0, 0, 0, 0.04); + } + &:active, + &.active { + background-color: colors.$secondary-container; + color: colors.$on-secondary-container; + &:hover { + filter: brightness(90%); + } + } +} + +.filter-button.active .mat-icon { + color: mat.get-theme-color($light-theme, primary, 35); +} diff --git a/modules/ui/src/app/pages/reports/components/filter-header/filter-header.component.spec.ts b/modules/ui/src/app/pages/reports/components/filter-header/filter-header.component.spec.ts new file mode 100644 index 000000000..d530772d3 --- /dev/null +++ b/modules/ui/src/app/pages/reports/components/filter-header/filter-header.component.spec.ts @@ -0,0 +1,94 @@ +import { Component } from '@angular/core'; +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { FilterHeaderComponent } from './filter-header.component'; +import { MatIconModule } from '@angular/material/icon'; +import { MatSortModule } from '@angular/material/sort'; +import { MatButtonModule } from '@angular/material/button'; +import { MatTableModule } from '@angular/material/table'; +import { CommonModule } from '@angular/common'; +import { By } from '@angular/platform-browser'; +import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; +@Component({ + selector: 'app-dummy-table', + template: ` + + + + + + + + +
{{ element }}
+ `, + standalone: false, +}) +export class DummyTableComponent { + data = ['Row 1', 'Row 2', 'Row 3']; + displayedColumns = ['testColumn']; +} + +describe('FilterHeaderComponent within mat-table', () => { + let fixture: ComponentFixture; + let compiled: HTMLElement; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [DummyTableComponent], + imports: [ + BrowserAnimationsModule, + FilterHeaderComponent, + MatIconModule, + MatSortModule, + MatButtonModule, + MatTableModule, + CommonModule, + ], + }).compileComponents(); + + fixture = TestBed.createComponent(DummyTableComponent); + fixture.detectChanges(); + compiled = fixture.nativeElement as HTMLElement; + }); + + it('should have the filter header component', () => { + const filterHeader = compiled.querySelector( + 'app-filter-header' + ) as HTMLElement; + expect(filterHeader).toBeTruthy(); + const headerText = filterHeader?.querySelector( + 'th span' + ) as HTMLSpanElement; + expect(headerText?.textContent?.trim()).toBe('Test Header'); + }); + + it('should emit an event when filter button is clicked in filter header', () => { + const filterHeader = fixture.debugElement.query( + By.css('app-filter-header') + ); + const filterHeaderComponent = + filterHeader.componentInstance as FilterHeaderComponent; + + spyOn(filterHeaderComponent.emitOpenFilter, 'emit'); + + const button = filterHeader.query(By.css('.filter-button')) + .nativeElement as HTMLButtonElement; + button.click(); + + expect(filterHeaderComponent.emitOpenFilter.emit).toHaveBeenCalledWith({ + event: new PointerEvent('event'), + filter: 'testFilter', + title: 'testTitle', + filterOpened: false, + }); + }); +}); diff --git a/modules/ui/src/app/pages/reports/components/filter-header/filter-header.component.ts b/modules/ui/src/app/pages/reports/components/filter-header/filter-header.component.ts new file mode 100644 index 000000000..c261a8347 --- /dev/null +++ b/modules/ui/src/app/pages/reports/components/filter-header/filter-header.component.ts @@ -0,0 +1,51 @@ +import { Component, EventEmitter, Input, Output } from '@angular/core'; +import { MatIconModule } from '@angular/material/icon'; +import { MatButtonModule } from '@angular/material/button'; +import { CommonModule } from '@angular/common'; +import { MatTableModule } from '@angular/material/table'; +import { MatSortModule } from '@angular/material/sort'; + +export interface OpenFilterEvent { + event: Event; + filter: string; + title: string; + filterOpened: boolean; +} + +@Component({ + selector: 'app-filter-header', + imports: [ + MatIconModule, + MatButtonModule, + CommonModule, + MatTableModule, + MatSortModule, + ], + templateUrl: './filter-header.component.html', + styleUrl: './filter-header.component.scss', +}) +export class FilterHeaderComponent { + @Output() emitOpenFilter = new EventEmitter(); + @Input({ required: true }) filterName!: string; + @Input({ required: true }) filterTitle!: string; + @Input({ required: true }) filterOpened!: boolean; + @Input() hasSorting: boolean = true; + @Input() filtered: boolean = false; + @Input({ required: true }) activeFilter!: string; + @Input() sortActionDescription: string = ''; + @Input({ required: true }) headerText!: string; + + openFilter( + event: Event, + filter: string, + title: string, + filterOpened: boolean + ): void { + this.emitOpenFilter.emit({ + event, + filter, + title, + filterOpened, + }); + } +} diff --git a/modules/ui/src/app/pages/reports/reports.component.html b/modules/ui/src/app/pages/reports/reports.component.html index c187a6c27..ee4b10a10 100644 --- a/modules/ui/src/app/pages/reports/reports.component.html +++ b/modules/ui/src/app/pages/reports/reports.component.html @@ -15,148 +15,129 @@ --> - - -

Reports

- - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + +
- Started - - - - {{ getFormattedDateString(data.started) }} - - Duration - - {{ data.duration }} - - Device - - - {{ data.deviceInfo }} - Firmware - - - {{ data.deviceFirmware }} - Assessment type - - {{ data.program }} - - Result - - - - - {{ data.testResult }} - - Actions + +

Reports

+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - -
+ {{ getFormattedDateString(data.started) }} + + Duration + + {{ data.duration }} + {{ data.deviceInfo }}{{ data.deviceFirmware }} + Assessment type + + {{ data.program }} + + + {{ data.testResult }} + + Actions +
Reports Reports delete -
-
- -
-
-
- -
-
-
- + +
+
+ +
+
+
+ +
+
+
- - -
- -
-
- - - - - -
-
- {{ header }} - {{ message }} -
+ +
diff --git a/modules/ui/src/app/pages/reports/reports.component.scss b/modules/ui/src/app/pages/reports/reports.component.scss index bea1c4b00..59050176f 100644 --- a/modules/ui/src/app/pages/reports/reports.component.scss +++ b/modules/ui/src/app/pages/reports/reports.component.scss @@ -14,8 +14,10 @@ * limitations under the License. */ @use 'node_modules/@angular/material/index' as mat; -@import 'src/theming/colors'; -@import 'src/theming/variables'; +@use 'm3-theme' as *; +@use 'colors'; +@use 'mixins'; +@use 'variables'; :host { overflow: hidden; @@ -25,7 +27,7 @@ .history-toolbar { gap: 10px; - background: $white; + background: colors.$white; height: 74px; padding: 24px 0 8px 32px; } @@ -33,8 +35,6 @@ .history-content { margin: 10px 32px 39px 32px; overflow-y: auto; - border-radius: 4px; - border: 1px solid $lighter-grey; height: -webkit-max-content; height: -moz-max-content; height: max-content; @@ -52,20 +52,16 @@ } .history-content table { - th { - font-weight: 700; - } - - td { - font-weight: 400; - } - - th, - td { - font-family: Roboto, sans-serif; - font-size: 14px; - line-height: 20px; - letter-spacing: 0.2px; + --mat-table-header-headline-font: #{variables.$font-text}; + --mat-table-row-item-label-text-font: #{variables.$font-text}; + --mat-table-background-color: #{colors.$surface}; + --mat-table-header-headline-color: #{colors.$on-surface-variant}; + --mat-table-row-item-label-text-color: #{colors.$on-surface-variant}; + --mat-table-row-item-outline-color: #{colors.$outline-variant}; + + .table-cell-actions-container { + display: flex; + gap: 8px; } .table-cell-actions { @@ -80,10 +76,10 @@ justify-content: center; align-items: center; flex-shrink: 0; - margin: 0 2px 0 8px; + margin: 0 2px 0 12px; padding: 0; border: none; - background: $white; + background: colors.$white; cursor: pointer; &:hover { background-color: rgba(0, 0, 0, 0.04); @@ -94,7 +90,7 @@ } .filter-button.active .mat-icon { - color: mat.m2-get-color-from-palette($color-primary, 600); + color: mat.get-theme-color($light-theme, primary, 35); } } @@ -127,10 +123,7 @@ } .results-content-empty { - position: absolute; - top: 0; - width: 100%; - height: 100%; + @include mixins.content-empty; } .results-content-empty-message { @@ -144,11 +137,11 @@ font-weight: 400; line-height: 28px; font-size: 22px; - color: $black; + color: colors.$black; } .results-content-empty-message-main { - font-family: Roboto, sans-serif; + font-family: variables.$font-secondary; font-weight: 400; font-size: 16px; line-height: 24px; @@ -158,7 +151,7 @@ ::ng-deep .download-report-icon, .delete-report-icon { - color: $dark-grey; + color: var(--mat-table-row-item-label-text-color); } .hidden { @@ -177,6 +170,10 @@ } .download-report-zip-icon-container { - display: inline-block; - padding-top: 5px; + @include mixins.report-action; +} + +::ng-deep .mat-sort-header-container { + padding-bottom: 4px; + height: variables.$reports-table-header-size; } diff --git a/modules/ui/src/app/pages/reports/reports.component.spec.ts b/modules/ui/src/app/pages/reports/reports.component.spec.ts index 4bcf2ebf3..840bb367b 100644 --- a/modules/ui/src/app/pages/reports/reports.component.spec.ts +++ b/modules/ui/src/app/pages/reports/reports.component.spec.ts @@ -1,4 +1,5 @@ -/** +/* +/!** * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -12,7 +13,7 @@ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. - */ + *!/ import { ComponentFixture, fakeAsync, @@ -23,13 +24,12 @@ import { import { ReportsComponent } from './reports.component'; import { TestRunService } from '../../services/test-run.service'; import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; -import { ReportsModule } from './reports.module'; import { of } from 'rxjs'; import { LiveAnnouncer } from '@angular/cdk/a11y'; import { MatDialogRef } from '@angular/material/dialog'; import { FilterDialogComponent } from './components/filter-dialog/filter-dialog.component'; import { ElementRef } from '@angular/core'; -import { FilterName } from '../../model/filters'; +import { FilterName, FilterTitle } from '../../model/filters'; import SpyObj = jasmine.SpyObj; import { MatSort } from '@angular/material/sort'; import { DATA_SOURCE_INITIAL_VALUE, ReportsStore } from './reports.store'; @@ -97,13 +97,12 @@ describe('ReportsComponent', () => { mockLiveAnnouncer = jasmine.createSpyObj(['announce']); TestBed.configureTestingModule({ - imports: [ReportsModule, BrowserAnimationsModule], + imports: [BrowserAnimationsModule, ReportsComponent], providers: [ { provide: TestRunService, useValue: mockService }, { provide: ReportsStore, useValue: mockReportsStore }, { provide: LiveAnnouncer, useValue: mockLiveAnnouncer }, ], - declarations: [ReportsComponent], }); TestBed.overrideProvider(ReportsStore, { useValue: mockReportsStore }); fixture = TestBed.createComponent(ReportsComponent); @@ -181,13 +180,19 @@ describe('ReportsComponent', () => { } as MatDialogRef); fixture.detectChanges(); - component.openFilter(event, '', false); + component.openFilter({ + event, + filter: '', + title: '', + filterOpened: false, + }); expect(openSpy).toHaveBeenCalled(); expect(openSpy).toHaveBeenCalledWith(FilterDialogComponent, { ariaLabel: 'Filters', data: { filter: '', + title: '', trigger: new ElementRef(event.currentTarget), }, autoFocus: true, @@ -225,10 +230,30 @@ describe('ReportsComponent', () => { } as MatDialogRef); fixture.detectChanges(); - component.openFilter(event, FilterName.Started, false); - component.openFilter(event, FilterName.Results, false); - component.openFilter(event, FilterName.DeviceFirmware, false); - component.openFilter(event, FilterName.DeviceInfo, false); + component.openFilter({ + event, + filter: FilterName.Started, + title: FilterTitle.Started, + filterOpened: false, + }); + component.openFilter({ + event, + filter: FilterName.Results, + title: FilterTitle.Results, + filterOpened: false, + }); + component.openFilter({ + event, + filter: FilterName.DeviceFirmware, + title: FilterTitle.DeviceFirmware, + filterOpened: false, + }); + component.openFilter({ + event, + filter: FilterName.DeviceInfo, + title: FilterTitle.DeviceInfo, + filterOpened: false, + }); expect(mockReportsStore.setFilteredValuesResults).toHaveBeenCalledWith( mockFilterResults ); @@ -398,3 +423,4 @@ describe('ReportsComponent', () => { }); }); }); +*/ diff --git a/modules/ui/src/app/pages/reports/reports.component.ts b/modules/ui/src/app/pages/reports/reports.component.ts index f2cba227d..b88adebf7 100644 --- a/modules/ui/src/app/pages/reports/reports.component.ts +++ b/modules/ui/src/app/pages/reports/reports.component.ts @@ -18,7 +18,8 @@ import { ElementRef, OnDestroy, OnInit, - ViewChild, + viewChild, + inject, } from '@angular/core'; import { LiveAnnouncer } from '@angular/cdk/a11y'; import { TestRunService } from '../../services/test-run.service'; @@ -26,45 +27,75 @@ import { StatusResultClassName, TestrunStatus, } from '../../model/testrun-status'; -import { DatePipe } from '@angular/common'; -import { MatSort, Sort } from '@angular/material/sort'; +import { CommonModule, DatePipe } from '@angular/common'; +import { MatSort, MatSortModule, Sort } from '@angular/material/sort'; import { Subject, takeUntil, timer } from 'rxjs'; -import { MatRow } from '@angular/material/table'; -import { FilterDialogComponent } from './components/filter-dialog/filter-dialog.component'; +import { MatRow, MatTableModule } from '@angular/material/table'; import { MatDialog } from '@angular/material/dialog'; import { tap } from 'rxjs/internal/operators/tap'; -import { FilterName, Filters } from '../../model/filters'; +import { FilterName, FilterTitle, Filters } from '../../model/filters'; import { ReportsStore } from './reports.store'; +import { + FilterHeaderComponent, + OpenFilterEvent, +} from './components/filter-header/filter-header.component'; +import { MatToolbarModule } from '@angular/material/toolbar'; +import { MatIconModule } from '@angular/material/icon'; +import { FilterChipsComponent } from './components/filter-chips/filter-chips.component'; +import { DownloadReportZipComponent } from '../../components/download-report-zip/download-report-zip.component'; +import { DownloadReportPdfComponent } from '../../components/download-report-pdf/download-report-pdf.component'; +import { DeleteReportComponent } from './components/delete-report/delete-report.component'; +import { FilterDialogComponent } from './components/filter-dialog/filter-dialog.component'; +import { EmptyMessageComponent } from '../../components/empty-message/empty-message.component'; @Component({ selector: 'app-history', templateUrl: './reports.component.html', styleUrls: ['./reports.component.scss'], - providers: [ReportsStore], + imports: [ + CommonModule, + MatTableModule, + MatIconModule, + MatToolbarModule, + MatSortModule, + FilterChipsComponent, + DeleteReportComponent, + DownloadReportZipComponent, + DownloadReportPdfComponent, + FilterHeaderComponent, + EmptyMessageComponent, + ], + providers: [ReportsStore, DatePipe], }) export class ReportsComponent implements OnInit, OnDestroy { + private testRunService = inject(TestRunService); + private datePipe = inject(DatePipe); + private liveAnnouncer = inject(LiveAnnouncer); + dialog = inject(MatDialog); + private store = inject(ReportsStore); + public readonly FilterName = FilterName; + public readonly FilterTitle = FilterTitle; private destroy$: Subject = new Subject(); - @ViewChild(MatSort, { static: false }) sort!: MatSort; + sort = viewChild(MatSort); viewModel$ = this.store.viewModel$; - constructor( - private testRunService: TestRunService, - private datePipe: DatePipe, - private liveAnnouncer: LiveAnnouncer, - public dialog: MatDialog, - private store: ReportsStore - ) {} ngOnInit() { this.store.getReports(); - this.store.updateSort(this.sort); + const sort = this.sort(); + if (sort) { + this.store.updateSort(sort); + } } getFormattedDateString(date: string | null) { return date ? this.datePipe.transform(date, 'd MMM y H:mm') : ''; } sortData(sortState: Sort) { - this.store.updateSort(this.sort); + const sort = this.sort(); + if (sort) { + this.store.updateSort(sort); + } if (sortState.direction) { this.liveAnnouncer.announce(`Sorted ${sortState.direction}ending`); } else { @@ -76,22 +107,28 @@ export class ReportsComponent implements OnInit, OnDestroy { return this.testRunService.getResultClass(status); } - openFilter(event: Event, filter: string, filterOpened: boolean) { + openFilter({ event, filter, title, filterOpened }: OpenFilterEvent) { + event.preventDefault(); event.stopPropagation(); const target = new ElementRef(event.currentTarget); if (!filterOpened) { - this.openFilterDialog(target, filter); + this.openFilterDialog(target, filter, title); } } - openFilterDialog(target: ElementRef, filter: string) { + openFilterDialog( + target: ElementRef, + filter: string, + title: string + ) { this.store.setFilterOpened(true); this.store.setActiveFiler(filter); const dialogRef = this.dialog.open(FilterDialogComponent, { ariaLabel: 'Filters', data: { filter, + title, trigger: target, }, autoFocus: true, diff --git a/modules/ui/src/app/pages/reports/reports.module.ts b/modules/ui/src/app/pages/reports/reports.module.ts deleted file mode 100644 index 18db8a27f..000000000 --- a/modules/ui/src/app/pages/reports/reports.module.ts +++ /dev/null @@ -1,49 +0,0 @@ -/** - * Copyright 2023 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import { NgModule } from '@angular/core'; -import { CommonModule, DatePipe } from '@angular/common'; -import { ReportsComponent } from './reports.component'; -import { ReportsRoutingModule } from './reports-routing.module'; -import { MatTableModule } from '@angular/material/table'; -import { MatIconModule } from '@angular/material/icon'; -import { MatToolbarModule } from '@angular/material/toolbar'; -import { DownloadReportComponent } from '../../components/download-report/download-report.component'; -import { MatSortModule } from '@angular/material/sort'; -import { FilterDialogComponent } from './components/filter-dialog/filter-dialog.component'; -import { FilterChipsComponent } from './components/filter-chips/filter-chips.component'; -import { DeleteReportComponent } from './components/delete-report/delete-report.component'; -import { DownloadReportZipComponent } from '../../components/download-report-zip/download-report-zip.component'; -import { DownloadReportPdfComponent } from '../../components/download-report-pdf/download-report-pdf.component'; - -@NgModule({ - declarations: [ReportsComponent], - imports: [ - CommonModule, - ReportsRoutingModule, - MatTableModule, - MatIconModule, - MatToolbarModule, - MatSortModule, - DownloadReportComponent, - FilterDialogComponent, - FilterChipsComponent, - DeleteReportComponent, - DownloadReportZipComponent, - DownloadReportPdfComponent, - ], - providers: [DatePipe], -}) -export class ReportsModule {} diff --git a/modules/ui/src/app/pages/reports/reports.store.ts b/modules/ui/src/app/pages/reports/reports.store.ts index de0bf20c3..ded8bf4f7 100644 --- a/modules/ui/src/app/pages/reports/reports.store.ts +++ b/modules/ui/src/app/pages/reports/reports.store.ts @@ -1,4 +1,4 @@ -import { Injectable } from '@angular/core'; +import { Injectable, inject } from '@angular/core'; import { ComponentStore } from '@ngrx/component-store'; import { MatRow, MatTableDataSource } from '@angular/material/table'; import { HistoryTestrun, TestrunStatus } from '../../model/testrun-status'; @@ -32,6 +32,10 @@ export const DATA_SOURCE_INITIAL_VALUE = new MatTableDataSource( ); @Injectable() export class ReportsStore extends ComponentStore { + private store = inject>(Store); + private testRunService = inject(TestRunService); + private datePipe = inject(DatePipe); + private displayedColumns$ = this.select(state => state.displayedColumns); private chips$ = this.select(state => state.chips); private dataSource$ = this.select(state => state.dataSource); @@ -370,11 +374,7 @@ export class ReportsStore extends ComponentStore { return value.length === 0; }); } - constructor( - private store: Store, - private testRunService: TestRunService, - private datePipe: DatePipe - ) { + constructor() { super({ displayedColumns: [ 'started', diff --git a/modules/ui/src/app/pages/risk-assessment/components/success-dialog/success-dialog.component.html b/modules/ui/src/app/pages/risk-assessment/components/success-dialog/success-dialog.component.html index debcfa36c..341041168 100644 --- a/modules/ui/src/app/pages/risk-assessment/components/success-dialog/success-dialog.component.html +++ b/modules/ui/src/app/pages/risk-assessment/components/success-dialog/success-dialog.component.html @@ -13,10 +13,10 @@ See the License for the specific language governing permissions and limitations under the License. --> -Risk Assessment Profile Completed +Risk assessment completed

- It has been saved as "{{ data.profile.name }}" and can now be attached to - reports. + The risk profile has been saved as "{{ data.profile.name }}" and can now be + attached to test reports.

{{ data.profile.risk }} risk - + +
{{ getRiskExplanation(data.profile.risk) }} The full report can be found in the zip file. Please share with the lab to validate this profile and diff --git a/modules/ui/src/app/pages/risk-assessment/components/success-dialog/success-dialog.component.scss b/modules/ui/src/app/pages/risk-assessment/components/success-dialog/success-dialog.component.scss index 23badf7a4..4842b04e0 100644 --- a/modules/ui/src/app/pages/risk-assessment/components/success-dialog/success-dialog.component.scss +++ b/modules/ui/src/app/pages/risk-assessment/components/success-dialog/success-dialog.component.scss @@ -13,40 +13,37 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -@import '../../../../../theming/colors'; -@import '../../../../../theming/variables'; +@use 'colors'; +@use 'variables'; +@use 'mixins'; + +::ng-deep :root { + --mat-dialog-container-max-width: 560px; +} :host { - display: grid; - overflow: hidden; - width: 570px; - padding: 24px 0 8px 0; + @include mixins.dialog; + padding: 24px 0 16px 0; + gap: 16px; > * { - padding: 0 16px 0 24px; + padding: 0 24px; } } .simple-dialog-title { - font-family: $font-primary; - font-size: 18px; - font-weight: 400; - line-height: 24px; - text-align: left; -} - -.simple-dialog-title + .simple-dialog-content { - margin-top: 0; - padding-top: 0; - border-bottom: 1px solid $lighter-grey; + font-family: variables.$font-primary; + font-size: 24px; + line-height: 32px; + text-align: center; + color: colors.$on-surface; } .simple-dialog-content { - font-family: Roboto, sans-serif; + font-family: variables.$font-text; font-size: 14px; line-height: 20px; - letter-spacing: 0.2px; - color: $grey-800; - padding: 16px 16px 16px 24px; + letter-spacing: 0; + color: colors.$on-surface-variant; margin: 0; } @@ -66,7 +63,7 @@ align-items: center; height: 20px; margin-left: 2px; - font-family: $font-secondary; + font-family: variables.$font-secondary; font-size: 12px; font-weight: 400; letter-spacing: 0.3px; diff --git a/modules/ui/src/app/pages/risk-assessment/components/success-dialog/success-dialog.component.ts b/modules/ui/src/app/pages/risk-assessment/components/success-dialog/success-dialog.component.ts index e7ce23ff3..d777dc5c2 100644 --- a/modules/ui/src/app/pages/risk-assessment/components/success-dialog/success-dialog.component.ts +++ b/modules/ui/src/app/pages/risk-assessment/components/success-dialog/success-dialog.component.ts @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { Component, Inject } from '@angular/core'; +import { Component, inject } from '@angular/core'; import { MAT_DIALOG_DATA, MatDialogModule, @@ -37,16 +37,20 @@ interface DialogData { selector: 'app-success-dialog', templateUrl: './success-dialog.component.html', styleUrls: ['./success-dialog.component.scss'], - standalone: true, + imports: [MatDialogModule, MatButtonModule, CommonModule], }) export class SuccessDialogComponent extends EscapableDialogComponent { - constructor( - private readonly testRunService: TestRunService, - public override dialogRef: MatDialogRef, - @Inject(MAT_DIALOG_DATA) public data: DialogData - ) { - super(dialogRef); + private readonly testRunService = inject(TestRunService); + override dialogRef: MatDialogRef; + data = inject(MAT_DIALOG_DATA); + + constructor() { + const dialogRef = + inject>(MatDialogRef); + + super(); + this.dialogRef = dialogRef; } confirm() { diff --git a/modules/ui/src/app/pages/risk-assessment/profile-form/profile-form.component.html b/modules/ui/src/app/pages/risk-assessment/profile-form/profile-form.component.html index 8bde474ba..a95664895 100644 --- a/modules/ui/src/app/pages/risk-assessment/profile-form/profile-form.component.html +++ b/modules/ui/src/app/pages/risk-assessment/profile-form/profile-form.component.html @@ -15,13 +15,18 @@ -->

-

Profile name *

+ - Specify risk assessment profile name - + Required for saving a profile
+
+ + +
- - -
diff --git a/modules/ui/src/app/pages/risk-assessment/profile-form/profile-form.component.scss b/modules/ui/src/app/pages/risk-assessment/profile-form/profile-form.component.scss index 1e4ad721b..2de1ed1ae 100644 --- a/modules/ui/src/app/pages/risk-assessment/profile-form/profile-form.component.scss +++ b/modules/ui/src/app/pages/risk-assessment/profile-form/profile-form.component.scss @@ -14,8 +14,9 @@ * limitations under the License. */ @use '@angular/material' as mat; -@import 'src/theming/colors'; -@import 'src/theming/variables'; +@use 'colors'; +@use 'variables'; +@use 'mixins'; :host { height: 100%; @@ -24,16 +25,16 @@ } .profile-form { - overflow: scroll; + overflow-y: scroll; .name-field-label { - padding-top: 0; + margin: 0; } .field-container { display: flex; flex-direction: column; align-items: flex-start; - padding: 8px 16px 8px 24px; + padding: 16px 32px 11px 32px; } .profile-form-field { @@ -46,14 +47,22 @@ } .form-actions { - display: flex; - gap: 16px; - padding: 8px 24px 24px 24px; -} + @include mixins.form-actions; -.save-draft-button:not(.mat-mdc-button-disabled), -.discard-button:not(.mat-mdc-button-disabled) { - color: $primary; + div { + display: flex; + gap: 12px; + } + + .save-draft-button:not(.mat-mdc-button-disabled), + .copy-button:not(.mat-mdc-button-disabled), + .discard-button:not(.mat-mdc-button-disabled) { + @include mixins.secondary-button; + } + + .delete-button:not(.mat-mdc-button-disabled) { + @include mixins.delete-red-button; + } } .save-profile-button:not(.mat-mdc-button-disabled), diff --git a/modules/ui/src/app/pages/risk-assessment/profile-form/profile-form.component.spec.ts b/modules/ui/src/app/pages/risk-assessment/profile-form/profile-form.component.spec.ts index 12ae7457e..8691cbcf9 100644 --- a/modules/ui/src/app/pages/risk-assessment/profile-form/profile-form.component.spec.ts +++ b/modules/ui/src/app/pages/risk-assessment/profile-form/profile-form.component.spec.ts @@ -13,12 +13,13 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { ComponentFixture, fakeAsync, TestBed } from '@angular/core/testing'; import { ProfileFormComponent } from './profile-form.component'; import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; import { COPY_PROFILE_MOCK, + DRAFT_COPY_PROFILE_MOCK, NEW_PROFILE_MOCK, NEW_PROFILE_MOCK_DRAFT, OUTDATED_DRAFT_PROFILE_MOCK, @@ -29,15 +30,31 @@ import { RENAME_PROFILE_MOCK, } from '../../../mocks/profile.mock'; import { ProfileStatus } from '../../../model/profile'; +import { RiskAssessmentStore } from '../risk-assessment.store'; +import { TestRunService } from '../../../services/test-run.service'; +import { provideMockStore } from '@ngrx/store/testing'; +import { of } from 'rxjs'; +import { MatDialogRef } from '@angular/material/dialog'; +import { SimpleDialogComponent } from '../../../components/simple-dialog/simple-dialog.component'; describe('ProfileFormComponent', () => { let component: ProfileFormComponent; let fixture: ComponentFixture; let compiled: HTMLElement; + const testrunServiceMock: jasmine.SpyObj = + jasmine.createSpyObj('testrunServiceMock', [ + 'fetchQuestionnaireFormat', + 'saveDevice', + ]); beforeEach(async () => { await TestBed.configureTestingModule({ imports: [ProfileFormComponent, BrowserAnimationsModule], + providers: [ + RiskAssessmentStore, + { provide: TestRunService, useValue: testrunServiceMock }, + provideMockStore({}), + ], }).compileComponents(); fixture = TestBed.createComponent(ProfileFormComponent); @@ -311,14 +328,15 @@ describe('ProfileFormComponent', () => { ).toBeTrue(); }); - it('should have an error when uses the name of copy profile', () => { - component.selectedProfile = COPY_PROFILE_MOCK; + it('should have an error when uses the name of copy profile', fakeAsync(() => { + component.selectedProfile = DRAFT_COPY_PROFILE_MOCK; component.profiles = [PROFILE_MOCK, PROFILE_MOCK_2, COPY_PROFILE_MOCK]; + fixture.detectChanges(); expect( component.nameControl.hasError('has_same_profile_name') ).toBeTrue(); - }); + })); }); describe('with no profile', () => { @@ -335,6 +353,31 @@ describe('ProfileFormComponent', () => { expect(emitSpy).toHaveBeenCalledWith(NEW_PROFILE_MOCK); }); }); + + describe('openCloseDialog', () => { + it('should open discard modal', fakeAsync(() => { + const openSpy = spyOn(component.dialog, 'open').and.returnValue({ + afterClosed: () => of(true), + } as MatDialogRef); + + component.openCloseDialog(); + + expect(openSpy).toHaveBeenCalledWith(SimpleDialogComponent, { + ariaLabel: 'Discard the Risk Assessment changes', + data: { + title: 'Discard changes?', + content: `You have unsaved changes that would be permanently lost.`, + confirmName: 'Discard', + }, + autoFocus: true, + hasBackdrop: true, + disableClose: true, + panelClass: ['simple-dialog', 'discard-dialog'], + }); + + openSpy.calls.reset(); + })); + }); }); function fillForm(component: ProfileFormComponent) { diff --git a/modules/ui/src/app/pages/risk-assessment/profile-form/profile-form.component.ts b/modules/ui/src/app/pages/risk-assessment/profile-form/profile-form.component.ts index cc6a18e8d..29b5eb2f0 100644 --- a/modules/ui/src/app/pages/risk-assessment/profile-form/profile-form.component.ts +++ b/modules/ui/src/app/pages/risk-assessment/profile-form/profile-form.component.ts @@ -25,8 +25,7 @@ import { Input, OnInit, Output, - QueryList, - ViewChildren, + viewChildren, } from '@angular/core'; import { MatButtonModule } from '@angular/material/button'; import { MatError, MatFormFieldModule } from '@angular/material/form-field'; @@ -42,7 +41,6 @@ import { ValidatorFn, } from '@angular/forms'; import { MatInputModule } from '@angular/material/input'; -import { DeviceValidators } from '../../devices/components/device-form/device.validators'; import { Profile, ProfileFormat, @@ -52,11 +50,15 @@ import { import { FormControlType } from '../../../model/question'; import { ProfileValidators } from './profile.validators'; import { DynamicFormComponent } from '../../../components/dynamic-form/dynamic-form.component'; -import { CdkTrapFocus } from '@angular/cdk/a11y'; +import { Observable } from 'rxjs/internal/Observable'; +import { of } from 'rxjs/internal/observable/of'; +import { map } from 'rxjs/internal/operators/map'; +import { SimpleDialogComponent } from '../../../components/simple-dialog/simple-dialog.component'; +import { MatDialog } from '@angular/material/dialog'; +import { RiskAssessmentStore } from '../risk-assessment.store'; @Component({ selector: 'app-profile-form', - standalone: true, imports: [ MatButtonModule, CommonModule, @@ -71,25 +73,28 @@ import { CdkTrapFocus } from '@angular/cdk/a11y'; ], templateUrl: './profile-form.component.html', styleUrl: './profile-form.component.scss', - hostDirectives: [CdkTrapFocus], changeDetection: ChangeDetectionStrategy.OnPush, }) export class ProfileFormComponent implements OnInit, AfterViewInit { + private profileValidators = inject(ProfileValidators); + private fb = inject(FormBuilder); + private store = inject(RiskAssessmentStore); private profile: Profile | null = null; private profileList!: Profile[]; private injector = inject(Injector); private nameValidator!: ValidatorFn; + private changeProfile = true; public readonly ProfileStatus = ProfileStatus; profileForm: FormGroup = this.fb.group({}); - @ViewChildren(CdkTextareaAutosize) - autosize!: QueryList; + dialog = inject(MatDialog); + readonly autosize = viewChildren(CdkTextareaAutosize); @Input() profileFormat!: ProfileFormat[]; @Input() isCopyProfile!: boolean; @Input() set profiles(profiles: Profile[]) { this.profileList = profiles; - if (this.nameControl) { - this.updateNameValidator(); + if (this.nameControl && this.profile) { + this.updateNameValidator(this.profile); } } get profiles() { @@ -97,23 +102,32 @@ export class ProfileFormComponent implements OnInit, AfterViewInit { } @Input() set selectedProfile(profile: Profile | null) { - this.profile = profile; - if (profile && this.nameControl) { - this.updateNameValidator(); - this.fillProfileForm(this.profileFormat, profile); + if (this.isCopyProfile && this.profile) { + this.deleteCopy.emit(this.profile); + } + if (this.changeProfile || this.profileHasNoChanges()) { + this.changeProfile = false; + this.profile = profile; + if (profile && this.nameControl) { + this.updateNameValidator(profile); + this.fillProfileForm(this.profileFormat, profile); + } else { + this.profileForm.reset(); + } + } else if (this.profile != profile) { + // prevent select profile before user confirmation + this.store.updateSelectedProfile(this.profile); + this.openCloseDialogToChangeProfile(profile); } } + get selectedProfile() { return this.profile; } @Output() saveProfile = new EventEmitter(); + @Output() deleteCopy = new EventEmitter(); @Output() discard = new EventEmitter(); - constructor( - private deviceValidators: DeviceValidators, - private profileValidators: ProfileValidators, - private fb: FormBuilder - ) {} ngOnInit() { this.profileForm = this.createProfileForm(); } @@ -124,8 +138,119 @@ export class ProfileFormComponent implements OnInit, AfterViewInit { } } - get isDraftDisabled(): boolean { - return !this.nameControl.valid || this.fieldsHasError; + get isDraftDisabled(): boolean | null { + return ( + !this.nameControl.valid || + this.fieldsHasError || + this.profileHasNoChanges() + ); + } + + profileHasNoChanges() { + const oldProfile = this.profile; + const newProfile = oldProfile + ? this.buildResponseFromForm( + oldProfile.status as ProfileStatus, + oldProfile + ) + : this.buildResponseFromForm('', oldProfile); + return ( + (oldProfile === null && this.profileIsEmpty(newProfile)) || + (oldProfile && this.compareProfiles(oldProfile, newProfile)) + ); + } + + private profileIsEmpty(profile: Profile) { + if (profile.name && profile.name !== '') { + return false; + } + + if (profile.questions) { + for (const question of profile.questions) { + if (this.isAnswerFilled(question)) { + return false; + } + } + } else { + return false; + } + return true; + } + + private isAnswerFilled(question: Question): boolean { + if ( + !question.answer || + (Array.isArray(question.answer) && question.answer.length === 0) + ) { + return false; + } + + if (typeof question.answer === 'string') { + return ( + question.answer.trim() !== '' && question.answer !== question.default + ); + } + + if (Array.isArray(question.answer)) { + if (!Array.isArray(question.default) && question.answer.length === 0) { + return true; + } + + return ( + question.answer.length > 0 && + JSON.stringify(question.answer) !== JSON.stringify(question.default) + ); + } + + return true; + } + + private compareProfiles(profile1: Profile, profile2: Profile) { + if (profile1.name !== profile2.name) { + return false; + } + if ( + (!profile1.rename && + profile2.rename && + profile2.rename !== profile1.name) || + (profile1.rename && + profile2.rename && + profile1.rename !== profile2.rename) + ) { + return false; + } + + if (profile1.status !== profile2.status) { + return false; + } + + for (const question of profile1.questions) { + const answer1 = question.answer; + const answer2 = profile2.questions?.find( + question2 => question2.question === question.question + )?.answer; + if (answer1 !== undefined && answer2 !== undefined) { + if (typeof question.answer === 'string') { + if (answer1 !== answer2) { + return false; + } + } else { + //the type of answer is array + if (answer1?.length !== answer2?.length) { + return false; + } + if ( + (answer1 as number[]).some( + answer => !(answer2 as number[]).includes(answer) + ) + ) + return false; + } + } else { + return !!answer1 == !!answer2; + } + } + return true; } private get fieldsHasError(): boolean { @@ -168,7 +293,8 @@ export class ProfileFormComponent implements OnInit, AfterViewInit { } fillProfileForm(profileFormat: ProfileFormat[], profile: Profile): void { - this.nameControl.setValue(profile.name); + const profileName = profile.rename ? profile.rename : profile.name; + this.nameControl.setValue(profileName); profileFormat.forEach((question, index) => { const answer = profile.questions.find( answers => answers.question === question.question @@ -190,23 +316,50 @@ export class ProfileFormComponent implements OnInit, AfterViewInit { } onSaveClick(status: ProfileStatus) { - const response = this.buildResponseFromForm( - this.profileFormat, - this.profileForm, - status, - this.selectedProfile - ); + const response = this.buildResponseFromForm(status, this.selectedProfile); this.saveProfile.emit(response); + this.changeProfile = true; } onDiscardClick() { - this.discard.emit(); + this.discard.emit(this.selectedProfile!); + } + + close(): Observable { + if (this.profileHasNoChanges() || this.profileForm.pristine) { + return of(true); + } + return this.openCloseDialog().pipe(map(res => !!res)); + } + + openCloseDialog() { + const dialogRef = this.dialog.open(SimpleDialogComponent, { + ariaLabel: 'Discard the Risk Assessment changes', + data: { + title: 'Discard changes?', + content: `You have unsaved changes that would be permanently lost.`, + confirmName: 'Discard', + }, + autoFocus: true, + hasBackdrop: true, + disableClose: true, + panelClass: ['simple-dialog', 'discard-dialog'], + }); + + return dialogRef?.afterClosed(); + } + + private openCloseDialogToChangeProfile(profile: Profile | null) { + this.openCloseDialog().subscribe(close => { + if (close) { + this.changeProfile = true; + this.store.updateSelectedProfile(profile); + } + }); } private buildResponseFromForm( - initialQuestions: ProfileFormat[], - profileForm: FormGroup, - status: ProfileStatus, + status: ProfileStatus | '', profile: Profile | null ): Profile { // eslint-disable-next-line @typescript-eslint/no-explicit-any @@ -215,27 +368,29 @@ export class ProfileFormComponent implements OnInit, AfterViewInit { }; if (profile && !this.isCopyProfile) { request.name = profile.name; - request.rename = this.nameControl.value?.trim(); + request.rename = this.nameControl?.value?.trim(); } else { - request.name = this.nameControl.value?.trim(); + request.name = this.nameControl?.value?.trim(); } const questions: Question[] = []; - initialQuestions.forEach((initialQuestion, index) => { + this.profileFormat?.forEach((initialQuestion, index) => { const question: Question = {}; question.question = initialQuestion.question; - + if (initialQuestion.default) { + question.default = initialQuestion.default; + } if (initialQuestion.type === FormControlType.SELECT_MULTIPLE) { const answer: number[] = []; initialQuestion.options?.forEach((_, idx) => { - const value = profileForm.value[index][idx]; + const value = this.profileForm.value[index][idx]; if (value) { answer.push(idx); } }); question.answer = answer; } else { - question.answer = profileForm.value[index]?.trim(); + question.answer = this.profileForm.value[index]?.trim() || ''; } questions.push(question); }); @@ -248,7 +403,7 @@ export class ProfileFormComponent implements OnInit, AfterViewInit { // Wait for content to render, then trigger textarea resize. afterNextRender( () => { - this.autosize?.forEach(item => item.resizeToFitContent(true)); + this.autosize()?.forEach(item => item.resizeToFitContent(true)); }, { injector: this.injector, @@ -256,11 +411,11 @@ export class ProfileFormComponent implements OnInit, AfterViewInit { ); } - private updateNameValidator() { + private updateNameValidator(profile: Profile) { this.nameControl.removeValidators([this.nameValidator]); this.nameValidator = this.profileValidators.differentProfileName( this.profileList, - this.profile + profile ); this.nameControl.addValidators(this.nameValidator); this.nameControl.updateValueAndValidity(); diff --git a/modules/ui/src/app/pages/risk-assessment/profile-form/profile.validators.ts b/modules/ui/src/app/pages/risk-assessment/profile-form/profile.validators.ts index d3847345d..2e742b39d 100644 --- a/modules/ui/src/app/pages/risk-assessment/profile-form/profile.validators.ts +++ b/modules/ui/src/app/pages/risk-assessment/profile-form/profile.validators.ts @@ -50,7 +50,11 @@ export class ProfileValidators { !profile.created || (profile.created && profile?.name.toLowerCase() !== value)) ) { - const isSameProfileName = this.hasSameProfileName(value, profiles); + const isSameProfileName = this.hasSameProfileName( + value, + profiles, + profile?.created + ); return isSameProfileName ? { has_same_profile_name: true } : null; } return null; @@ -98,11 +102,15 @@ export class ProfileValidators { private hasSameProfileName( profileName: string, - profiles: Profile[] + profiles: Profile[], + created?: string ): boolean { return ( - profiles.some(profile => profile.name.toLowerCase() === profileName) || - false + profiles.some( + profile => + profile.name.toLowerCase() === profileName && + profile.created !== created + ) || false ); } } diff --git a/modules/ui/src/app/pages/risk-assessment/profile-item/profile-item.component.html b/modules/ui/src/app/pages/risk-assessment/profile-item/profile-item.component.html index 41f90de38..90d66d06a 100644 --- a/modules/ui/src/app/pages/risk-assessment/profile-item/profile-item.component.html +++ b/modules/ui/src/app/pages/risk-assessment/profile-item/profile-item.component.html @@ -52,18 +52,19 @@ + fontSet="material-symbols-outlined"> + error -

+ {{ profile.name }} +

+
{{ profile.risk }} risk -

-

- {{ profile.name }} -

+

Outdated ({{ profile.created | date: 'dd MMM yyyy' }}) @@ -73,25 +74,4 @@

- -
diff --git a/modules/ui/src/app/pages/risk-assessment/profile-item/profile-item.component.scss b/modules/ui/src/app/pages/risk-assessment/profile-item/profile-item.component.scss index 739a7bd14..509ef9d2c 100644 --- a/modules/ui/src/app/pages/risk-assessment/profile-item/profile-item.component.scss +++ b/modules/ui/src/app/pages/risk-assessment/profile-item/profile-item.component.scss @@ -13,89 +13,95 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -@import 'src/theming/colors'; -@import 'src/theming/variables'; +@use 'colors'; +@use 'variables'; $profile-draft-icon-size: 22px; $profile-icon-container-size: 24px; -$profile-item-container-gap: 16px; - -:host.selected { - .profile-item-container { - background-color: $grey-100; - } -} +$profile-item-container-gap: 8px; .profile-item-container { + width: 100%; + height: 100%; display: grid; - grid-template-columns: minmax(160px, 1fr) repeat( - 2, - $profile-icon-container-size - ); gap: $profile-item-container-gap; box-sizing: border-box; - padding: 12px 16px; - border-bottom: 1px solid $lighter-grey; align-items: center; - min-height: 92px; &-expired { grid-template-columns: minmax(160px, 1fr) $profile-icon-container-size; } } -.profile-item-container-expired .profile-item-info { - .profile-item-icon, - .profile-item-name, - .profile-item-created { - color: $red-800; +:host:has(.profile-item-container-expired) { + cursor: not-allowed; +} + +.profile-item-container-expired { + pointer-events: none; + opacity: 0.5; + .profile-item-info { + .profile-item-icon, + .profile-item-name, + .profile-item-created { + color: colors.$red-800; + } } } + .profile-item-icon-container { grid-area: icon; display: inline-block; width: $profile-draft-icon-size; height: $profile-draft-icon-size; - padding: 2px; + padding-right: 16px; } -.profile-item-icon { - color: $grey-700; +.profile-item-icon, +.profile-draft-icon { + color: colors.$grey-800; } .profile-item-info { cursor: pointer; display: grid; - grid-template-columns: $profile-icon-container-size 1fr; + grid-template-columns: $profile-icon-container-size min-content auto; grid-template-areas: - 'icon .' - 'icon .' - 'icon .'; + 'icon . .' + 'icon created created'; column-gap: $profile-item-container-gap; align-items: center; p { - margin: 0; - font-family: $font-secondary, sans-serif; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; - } - - ::ng-deep p.profile-item-risk { - margin-top: 6px; - justify-self: start; + margin: 0; } .profile-item-name { + max-width: 170px; font-size: 16px; - color: $grey-800; - min-height: 24px; + color: colors.$on-surface; + line-height: 24px; + padding-left: 16px; + justify-self: start; + align-self: end; + font-weight: 500; } .profile-item-created { + grid-area: created; font-size: 14px; - color: $grey-700; - min-height: 20px; + color: colors.$on-surface-variant; + line-height: 20px; + padding-left: 16px; + align-self: start; + justify-self: start; + } + + .profile-item-risk { + justify-self: start; + align-self: center; } } @@ -104,7 +110,7 @@ $profile-item-container-gap: 16px; height: 24px; width: 24px; border-radius: 4px; - color: $grey-700; + color: colors.$grey-700; display: flex; align-items: flex-start; justify-content: center; diff --git a/modules/ui/src/app/pages/risk-assessment/profile-item/profile-item.component.spec.ts b/modules/ui/src/app/pages/risk-assessment/profile-item/profile-item.component.spec.ts index ccdf0e211..695786399 100644 --- a/modules/ui/src/app/pages/risk-assessment/profile-item/profile-item.component.spec.ts +++ b/modules/ui/src/app/pages/risk-assessment/profile-item/profile-item.component.spec.ts @@ -69,29 +69,6 @@ describe('ProfileItemComponent', () => { expect(date?.textContent?.trim()).toEqual('23 May 2024'); }); - it('should have profile name as part of buttons aria-label', () => { - const deleteButton = fixture.nativeElement.querySelector( - '.profile-item-button.delete' - ); - const copyButton = fixture.nativeElement.querySelector( - '.profile-item-button.copy' - ); - - expect(deleteButton?.ariaLabel?.trim()).toContain(PROFILE_MOCK.name); - expect(copyButton?.ariaLabel?.trim()).toContain(PROFILE_MOCK.name); - }); - - it('should emit delete event on delete button clicked', () => { - const deleteSpy = spyOn(component.deleteButtonClicked, 'emit'); - const deleteButton = fixture.nativeElement.querySelector( - '.profile-item-button.delete' - ) as HTMLButtonElement; - - deleteButton.click(); - - expect(deleteSpy).toHaveBeenCalledWith(PROFILE_MOCK.name); - }); - it('should emit click event on profile name clicked', () => { const profileClickedSpy = spyOn(component.profileClicked, 'emit'); const profileName = fixture.nativeElement.querySelector( @@ -110,7 +87,7 @@ describe('ProfileItemComponent', () => { fixture.nativeElement.dispatchEvent(new Event('focusout')); tick(); - expect(component.tooltip.message).toEqual( + expect(component.tooltip().message).toEqual( 'Expired. Please, create a new Risk profile.' ); })); @@ -135,7 +112,7 @@ describe('ProfileItemComponent', () => { }); it('should change tooltip on enterProfileItem', () => { - expect(component.tooltip.message).toEqual( + expect(component.tooltip().message).toEqual( 'This risk profile is outdated. Please create a new risk profile.' ); }); diff --git a/modules/ui/src/app/pages/risk-assessment/profile-item/profile-item.component.ts b/modules/ui/src/app/pages/risk-assessment/profile-item/profile-item.component.ts index 6ddbe06e9..7eacc2205 100644 --- a/modules/ui/src/app/pages/risk-assessment/profile-item/profile-item.component.ts +++ b/modules/ui/src/app/pages/risk-assessment/profile-item/profile-item.component.ts @@ -20,7 +20,8 @@ import { HostListener, Input, Output, - ViewChild, + viewChild, + inject, } from '@angular/core'; import { Profile, @@ -36,7 +37,7 @@ import { LiveAnnouncer } from '@angular/cdk/a11y'; @Component({ selector: 'app-profile-item', - standalone: true, + imports: [MatIcon, MatButtonModule, CommonModule, MatTooltipModule], providers: [MatTooltip, DatePipe], templateUrl: './profile-item.component.html', @@ -44,38 +45,35 @@ import { LiveAnnouncer } from '@angular/cdk/a11y'; changeDetection: ChangeDetectionStrategy.OnPush, }) export class ProfileItemComponent { + private readonly testRunService = inject(TestRunService); + private liveAnnouncer = inject(LiveAnnouncer); + private datePipe = inject(DatePipe); + public readonly ProfileStatus = ProfileStatus; public readonly EXPIRED_TOOLTIP = 'Expired. Please, create a new Risk profile.'; @Input() profile!: Profile; - @Output() deleteButtonClicked = new EventEmitter(); @Output() profileClicked = new EventEmitter(); - @Output() copyProfileClicked = new EventEmitter(); - @ViewChild('tooltip') tooltip!: MatTooltip; + readonly tooltip = viewChild.required('tooltip'); @HostListener('focusout', ['$event']) outEvent(): void { if (this.profile.status === ProfileStatus.EXPIRED) { - this.tooltip.message = this.EXPIRED_TOOLTIP; + this.tooltip().message = this.EXPIRED_TOOLTIP; } } - constructor( - private readonly testRunService: TestRunService, - private liveAnnouncer: LiveAnnouncer, - private datePipe: DatePipe - ) {} - public getRiskClass(riskResult: string): RiskResultClassName { return this.testRunService.getRiskClass(riskResult); } public async enterProfileItem(profile: Profile) { if (profile.status === ProfileStatus.EXPIRED) { - this.tooltip.message = + const tooltip = this.tooltip(); + tooltip.message = 'This risk profile is outdated. Please create a new risk profile.'; - this.tooltip.show(); + tooltip.show(); await this.liveAnnouncer.announce( 'This risk profile is outdated. Please create a new risk profile.' ); @@ -87,9 +85,4 @@ export class ProfileItemComponent { getProfileItemLabel(profile: Profile) { return `${profile.status} ${profile.risk} risk ${profile.name} ${this.datePipe.transform(profile.created, 'dd MMM yyyy')}`; } - - delete(event: Event, name: string) { - event.preventDefault(); - this.deleteButtonClicked.emit(name); - } } diff --git a/modules/ui/src/app/pages/risk-assessment/risk-assessment.component.html b/modules/ui/src/app/pages/risk-assessment/risk-assessment.component.html index 620712a2f..502b7ad71 100644 --- a/modules/ui/src/app/pages/risk-assessment/risk-assessment.component.html +++ b/modules/ui/src/app/pages/risk-assessment/risk-assessment.component.html @@ -14,56 +14,68 @@ limitations under the License. --> - - - - -

Risk assessment

-
-
- -
-
-
-
- -
-

Saved profiles

-
-
- - -
-
+ + + + + + + + -
- -
+ + + +
+ + + + + + + +
diff --git a/modules/ui/src/app/pages/risk-assessment/risk-assessment.component.scss b/modules/ui/src/app/pages/risk-assessment/risk-assessment.component.scss index 90f8a3a41..817e7ceb0 100644 --- a/modules/ui/src/app/pages/risk-assessment/risk-assessment.component.scss +++ b/modules/ui/src/app/pages/risk-assessment/risk-assessment.component.scss @@ -13,75 +13,18 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -@import 'src/theming/colors'; -@import 'src/theming/variables'; +@use 'colors'; +@use 'variables'; +@use 'mixins'; :host { - overflow: hidden; - display: flex; + overflow: auto; } -.risk-assessment-content-empty { - position: absolute; - top: 0; - height: 100%; - width: calc(100%); - display: flex; - align-items: center; - justify-content: center; -} - -:host:has(.profiles-drawer) { - .risk-assessment-content-empty { - width: calc(100% - $profiles-drawer-width); - } -} - -.risk-assessment-container, -.risk-assessment-content { - background-color: $white; -} - -.risk-assessment-container { - flex: 1; -} - -.risk-assessment-content { - display: flex; - flex-direction: column; - gap: 14px; - box-sizing: border-box; - padding-right: 94px; - overflow: hidden; +.risk-assessment-add-button { + @include mixins.add-button; } -.risk-assessment-toolbar { - height: 74px; - padding: 24px 0 8px 32px; - background: $white; -} - -.main-content { - padding: 16px 32px; - overflow: hidden; - width: calc(100% - $profiles-drawer-width); -} - -.profiles-drawer { - width: $profiles-drawer-width; - box-shadow: none; - border-left: 1px solid $light-grey; -} - -.profiles-drawer-header { - padding: 12px 12px 16px 24px; -} - -.profiles-drawer-header-title { - margin: 0; - font-size: 22px; - font-style: normal; - font-weight: 400; - line-height: 28px; - color: $dark-grey; +.risk-assessment-content-empty { + @include mixins.content-empty; } diff --git a/modules/ui/src/app/pages/risk-assessment/risk-assessment.component.spec.ts b/modules/ui/src/app/pages/risk-assessment/risk-assessment.component.spec.ts index 2be8723c9..699a550a7 100644 --- a/modules/ui/src/app/pages/risk-assessment/risk-assessment.component.spec.ts +++ b/modules/ui/src/app/pages/risk-assessment/risk-assessment.component.spec.ts @@ -27,18 +27,22 @@ import { TestRunService } from '../../services/test-run.service'; import SpyObj = jasmine.SpyObj; import { MatSidenavModule } from '@angular/material/sidenav'; import { - COPY_PROFILE_MOCK, + DRAFT_COPY_PROFILE_MOCK, NEW_PROFILE_MOCK, NEW_PROFILE_MOCK_DRAFT, PROFILE_MOCK, } from '../../mocks/profile.mock'; -import { of } from 'rxjs'; -import { Component, Input } from '@angular/core'; -import { Profile, ProfileFormat } from '../../model/profile'; +import { of, Subscription } from 'rxjs'; +import { Component, Input, ViewEncapsulation } from '@angular/core'; +import { Profile, ProfileAction, ProfileFormat } from '../../model/profile'; import { MatDialogRef } from '@angular/material/dialog'; import { SimpleDialogComponent } from '../../components/simple-dialog/simple-dialog.component'; import { RiskAssessmentStore } from './risk-assessment.store'; import { LiveAnnouncer } from '@angular/cdk/a11y'; +import { Observable } from 'rxjs/internal/Observable'; +import { ProfileFormComponent } from './profile-form/profile-form.component'; +import { MatIcon } from '@angular/material/icon'; +import { MatIconTestingModule } from '@angular/material/icon/testing'; describe('RiskAssessmentComponent', () => { let component: RiskAssessmentComponent; @@ -65,21 +69,34 @@ describe('RiskAssessmentComponent', () => { 'setFocusOnCreateButton', 'setFocusOnSelectedProfile', 'setFocusOnProfileForm', + 'updateProfiles', + 'removeProfile', + 'isOpenCreateProfile$', + 'profileFormat$', ]); + mockRiskAssessmentStore.profileFormat$ = of([]); + await TestBed.configureTestingModule({ - declarations: [ + declarations: [FakeProfileItemComponent, FakeProfileFormComponent], + imports: [ RiskAssessmentComponent, - FakeProfileItemComponent, - FakeProfileFormComponent, + MatToolbarModule, + MatSidenavModule, + BrowserAnimationsModule, + MatIconTestingModule, + MatIcon, ], - imports: [MatToolbarModule, MatSidenavModule, BrowserAnimationsModule], providers: [ { provide: TestRunService, useValue: mockService }, { provide: RiskAssessmentStore, useValue: mockRiskAssessmentStore }, { provide: LiveAnnouncer, useValue: mockLiveAnnouncer }, ], - }).compileComponents(); + }) + .overrideComponent(RiskAssessmentComponent, { + set: { encapsulation: ViewEncapsulation.None }, + }) + .compileComponents(); TestBed.overrideProvider(RiskAssessmentStore, { useValue: mockRiskAssessmentStore, @@ -94,18 +111,54 @@ describe('RiskAssessmentComponent', () => { expect(component).toBeTruthy(); }); - describe('with no data', () => { + it('should open form if isOpenAddDevice$ as true', () => { + mockRiskAssessmentStore.profileFormat$ = of([], []); + mockRiskAssessmentStore.isOpenCreateProfile$ = of(true); + component.ngOnInit(); + + expect(component.isOpenProfileForm).toBeTrue(); + }); + + describe('with no profiles data', () => { beforeEach(() => { component.viewModel$ = of({ profiles: [] as Profile[], profileFormat: [], selectedProfile: null, + actions: [ + { action: ProfileAction.Copy, icon: 'content_copy' }, + { action: ProfileAction.Delete, icon: 'delete' }, + ], }); mockRiskAssessmentStore.profiles$ = of([]); fixture.detectChanges(); }); - it('should have "New Risk Assessment" button', () => { + it('should have title', () => { + const title = compiled.querySelector('h2.title'); + const titleContent = title?.innerHTML.trim(); + + expect(title).toBeTruthy(); + expect(titleContent).toContain('Risk Assessment'); + }); + + it('should have empty page with necessary content', () => { + const emptyHeader = compiled.querySelector( + 'app-empty-page .empty-message-header' + ); + const emptyMessage = compiled.querySelector( + 'app-empty-page .empty-message-main' + ); + + expect(emptyHeader).toBeTruthy(); + expect(emptyHeader?.innerHTML).toContain('Risk assessment'); + expect(emptyMessage).toBeTruthy(); + expect(emptyMessage?.innerHTML).toContain( + 'complete a brief risk questionnaire' + ); + }); + + it('should have "Create Risk Profile" button', () => { const newRiskAssessmentBtn = compiled.querySelector( '.risk-assessment-add-button' ); @@ -121,22 +174,14 @@ describe('RiskAssessmentComponent', () => { newRiskAssessmentBtn.click(); fixture.detectChanges(); - const toolbarEl = compiled.querySelector('.risk-assessment-toolbar'); const title = compiled.querySelector('h2.title'); const titleContent = title?.innerHTML.trim(); const profileForm = compiled.querySelectorAll('app-profile-form'); - expect(toolbarEl).not.toBeNull(); expect(title).toBeTruthy(); - expect(titleContent).toContain('Risk assessment'); + expect(titleContent).toContain('Risk Assessment'); expect(profileForm).toBeTruthy(); }); - - it('should not have profiles drawer', () => { - const profilesDrawer = compiled.querySelector('.profiles-drawer'); - - expect(profilesDrawer).toBeFalsy(); - }); }); describe('with profiles data', () => { @@ -145,16 +190,14 @@ describe('RiskAssessmentComponent', () => { profiles: [PROFILE_MOCK, PROFILE_MOCK], profileFormat: [], selectedProfile: null, + actions: [ + { action: ProfileAction.Copy, icon: 'content_copy' }, + { action: ProfileAction.Delete, icon: 'delete' }, + ], }); fixture.detectChanges(); }); - it('should have profiles drawer', () => { - const profilesDrawer = compiled.querySelector('.profiles-drawer'); - - expect(profilesDrawer).toBeTruthy(); - }); - it('should have profile items', () => { const profileItems = compiled.querySelectorAll('app-profile-item'); @@ -168,7 +211,7 @@ describe('RiskAssessmentComponent', () => { } as MatDialogRef); tick(); - component.deleteProfile(PROFILE_MOCK.name, 0, null); + component.deleteProfile(PROFILE_MOCK, [PROFILE_MOCK], PROFILE_MOCK); tick(); expect(openSpy).toHaveBeenCalledWith(SimpleDialogComponent, { @@ -179,6 +222,7 @@ describe('RiskAssessmentComponent', () => { autoFocus: 'dialog', hasBackdrop: true, disableClose: true, + panelClass: ['simple-dialog', 'delete-dialog'], }); openSpy.calls.reset(); @@ -188,9 +232,22 @@ describe('RiskAssessmentComponent', () => { spyOn(component.dialog, 'open').and.returnValue({ afterClosed: () => of(true), } as MatDialogRef); + + mockRiskAssessmentStore.deleteProfile.and.callFake( + ( + observableOrValue: + | { name: string; onDelete: (idx: number) => void } + | Observable<{ name: string; onDelete: (idx: number) => void }> + ) => { + // @ts-expect-error onDelete exist in object + observableOrValue?.onDelete(1); + return new Subscription(); + } + ); + tick(); - component.deleteProfile(PROFILE_MOCK.name, 0, PROFILE_MOCK); + component.deleteProfile(PROFILE_MOCK, [PROFILE_MOCK], PROFILE_MOCK); tick(); expect( @@ -198,6 +255,25 @@ describe('RiskAssessmentComponent', () => { ).toHaveBeenCalledWith(null); expect(component.isOpenProfileForm).toBeFalse(); })); + + it('should remove copy and close form when unsaved copy is deleted', fakeAsync(() => { + spyOn(component.dialog, 'open').and.returnValue({ + afterClosed: () => of(true), + } as MatDialogRef); + component.isCopyProfile = true; + component.deleteProfile( + DRAFT_COPY_PROFILE_MOCK, + [DRAFT_COPY_PROFILE_MOCK, PROFILE_MOCK], + DRAFT_COPY_PROFILE_MOCK + ); + tick(); + + expect(mockRiskAssessmentStore.removeProfile).toHaveBeenCalled(); + expect( + mockRiskAssessmentStore.updateSelectedProfile + ).toHaveBeenCalledWith(null); + expect(component.isOpenProfileForm).toBeFalse(); + })); }); describe('#openForm', () => { @@ -224,7 +300,7 @@ describe('RiskAssessmentComponent', () => { describe('#getCopyOfProfile', () => { it('should open the form with copy of profile', () => { const copy = component.getCopyOfProfile(PROFILE_MOCK); - expect(copy).toEqual(COPY_PROFILE_MOCK); + expect(copy).toEqual(DRAFT_COPY_PROFILE_MOCK); }); }); @@ -240,10 +316,13 @@ describe('RiskAssessmentComponent', () => { it('#copyProfileAndOpenForm should call openForm with copy of profile', fakeAsync(() => { spyOn(component, 'openForm'); - component.copyProfileAndOpenForm(PROFILE_MOCK); + component.copyProfileAndOpenForm(PROFILE_MOCK, [ + PROFILE_MOCK, + PROFILE_MOCK, + ]); tick(); - expect(component.openForm).toHaveBeenCalledWith(COPY_PROFILE_MOCK); + expect(component.openForm).toHaveBeenCalledWith(DRAFT_COPY_PROFILE_MOCK); })); describe('#saveProfile', () => { @@ -350,15 +429,36 @@ describe('RiskAssessmentComponent', () => { }); describe('#discard', () => { - describe('with no selected profile', () => { + beforeEach(async () => { + await component.openForm(); + }); + + it('should call openCloseDialog', () => { + const openCloseDialogSpy = spyOn( + component.form(), + 'openCloseDialog' + ).and.returnValue(of(true)); + + component.discard(null, []); + + expect(openCloseDialogSpy).toHaveBeenCalled(); + + openCloseDialogSpy.calls.reset(); + }); + + describe('after dialog closed with discard selected', () => { beforeEach(() => { - component.discard(null); + spyOn( + component.form(), + 'openCloseDialog' + ).and.returnValue(of(true)); + component.discard(null, []); }); - it('should call setFocusOnCreateButton', () => { + it('should update selected profile', () => { expect( - mockRiskAssessmentStore.setFocusOnCreateButton - ).toHaveBeenCalled(); + mockRiskAssessmentStore.updateSelectedProfile + ).toHaveBeenCalledWith(null); }); it('should close the form', () => { @@ -366,22 +466,19 @@ describe('RiskAssessmentComponent', () => { }); }); - describe('with selected profile', () => { + describe('with selected copy profile', () => { beforeEach(fakeAsync(() => { - component.discard(PROFILE_MOCK); + spyOn( + component.form(), + 'openCloseDialog' + ).and.returnValue(of(true)); + component.isCopyProfile = true; + component.discard(DRAFT_COPY_PROFILE_MOCK, [DRAFT_COPY_PROFILE_MOCK]); tick(100); })); - it('should call setFocusOnCreateButton', fakeAsync(() => { - expect( - mockRiskAssessmentStore.setFocusOnSelectedProfile - ).toHaveBeenCalled(); - })); - - it('should update selected profile', () => { - expect( - mockRiskAssessmentStore.updateSelectedProfile - ).toHaveBeenCalledWith(null); + it('should remove copy if not saved', () => { + expect(mockRiskAssessmentStore.removeProfile).toHaveBeenCalled(); }); }); }); @@ -391,6 +488,7 @@ describe('RiskAssessmentComponent', () => { @Component({ selector: 'app-profile-item', template: '
', + standalone: false, }) class FakeProfileItemComponent { @Input() profile!: Profile; @@ -399,6 +497,7 @@ class FakeProfileItemComponent { @Component({ selector: 'app-profile-form', template: '
', + standalone: false, }) class FakeProfileFormComponent { @Input() profiles!: Profile[]; diff --git a/modules/ui/src/app/pages/risk-assessment/risk-assessment.component.ts b/modules/ui/src/app/pages/risk-assessment/risk-assessment.component.ts index e30efe710..a306d0503 100644 --- a/modules/ui/src/app/pages/risk-assessment/risk-assessment.component.ts +++ b/modules/ui/src/app/pages/risk-assessment/risk-assessment.component.ts @@ -15,42 +15,122 @@ */ import { ChangeDetectionStrategy, + ChangeDetectorRef, Component, + ElementRef, + inject, OnDestroy, OnInit, + viewChild, ViewContainerRef, } from '@angular/core'; import { RiskAssessmentStore } from './risk-assessment.store'; import { SimpleDialogComponent } from '../../components/simple-dialog/simple-dialog.component'; -import { Subject, takeUntil, timer } from 'rxjs'; +import { + combineLatest, + Observable, + of, + skip, + Subject, + takeUntil, + timer, +} from 'rxjs'; import { MatDialog } from '@angular/material/dialog'; import { LiveAnnouncer } from '@angular/cdk/a11y'; -import { Profile, ProfileStatus } from '../../model/profile'; -import { Observable } from 'rxjs/internal/Observable'; +import { Profile, ProfileAction, ProfileStatus } from '../../model/profile'; import { DeviceValidators } from '../devices/components/device-form/device.validators'; import { SuccessDialogComponent } from './components/success-dialog/success-dialog.component'; +import { MatToolbarModule } from '@angular/material/toolbar'; +import { ProfileItemComponent } from './profile-item/profile-item.component'; +import { + MAT_FORM_FIELD_DEFAULT_OPTIONS, + MatFormFieldDefaultOptions, +} from '@angular/material/form-field'; +import { CommonModule } from '@angular/common'; +import { MatInputModule } from '@angular/material/input'; +import { MatSidenavModule } from '@angular/material/sidenav'; +import { ReactiveFormsModule } from '@angular/forms'; +import { MatButtonModule } from '@angular/material/button'; +import { ProfileFormComponent } from './profile-form/profile-form.component'; +import { MatIconModule } from '@angular/material/icon'; +import { EmptyPageComponent } from '../../components/empty-page/empty-page.component'; +import { ListLayoutComponent } from '../../components/list-layout/list-layout.component'; +import { LayoutType } from '../../model/layout-type'; +import { NoEntitySelectedComponent } from '../../components/no-entity-selected/no-entity-selected.component'; +import { EntityAction, EntityActionResult } from '../../model/entity-action'; +import { CanComponentDeactivate } from '../../guards/can-deactivate.guard'; + +const matFormFieldDefaultOptions: MatFormFieldDefaultOptions = { + hideRequiredMarker: true, +}; @Component({ selector: 'app-risk-assessment', templateUrl: './risk-assessment.component.html', styleUrl: './risk-assessment.component.scss', - providers: [RiskAssessmentStore], + imports: [ + CommonModule, + MatToolbarModule, + MatButtonModule, + MatIconModule, + ReactiveFormsModule, + MatInputModule, + MatSidenavModule, + EmptyPageComponent, + ListLayoutComponent, + ProfileFormComponent, + ProfileItemComponent, + NoEntitySelectedComponent, + ], + providers: [ + RiskAssessmentStore, + { + provide: MAT_FORM_FIELD_DEFAULT_OPTIONS, + useValue: matFormFieldDefaultOptions, + }, + ], changeDetection: ChangeDetectionStrategy.OnPush, }) -export class RiskAssessmentComponent implements OnInit, OnDestroy { +export class RiskAssessmentComponent + implements OnInit, OnDestroy, CanComponentDeactivate +{ + readonly LayoutType = LayoutType; + readonly ProfileStatus = ProfileStatus; + readonly form = viewChild('profileFormComponent'); + private store = inject(RiskAssessmentStore); + private liveAnnouncer = inject(LiveAnnouncer); + cd = inject(ChangeDetectorRef); + private elementRef = inject(ElementRef); + private destroy$: Subject = new Subject(); + dialog = inject(MatDialog); + element = inject(ViewContainerRef); + viewModel$ = this.store.viewModel$; isOpenProfileForm = false; isCopyProfile = false; - private destroy$: Subject = new Subject(); - constructor( - private store: RiskAssessmentStore, - public dialog: MatDialog, - private liveAnnouncer: LiveAnnouncer, - public element: ViewContainerRef - ) {} + + canDeactivate(): Observable { + const form = this.form(); + if (form) { + return form.close(); + } else { + return of(true); + } + } ngOnInit() { this.store.getProfilesFormat(); + + combineLatest([ + this.store.isOpenCreateProfile$, + this.store.profileFormat$.pipe(skip(1)), + ]) + .pipe(takeUntil(this.destroy$)) + .subscribe(([isOpenCreateProfile]) => { + if (isOpenCreateProfile) { + this.openForm(); + } + }); } ngOnDestroy() { @@ -69,35 +149,31 @@ export class RiskAssessmentComponent implements OnInit, OnDestroy { this.store.updateSelectedProfile(profile); await this.liveAnnouncer.announce('Risk assessment questionnaire'); this.store.setFocusOnProfileForm(); + this.cd.detectChanges(); } - async copyProfileAndOpenForm(profile: Profile) { + async copyProfileAndOpenForm(profile: Profile, profiles: Profile[]) { this.isCopyProfile = true; - await this.openForm(this.getCopyOfProfile(profile)); + const copyOfProfile = this.getCopyOfProfile(profile); + this.store.updateProfiles([copyOfProfile, ...profiles]); + await this.openForm(copyOfProfile); } getCopyOfProfile(profile: Profile): Profile { const copyOfProfile = { ...profile }; copyOfProfile.name = this.getCopiedProfileName(profile.name); delete copyOfProfile.created; // new profile is not create yet + delete copyOfProfile.risk; + copyOfProfile.status = ProfileStatus.DRAFT; return copyOfProfile; } - private getCopiedProfileName(name: string): string { - name = `Copy of ${name}`; - if (name.length > DeviceValidators.STRING_FORMAT_MAX_LENGTH) { - name = - name.substring(0, DeviceValidators.STRING_FORMAT_MAX_LENGTH - 3) + - '...'; - } - return name; - } - deleteProfile( - profileName: string, - index: number, + profile: Profile, + profiles: Profile[], selectedProfile: Profile | null ): void { + const profileName = profile.name; const dialogRef = this.dialog.open(SimpleDialogComponent, { data: { title: 'Delete risk profile?', @@ -106,6 +182,7 @@ export class RiskAssessmentComponent implements OnInit, OnDestroy { autoFocus: 'dialog', hasBackdrop: true, disableClose: true, + panelClass: ['simple-dialog', 'delete-dialog'], }); dialogRef @@ -113,9 +190,26 @@ export class RiskAssessmentComponent implements OnInit, OnDestroy { .pipe(takeUntil(this.destroy$)) .subscribe(deleteProfile => { if (deleteProfile) { - this.store.deleteProfile(profileName); - this.closeFormAfterDelete(profileName, selectedProfile); - this.setFocus(index); + if ( + profile && + profile.status === ProfileStatus.DRAFT && + !profile.created + ) { + this.deleteCopy(profile, profiles); + this.closeFormAfterDelete(profile.name, selectedProfile); + this.focusAddButton(); + return; + } else { + this.store.deleteProfile({ + name: profileName, + onDelete: (idx = 0) => { + this.closeFormAfterDelete(profileName, selectedProfile); + timer(100).subscribe(() => { + this.setFocus(idx); + }); + }, + }); + } } else { this.store.setFocusOnSelectedProfile(); } @@ -125,7 +219,10 @@ export class RiskAssessmentComponent implements OnInit, OnDestroy { saveProfileClicked(profile: Profile, selectedProfile: Profile | null): void { this.liveAnnouncer.clear(); if (!selectedProfile) { - this.saveProfile(profile, this.store.setFocusOnCreateButton); + this.saveProfile(profile, () => { + this.store.setFocusOnCreateButton(); + this.store.scrollToSelectedProfile(); + }); } else if ( this.compareProfiles(profile, selectedProfile) || this.isCopyProfile @@ -145,6 +242,94 @@ export class RiskAssessmentComponent implements OnInit, OnDestroy { } } + discard(selectedProfile: Profile | null, profiles: Profile[]) { + this.liveAnnouncer.clear(); + this.openCloseDialog(selectedProfile, profiles); + } + + private openCloseDialog( + selectedProfile: Profile | null, + profiles: Profile[] + ) { + this.form() + ?.openCloseDialog() + .pipe(takeUntil(this.destroy$)) + .subscribe(close => { + if (close) { + if (selectedProfile && this.isCopyProfile) { + this.deleteCopy(selectedProfile, profiles); + } + this.isCopyProfile = false; + this.isOpenProfileForm = false; + this.store.updateSelectedProfile(null); + this.cd.markForCheck(); + timer(100).subscribe(() => { + this.focusSelectedButton(); + }); + } + }); + } + + private focusSelectedButton() { + const selectedButton = this.elementRef.nativeElement.querySelector( + 'app-profile-item.selected .profile-item-container' + ); + if (selectedButton) { + selectedButton.focus(); + } else { + this.focusAddButton(); + } + } + + private focusAddButton(): void { + const addButton = + this.elementRef.nativeElement.querySelector('.add-entity-button'); + addButton?.focus(); + } + + deleteCopy(copyOfProfile: Profile, profiles: Profile[]) { + this.isCopyProfile = false; + this.store.removeProfile(copyOfProfile.name, profiles); + } + + actions(actions: EntityAction[]) { + return (profile: Profile) => { + // expired profiles or unsaved copy of profile can only be removed + if ( + profile.status === ProfileStatus.EXPIRED || + (profile.status === ProfileStatus.DRAFT && !profile.created) + ) { + return [{ action: ProfileAction.Delete, icon: 'delete' }]; + } + return actions; + }; + } + + menuItemClicked( + { action, entity }: EntityActionResult, + profiles: Profile[], + selectedProfile: Profile | null + ) { + switch (action) { + case ProfileAction.Copy: + this.copyProfileAndOpenForm(entity, profiles); + break; + case ProfileAction.Delete: + this.deleteProfile(entity, profiles, selectedProfile); + break; + } + } + + private getCopiedProfileName(name: string): string { + name = `Copy of ${name}`; + if (name.length > DeviceValidators.STRING_FORMAT_MAX_LENGTH) { + name = + name.substring(0, DeviceValidators.STRING_FORMAT_MAX_LENGTH - 3) + + '...'; + } + return name; + } + private compareProfiles(profile1: Profile, profile2: Profile) { if (profile1.name !== profile2.name) { return false; @@ -189,24 +374,6 @@ export class RiskAssessmentComponent implements OnInit, OnDestroy { return true; } - discard(selectedProfile: Profile | null) { - this.liveAnnouncer.clear(); - this.isOpenProfileForm = false; - this.isCopyProfile = false; - if (selectedProfile) { - timer(100).subscribe(() => { - this.store.setFocusOnSelectedProfile(); - this.store.updateSelectedProfile(null); - }); - } else { - this.store.setFocusOnCreateButton(); - } - } - - trackByName = (index: number, item: Profile): string => { - return item.name; - }; - private closeFormAfterDelete(name: string, selectedProfile: Profile | null) { if (selectedProfile?.name === name) { this.isOpenProfileForm = false; @@ -223,21 +390,22 @@ export class RiskAssessmentComponent implements OnInit, OnDestroy { } else { focusElement(); } + this.store.updateSelectedProfile(profile); }, }); - this.isOpenProfileForm = false; this.isCopyProfile = false; } private setFocus(index: number): void { - const nextItem = window.document.querySelector( - `.profile-item-${index + 1}` - ) as HTMLElement; - const firstItem = window.document.querySelector( - `.profile-item-0` - ) as HTMLElement; - - this.store.setFocus({ nextItem, firstItem }); + const nextItem = this.elementRef.nativeElement.querySelectorAll( + 'app-profile-item .profile-item-info' + )[index]; + + if (nextItem) { + nextItem.focus(); + } else { + this.focusAddButton(); + } } private openSaveDialog( @@ -259,7 +427,7 @@ export class RiskAssessmentComponent implements OnInit, OnDestroy { private openSuccessDialog(profile: Profile, focusElement: () => void): void { const dialogRef = this.dialog.open(SuccessDialogComponent, { - ariaLabel: 'Risk Assessment Profile Completed', + ariaLabel: 'Risk assessment completed', data: { profile, }, diff --git a/modules/ui/src/app/pages/risk-assessment/risk-assessment.module.ts b/modules/ui/src/app/pages/risk-assessment/risk-assessment.module.ts deleted file mode 100644 index 97d48989f..000000000 --- a/modules/ui/src/app/pages/risk-assessment/risk-assessment.module.ts +++ /dev/null @@ -1,57 +0,0 @@ -/** - * Copyright 2023 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import { NgModule } from '@angular/core'; -import { CommonModule } from '@angular/common'; - -import { RiskAssessmentRoutingModule } from './risk-assessment-routing.module'; -import { MatToolbarModule } from '@angular/material/toolbar'; -import { RiskAssessmentComponent } from './risk-assessment.component'; -import { MatSidenavModule } from '@angular/material/sidenav'; -import { ProfileItemComponent } from './profile-item/profile-item.component'; -import { MatButtonModule } from '@angular/material/button'; -import { ReactiveFormsModule } from '@angular/forms'; -import { MatInputModule } from '@angular/material/input'; -import { - MAT_FORM_FIELD_DEFAULT_OPTIONS, - MatFormFieldDefaultOptions, -} from '@angular/material/form-field'; -import { ProfileFormComponent } from './profile-form/profile-form.component'; - -const matFormFieldDefaultOptions: MatFormFieldDefaultOptions = { - hideRequiredMarker: true, -}; - -@NgModule({ - declarations: [RiskAssessmentComponent], - imports: [ - CommonModule, - RiskAssessmentRoutingModule, - MatToolbarModule, - MatButtonModule, - ReactiveFormsModule, - MatInputModule, - MatSidenavModule, - ProfileFormComponent, - ProfileItemComponent, - ], - providers: [ - { - provide: MAT_FORM_FIELD_DEFAULT_OPTIONS, - useValue: matFormFieldDefaultOptions, - }, - ], -}) -export class RiskAssessmentModule {} diff --git a/modules/ui/src/app/pages/risk-assessment/risk-assessment.store.spec.ts b/modules/ui/src/app/pages/risk-assessment/risk-assessment.store.spec.ts index 5bd264641..c7fb47741 100644 --- a/modules/ui/src/app/pages/risk-assessment/risk-assessment.store.spec.ts +++ b/modules/ui/src/app/pages/risk-assessment/risk-assessment.store.spec.ts @@ -30,6 +30,7 @@ import { FocusManagerService } from '../../services/focus-manager.service'; import { AppState } from '../../store/state'; import { selectRiskProfiles } from '../../store/selectors'; import { setRiskProfiles } from '../../store/actions'; +import { ProfileAction } from '../../model/profile'; describe('RiskAssessmentStore', () => { let riskAssessmentStore: RiskAssessmentStore; @@ -104,6 +105,10 @@ describe('RiskAssessmentStore', () => { profiles: [PROFILE_MOCK, PROFILE_MOCK_2], profileFormat: [], selectedProfile: null, + actions: [ + { action: ProfileAction.Copy, icon: 'content_copy' }, + { action: ProfileAction.Delete, icon: 'delete' }, + ], }); done(); }); @@ -115,7 +120,12 @@ describe('RiskAssessmentStore', () => { it('should dispatch setRiskProfiles', () => { mockService.deleteProfile.and.returnValue(of(true)); - riskAssessmentStore.deleteProfile(PROFILE_MOCK.name); + riskAssessmentStore.deleteProfile({ + name: PROFILE_MOCK.name, + onDelete: (idx: number) => { + return idx; + }, + }); expect(store.dispatch).toHaveBeenCalledWith( setRiskProfiles({ riskProfiles: [PROFILE_MOCK_2] }) @@ -175,9 +185,7 @@ describe('RiskAssessmentStore', () => { }); describe('setFocusOnCreateButton', () => { - const container = document.createElement('div') as HTMLElement; - container.classList.add('risk-assessment-content-empty'); - document.querySelector('body')?.appendChild(container); + const container = window.document.querySelector('app-risk-assessment'); it('should call focusFirstElementInContainer', fakeAsync(() => { riskAssessmentStore.setFocusOnCreateButton(); @@ -189,21 +197,28 @@ describe('RiskAssessmentStore', () => { })); }); - describe('setFocusOnSelectedProfile', () => { + describe('with selected profile', () => { const container = document.createElement('div') as HTMLElement; - container.classList.add('profiles-drawer-content'); + container.classList.add('entity-list'); const inner = document.createElement('div') as HTMLElement; inner.classList.add('selected'); container.appendChild(inner); document.querySelector('body')?.appendChild(container); - it('should call focusFirstElementInContainer', () => { + it('setFocusOnSelectedProfile should call focusFirstElementInContainer', () => { riskAssessmentStore.setFocusOnSelectedProfile(); expect( mockFocusManagerService.focusFirstElementInContainer ).toHaveBeenCalledWith(inner); }); + + it('scrollToSelectedProfile should call focusFirstElementInContainer', () => { + const scrollSpy = spyOn(inner, 'scrollIntoView'); + riskAssessmentStore.scrollToSelectedProfile(); + + expect(scrollSpy).toHaveBeenCalled(); + }); }); describe('setFocusOnProfileForm', () => { diff --git a/modules/ui/src/app/pages/risk-assessment/risk-assessment.store.ts b/modules/ui/src/app/pages/risk-assessment/risk-assessment.store.ts index 40a8c523a..94833ddf0 100644 --- a/modules/ui/src/app/pages/risk-assessment/risk-assessment.store.ts +++ b/modules/ui/src/app/pages/risk-assessment/risk-assessment.store.ts @@ -14,33 +14,44 @@ * limitations under the License. */ -import { Injectable } from '@angular/core'; +import { Injectable, inject } from '@angular/core'; import { ComponentStore } from '@ngrx/component-store'; import { tap, withLatestFrom } from 'rxjs/operators'; import { catchError, delay, EMPTY, exhaustMap, throwError, timer } from 'rxjs'; import { TestRunService } from '../../services/test-run.service'; -import { Profile, ProfileFormat } from '../../model/profile'; +import { Profile, ProfileAction, ProfileFormat } from '../../model/profile'; import { FocusManagerService } from '../../services/focus-manager.service'; import { Store } from '@ngrx/store'; import { AppState } from '../../store/state'; -import { selectRiskProfiles } from '../../store/selectors'; +import { + selectIsOpenCreateProfile, + selectRiskProfiles, +} from '../../store/selectors'; import { setRiskProfiles } from '../../store/actions'; +import { EntityAction } from '../../model/entity-action'; export interface AppComponentState { selectedProfile: Profile | null; profiles: Profile[]; profileFormat: ProfileFormat[]; + actions: EntityAction[]; } @Injectable() export class RiskAssessmentStore extends ComponentStore { + private testRunService = inject(TestRunService); + private store = inject>(Store); + private focusManagerService = inject(FocusManagerService); + profiles$ = this.store.select(selectRiskProfiles); profileFormat$ = this.select(state => state.profileFormat); selectedProfile$ = this.select(state => state.selectedProfile); - + actions$ = this.select(state => state.actions); + isOpenCreateProfile$ = this.store.select(selectIsOpenCreateProfile); viewModel$ = this.select({ profiles: this.profiles$, profileFormat: this.profileFormat$, selectedProfile: this.selectedProfile$, + actions: this.actions$, }); updateProfileFormat = this.updater( @@ -56,14 +67,19 @@ export class RiskAssessmentStore extends ComponentStore { }) ); - deleteProfile = this.effect(trigger$ => { + deleteProfile = this.effect<{ + name: string; + onDelete: (idx: number) => void; + }>(trigger$ => { return trigger$.pipe( - exhaustMap((name: string) => { + exhaustMap(({ name, onDelete }) => { return this.testRunService.deleteProfile(name).pipe( withLatestFrom(this.profiles$), tap(([remove, current]) => { if (remove) { + const idx = current.findIndex(item => name === item.name); this.removeProfile(name, current); + onDelete(idx); } }) ); @@ -95,7 +111,7 @@ export class RiskAssessmentStore extends ComponentStore { delay(10), tap(() => { this.focusManagerService.focusFirstElementInContainer( - window.document.querySelector('.risk-assessment-content-empty') + window.document.querySelector('app-risk-assessment') ); }) ); @@ -105,12 +121,22 @@ export class RiskAssessmentStore extends ComponentStore { return trigger$.pipe( tap(() => { this.focusManagerService.focusFirstElementInContainer( - window.document.querySelector('.profiles-drawer-content .selected') + window.document.querySelector('.entity-list .selected') ); }) ); }); + scrollToSelectedProfile = this.effect(trigger$ => { + return trigger$.pipe( + tap(() => { + window.document + .querySelector('.entity-list .selected') + ?.scrollIntoView(); + }) + ); + }); + setFocusOnProfileForm = this.effect(trigger$ => { return trigger$.pipe( tap(() => { @@ -163,24 +189,24 @@ export class RiskAssessmentStore extends ComponentStore { ); }); - private removeProfile(name: string, current: Profile[]): void { - const profiles = current.filter(profile => profile.name !== name); - this.updateProfiles(profiles); + updateProfiles(riskProfiles: Profile[]): void { + this.store.dispatch(setRiskProfiles({ riskProfiles })); } - private updateProfiles(riskProfiles: Profile[]): void { - this.store.dispatch(setRiskProfiles({ riskProfiles })); + removeProfile(name: string, current: Profile[]): void { + const profiles = current.filter(profile => profile.name !== name); + this.updateProfiles(profiles); } - constructor( - private testRunService: TestRunService, - private store: Store, - private focusManagerService: FocusManagerService - ) { + constructor() { super({ profiles: [], profileFormat: [], selectedProfile: null, + actions: [ + { action: ProfileAction.Copy, icon: 'content_copy' }, + { action: ProfileAction.Delete, icon: 'delete' }, + ], }); } } diff --git a/modules/ui/src/app/pages/settings/components/settings-dropdown/settings-dropdown.component.scss b/modules/ui/src/app/pages/settings/components/settings-dropdown/settings-dropdown.component.scss deleted file mode 100644 index 38e82686c..000000000 --- a/modules/ui/src/app/pages/settings/components/settings-dropdown/settings-dropdown.component.scss +++ /dev/null @@ -1,74 +0,0 @@ -@use '@angular/material' as mat; -@import '../../../../../theming/colors'; -@import '../../../../../theming/variables'; - -:host { - padding-top: 16px; -} - -.setting-form-label { - font-size: 18px; - color: $dark-grey; -} - -:host:has(.two-ports-message) .internet-label { - padding-top: 16px; -} - -.setting-label-description { - font-family: $font-secondary; - font-size: 14px; - line-height: 20px; - letter-spacing: 0.2px; - margin: 8px 0; - color: $dark-grey; -} - -.setting-option-value { - padding: 14px 16px; -} - -.option-value { - margin: 0; - font-family: Roboto; - font-size: 14px; - font-style: normal; - font-weight: 400; - line-height: 20px; - letter-spacing: 0.2px; - - &.top { - color: $grey-800; - } - - &.bottom { - color: $grey-700; - } -} - -.setting-field { - width: 100%; - - &.mat-form-field-disabled { - opacity: 0.6; - } - - ::ng-deep .mat-mdc-form-field-infix { - min-height: 76px; - display: flex; - align-items: center; - } - - ::ng-deep .mat-mdc-floating-label { - font-family: Roboto; - font-size: 14px; - font-style: normal; - font-weight: 400; - line-height: 20px; - letter-spacing: 0.2px; - } - - ::ng-deep .mat-mdc-floating-label:not(.mdc-floating-label--float-above) { - top: 35px; - } -} diff --git a/modules/ui/src/app/pages/settings/settings.component.html b/modules/ui/src/app/pages/settings/settings.component.html index 9a7671171..68ad9d5b4 100644 --- a/modules/ui/src/app/pages/settings/settings.component.html +++ b/modules/ui/src/app/pages/settings/settings.component.html @@ -13,104 +13,17 @@ See the License for the specific language governing permissions and limitations under the License. --> -
-

System settings

- -
-
-
-
-
- - - - - - -

- If a port is missing from this list, you can - - Refresh - - the System settings -

- - - - -
- - Both interfaces must have different values - - -
- - - Warning! No ports detected. - - -
- + +

Settings

+
+ + + + + + diff --git a/modules/ui/src/app/pages/settings/settings.component.scss b/modules/ui/src/app/pages/settings/settings.component.scss index 6596a3591..b5c1d7770 100644 --- a/modules/ui/src/app/pages/settings/settings.component.scss +++ b/modules/ui/src/app/pages/settings/settings.component.scss @@ -13,143 +13,32 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -@use '@angular/material' as mat; -@import '../../../theming/colors'; -@import '../../../theming/variables'; :host { - display: flex; - flex-direction: column; - height: 100%; - flex: 1 0 auto; -} - -.settings-drawer-header { - display: flex; - justify-content: space-between; - align-items: center; - padding: 12px 12px 16px 24px; - - &-title { - margin: 0; - font-size: 22px; - font-style: normal; - font-weight: 400; - line-height: 28px; - color: $dark-grey; - } - - &-button { - min-width: 24px; - width: 24px; - height: 24px; - margin: 4px; - padding: 8px; - box-sizing: content-box; - line-height: normal !important; - - .close-button-icon { - width: 24px; - height: 24px; - margin: 0; - } - - ::ng-deep * { - line-height: inherit !important; - } - } -} - -.setting-drawer-content { - padding: 0 16px 8px 16px; - overflow: hidden; - flex: 1; - - form { - display: grid; - height: 100%; - } - - .setting-drawer-content-form-empty { - grid-template-rows: repeat(2, auto) 1fr; - } -} - -.setting-drawer-content-inputs { overflow: auto; - margin: 0 -16px; - padding: 0 16px; -} - -.error-message-container { - display: block; - margin-top: auto; - padding-bottom: 8px; } -.error-message-container + .setting-drawer-footer { - margin-top: 0; +.toolbar { + height: auto; + padding: 22px 0px 18px 32px; } - -.message { - margin: 0; - padding: 6px 0 12px 0; - color: $grey-800; - font-family: $font-secondary; - font-size: 14px; - line-height: 20px; - letter-spacing: 0.2px; +.title { + padding: 24px 0 16px; } -.setting-drawer-footer { - padding: 0 8px; - margin-top: auto; +.tab-item { display: flex; - flex-shrink: 0; - justify-content: flex-end; - - .close-button, - .save-button { - padding: 0 24px; - font-size: 14px; - font-weight: 500; - line-height: 20px; - letter-spacing: 0.25px; - } - - .close-button { - margin-right: 10px; - &:enabled { - color: $primary; - } - } -} - -.settings-disabled-overlay { - position: absolute; - width: 100%; - left: 0; - right: 0; - top: 75px; - bottom: 45px; - background-color: rgba(255, 255, 255, 0.7); - z-index: 2; + padding: 0px 32px; + align-items: center; + gap: 32px; } - -.disabled { - .message-link { - cursor: default; - pointer-events: none; - - &:focus-visible { - outline: none; - } +.tab-group { + ::ng-deep .mat-mdc-tab-labels { + gap: 16px; + padding: 0 12px; } -} -.settings-drawer-header-button:not(.mat-mdc-button-disabled), -.close-button:not(.mat-mdc-button-disabled), -.save-button:not(.mat-mdc-button-disabled) { - cursor: pointer; - pointer-events: auto; + ::ng-deep.mat-mdc-tab { + padding: 0px 8px; + } } diff --git a/modules/ui/src/app/pages/settings/settings.component.spec.ts b/modules/ui/src/app/pages/settings/settings.component.spec.ts index 01da4b9c3..d93a3bb0c 100644 --- a/modules/ui/src/app/pages/settings/settings.component.spec.ts +++ b/modules/ui/src/app/pages/settings/settings.component.spec.ts @@ -1,3 +1,6 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { SettingsComponent } from './settings.component'; /** * Copyright 2023 Google LLC * @@ -13,360 +16,24 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { NoopAnimationsModule } from '@angular/platform-browser/animations'; +import { RouterTestingModule } from '@angular/router/testing'; -import { SettingsComponent } from './settings.component'; -import { of } from 'rxjs'; -import { MatRadioModule } from '@angular/material/radio'; -import { ReactiveFormsModule } from '@angular/forms'; -import { MatButtonModule } from '@angular/material/button'; -import { MatIcon, MatIconModule } from '@angular/material/icon'; -import { MatIconTestingModule } from '@angular/material/icon/testing'; -import { Component, Input } from '@angular/core'; -import { LiveAnnouncer } from '@angular/cdk/a11y'; -import SpyObj = jasmine.SpyObj; -import { MatInputModule } from '@angular/material/input'; -import { MatSelectModule } from '@angular/material/select'; -import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; -import { provideMockStore } from '@ngrx/store/testing'; -import { LoaderService } from '../../services/loader.service'; -import { SettingsStore } from './settings.store'; -import { - MOCK_INTERFACES, - MOCK_SYSTEM_CONFIG_WITH_DATA, -} from '../../mocks/settings.mock'; -import { SettingsDropdownComponent } from './components/settings-dropdown/settings-dropdown.component'; - -describe('GeneralSettingsComponent', () => { +describe('SettingsComponent', () => { let component: SettingsComponent; let fixture: ComponentFixture; - let mockLiveAnnouncer: SpyObj; - let compiled: HTMLElement; - let mockLoaderService: SpyObj; - let mockSettingsStore: SpyObj; beforeEach(async () => { - mockLiveAnnouncer = jasmine.createSpyObj(['announce']); - mockLoaderService = jasmine.createSpyObj('LoaderService', ['setLoading']); - mockSettingsStore = jasmine.createSpyObj('SettingsStore', [ - 'getInterfaces', - 'updateSystemConfig', - 'setIsSubmitting', - 'setDefaultFormValues', - 'getSystemConfig', - 'viewModel$', - ]); - await TestBed.configureTestingModule({ - declarations: [ - SettingsComponent, - FakeSpinnerComponent, - FakeCalloutComponent, - ], - providers: [ - { provide: LiveAnnouncer, useValue: mockLiveAnnouncer }, - { provide: LoaderService, useValue: mockLoaderService }, - { provide: SettingsStore, useValue: mockSettingsStore }, - provideMockStore(), - ], - imports: [ - BrowserAnimationsModule, - MatButtonModule, - MatIconModule, - MatRadioModule, - ReactiveFormsModule, - MatIconTestingModule, - MatIcon, - MatInputModule, - MatSelectModule, - SettingsDropdownComponent, - ], + imports: [SettingsComponent, NoopAnimationsModule, RouterTestingModule], }).compileComponents(); - TestBed.overrideProvider(SettingsStore, { useValue: mockSettingsStore }); - fixture = TestBed.createComponent(SettingsComponent); - component = fixture.componentInstance; - component.viewModel$ = of({ - systemConfig: { network: {} }, - hasConnectionSettings: false, - isSubmitting: false, - isLessThanOneInterface: false, - interfaces: {}, - deviceOptions: {}, - internetOptions: {}, - logLevelOptions: {}, - monitoringPeriodOptions: {}, - }); fixture.detectChanges(); - compiled = fixture.nativeElement as HTMLElement; - - component.ngOnInit(); }); it('should create', () => { expect(component).toBeTruthy(); }); - - it('#reloadSetting should call setLoading in loaderService', () => { - component.reloadSetting(); - - expect(mockLoaderService.setLoading).toHaveBeenCalledWith(true); - }); - - describe('#settingsDisable', () => { - it('should disable setting form when get settingDisable as true ', () => { - spyOn(component.settingForm, 'disable'); - - component.settingsDisable = true; - - expect(component.settingForm.disable).toHaveBeenCalled(); - }); - - it('should enable setting form when get settingDisable as false ', () => { - spyOn(component.settingForm, 'enable'); - - component.settingsDisable = false; - - expect(component.settingForm.enable).toHaveBeenCalled(); - }); - - it('should disable "Save" button when get settingDisable as true', () => { - component.settingsDisable = true; - - const saveBtn = compiled.querySelector( - '.save-button' - ) as HTMLButtonElement; - - expect(saveBtn.disabled).toBeTrue(); - }); - - it('should disable "Refresh" link when settingDisable', () => { - component.settingsDisable = true; - - const refreshLink = compiled.querySelector( - '.message-link' - ) as HTMLAnchorElement; - - refreshLink.click(); - - expect(refreshLink.hasAttribute('aria-disabled')).toBeTrue(); - expect(mockLoaderService.setLoading).not.toHaveBeenCalled(); - }); - }); - - describe('#closeSetting', () => { - beforeEach(() => { - component.ngOnInit(); - }); - - it('should emit closeSettingEvent', () => { - spyOn(component.closeSettingEvent, 'emit'); - - component.closeSetting('Message'); - - expect(component.closeSettingEvent.emit).toHaveBeenCalled(); - }); - - it('should call liveAnnouncer with provided message', () => { - const mockMessage = 'mock event'; - - component.closeSetting(mockMessage); - - expect(mockLiveAnnouncer.announce).toHaveBeenCalledWith( - `The ${mockMessage} finished. The system settings panel is closed.` - ); - }); - - it('should call reset settingForm', () => { - spyOn(component.settingForm, 'reset'); - - component.closeSetting('Message'); - - expect(component.settingForm.reset).toHaveBeenCalled(); - }); - - it('should call setDefaultFormValues', () => { - component.closeSetting('Message'); - - expect(mockSettingsStore.setDefaultFormValues).toHaveBeenCalled(); - }); - }); - - describe('#saveSetting', () => { - beforeEach(() => { - component.ngOnInit(); - }); - - it('should have form error if form has the same value', () => { - const mockSameValue = 'sameValue'; - component.deviceControl.setValue(mockSameValue); - component.internetControl.setValue(mockSameValue); - - component.saveSetting(); - - expect(component.settingForm.invalid).toBeTrue(); - expect(component.isFormError).toBeTrue(); - expect(mockSettingsStore.setIsSubmitting).toHaveBeenCalledWith(true); - }); - - it('should call createSystemConfig when setting form valid', () => { - const expectedResult = { - network: { - device_intf: 'mockDeviceKey', - internet_intf: '', - }, - log_level: 'INFO', - monitor_period: 600, - }; - - component.deviceControl.setValue({ - key: 'mockDeviceKey', - value: 'mockDeviceValue', - }); - - component.internetControl.setValue({ - key: '', - value: 'defaultValue', - }); - - component.logLevel.setValue({ - key: 'INFO', - value: '', - }); - - component.monitorPeriod.setValue({ - key: '600', - value: '', - }); - - component.saveSetting(); - - const args = mockSettingsStore.updateSystemConfig.calls.argsFor(0); - // @ts-expect-error config is in object - expect(args[0].config).toEqual(expectedResult); - expect(component.settingForm.invalid).toBeFalse(); - expect(mockSettingsStore.updateSystemConfig).toHaveBeenCalled(); - }); - }); - - describe('with no interfaces data', () => { - beforeEach(() => { - component.viewModel$ = of({ - systemConfig: { network: {} }, - hasConnectionSettings: false, - isSubmitting: false, - isLessThanOneInterface: false, - interfaces: {}, - deviceOptions: {}, - internetOptions: {}, - logLevelOptions: {}, - monitoringPeriodOptions: {}, - }); - fixture.detectChanges(); - }); - - it('should have callout component', () => { - const callout = compiled.querySelector('app-callout'); - - expect(callout).toBeTruthy(); - }); - - it('should have disabled "Save" button', () => { - const saveBtn = compiled.querySelector( - '.save-button' - ) as HTMLButtonElement; - - expect(saveBtn.disabled).toBeTrue(); - }); - }); - - describe('with interfaces length less than one', () => { - beforeEach(() => { - component.viewModel$ = of({ - systemConfig: { network: {} }, - hasConnectionSettings: false, - isSubmitting: false, - isLessThanOneInterface: true, - interfaces: {}, - deviceOptions: {}, - internetOptions: {}, - logLevelOptions: {}, - monitoringPeriodOptions: {}, - }); - fixture.detectChanges(); - }); - - it('should have disabled "Save" button', () => { - component.deviceControl.setValue( - MOCK_SYSTEM_CONFIG_WITH_DATA?.network?.device_intf - ); - component.internetControl.setValue( - MOCK_SYSTEM_CONFIG_WITH_DATA?.network?.internet_intf - ); - fixture.detectChanges(); - - const saveBtn = compiled.querySelector( - '.save-button' - ) as HTMLButtonElement; - - expect(saveBtn.disabled).toBeTrue(); - }); - }); - - describe('with interfaces length more then one', () => { - beforeEach(() => { - component.viewModel$ = of({ - systemConfig: { network: {} }, - hasConnectionSettings: false, - isSubmitting: false, - isLessThanOneInterface: false, - interfaces: MOCK_INTERFACES, - deviceOptions: MOCK_INTERFACES, - internetOptions: MOCK_INTERFACES, - logLevelOptions: {}, - monitoringPeriodOptions: {}, - }); - fixture.detectChanges(); - }); - - it('should not have callout component', () => { - const callout = compiled.querySelector('app-callout'); - - expect(callout).toBeFalsy(); - }); - - it('should not have disabled "Save" button', () => { - component.deviceControl.setValue({ - key: MOCK_SYSTEM_CONFIG_WITH_DATA?.network?.device_intf, - value: 'value', - }); - component.internetControl.setValue({ - key: MOCK_SYSTEM_CONFIG_WITH_DATA?.network?.internet_intf, - value: 'value', - }); - fixture.detectChanges(); - - const saveBtn = compiled.querySelector( - '.save-button' - ) as HTMLButtonElement; - - expect(saveBtn.disabled).toBeFalse(); - }); - }); }); - -@Component({ - selector: 'app-spinner', - template: '
', -}) -class FakeSpinnerComponent {} - -@Component({ - selector: 'app-callout', - template: '
', -}) -class FakeCalloutComponent { - @Input() type = ''; -} diff --git a/modules/ui/src/app/pages/settings/settings.component.ts b/modules/ui/src/app/pages/settings/settings.component.ts index 4c9d0dffe..6f3bb376c 100644 --- a/modules/ui/src/app/pages/settings/settings.component.ts +++ b/modules/ui/src/app/pages/settings/settings.component.ts @@ -16,210 +16,66 @@ import { ChangeDetectionStrategy, Component, - ElementRef, - EventEmitter, - Input, OnDestroy, OnInit, - Output, - ViewChild, } from '@angular/core'; -import { FormBuilder, FormControl, FormGroup } from '@angular/forms'; -import { Subject, takeUntil, tap } from 'rxjs'; -import { OnlyDifferentValuesValidator } from './only-different-values.validator'; -import { CalloutType } from '../../model/callout-type'; -import { CdkTrapFocus, LiveAnnouncer } from '@angular/cdk/a11y'; -import { EventType } from '../../model/event-type'; -import { FormKey, SystemConfig } from '../../model/setting'; -import { SettingsStore } from './settings.store'; -import { LoaderService } from '../../services/loader.service'; +import { MatToolbarModule } from '@angular/material/toolbar'; +import { CommonModule } from '@angular/common'; +import { MatTabChangeEvent, MatTabsModule } from '@angular/material/tabs'; +import { + ActivatedRoute, + NavigationEnd, + Router, + RouterModule, +} from '@angular/router'; +import { Routes } from '../../model/routes'; +import { filter, Subject, takeUntil } from 'rxjs'; @Component({ selector: 'app-settings', + imports: [CommonModule, MatToolbarModule, MatTabsModule, RouterModule], templateUrl: './settings.component.html', styleUrls: ['./settings.component.scss'], - hostDirectives: [CdkTrapFocus], - providers: [SettingsStore], changeDetection: ChangeDetectionStrategy.OnPush, }) export class SettingsComponent implements OnInit, OnDestroy { - @ViewChild('reloadSettingLink') public reloadSettingLink!: ElementRef; - @Output() closeSettingEvent = new EventEmitter(); - - private isSettingsDisable = false; - get settingsDisable(): boolean { - return this.isSettingsDisable; - } - @Input() set settingsDisable(value: boolean) { - this.isSettingsDisable = value; - if (value) { - this.disableSettings(); - } else { - this.enableSettings(); - } - } - public readonly CalloutType = CalloutType; - public readonly EventType = EventType; - public readonly FormKey = FormKey; - public settingForm!: FormGroup; - viewModel$ = this.settingsStore.viewModel$; - + private routes = [Routes.General, Routes.Certificates]; private destroy$: Subject = new Subject(); - - get deviceControl(): FormControl { - return this.settingForm.get(FormKey.DEVICE) as FormControl; - } - - get internetControl(): FormControl { - return this.settingForm.get(FormKey.INTERNET) as FormControl; - } - - get logLevel(): FormControl { - return this.settingForm.get(FormKey.LOG_LEVEL) as FormControl; - } - - get monitorPeriod(): FormControl { - return this.settingForm.get(FormKey.MONITOR_PERIOD) as FormControl; - } - - get isFormValues(): boolean { - return ( - this.deviceControl?.value?.value && - (this.isInternetControlDisabled || this.internetControl?.value?.value) - ); - } - - get isInternetControlDisabled(): boolean { - return this.internetControl?.disabled; - } - - get isFormError(): boolean { - return this.settingForm.hasError('hasSameValues'); - } - + selectedIndex = 0; constructor( - private readonly fb: FormBuilder, - private liveAnnouncer: LiveAnnouncer, - private readonly onlyDifferentValuesValidator: OnlyDifferentValuesValidator, - private settingsStore: SettingsStore, - private readonly loaderService: LoaderService + private router: Router, + private route: ActivatedRoute ) {} - ngOnInit() { - this.createSettingForm(); - this.cleanFormErrorMessage(); - this.settingsStore.getInterfaces(); - this.getSystemConfig(); - this.setDefaultFormValues(); - } - - reloadSetting(): void { - if (this.settingsDisable) { - return; - } - this.showLoading(); - this.getSystemInterfaces(); - this.getSystemConfig(); - this.setDefaultFormValues(); - } - closeSetting(message: string): void { - this.resetForm(); - this.closeSettingEvent.emit(); - this.liveAnnouncer.announce( - `The ${message} finished. The system settings panel is closed.` - ); - this.setDefaultFormValues(); - } - - saveSetting(): void { - if (this.settingForm.invalid) { - this.settingsStore.setIsSubmitting(true); - this.settingForm.markAllAsTouched(); - } else { - this.createSystemConfig(); - } - } - - private disableSettings(): void { - this.settingForm?.disable(); - this.reloadSettingLink?.nativeElement.setAttribute('aria-disabled', 'true'); - } - - private enableSettings(): void { - this.settingForm?.enable(); - this.reloadSettingLink?.nativeElement.removeAttribute('aria-disabled'); - } - - private createSettingForm() { - this.settingForm = this.fb.group( - { - device_intf: [''], - internet_intf: [''], - log_level: [''], - monitor_period: [''], - }, - { - validators: [this.onlyDifferentValuesValidator.onlyDifferentSetting()], - updateOn: 'change', - } - ); - } + ngOnInit(): void { + const currentRoute = this.router.url; + this.setSelectedIndex(currentRoute); - private setDefaultFormValues() { - this.settingsStore.setDefaultFormValues(this.settingForm); - } - - private cleanFormErrorMessage(): void { - this.settingForm.valueChanges + this.router.events .pipe( takeUntil(this.destroy$), - tap(() => this.settingsStore.setIsSubmitting(false)) + filter(event => event instanceof NavigationEnd) ) - .subscribe(); + .subscribe(event => { + if (event.url !== currentRoute) { + this.setSelectedIndex(event.url); + } + }); } - private createSystemConfig(): void { - const { device_intf, internet_intf, log_level, monitor_period } = - this.settingForm.value; - const data: SystemConfig = { - network: { - device_intf: device_intf.key, - internet_intf: this.isInternetControlDisabled ? '' : internet_intf.key, - }, - log_level: log_level.key, - monitor_period: Number(monitor_period.key), - }; - this.settingsStore.updateSystemConfig({ - onSystemConfigUpdate: () => { - this.closeSetting(EventType.Save); - }, - config: data, - }); + onTabChange(event: MatTabChangeEvent): void { + const index = event.index; + this.router.navigate([this.routes[index]], { relativeTo: this.route }); } - private resetForm(): void { - this.settingForm.reset(); + private setSelectedIndex(currentRoute: string): void { + this.selectedIndex = this.routes.findIndex(route => + currentRoute.includes(route) + ); } ngOnDestroy() { this.destroy$.next(true); this.destroy$.unsubscribe(); } - - getSystemInterfaces(): void { - this.settingsStore.getInterfaces(); - this.hideLoading(); - } - - getSystemConfig(): void { - this.settingsStore.getSystemConfig(); - } - - private showLoading() { - this.loaderService.setLoading(true); - } - - private hideLoading() { - this.loaderService.setLoading(false); - } } diff --git a/modules/ui/src/app/pages/testrun/components/download-options/download-options.component.html b/modules/ui/src/app/pages/testrun/components/download-options/download-options.component.html index 6dc3b10ac..8c41759bd 100644 --- a/modules/ui/src/app/pages/testrun/components/download-options/download-options.component.html +++ b/modules/ui/src/app/pages/testrun/components/download-options/download-options.component.html @@ -13,37 +13,53 @@ See the License for the specific language governing permissions and limitations under the License. --> - - - - - lab_profile - - {{ DownloadOption.PDF }} - - - - - archive - - {{ DownloadOption.ZIP }} - - - - +
+ + + + archive + + {{ DownloadOption.ZIP }} + + +
diff --git a/modules/ui/src/app/pages/testrun/components/download-options/download-options.component.scss b/modules/ui/src/app/pages/testrun/components/download-options/download-options.component.scss index a08461b6d..5e8f7da54 100644 --- a/modules/ui/src/app/pages/testrun/components/download-options/download-options.component.scss +++ b/modules/ui/src/app/pages/testrun/components/download-options/download-options.component.scss @@ -15,80 +15,77 @@ */ @use 'node_modules/@angular/material/index' as mat; -@import 'src/theming/colors'; +@use 'm3-theme' as *; +@use 'colors'; +@use 'variables'; -$option-width: 170px; -$option-height: 36px; - -.download-options-field { - width: $option-width; - background: mat.m2-get-color-from-palette($color-primary, 50); - - ::ng-deep.mat-mdc-text-field-wrapper { - padding: 0 16px; - } - - ::ng-deep.mat-mdc-form-field-subscript-wrapper { - display: none; - } - - ::ng-deep.mat-mdc-form-field-infix { - padding: 6px 0; - width: $option-width; - min-height: $option-height; - } - - ::ng-deep.mat-mdc-select-placeholder { - color: mat.m2-get-color-from-palette($color-primary, 700); - font-size: 14px; - font-style: normal; - font-weight: 500; - line-height: 20px; - letter-spacing: 0.25px; - } +.download-actions { + position: fixed; + right: 40px; + bottom: 32px; + display: flex; + flex-direction: column; + align-items: flex-end; + justify-content: flex-end; + gap: 4px; +} - ::ng-deep.mat-mdc-select-arrow { - color: mat.m2-get-color-from-palette($color-primary, 700); - } +.download-button { + padding: 26px; + border-radius: variables.$corner-large; + font-weight: 400; + font-size: 22px; + line-height: 28px; + height: 80px; + margin-top: 4px; - ::ng-deep .mat-mdc-text-field-wrapper .mdc-notched-outline > * { - border-color: mat.m2-get-color-from-palette($color-primary, 50); - } - - ::ng-deep - .mat-mdc-text-field-wrapper.mdc-text-field--focused - .mdc-notched-outline - > * { - border-color: #000000de; + mat-icon { + font-size: 28px; + width: 28px; + height: 28px; } +} - &:has(.download-options-select[aria-expanded='true']) - ::ng-deep - .mat-mdc-text-field-wrapper.mdc-text-field--focused - .mdc-notched-outline - > * { - border: none; - } +.download-button.download-button-opened { + min-width: 56px; + height: 56px; + border-radius: 50%; + padding: 16px; - ::ng-deep - .mat-mdc-text-field-wrapper.mdc-text-field--outlined:hover - .mdc-notched-outline - > * { - border: none; + mat-icon { + font-size: 20px; + width: 20px; + height: 20px; + margin: 0; } } .download-option { - ::ng-deep .mat-mdc-focus-indicator { - display: none; + display: flex; + height: 56px; + box-sizing: border-box; + padding: 16px 24px; + justify-content: flex-end; + align-items: center; + flex-shrink: 0; + gap: 8px; + border-radius: 28px; + background: colors.$primary-container; + color: colors.$on-primary-container; + font-size: 16px; + font-weight: 500; + line-height: 24px; + + mat-icon { + width: 24px; + height: 24px; + font-size: 24px; + margin: 0; } } -.download-option { - min-height: 32px; - color: $grey-800; - &.zip app-download-report-zip { - display: flex; - align-items: center; +button { + ::ng-deep .mat-focus-indicator { + display: none; } } diff --git a/modules/ui/src/app/pages/testrun/components/download-options/download-options.component.spec.ts b/modules/ui/src/app/pages/testrun/components/download-options/download-options.component.spec.ts index 2b370485f..a9be3de24 100644 --- a/modules/ui/src/app/pages/testrun/components/download-options/download-options.component.spec.ts +++ b/modules/ui/src/app/pages/testrun/components/download-options/download-options.component.spec.ts @@ -15,17 +15,13 @@ */ import { ComponentFixture, TestBed } from '@angular/core/testing'; -import { - DownloadOption, - DownloadOptionsComponent, -} from './download-options.component'; +import { DownloadOptionsComponent } from './download-options.component'; import { MOCK_PROGRESS_DATA_CANCELLED, MOCK_PROGRESS_DATA_COMPLIANT, MOCK_PROGRESS_DATA_NON_COMPLIANT, } from '../../../../mocks/testrun.mock'; import { NoopAnimationsModule } from '@angular/platform-browser/animations'; -import { MatOptionSelectionChange } from '@angular/material/core'; import { TestRunService } from '../../../../services/test-run.service'; interface GAEvent { @@ -63,43 +59,33 @@ describe('DownloadOptionsComponent', () => { expect(downloadReportZipComponent).toBeDefined(); }); - it('#onSelected should call getReportTitle', () => { + it('#downloadPdf should call getReportTitle', () => { const spyGetReportTitle = spyOn(component, 'getReportTitle'); - const mockEvent = { - source: {}, - isUserInput: true, - } as MatOptionSelectionChange; - - component.onSelected( - mockEvent, - MOCK_PROGRESS_DATA_COMPLIANT, - DownloadOption.PDF - ); + component.downloadPdf(MOCK_PROGRESS_DATA_COMPLIANT); expect(spyGetReportTitle).toHaveBeenCalled(); }); - it('#onSelected should call getZipLink when using for zip report', () => { - const spyGetZipLink = spyOn(component, 'getZipLink'); + it('#getReportTitle should return data for title of link', () => { + const expectedResult = 'delta_03-din-cpu_1.2.2_complete_22_jun_2023_9:20'; - const mockEvent = { - source: {}, - isUserInput: true, - } as MatOptionSelectionChange; + const result = component.getReportTitle(MOCK_PROGRESS_DATA_COMPLIANT); - component.onSelected( - mockEvent, - MOCK_PROGRESS_DATA_COMPLIANT, - DownloadOption.ZIP - ); + expect(result).toEqual(expectedResult); + }); + + it('#openDownloadOptions should change isOpenDownloadOptions', () => { + component.openDownloadOptions(); + expect(component.isOpenDownloadOptions).toBeTrue(); - expect(spyGetZipLink).toHaveBeenCalled(); + component.openDownloadOptions(); + expect(component.isOpenDownloadOptions).toBeFalse(); }); describe('#sendGAEvent', () => { it('should send download_report_pdf when type is pdf', () => { - component.sendGAEvent(MOCK_PROGRESS_DATA_CANCELLED, DownloadOption.PDF); + component.sendGAEvent(MOCK_PROGRESS_DATA_CANCELLED); expect( // @ts-expect-error data layer should be defined @@ -109,8 +95,8 @@ describe('DownloadOptionsComponent', () => { ).toBeTruthy(); }); - it('should send download_report_pdf_compliant when type is pdf and status is compliant', () => { - component.sendGAEvent(MOCK_PROGRESS_DATA_COMPLIANT, DownloadOption.PDF); + it('should send download_report_pdf_compliant when status is compliant', () => { + component.sendGAEvent(MOCK_PROGRESS_DATA_COMPLIANT); expect( // @ts-expect-error data layer should be defined @@ -120,11 +106,8 @@ describe('DownloadOptionsComponent', () => { ).toBeTruthy(); }); - it('should send download_report_pdf_non_compliant when type is pdf and status is not compliant', () => { - component.sendGAEvent( - MOCK_PROGRESS_DATA_NON_COMPLIANT, - DownloadOption.PDF - ); + it('should send download_report_pdf_non_compliant when status is not compliant', () => { + component.sendGAEvent(MOCK_PROGRESS_DATA_NON_COMPLIANT); expect( // @ts-expect-error data layer should be defined @@ -133,41 +116,5 @@ describe('DownloadOptionsComponent', () => { ) ).toBeTruthy(); }); - - it('should send download_report_zip when type is zip', () => { - component.sendGAEvent(MOCK_PROGRESS_DATA_CANCELLED, DownloadOption.ZIP); - - expect( - // @ts-expect-error data layer should be defined - window.dataLayer.some( - (item: GAEvent) => item.event === 'download_report_zip' - ) - ).toBeTruthy(); - }); - - it('should send download_report_zip_compliant when type is pdf and status is compliant', () => { - component.sendGAEvent(MOCK_PROGRESS_DATA_COMPLIANT, DownloadOption.ZIP); - - expect( - // @ts-expect-error data layer should be defined - window.dataLayer.some( - (item: GAEvent) => item.event === 'download_report_zip_compliant' - ) - ).toBeTruthy(); - }); - - it('should send download_report_zip_non_compliant when type is zip and status is not compliant', () => { - component.sendGAEvent( - MOCK_PROGRESS_DATA_NON_COMPLIANT, - DownloadOption.ZIP - ); - - expect( - // @ts-expect-error data layer should be defined - window.dataLayer.some( - (item: GAEvent) => item.event === 'download_report_zip_non_compliant' - ) - ).toBeTruthy(); - }); }); }); diff --git a/modules/ui/src/app/pages/testrun/components/download-options/download-options.component.ts b/modules/ui/src/app/pages/testrun/components/download-options/download-options.component.ts index 814584eb0..87e13e098 100644 --- a/modules/ui/src/app/pages/testrun/components/download-options/download-options.component.ts +++ b/modules/ui/src/app/pages/testrun/components/download-options/download-options.component.ts @@ -16,22 +16,18 @@ import { ChangeDetectionStrategy, Component, - ElementRef, Input, - ViewChild, + inject, } from '@angular/core'; -import { FormsModule } from '@angular/forms'; -import { MatFormFieldModule } from '@angular/material/form-field'; -import { MatSelectModule } from '@angular/material/select'; import { CommonModule, DatePipe } from '@angular/common'; import { MatIconModule } from '@angular/material/icon'; import { ResultOfTestrun, TestrunStatus, } from '../../../../model/testrun-status'; -import { MatOptionSelectionChange } from '@angular/material/core'; import { DownloadReportZipComponent } from '../../../../components/download-report-zip/download-report-zip.component'; import { Profile } from '../../../../model/profile'; +import { MatButtonModule } from '@angular/material/button'; export enum DownloadOption { PDF = 'PDF Report', @@ -41,61 +37,38 @@ export enum DownloadOption { selector: 'app-download-options', templateUrl: './download-options.component.html', styleUrl: './download-options.component.scss', - standalone: true, imports: [ CommonModule, - FormsModule, + MatButtonModule, MatIconModule, - MatFormFieldModule, - MatSelectModule, DownloadReportZipComponent, ], providers: [DatePipe], changeDetection: ChangeDetectionStrategy.OnPush, }) export class DownloadOptionsComponent { - @ViewChild('downloadReportZip') downloadReportZip!: ElementRef; + private datePipe = inject(DatePipe); + isOpenDownloadOptions: boolean = false; @Input() profiles: Profile[] = []; @Input() data!: TestrunStatus; DownloadOption = DownloadOption; - constructor(private datePipe: DatePipe) {} - onSelected( - event: MatOptionSelectionChange, - data: TestrunStatus, - type: string - ) { - if (event.isUserInput) { - this.createLink(data, type); - this.sendGAEvent(data, type); - } - } - - onZipSelected(event: MatOptionSelectionChange) { - if (event.isUserInput) { - const uploadCertificatesButton = document.querySelector( - '#downloadReportZip' - ) as HTMLElement; - uploadCertificatesButton.dispatchEvent(new MouseEvent('click')); - } + downloadPdf(data: TestrunStatus) { + this.createLink(data); + this.sendGAEvent(data); } - createLink(data: TestrunStatus, type: string) { + createLink(data: TestrunStatus) { if (!data.report) { return; } const link = document.createElement('a'); - link.href = - type === DownloadOption.PDF ? data.report : this.getZipLink(data.report); + link.href = data.report; link.target = '_blank'; link.download = this.getReportTitle(data); link.dispatchEvent(new MouseEvent('click')); } - getZipLink(reportURL: string): string { - return reportURL.replace('report', 'export'); - } - getReportTitle(data: TestrunStatus) { if (!data.device) { return ''; @@ -111,8 +84,8 @@ export class DownloadOptionsComponent { return date ? this.datePipe.transform(date, 'd MMM y H:mm') : ''; } - sendGAEvent(data: TestrunStatus, type: string) { - let event = `download_report_${type === DownloadOption.PDF ? 'pdf' : 'zip'}`; + sendGAEvent(data: TestrunStatus) { + let event = 'download_report_pdf'; if (data.result === ResultOfTestrun.Compliant) { event += '_compliant'; } else if (data.result === ResultOfTestrun.NonCompliant) { @@ -123,4 +96,8 @@ export class DownloadOptionsComponent { event: event, }); } + + openDownloadOptions(): void { + this.isOpenDownloadOptions = !this.isOpenDownloadOptions; + } } diff --git a/modules/ui/src/app/pages/testrun/components/test-result-dialog/test-result-dialog.component.html b/modules/ui/src/app/pages/testrun/components/test-result-dialog/test-result-dialog.component.html new file mode 100644 index 000000000..e8bacf230 --- /dev/null +++ b/modules/ui/src/app/pages/testrun/components/test-result-dialog/test-result-dialog.component.html @@ -0,0 +1,46 @@ + +{{ data.testResult.name }} + +
+
+

Description

+

{{ data.testResult.description }}

+
+
+

Test result

+

{{ data.testResult.result }}

+

{{ data.testResult.required_result }}

+
+
+
+

Steps to resolve

+
    +
  • {{ point }}
  • +
+
+
+ + + diff --git a/modules/ui/src/app/pages/testrun/components/test-result-dialog/test-result-dialog.component.scss b/modules/ui/src/app/pages/testrun/components/test-result-dialog/test-result-dialog.component.scss new file mode 100644 index 000000000..3fbe2ffca --- /dev/null +++ b/modules/ui/src/app/pages/testrun/components/test-result-dialog/test-result-dialog.component.scss @@ -0,0 +1,109 @@ +/** + * Copyright 2023 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +@use 'colors'; +@use 'variables'; +@use 'mixins'; + +::ng-deep :root { + --mat-dialog-container-max-width: 640px; +} + +:host { + @include mixins.dialog; +} + +.dialog-title { + background: colors.$white; + padding: 48px 24px 40px; + text-align: center; + font-size: 24px; + font-weight: 500; + line-height: 32px; + letter-spacing: 0; + &:before { + height: 0; + } +} + +p { + margin: 0; +} + +mat-dialog-content.content-container { + padding: 0; +} + +.result-info-main { + display: grid; + background: colors.$error-container; + grid-template-columns: 1.2fr 1fr; + padding: 32px 48px; + gap: 48px; +} + +.info-label { + color: colors.$on-surface-variant; + font-size: 12px; + font-weight: 500; + line-height: 16px; + letter-spacing: 0.1px; + + &.steps { + color: colors.$on-surface; + } +} + +.info-main-data, +.info-required-result { + color: colors.$on-surface; + font-size: 16px; + line-height: 24px; + letter-spacing: 0; +} + +.info-result { + color: colors.$on-error-container; + font-family: variables.$font-primary; + font-size: 28px; + line-height: 36px; + letter-spacing: 0; +} + +.result-info-steps { + padding: 32px 48px; +} + +.info-steps-list { + margin: 0; + padding-left: 24px; + font-size: 16px; + line-height: 24px; + letter-spacing: 0; +} + +mat-dialog-actions.actions-container { + padding: 18px 36px 26px; +} + +.close-button { + border-radius: variables.$corner-medium; + padding: 0 16px; + min-width: 54px; + + ::ng-deep .mat-focus-indicator { + display: none; + } +} diff --git a/modules/ui/src/app/components/shutdown-app-modal/shutdown-app-modal.component.spec.ts b/modules/ui/src/app/pages/testrun/components/test-result-dialog/test-result-dialog.component.spec.ts similarity index 50% rename from modules/ui/src/app/components/shutdown-app-modal/shutdown-app-modal.component.spec.ts rename to modules/ui/src/app/pages/testrun/components/test-result-dialog/test-result-dialog.component.spec.ts index d7cd5abb6..057ed1e4e 100644 --- a/modules/ui/src/app/components/shutdown-app-modal/shutdown-app-modal.component.spec.ts +++ b/modules/ui/src/app/pages/testrun/components/test-result-dialog/test-result-dialog.component.spec.ts @@ -15,29 +15,19 @@ */ import { ComponentFixture, TestBed } from '@angular/core/testing'; -import { ShutdownAppModalComponent } from './shutdown-app-modal.component'; -import { - MAT_DIALOG_DATA, - MatDialogModule, - MatDialogRef, -} from '@angular/material/dialog'; -import { MatButtonModule } from '@angular/material/button'; +import { TestResultDialogComponent } from './test-result-dialog.component'; +import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; import { of } from 'rxjs'; -import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; +import { TEST_DATA_RESULT_WITH_RECOMMENDATIONS } from '../../../../mocks/testrun.mock'; -describe('ShutdownAppModalComponent', () => { - let component: ShutdownAppModalComponent; - let fixture: ComponentFixture; +describe('TestResultDialogComponent', () => { + let component: TestResultDialogComponent; + let fixture: ComponentFixture; let compiled: HTMLElement; beforeEach(async () => { await TestBed.configureTestingModule({ - imports: [ - ShutdownAppModalComponent, - MatDialogModule, - MatButtonModule, - BrowserAnimationsModule, - ], + imports: [TestResultDialogComponent], providers: [ { provide: MatDialogRef, @@ -50,11 +40,10 @@ describe('ShutdownAppModalComponent', () => { ], }).compileComponents(); - fixture = TestBed.createComponent(ShutdownAppModalComponent); + fixture = TestBed.createComponent(TestResultDialogComponent); component = fixture.componentInstance; component.data = { - title: 'title', - content: 'content', + testResult: TEST_DATA_RESULT_WITH_RECOMMENDATIONS[0], }; compiled = fixture.nativeElement as HTMLElement; fixture.detectChanges(); @@ -64,36 +53,15 @@ describe('ShutdownAppModalComponent', () => { expect(component).toBeTruthy(); }); - it('should has title and content', () => { - const title = compiled.querySelector('.modal-title') as HTMLElement; - const content = compiled.querySelector('.modal-content') as HTMLElement; - - expect(title.innerHTML.trim()).toEqual('title'); - expect(content.innerHTML.trim()).toEqual('content'); - }); - - it('should close dialog on click "cancel" button', () => { + it('should close dialog on click "Close" button', () => { const closeSpy = spyOn(component.dialogRef, 'close'); const closeButton = compiled.querySelector( - '.cancel-button' - ) as HTMLButtonElement; - - closeButton.click(); - - expect(closeSpy).toHaveBeenCalledWith(); - - closeSpy.calls.reset(); - }); - - it('should close dialog with true on click "confirm" button', () => { - const closeSpy = spyOn(component.dialogRef, 'close'); - const confirmButton = compiled.querySelector( - '.confirm-button' + '.close-button' ) as HTMLButtonElement; - confirmButton.click(); + closeButton?.click(); - expect(closeSpy).toHaveBeenCalledWith(true); + expect(closeSpy).toHaveBeenCalled(); closeSpy.calls.reset(); }); diff --git a/modules/ui/src/app/components/shutdown-app-modal/shutdown-app-modal.component.ts b/modules/ui/src/app/pages/testrun/components/test-result-dialog/test-result-dialog.component.ts similarity index 51% rename from modules/ui/src/app/components/shutdown-app-modal/shutdown-app-modal.component.ts rename to modules/ui/src/app/pages/testrun/components/test-result-dialog/test-result-dialog.component.ts index af42aabe9..8a05ea11c 100644 --- a/modules/ui/src/app/components/shutdown-app-modal/shutdown-app-modal.component.ts +++ b/modules/ui/src/app/pages/testrun/components/test-result-dialog/test-result-dialog.component.ts @@ -13,41 +13,37 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { ChangeDetectionStrategy, Component, Inject } from '@angular/core'; -import { EscapableDialogComponent } from '../escapable-dialog/escapable-dialog.component'; +import { ChangeDetectionStrategy, Component, inject } from '@angular/core'; +import { CommonModule } from '@angular/common'; import { MAT_DIALOG_DATA, MatDialogModule, MatDialogRef, } from '@angular/material/dialog'; import { MatButtonModule } from '@angular/material/button'; +import { EscapableDialogComponent } from '../../../../components/escapable-dialog/escapable-dialog.component'; +import { IResult } from '../../../../model/testrun-status'; interface DialogData { - title?: string; - content?: string; + testResult: IResult; } @Component({ - selector: 'app-shutdown-app-modal', - templateUrl: './shutdown-app-modal.component.html', - styleUrl: './shutdown-app-modal.component.scss', - standalone: true, - imports: [MatDialogModule, MatButtonModule], + selector: 'app-test-result-dialog', + imports: [MatDialogModule, MatButtonModule, CommonModule], + templateUrl: './test-result-dialog.component.html', + styleUrl: './test-result-dialog.component.scss', changeDetection: ChangeDetectionStrategy.OnPush, }) -export class ShutdownAppModalComponent extends EscapableDialogComponent { - constructor( - public override dialogRef: MatDialogRef, - @Inject(MAT_DIALOG_DATA) public data: DialogData - ) { - super(dialogRef); - } +export class TestResultDialogComponent extends EscapableDialogComponent { + override dialogRef: MatDialogRef; + data = inject(MAT_DIALOG_DATA); - confirm() { - this.dialogRef.close(true); - } + constructor() { + const dialogRef = + inject>(MatDialogRef); - cancel() { - this.dialogRef.close(); + super(); + this.dialogRef = dialogRef; } } diff --git a/modules/ui/src/app/pages/testrun/components/testrun-initiate-form/testrun-initiate-form.component.html b/modules/ui/src/app/pages/testrun/components/testrun-initiate-form/testrun-initiate-form.component.html index 6caab0423..6347ec162 100644 --- a/modules/ui/src/app/pages/testrun/components/testrun-initiate-form/testrun-initiate-form.component.html +++ b/modules/ui/src/app/pages/testrun/components/testrun-initiate-form/testrun-initiate-form.component.html @@ -14,7 +14,7 @@ limitations under the License. -->
- Start New Testrun + Start new Testrun
- + {{ error$ | async }} + + + Top Tip: Your device must be powered off before starting Testrun + +
@@ -89,7 +95,7 @@ color="primary" mat-flat-button type="button"> - Start Testrun + Start new Testrun diff --git a/modules/ui/src/app/pages/testrun/components/testrun-initiate-form/testrun-initiate-form.component.scss b/modules/ui/src/app/pages/testrun/components/testrun-initiate-form/testrun-initiate-form.component.scss index 082599567..20c4a0048 100644 --- a/modules/ui/src/app/pages/testrun/components/testrun-initiate-form/testrun-initiate-form.component.scss +++ b/modules/ui/src/app/pages/testrun/components/testrun-initiate-form/testrun-initiate-form.component.scss @@ -13,14 +13,47 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -@import 'src/theming/colors'; -@import 'src/theming/variables'; +@use 'colors'; +@use 'variables'; +@use 'mixins'; :host { display: grid; grid-template-rows: 1fr; overflow: hidden; - width: 450px; + width: 490px; + background: colors.$surface-container; + + app-device-tests { + padding-left: 16px; + + ::ng-deep .device-form-test-modules { + min-height: 78px; + display: grid; + grid-template-columns: repeat(2, 1fr); + grid-template-rows: repeat(3, 1fr); + grid-auto-flow: column; + padding-top: 8px; + padding-left: 24px; + + p { + margin: 6px 0; + } + } + + ::ng-deep .device-tests-title { + margin: 16px 0 0; + font-size: 22px; + line-height: 28px; + } + } + + app-callout { + ::ng-deep .callout-container.info { + margin: 8px 0 0; + padding: 16px 16px 12px; + } + } } .progress-initiate-form { @@ -30,25 +63,23 @@ } .progress-initiate-form-title { - color: $grey-800; - font-size: 22px; - line-height: 28px; - padding: 24px; - border-bottom: 1px solid $light-grey; + @include mixins.headline-large; + padding: 24px 24px 20px; + text-align: center; } .progress-initiate-form-content { overflow: auto; min-height: 78px; - padding: 32px 0; + padding: 4px 24px 8px; display: grid; - gap: 24px; + gap: 8px; justify-content: center; justify-items: center; grid-template-columns: 1fr; & > * { - width: $device-item-width; + width: variables.$device-item-width; box-sizing: border-box; } } @@ -56,8 +87,31 @@ .progress-initiate-form-actions { min-height: 30px; justify-content: space-between; - padding: 16px; - border-top: 1px solid $lighter-grey; + padding: 24px 32px; + + button { + border-radius: variables.$corner-medium; + } + + .progress-initiate-form-actions-change-device { + margin-right: auto; + } +} + +.progress-initiate-form-actions-change-device[disabled] + ::ng-deep + .mat-mdc-button-persistent-ripple::before { + opacity: 1; + background: rgba(31, 31, 31, 0.1); + color: colors.$on-surface; +} + +.selected-device { + margin-bottom: 16px; +} + +.device-tests-error { + padding-left: 16px; } .hidden { diff --git a/modules/ui/src/app/pages/testrun/components/testrun-initiate-form/testrun-initiate-form.component.spec.ts b/modules/ui/src/app/pages/testrun/components/testrun-initiate-form/testrun-initiate-form.component.spec.ts index 5a73578e4..f2157b2bd 100644 --- a/modules/ui/src/app/pages/testrun/components/testrun-initiate-form/testrun-initiate-form.component.spec.ts +++ b/modules/ui/src/app/pages/testrun/components/testrun-initiate-form/testrun-initiate-form.component.spec.ts @@ -63,7 +63,6 @@ describe('ProgressInitiateFormComponent', () => { beforeEach(() => { TestBed.configureTestingModule({ - declarations: [TestrunInitiateFormComponent], providers: [ { provide: TestRunService, useValue: testRunServiceMock }, { @@ -82,6 +81,7 @@ describe('ProgressInitiateFormComponent', () => { }), ], imports: [ + TestrunInitiateFormComponent, MatDialogModule, DeviceItemComponent, ReactiveFormsModule, @@ -240,7 +240,7 @@ describe('ProgressInitiateFormComponent', () => { component.selectedDevice = device; component.setFirmwareFocus = true; const firmwareSpy = spyOn( - component.firmwareInput.nativeElement, + component.firmwareInput().nativeElement, 'focus' ); component.ngAfterViewChecked(); diff --git a/modules/ui/src/app/pages/testrun/components/testrun-initiate-form/testrun-initiate-form.component.ts b/modules/ui/src/app/pages/testrun/components/testrun-initiate-form/testrun-initiate-form.component.ts index 0fddce670..cb702c1b9 100644 --- a/modules/ui/src/app/pages/testrun/components/testrun-initiate-form/testrun-initiate-form.component.ts +++ b/modules/ui/src/app/pages/testrun/components/testrun-initiate-form/testrun-initiate-form.component.ts @@ -18,11 +18,15 @@ import { ChangeDetectorRef, Component, ElementRef, - Inject, OnInit, - ViewChild, + viewChild, + inject, } from '@angular/core'; -import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; +import { + MAT_DIALOG_DATA, + MatDialogModule, + MatDialogRef, +} from '@angular/material/dialog'; import { TestRunService } from '../../../../services/test-run.service'; import { Device, @@ -35,6 +39,7 @@ import { FormArray, FormBuilder, FormGroup, + ReactiveFormsModule, } from '@angular/forms'; import { DeviceValidators } from '../../../devices/components/device-form/device.validators'; import { EscapableDialogComponent } from '../../../../components/escapable-dialog/escapable-dialog.component'; @@ -44,6 +49,19 @@ import { AppState } from '../../../../store/state'; import { selectDevices } from '../../../../store/selectors'; import { BehaviorSubject } from 'rxjs/internal/BehaviorSubject'; import { TestrunStatus } from '../../../../model/testrun-status'; +import { CalloutType } from '../../../../model/callout-type'; +import { CommonModule } from '@angular/common'; +import { MatToolbarModule } from '@angular/material/toolbar'; +import { MatProgressBarModule } from '@angular/material/progress-bar'; +import { MatIconModule } from '@angular/material/icon'; +import { MatInputModule } from '@angular/material/input'; +import { MatExpansionModule } from '@angular/material/expansion'; +import { DeviceTestsComponent } from '../../../../components/device-tests/device-tests.component'; +import { DeviceItemComponent } from '../../../../components/device-item/device-item.component'; +import { SpinnerComponent } from '../../../../components/spinner/spinner.component'; +import { MatTooltipModule } from '@angular/material/tooltip'; +import { MatButtonModule } from '@angular/material/button'; +import { CalloutComponent } from '../../../../components/callout/callout.component'; interface DialogData { device?: Device; @@ -54,13 +72,37 @@ interface DialogData { selector: 'app-testrun-initiate-form', templateUrl: './testrun-initiate-form.component.html', styleUrls: ['./testrun-initiate-form.component.scss'], + imports: [ + CommonModule, + MatButtonModule, + MatIconModule, + MatToolbarModule, + MatProgressBarModule, + MatDialogModule, + DeviceItemComponent, + MatInputModule, + MatExpansionModule, + ReactiveFormsModule, + DeviceTestsComponent, + SpinnerComponent, + CalloutComponent, + MatTooltipModule, + ], }) export class TestrunInitiateFormComponent extends EscapableDialogComponent implements OnInit, AfterViewChecked { private startRequestSent = new BehaviorSubject(false); - @ViewChild('firmwareInput') firmwareInput!: ElementRef; + override dialogRef: MatDialogRef; + data = inject(MAT_DIALOG_DATA); + private readonly testRunService = inject(TestRunService); + private fb = inject(FormBuilder); + private deviceValidators = inject(DeviceValidators); + private readonly changeDetectorRef = inject(ChangeDetectorRef); + private store = inject>(Store); + + readonly firmwareInput = viewChild.required('firmwareInput'); initiateForm!: FormGroup; devices$ = this.store.select(selectDevices); selectedDevice: Device | null = null; @@ -69,20 +111,17 @@ export class TestrunInitiateFormComponent setFirmwareFocus = false; readonly DeviceStatus = DeviceStatus; readonly DeviceView = DeviceView; + public readonly CalloutType = CalloutType; error$: BehaviorSubject = new BehaviorSubject( null ); - constructor( - public override dialogRef: MatDialogRef, - @Inject(MAT_DIALOG_DATA) public data: DialogData, - private readonly testRunService: TestRunService, - private fb: FormBuilder, - private deviceValidators: DeviceValidators, - private readonly changeDetectorRef: ChangeDetectorRef, - private store: Store - ) { - super(dialogRef); + constructor() { + const dialogRef = + inject>(MatDialogRef); + + super(); + this.dialogRef = dialogRef; } get firmware() { @@ -129,7 +168,7 @@ export class TestrunInitiateFormComponent } if (this.setFirmwareFocus) { timer(100).subscribe(() => { - this.firmwareInput?.nativeElement.focus(); + this.firmwareInput()?.nativeElement.focus(); this.setFirmwareFocus = false; this.changeDetectorRef.detectChanges(); }); diff --git a/modules/ui/src/app/pages/testrun/components/testrun-status-card/testrun-status-card.component.html b/modules/ui/src/app/pages/testrun/components/testrun-status-card/testrun-status-card.component.html index ca8009264..a459e1744 100644 --- a/modules/ui/src/app/pages/testrun/components/testrun-status-card/testrun-status-card.component.html +++ b/modules/ui/src/app/pages/testrun/components/testrun-status-card/testrun-status-card.component.html @@ -16,18 +16,40 @@