diff --git a/.gitignore b/.gitignore
index b4d001453..26fdf0fcd 100644
--- a/.gitignore
+++ b/.gitignore
@@ -12,6 +12,7 @@ testing/unit/dns/output/
testing/unit/nmap/output/
testing/unit/ntp/output/
testing/unit/tls/output/
+testing/unit/tls/tmp/
testing/unit/report/output/
*.deb
diff --git a/cmd/package b/cmd/package
index 823c55a43..cdc7cc8cf 100755
--- a/cmd/package
+++ b/cmd/package
@@ -17,6 +17,10 @@
# Creates a package for Testrun
MAKE_SRC_DIR=make
+TESTRUN_VER="1-2-2"
+
+# Delete existing make files
+rm -rf $MAKE_SRC_DIR/usr
# Delete existing make files
rm -rf $MAKE_SRC_DIR/usr
@@ -56,4 +60,7 @@ cp -r {framework,modules} $MAKE_SRC_DIR/usr/local/testrun
dpkg-deb --build --root-owner-group make
# Rename the .deb file
-mv make.deb testrun_1-2-1_amd64.deb
+mv make.deb testrun_${TESTRUN_VER}_amd64.deb
+
+# Echo package version
+echo Created installation package at testrun_${TESTRUN_VER}_amd64.deb
diff --git a/docs/get_started.md b/docs/get_started.md
index c9f508043..a7b893d13 100644
--- a/docs/get_started.md
+++ b/docs/get_started.md
@@ -109,5 +109,8 @@ If you encounter any issues or need assistance, consider the following:
- Check the configuration settings.
- Refer to the Testrun documentation or ask for assistance in the issues page: https://github.com/google/testrun/issues
+# Reviewing
+Once you have completed a test attempt, you may want to review the test report provided by Testrun. For more information about what Testrun looks for when testing, and what the output means, take a look at the testing documentation: [Testing](/docs/test/index.md).
+
# Uninstall
-To uninstall Testrun, use the built-in dpkg uninstall command to remove Testrun correctly. For Testrun, this would be: ```sudo apt-get remove testrun```
+To uninstall Testrun, use the built-in dpkg uninstall command to remove Testrun correctly. For Testrun, this would be: ```sudo apt-get remove testrun```. Note that this
diff --git a/docs/test/index.md b/docs/test/index.md
new file mode 100644
index 000000000..b440a86f1
--- /dev/null
+++ b/docs/test/index.md
@@ -0,0 +1,5 @@
+# Testing
+
+The test requirements that are investigated by Testrun can be found in the [test modules documentation](/docs/test/modules.md).
+
+To understand the testing results, various definitions of test results and requirements are specified in the [statuses documentation](/docs/test/statuses.md).
\ No newline at end of file
diff --git a/docs/test/statuses.md b/docs/test/statuses.md
new file mode 100644
index 000000000..5a2ba626d
--- /dev/null
+++ b/docs/test/statuses.md
@@ -0,0 +1,32 @@
+# Test Statuses
+Testrun will output the result and description of each automated test. The test results will be one of the following:
+
+| Name | Description | What next? |
+|---|---|---|
+| Compliant | The device implements the required feature correctly | Nothing |
+| Non-Compliant | The device does not support the specified requirements for the test | Modify or implement the required functionality on the device |
+| Feature Not Present | The device does not implement a feature covered by the test | You may implement the functionality (not required) |
+| Error | An error occured whilst running the test | Create a bug report requesting additional support to diagnose the issue |
+| Skipped | The test has not been executed because a linked test did not produce a compliant result | You may implement the functionality (not required) |
+
+## Test requirement
+Testrun also determines whether each test is required for the device to receive an overall compliant result. These rules are:
+
+| Name | Description |
+|---|---|
+| Required | The device must implement the feature |
+| Recommended | The device should implement the feature, but will not receive an overall Non-Compliant if not implemented |
+| Roadmap | The device should implement this feature in the future, but is not required at the moment |
+| Required If Applicable | If the device implements this feature, it must be implemented correctly (as per the test requirements) |
+
+## Testrun Statuses
+Once testing is completed, an overall result for the test attempt will be produced. This is calculated by comparing the result of all tests, and whether they are required or not required.
+
+### Compliant
+All required tests are implemented correctly, and all required if applicable tests are implemented correctly (where the feature has been implemented).
+
+### Non-Compliant
+One or more of the required tests (or required if applicable tests) have produced a non-compliant result.
+
+### Error
+One of more of the required tests (or required if applicable tests) have not executed correctly. This does not necessarily indicate that the device is compliant or non-compliant.
\ No newline at end of file
diff --git a/framework/python/src/api/api.py b/framework/python/src/api/api.py
index 0a00c19cd..758da6656 100644
--- a/framework/python/src/api/api.py
+++ b/framework/python/src/api/api.py
@@ -21,6 +21,7 @@
import os
import psutil
import requests
+import signal
import threading
import uvicorn
from urllib.parse import urlparse
@@ -46,7 +47,7 @@ class Api:
def __init__(self, test_run):
self._test_run = test_run
- self._name = "TestRun API"
+ self._name = "Testrun API"
self._router = APIRouter()
self._session = self._test_run.get_session()
@@ -66,6 +67,9 @@ def __init__(self, test_run):
methods=["POST"])
self._router.add_api_route("/system/status",
self.get_status)
+ self._router.add_api_route("/system/shutdown",
+ self.shutdown,
+ methods=["POST"])
self._router.add_api_route("/system/version",
self.get_version)
@@ -243,8 +247,35 @@ async def stop_test_run(self):
async def get_status(self):
return self._test_run.get_session().to_json()
+ def shutdown(self, response: Response):
+
+ LOGGER.debug("Received request to shutdown Testrun")
+
+ # Check that Testrun is not currently running
+ if (self._session.get_status() not in [
+ "Cancelled",
+ "Compliant",
+ "Non-Compliant",
+ "Idle"]):
+ LOGGER.debug("Unable to shutdown Testrun as Testrun is in progress")
+ response.status_code = 400
+ return self._generate_msg(
+ False,
+ "Unable to shutdown. A test is currently in progress."
+ )
+
+ self._test_run.shutdown()
+ os.kill(os.getpid(), signal.SIGTERM)
+
async def get_version(self, response: Response):
+
+ # Add defaults
json_response = {}
+ json_response["installed_version"] = "v" + self._test_run.get_version()
+ json_response["update_available"] = False
+ json_response["latest_version"] = None
+ json_response["latest_version_url"] = (
+ "https://github.com/google/testrun/releases")
# Obtain the current version
current_version = self._session.get_version()
@@ -264,9 +295,9 @@ async def get_version(self, response: Response):
# Check OK response was received
if version_check.status_code != 200:
response.status_code = 500
- LOGGER.error(version_check.content)
- return self._generate_msg(False, "Failed to fetch latest version. " +
- "Please, check the internet connection")
+ LOGGER.debug(version_check.content)
+ LOGGER.error("Failed to fetch latest version")
+ return json_response
# Extract version number from response, removing the leading 'v'
latest_version_no = version_check.json()["name"].strip("v")
@@ -289,8 +320,7 @@ async def get_version(self, response: Response):
response.status_code = 500
LOGGER.error("Failed to fetch latest version")
LOGGER.debug(e)
- return self._generate_msg(False, "Failed to fetch latest version. " +
- "Please, check the internet connection")
+ return json_response
async def get_reports(self, request: Request):
LOGGER.debug("Received reports list request")
@@ -304,7 +334,7 @@ async def get_reports(self, request: Request):
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", server_ip)
return reports
async def delete_report(self, request: Request, response: Response):
@@ -531,8 +561,8 @@ def _validate_device_json(self, json_obj):
return False
# Check length of strings
- if len(json_obj.get(DEVICE_MANUFACTURER_KEY)) > 64 or len(
- json_obj.get(DEVICE_MODEL_KEY)) > 64:
+ if len(json_obj.get(DEVICE_MANUFACTURER_KEY)) > 28 or len(
+ json_obj.get(DEVICE_MODEL_KEY)) > 28:
return False
disallowed_chars = ["/", "\\", "\'", "\"", ";"]
diff --git a/framework/python/src/common/device.py b/framework/python/src/common/device.py
index 51d68dfc6..c6a289d2c 100644
--- a/framework/python/src/common/device.py
+++ b/framework/python/src/common/device.py
@@ -41,6 +41,9 @@ def add_report(self, report):
def get_reports(self):
return self.reports
+ def clear_reports(self):
+ self.reports = []
+
def remove_report(self, timestamp: datetime):
for report in self.reports:
if report.get_started().strftime('%Y-%m-%dT%H:%M:%S') == timestamp:
diff --git a/framework/python/src/common/session.py b/framework/python/src/common/session.py
index 02edbc425..09eef117e 100644
--- a/framework/python/src/common/session.py
+++ b/framework/python/src/common/session.py
@@ -31,10 +31,10 @@
LOGGER = logger.get_logger('session')
-class TestRunSession():
+class TestrunSession():
"""Represents the current session of Test Run."""
- def __init__(self, config_file):
+ def __init__(self, config_file, version):
self._status = 'Idle'
self._device = None
self._started = None
@@ -46,8 +46,7 @@ def __init__(self, config_file):
self._total_tests = 0
self._report_url = None
- self._version = None
- self._load_version()
+ self._load_version(default_version=version)
self._config_file = config_file
self._config = self._get_default_config()
@@ -90,7 +89,7 @@ def _get_default_config(self):
'log_level': 'INFO',
'startup_timeout': 60,
'monitor_period': 30,
- 'max_device_reports': 5,
+ 'max_device_reports': 0,
'api_url': 'http://localhost',
'api_port': 8000
}
@@ -141,14 +140,18 @@ def _load_config(self):
LOGGER.debug(self._config)
- def _load_version(self):
+ def _load_version(self, default_version):
version_cmd = util.run_command(
'dpkg-query --showformat=\'${Version}\' --show testrun')
-
- if version_cmd:
+ # index 1 of response is the stderr byte stream so if
+ # it has any data in it, there was an error and we
+ # did not resolve the version and we'll use the fallback
+ if len(version_cmd[1]) == 0:
version = version_cmd[0]
- LOGGER.info(f'Running Testrun version {version}')
- self._version = version
+ self._version = version
+ else:
+ self._version = default_version
+ LOGGER.info(f'Running Testrun version {self._version}')
def get_version(self):
return self._version
@@ -294,6 +297,7 @@ def reset(self):
self.set_target_device(None)
self._report_url = None
self._total_tests = 0
+ self._module_reports = []
self._results = []
self._started = None
self._finished = None
diff --git a/framework/python/src/common/testreport.py b/framework/python/src/common/testreport.py
index ac42a064b..13c91482b 100644
--- a/framework/python/src/common/testreport.py
+++ b/framework/python/src/common/testreport.py
@@ -11,7 +11,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.
-"""Store previous test run information."""
+"""Store previous Testrun information."""
from datetime import datetime
from weasyprint import HTML
@@ -23,7 +23,7 @@
DATE_TIME_FORMAT = '%Y-%m-%d %H:%M:%S'
RESOURCES_DIR = 'resources/report'
-TESTS_FIRST_PAGE = 12
+TESTS_FIRST_PAGE = 11
TESTS_PER_PAGE = 20
# Locate parent directory
@@ -48,6 +48,7 @@ def __init__(self,
finished=None,
total_tests=0):
self._device = {}
+ self._mac_addr = None
self._status: str = status
self._started = started
self._finished = finished
@@ -57,7 +58,10 @@ def __init__(self,
self._report_url = ''
self._cur_page = 0
# Placeholder until available in json report
- self._version = 'v1.2.1'
+ self._version = 'v1.2.2'
+
+ def get_mac_addr(self):
+ return self._mac_addr
def add_module_reports(self, module_reports):
self._module_reports = module_reports
@@ -86,9 +90,14 @@ def set_report_url(self, url):
def get_report_url(self):
return self._report_url
+
+ def set_mac_addr(self, mac_addr):
+ self._mac_addr = mac_addr
def to_json(self):
report_json = {}
+
+ report_json['mac_addr'] = self._mac_addr
report_json['device'] = self._device
report_json['status'] = self._status
report_json['started'] = self._started.strftime(DATE_TIME_FORMAT)
@@ -96,13 +105,18 @@ def to_json(self):
test_results = []
for test in self._results:
- test_results.append({
+ test_dict = {
'name': test.name,
'description': test.description,
'expected_behavior': test.expected_behavior,
'required_result': test.required_result,
'result': test.result
- })
+ }
+
+ if test.recommendations is not None and len(test.recommendations) > 0:
+ test_dict['recommendations'] = test.recommendations
+
+ test_results.append(test_dict)
report_json['tests'] = {'total': self._total_tests,
'results': test_results}
@@ -115,6 +129,7 @@ def from_json(self, json_file):
self._device['manufacturer'] = json_file['device']['manufacturer']
self._device['model'] = json_file['device']['model']
+ # Firmware is not specified for non-UI devices
if 'firmware' in json_file['device']:
self._device['firmware'] = json_file['device']['firmware']
@@ -137,6 +152,8 @@ def from_json(self, json_file):
expected_behavior=test_result['expected_behavior'],
required_result=test_result['required_result'],
result=test_result['result'])
+ if 'recommendations' in test_result:
+ test_case.recommendations = test_result['recommendations']
self.add_test(test_case)
# Create a pdf file in memory and return the bytes
@@ -231,10 +248,86 @@ def generate_module_page(self, json_data, module_report):
{module_report}
'''
page += self.generate_footer(self._cur_page)
- page += '' #Page end
+ page += '' # Page end
page += '
'
return page
+ def generate_steps_to_resolve(self, json_data):
+
+ steps_so_far = 0
+ tests_with_recommendations = []
+ index = 1
+
+ # Collect all tests with recommendations
+ for test in json_data['tests']['results']:
+ if 'recommendations' in test:
+ tests_with_recommendations.append(test)
+
+ # Check if test has recommendations
+ if len(tests_with_recommendations) == 0:
+ return ''
+
+ # Start new page
+ self._cur_page += 1
+ page = ''
+ page += self.generate_header(json_data, False)
+
+ # Add title
+ page += '
Steps to Resolve '
+
+ for test in tests_with_recommendations:
+
+ # Generate new page
+ if steps_so_far == 4 and (
+ len(tests_with_recommendations) - (index-1) > 0):
+
+ # Reset steps counter
+ steps_so_far = 0
+
+ # Render footer
+ page += self.generate_footer(self._cur_page)
+ page += '' # Page end
+ page += '
'
+
+ # Render new header
+ self._cur_page += 1
+ page += ''
+ page += self.generate_header(json_data, False)
+
+ # Render test recommendations
+ page += f'''
+
+
+
+ {index}.
+ Name {test["name"]}
+ Description {test["description"]}
+
+
+
+
+ Steps to resolve
+ '''
+
+ step_number = 1
+ for recommendation in test['recommendations']:
+ page += f'''
+ {
+ step_number}. {recommendation} '''
+ step_number += 1
+
+ page += '
'
+
+ index += 1
+ steps_so_far += 1
+
+ # Render final footer
+ page += self.generate_footer(self._cur_page)
+ page += '
' # Page end
+ page += '
'
+
+ return page
+
def generate_module_pages(self, json_data):
pages = ''
content_max_size = 913
@@ -248,7 +341,7 @@ def generate_module_pages(self, json_data):
# Reset values for each module report
data_table_active = False
- data_rows_active=False
+ data_rows_active = False
page_content = ''
content_size = 0
content = module_reports.split('\n')
@@ -279,16 +372,16 @@ def generate_module_pages(self, json_data):
# Add appropriate content size for each data row
# update if CSS changes for this element
elif '' in line and data_rows_active:
- content_size += 40.667
+ content_size += 42
# If the current line is within the content size limit
# we'll add it to this page, otherweise, we'll put it on the next
# page. Also make sure that if there is less than 40 pixels
# left after a data row, start a new page or the row will get cut off.
- # Current row size is 40.667 so rounding to 41 padding,
- # adjust if we update the "module-data tbody tr" element.
+ # Current row size is 42 # adjust if we update the
+ # "module-data tbody tr" element.
if content_size >= content_max_size or (
- data_rows_active and content_max_size - content_size < 41):
+ data_rows_active and content_max_size - content_size < 42):
# If in the middle of a table, close the table
if data_rows_active:
page_content += ''
@@ -311,6 +404,7 @@ def generate_body(self, json_data):
body = f'''
{self.generate_pages(json_data)}
+ {self.generate_steps_to_resolve(json_data)}
{self.generate_module_pages(json_data)}
'''
@@ -356,6 +450,8 @@ def generate_result(self, result):
result_class = 'result-test-result-non-compliant'
elif result['result'] == 'Compliant':
result_class = 'result-test-result-compliant'
+ elif result['result'] == 'Error':
+ result_class = 'result-test-result-error'
else:
result_class = 'result-test-result-skipped'
@@ -410,7 +506,8 @@ def generate_summary(self, json_data):
mac = (json_data['device']['mac_addr']
if 'mac_addr' in json_data['device'] else 'Undefined')
- summary += ''
# Add device configuration
summary += '''
@@ -562,7 +659,7 @@ def generate_css(self):
--header-width: 8.5in;
--header-pos-x: 0in;
--header-pos-y: 0in;
- --summary-width: 8.5in;
+ --page-width: 8.5in;
--summary-height: 2.8in;
--vertical-line-height: calc(var(--summary-height)-.2in);
--vertical-line-pos-x: 25%;
@@ -690,6 +787,32 @@ def generate_css(self):
font-family: 'Roboto Mono', monospace;
}
+ table.steps-to-resolve {
+ background-color: #F8F9FA;
+ margin-bottom: 30px;
+ width: var(--page-width);
+ }
+
+ td.steps-to-resolve {
+ padding-left: 20px;
+ padding-top: 20px;
+ padding-right: 15px;
+ vertical-align: top;
+ }
+
+ .steps-to-resolve.subtitle {
+ text-align: left;
+ padding-top: 15px;
+ font-weight: 500;
+ color: #5F6368;
+ font-size: 14px;
+ }
+
+ .steps-to-resolve.index {
+ font-size: 40px;
+ padding-left: 30px;
+ }
+
.callout-container.info {
background-color: #e8f0fe;
}
@@ -717,20 +840,21 @@ def generate_css(self):
.device-information {
padding-top: 0.2in;
- padding-left: 0.3in;
+ padding-left: 0.2in;
background-color: #F8F9FA;
width: 250px;
- height: 2.6in;
+ height: 100.4%;
}
/* Define the summary related css elements*/
.summary-content {
position: relative;
- width: var(--summary-width);
+ width: var(--page-width);
height: var(--summary-height);
margin-top: 19px;
margin-bottom: 19px;
background-color: #E8EAED;
+ padding-bottom: 20px;
}
.summary-item-label {
@@ -780,7 +904,7 @@ def generate_css(self):
right: 0in;
top: 0in;
width: 2.6in;
- height: var(--summary-height);
+ height: 100%;
}
.summary-box-compliant {
@@ -857,10 +981,16 @@ def generate_css(self):
max-width: 380px;
}
+ .result-test-result-error {
+ background-color: #FCE8E6;
+ color: #C5221F;
+ left: 7.3in;
+ }
+
.result-test-result-non-compliant {
background-color: #FCE8E6;
color: #C5221F;
- left: 7.02in;
+ left: 7.04in;
}
.result-test-result {
@@ -882,7 +1012,7 @@ def generate_css(self):
.result-test-result-skipped {
background-color: #e3e3e3;
color: #393939;
- left: 7.2in;
+ left: 7.22in;
}
/* CSS for the footer */
diff --git a/framework/python/src/common/util.py b/framework/python/src/common/util.py
index c485d5ff2..70c0b76c8 100644
--- a/framework/python/src/common/util.py
+++ b/framework/python/src/common/util.py
@@ -28,7 +28,7 @@ def run_command(cmd, output=True):
By default, returns the standard output and error output
If the caller sets optional output parameter to False,
will only return a boolean result indicating if it was
- succesful in running the command. Failure is indicated
+ successful in running the command. Failure is indicated
by any return code from the process other than zero."""
success = False
@@ -43,8 +43,11 @@ def run_command(cmd, output=True):
LOGGER.error('Error: ' + err_msg)
else:
success = True
+ LOGGER.debug('Command succeeded: ' + cmd)
if output:
- return stdout.strip().decode('utf-8'), stderr
+ out = stdout.strip().decode('utf-8')
+ LOGGER.debug('Command output: ' + out)
+ return out, stderr
else:
return success
@@ -70,10 +73,10 @@ def get_os_user():
user = os.getlogin()
except OSError:
# Handle the OSError exception
- LOGGER.error('An OS error occured whilst calling os.getlogin()')
+ LOGGER.error('An OS error occurred whilst calling os.getlogin()')
except Exception: # pylint: disable=W0703
# Catch any other unexpected exceptions
- LOGGER.error('An unknown exception occured whilst calling os.getlogin()')
+ LOGGER.error('An unknown exception occurred whilst calling os.getlogin()')
return user
def get_user():
diff --git a/framework/python/src/core/test_runner.py b/framework/python/src/core/test_runner.py
index 07b6121b5..870e97752 100644
--- a/framework/python/src/core/test_runner.py
+++ b/framework/python/src/core/test_runner.py
@@ -12,7 +12,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-"""Wrapper for the TestRun that simplifies
+"""Wrapper for the Testrun that simplifies
virtual testing procedure by allowing direct calling
from the command line.
@@ -22,7 +22,7 @@
import argparse
import sys
-from testrun import TestRun
+from testrun import Testrun
from common import logger
import signal
@@ -39,7 +39,7 @@ def __init__(self,
single_intf=False,
no_ui=False):
self._register_exits()
- self.test_run = TestRun(config_file=config_file,
+ self.test_run = Testrun(config_file=config_file,
validate=validate,
net_only=net_only,
single_intf=single_intf,
diff --git a/framework/python/src/core/testrun.py b/framework/python/src/core/testrun.py
index 4ca176a79..71070e2cc 100644
--- a/framework/python/src/core/testrun.py
+++ b/framework/python/src/core/testrun.py
@@ -29,7 +29,7 @@
import time
from common import logger, util
from common.device import Device
-from common.session import TestRunSession
+from common.session import TestrunSession
from common.testreport import TestReport
from api.api import Api
from net_orc.listener import NetworkEvent
@@ -60,7 +60,9 @@
DEVICE_TEST_MODULES = 'test_modules'
MAX_DEVICE_REPORTS_KEY = 'max_device_reports'
-class TestRun: # pylint: disable=too-few-public-methods
+VERSION = '1.2.2'
+
+class Testrun: # pylint: disable=too-few-public-methods
"""Test Run controller.
Creates an instance of the network orchestrator, test
@@ -87,7 +89,8 @@ def __init__(self,
self._register_exits()
# Create session
- self._session = TestRunSession(config_file=self._config_file)
+ self._session = TestrunSession(config_file=self._config_file,
+ version=self.get_version())
# Register runtime parameters
if single_intf:
@@ -127,6 +130,9 @@ def __init__(self,
while True:
time.sleep(1)
+ def get_version(self):
+ return VERSION
+
def load_all_devices(self):
self._session.clear_device_repository()
self._load_devices(device_dir=LOCAL_DEVICES_DIR)
@@ -187,6 +193,9 @@ def _load_test_reports(self, device):
LOGGER.debug(f'Loading test reports for device {device.model}')
+ # Remove the existing reports in memory
+ device.clear_reports()
+
# Locate reports folder
reports_folder = os.path.join(root_dir,
LOCAL_DEVICES_DIR,
@@ -213,6 +222,7 @@ def _load_test_reports(self, device):
report_json = json.load(report_json_file)
test_report = TestReport()
test_report.from_json(report_json)
+ test_report.set_mac_addr(device.mac_addr)
device.add_report(test_report)
def delete_report(self, device: Device, timestamp):
@@ -282,6 +292,9 @@ def save_device(self, device: Device, device_json):
with open(config_file_path, 'w+', encoding='utf-8') as config_file:
config_file.writelines(json.dumps(device.to_config_json(), indent=4))
+ # Reload device reports
+ self._load_test_reports(device)
+
return device.to_config_json()
def delete_device(self, device: Device):
@@ -343,12 +356,16 @@ def _register_exits(self):
signal.signal(signal.SIGABRT, self._exit_handler)
signal.signal(signal.SIGQUIT, self._exit_handler)
+ def shutdown(self):
+ LOGGER.info('Shutting down Testrun')
+ self.stop()
+ self._stop_ui()
+
def _exit_handler(self, signum, arg): # pylint: disable=unused-argument
LOGGER.debug('Exit signal received: ' + str(signum))
if signum in (2, signal.SIGTERM):
LOGGER.info('Exit signal received.')
- self.stop()
- self._stop_ui()
+ self.shutdown()
sys.exit(1)
def _get_config_abs(self, config_file=None):
@@ -399,8 +416,6 @@ def _device_discovered(self, mac_addr):
self.get_session().set_target_device(device)
- self._set_status('In Progress')
-
LOGGER.info(
f'Discovered {device.manufacturer} {device.model} on the network. ' +
'Waiting for device to obtain IP')
@@ -409,6 +424,7 @@ def _device_stable(self, mac_addr):
# Do not continue testing if Testrun has cancelled during monitor phase
if self.get_session().get_status() == 'Cancelled':
+ self._stop_network()
return
LOGGER.info(f'Device with mac address {mac_addr} is ready for testing.')
diff --git a/framework/python/src/net_orc/ip_control.py b/framework/python/src/net_orc/ip_control.py
index 5c9f86d18..1eeafc1a9 100644
--- a/framework/python/src/net_orc/ip_control.py
+++ b/framework/python/src/net_orc/ip_control.py
@@ -41,6 +41,13 @@ def add_namespace(self, namespace):
success = util.run_command('ip netns add ' + namespace)
return success
+ def check_interface_status(self, interface_name):
+ output = util.run_command(cmd=f'ip link show {interface_name}',output=True)
+ if 'state DOWN ' in output[0]:
+ return False
+ else:
+ return True
+
def delete_link(self, interface_name):
"""Delete an ip link"""
success = util.run_command('ip link delete ' + interface_name)
diff --git a/framework/python/src/net_orc/network_orchestrator.py b/framework/python/src/net_orc/network_orchestrator.py
index 94c747758..aa23f1918 100644
--- a/framework/python/src/net_orc/network_orchestrator.py
+++ b/framework/python/src/net_orc/network_orchestrator.py
@@ -16,7 +16,7 @@
import ipaddress
import json
import os
-from scapy.all import sniff, wrpcap, BOOTP
+from scapy.all import sniff, wrpcap, BOOTP, AsyncSniffer
import shutil
import subprocess
import sys
@@ -47,11 +47,11 @@
class NetworkOrchestrator:
"""Manage and controls a virtual testing network."""
- def __init__(self,
- session):
+ def __init__(self, session):
self._session = session
self._monitor_in_progress = False
+ self._monitor_packets = []
self._listener = None
self._net_modules = []
@@ -74,12 +74,12 @@ def start(self):
shutil.rmtree(os.path.join(os.getcwd(), NET_DIR), ignore_errors=True)
# Cleanup any old config files test files
- conf_runtime_dir = os.path.join(RUNTIME_DIR,'conf')
+ conf_runtime_dir = os.path.join(RUNTIME_DIR, 'conf')
shutil.rmtree(conf_runtime_dir, ignore_errors=True)
os.makedirs(conf_runtime_dir, exist_ok=True)
# Copy the system config file to the runtime directory
- system_conf_runtime = os.path.join(conf_runtime_dir,'system.json')
+ system_conf_runtime = os.path.join(conf_runtime_dir, 'system.json')
with open(system_conf_runtime, 'w', encoding='utf-8') as f:
json.dump(self.get_session().get_config(), f, indent=2)
@@ -199,7 +199,7 @@ def _device_discovered(self, mac_addr):
wrpcap(os.path.join(device_runtime_dir, 'startup.pcap'), packet_capture)
# Copy the device config file to the runtime directory
- runtime_device_conf = os.path.join(device_runtime_dir,'device_config.json')
+ runtime_device_conf = os.path.join(device_runtime_dir, 'device_config.json')
with open(runtime_device_conf, 'w', encoding='utf-8') as f:
json.dump(self._session.get_target_device().to_config_json(), f, indent=2)
@@ -240,20 +240,33 @@ def _start_device_monitor(self, device):
"""Start a timer until the steady state has been reached and
callback the steady state method for this device."""
self.get_session().set_status('Monitoring')
+ self._monitor_packets = []
LOGGER.info(f'Monitoring device with mac addr {device.mac_addr} '
f'for {str(self._session.get_monitor_period())} seconds')
device_runtime_dir = os.path.join(RUNTIME_DIR, TEST_DIR,
device.mac_addr.replace(':', ''))
- packet_capture = sniff(iface=self._session.get_device_interface(),
- timeout=self._session.get_monitor_period())
- wrpcap(os.path.join(device_runtime_dir, 'monitor.pcap'), packet_capture)
-
+ sniffer = AsyncSniffer(iface=self._session.get_device_interface(),
+ timeout=self._session.get_monitor_period(),
+ prn=self._monitor_packet_callback)
+ sniffer.start()
+
+ while sniffer.running:
+ if not self._ip_ctrl.check_interface_status(
+ self._session.get_device_interface()):
+ sniffer.stop()
+ self._session.set_status('Cancelled')
+ LOGGER.error('Device interface disconnected, cancelling Testrun')
+ wrpcap(os.path.join(device_runtime_dir, 'monitor.pcap'),
+ self._monitor_packets)
self._monitor_in_progress = False
self.get_listener().call_callback(NetworkEvent.DEVICE_STABLE,
device.mac_addr)
+ def _monitor_packet_callback(self, packet):
+ self._monitor_packets.append(packet)
+
def _check_network_services(self):
LOGGER.debug('Checking network modules...')
for net_module in self._net_modules:
@@ -729,6 +742,7 @@ def restore_net(self):
def get_session(self):
return self._session
+
class NetworkModule:
"""Define all the properties of a Network Module"""
diff --git a/framework/python/src/test_orc/test_case.py b/framework/python/src/test_orc/test_case.py
index 72097b2a9..cf0d6593a 100644
--- a/framework/python/src/test_orc/test_case.py
+++ b/framework/python/src/test_orc/test_case.py
@@ -28,10 +28,21 @@ class TestCase: # pylint: disable=too-few-public-methods,too-many-instance-attr
recommendations: list = field(default_factory=lambda: [])
def to_dict(self):
+
+ if self.recommendations is not None and len(self.recommendations) > 0:
+ return {
+ "name": self.name,
+ "description": self.description,
+ "expected_behavior": self.expected_behavior,
+ "required_result": self.required_result,
+ "result": self.result,
+ "recommendations": self.recommendations
+ }
+
return {
- "name": self.name,
- "description": self.description,
- "expected_behavior": self.expected_behavior,
- "required_result": self.required_result,
- "result": self.result
- }
+ "name": self.name,
+ "description": self.description,
+ "expected_behavior": self.expected_behavior,
+ "required_result": self.required_result,
+ "result": self.result
+ }
diff --git a/framework/python/src/test_orc/test_orchestrator.py b/framework/python/src/test_orc/test_orchestrator.py
index 21d99dcbd..d7f94bcd7 100644
--- a/framework/python/src/test_orc/test_orchestrator.py
+++ b/framework/python/src/test_orc/test_orchestrator.py
@@ -160,6 +160,7 @@ def _write_reports(self, test_report):
def _generate_report(self):
report = {}
+ report["mac_addr"] = self.get_session().get_target_device().mac_addr
report["device"] = self.get_session().get_target_device().to_dict()
report["started"] = self.get_session().get_started().strftime(
"%Y-%m-%d %H:%M:%S")
@@ -190,32 +191,33 @@ def _cleanup_old_test_results(self, device):
else:
max_device_reports = self._session.get_max_device_reports()
- completed_results_dir = os.path.join(
- self._root_path,
- LOCAL_DEVICE_REPORTS.replace("{device_folder}", device.device_folder))
-
- completed_tests = os.listdir(completed_results_dir)
- cur_test_count = len(completed_tests)
- if cur_test_count > max_device_reports:
- LOGGER.debug("Current device has more than max tests results allowed: " +
- str(cur_test_count) + ">" + str(max_device_reports))
-
- # Find and delete the oldest test
- oldest_test = self._find_oldest_test(completed_results_dir)
- if oldest_test is not None:
- LOGGER.debug("Oldest test found, removing: " + str(oldest_test[1]))
- shutil.rmtree(oldest_test[1], ignore_errors=True)
-
- # Remove oldest test from session
- oldest_timestamp = oldest_test[0]
- self.get_session().get_target_device().remove_report(oldest_timestamp)
-
- # Confirm the delete was succesful
- new_test_count = len(os.listdir(completed_results_dir))
- if (new_test_count != cur_test_count
- and new_test_count > max_device_reports):
- # Continue cleaning up until we're under the max
- self._cleanup_old_test_results(device)
+ if max_device_reports > 0:
+ completed_results_dir = os.path.join(
+ self._root_path,
+ LOCAL_DEVICE_REPORTS.replace("{device_folder}", device.device_folder))
+
+ completed_tests = os.listdir(completed_results_dir)
+ cur_test_count = len(completed_tests)
+ if cur_test_count > max_device_reports:
+ LOGGER.debug("Current device has more than max results allowed: " +
+ str(cur_test_count) + ">" + str(max_device_reports))
+
+ # Find and delete the oldest test
+ oldest_test = self._find_oldest_test(completed_results_dir)
+ if oldest_test is not None:
+ LOGGER.debug("Oldest test found, removing: " + str(oldest_test[1]))
+ shutil.rmtree(oldest_test[1], ignore_errors=True)
+
+ # Remove oldest test from session
+ oldest_timestamp = oldest_test[0]
+ self.get_session().get_target_device().remove_report(oldest_timestamp)
+
+ # Confirm the delete was succesful
+ new_test_count = len(os.listdir(completed_results_dir))
+ if (new_test_count != cur_test_count
+ and new_test_count > max_device_reports):
+ # Continue cleaning up until we're under the max
+ self._cleanup_old_test_results(device)
def _find_oldest_test(self, completed_tests_dir):
oldest_timestamp = None
@@ -322,11 +324,12 @@ def _run_test_module(self, module):
device_test_dir = os.path.join(self._root_path, RUNTIME_TEST_DIR,
device.mac_addr.replace(":", ""))
- root_certs_dir = os.path.join(self._root_path, DEVICE_ROOT_CERTS)
-
container_runtime_dir = os.path.join(device_test_dir, module.name)
os.makedirs(container_runtime_dir, exist_ok=True)
+ config_file = os.path.join(self._root_path, "local/system.json")
+ root_certs_dir = os.path.join(self._root_path, "local/root_certs")
+
container_log_file = os.path.join(container_runtime_dir, "module.log")
network_runtime_dir = os.path.join(self._root_path, "runtime/network")
@@ -339,9 +342,6 @@ def _run_test_module(self, module):
client = docker.from_env()
- # if module.name == 'connection':
- # self._net_orc.remove_arp_filters()
-
module.container = client.containers.run(
module.image_name,
auto_remove=True,
@@ -351,6 +351,14 @@ def _run_test_module(self, module):
privileged=True,
detach=True,
mounts=[
+ Mount(target="/testrun/system.json",
+ source=config_file,
+ type="bind",
+ read_only=True),
+ Mount(target="/testrun/root_certs",
+ source=root_certs_dir,
+ type="bind",
+ read_only=True),
Mount(target="/runtime/output",
source=container_runtime_dir,
type="bind"),
@@ -365,10 +373,6 @@ def _run_test_module(self, module):
Mount(target="/runtime/device/monitor.pcap",
source=device_monitor_capture,
type="bind",
- read_only=True),
- Mount(target="/testrun/root_certs",
- source=root_certs_dir,
- type="bind",
read_only=True)
],
environment={
diff --git a/framework/requirements.txt b/framework/requirements.txt
index 9f9e4ea91..9522bd3b0 100644
--- a/framework/requirements.txt
+++ b/framework/requirements.txt
@@ -14,11 +14,11 @@ weasyprint==60.2
fastapi==0.109.1
psutil==5.9.8
uvicorn==0.27.0
-pydantic==1.10.11
+pydantic==1.10.13
# Requirements for testing
pytest==7.4.4
pytest-timeout==2.2.0
# Requirements for the report
-markdown==3.5.2
\ No newline at end of file
+markdown==3.5.2
diff --git a/local/system.json.example b/local/system.json.example
index d2f398b24..23023bead 100644
--- a/local/system.json.example
+++ b/local/system.json.example
@@ -6,5 +6,5 @@
"log_level": "INFO",
"startup_timeout": 60,
"monitor_period": 300,
- "max_device_reports": 5
+ "max_device_reports": 0
}
diff --git a/make/DEBIAN/control b/make/DEBIAN/control
index c1039787d..dff4ce378 100644
--- a/make/DEBIAN/control
+++ b/make/DEBIAN/control
@@ -1,5 +1,5 @@
Package: Testrun
-Version: 1.2.1
+Version: 1.2.2
Architecture: amd64
Maintainer: Google
Homepage: https://github.com/google/testrun
diff --git a/modules/devices/faux-dev/python/src/util.py b/modules/devices/faux-dev/python/src/util.py
index 920752217..8ffc70cbc 100644
--- a/modules/devices/faux-dev/python/src/util.py
+++ b/modules/devices/faux-dev/python/src/util.py
@@ -16,15 +16,15 @@
import subprocess
import shlex
-# Runs a process at the os level
-# By default, returns the standard output and error output
-# If the caller sets optional output parameter to False,
-# will only return a boolean result indicating if it was
-# succesful in running the command. Failure is indicated
-# by any return code from the process other than zero.
-
def run_command(cmd, logger, output=True):
+ """Runs a process at the os level
+ By default, returns the standard output and error output
+ If the caller sets optional output parameter to False,
+ will only return a boolean result indicating if it was
+ successful in running the command. Failure is indicated
+ by any return code from the process other than zero."""
+
success = False
process = subprocess.Popen(shlex.split(cmd),
stdout=subprocess.PIPE,
@@ -37,8 +37,10 @@ def run_command(cmd, logger, output=True):
logger.error('Error: ' + err_msg)
else:
success = True
-
+ logger.debug('Command succeeded: ' + cmd)
if output:
- return success, stdout.strip().decode('utf-8')
+ out = stdout.strip().decode('utf-8')
+ logger.debug('Command output: ' + out)
+ return success, out
else:
return success, None
diff --git a/modules/network/dhcp-1/python/src/grpc_server/dhcp_config.py b/modules/network/dhcp-1/python/src/grpc_server/dhcp_config.py
index 877d49610..d38d95785 100644
--- a/modules/network/dhcp-1/python/src/grpc_server/dhcp_config.py
+++ b/modules/network/dhcp-1/python/src/grpc_server/dhcp_config.py
@@ -56,6 +56,12 @@ def enable_failover(self):
for subnet in self._subnets:
subnet.enable_peer()
+ def get_peer(self):
+ return self._peer
+
+ def get_subnets(self):
+ return self._subnets
+
def get_reserved_host(self, hw_addr):
for host in self._reserved_hosts:
if hw_addr == host.hw_addr:
diff --git a/modules/network/dhcp-1/python/src/grpc_server/dhcp_config_test.py b/modules/network/dhcp-1/python/src/grpc_server/dhcp_config_test.py
index 4bc1bd52d..493266c2c 100644
--- a/modules/network/dhcp-1/python/src/grpc_server/dhcp_config_test.py
+++ b/modules/network/dhcp-1/python/src/grpc_server/dhcp_config_test.py
@@ -61,14 +61,14 @@ def test_resolve_config(self):
def test_disable_failover(self):
DHCP_CONFIG.disable_failover()
print('Test Disable Config:\n' + str(DHCP_CONFIG))
- config_lines = str(DHCP_CONFIG._peer).split('\n')
+ config_lines = str(DHCP_CONFIG.get_peer()).split('\n')
for line in config_lines:
self.assertTrue(line.startswith('#'))
def test_enable_failover(self):
DHCP_CONFIG.enable_failover()
print('Test Enable Config:\n' + str(DHCP_CONFIG))
- config_lines = str(DHCP_CONFIG._peer).split('\n')
+ config_lines = str(DHCP_CONFIG.get_peer()).split('\n')
for line in config_lines:
self.assertFalse(line.startswith('#'))
diff --git a/modules/network/dhcp-1/python/src/grpc_server/network_service.py b/modules/network/dhcp-1/python/src/grpc_server/network_service.py
index 92726025d..5124a07ad 100644
--- a/modules/network/dhcp-1/python/src/grpc_server/network_service.py
+++ b/modules/network/dhcp-1/python/src/grpc_server/network_service.py
@@ -142,7 +142,7 @@ def GetDHCPRange(self, request, context): # pylint: disable=W0613
"""
LOGGER.info('Get DHCP range called')
try:
- pool = self._get_dhcp_config()._subnets[0].pools[0]
+ pool = self._get_dhcp_config().get_subnets()[0].pools[0]
return pb2.DHCPRange(code=200, start=pool.range_start, end=pool.range_end)
except Exception as e: # pylint: disable=W0718
fail_message = 'Failed to get DHCP range: ' + str(e)
diff --git a/modules/network/dhcp-2/python/src/grpc_server/dhcp_config.py b/modules/network/dhcp-2/python/src/grpc_server/dhcp_config.py
index 5357ba7ed..c49523a64 100644
--- a/modules/network/dhcp-2/python/src/grpc_server/dhcp_config.py
+++ b/modules/network/dhcp-2/python/src/grpc_server/dhcp_config.py
@@ -58,6 +58,12 @@ def enable_failover(self):
for subnet in self._subnets:
subnet.enable_peer()
+ def get_peer(self):
+ return self._peer
+
+ def get_subnets(self):
+ return self._subnets
+
def get_reserved_host(self, hw_addr):
for host in self._reserved_hosts:
if hw_addr == host.hw_addr:
diff --git a/modules/network/dhcp-2/python/src/grpc_server/dhcp_config_test.py b/modules/network/dhcp-2/python/src/grpc_server/dhcp_config_test.py
index 0a156db68..7d368265a 100644
--- a/modules/network/dhcp-2/python/src/grpc_server/dhcp_config_test.py
+++ b/modules/network/dhcp-2/python/src/grpc_server/dhcp_config_test.py
@@ -58,14 +58,14 @@ def test_resolve_config(self):
def test_disable_failover(self):
DHCP_CONFIG.disable_failover()
print('Test Disable Config:\n' + str(DHCP_CONFIG))
- config_lines = str(DHCP_CONFIG._peer).split('\n')
+ config_lines = str(DHCP_CONFIG.get_peer()).split('\n')
for line in config_lines:
self.assertTrue(line.startswith('#'))
def test_enable_failover(self):
DHCP_CONFIG.enable_failover()
print('Test Enable Config:\n' + str(DHCP_CONFIG))
- config_lines = str(DHCP_CONFIG._peer).split('\n')
+ config_lines = str(DHCP_CONFIG.get_peer()).split('\n')
for line in config_lines:
self.assertFalse(line.startswith('#'))
diff --git a/modules/network/dhcp-2/python/src/grpc_server/network_service.py b/modules/network/dhcp-2/python/src/grpc_server/network_service.py
index f9deba965..7c5c61d4f 100644
--- a/modules/network/dhcp-2/python/src/grpc_server/network_service.py
+++ b/modules/network/dhcp-2/python/src/grpc_server/network_service.py
@@ -142,7 +142,7 @@ def GetDHCPRange(self, request, context): # pylint: disable=W0613
"""
LOGGER.info('Get DHCP range called')
try:
- pool = self._get_dhcp_config()._subnets[0].pools[0]
+ pool = self._get_dhcp_config().get_subnets()[0].pools[0]
return pb2.DHCPRange(code=200, start=pool.range_start, end=pool.range_end)
except Exception as e: # pylint: disable=W0718
fail_message = 'Failed to get DHCP range: ' + str(e)
diff --git a/modules/network/ntp/python/src/chronyd.py b/modules/network/ntp/python/src/chronyd.py
index b8ce7db56..23dd834df 100644
--- a/modules/network/ntp/python/src/chronyd.py
+++ b/modules/network/ntp/python/src/chronyd.py
@@ -46,4 +46,5 @@ def is_running(self):
LOGGER.info('Checking chronyd server')
running = os.path.exists(PID_FILE)
LOGGER.info('chronyd server status: ' + str(running))
- return running
\ No newline at end of file
+ return running
+
\ No newline at end of file
diff --git a/modules/network/ntp/python/src/ntp_server.py b/modules/network/ntp/python/src/ntp_server.py
index 14a3d9bac..42fe21e77 100644
--- a/modules/network/ntp/python/src/ntp_server.py
+++ b/modules/network/ntp/python/src/ntp_server.py
@@ -16,7 +16,7 @@
from common import logger
from chronyd import ChronydServer
import time
-
+LOGGER = None
LOG_NAME = 'ntp_server'
class NTPServer:
diff --git a/modules/test/base/python/src/logger.py b/modules/test/base/python/src/logger.py
index a5e5ab18d..e6a2b004c 100644
--- a/modules/test/base/python/src/logger.py
+++ b/modules/test/base/python/src/logger.py
@@ -15,19 +15,17 @@
"""Sets up the logger to be used for the test modules."""
import json
import logging
-import os
LOGGERS = {}
_LOG_FORMAT = '%(asctime)s %(name)-8s %(levelname)-7s %(message)s'
_DATE_FORMAT = '%b %02d %H:%M:%S'
_DEFAULT_LEVEL = logging.INFO
-_CONF_DIR = 'conf'
-_CONF_FILE_NAME = 'system.json'
+_CONF_FILE_NAME = 'testrun/system.json'
_LOG_DIR = '/runtime/output/'
# Set log level
try:
- with open(os.path.join(_CONF_DIR, _CONF_FILE_NAME),
+ with open(_CONF_FILE_NAME,
encoding='UTF-8') as config_json_file:
system_conf_json = json.load(config_json_file)
diff --git a/modules/test/base/python/src/test_module.py b/modules/test/base/python/src/test_module.py
index f7707ba3a..471f22222 100644
--- a/modules/test/base/python/src/test_module.py
+++ b/modules/test/base/python/src/test_module.py
@@ -26,21 +26,31 @@
class TestModule:
"""An example test module."""
- def __init__(self, module_name, log_name, log_dir=None,conf_file=CONF_FILE,results_dir=RESULTS_DIR):
+ def __init__(self,
+ module_name,
+ log_name,
+ log_dir=None,
+ conf_file=CONF_FILE,
+ results_dir=RESULTS_DIR):
self._module_name = module_name
- self._results_dir=results_dir if results_dir is not None else RESULTS_DIR
- self._device_mac = os.environ.get('DEVICE_MAC','')
- self._ipv4_addr = os.environ.get('IPV4_ADDR','')
- self._ipv4_subnet = os.environ.get('IPV4_SUBNET','')
- self._ipv6_subnet = os.environ.get('IPV6_SUBNET','')
- self._add_logger(log_name=log_name, module_name=module_name, log_dir=log_dir)
- self._config = self._read_config(conf_file=conf_file if conf_file is not None else CONF_FILE)
+ self._results_dir = results_dir if results_dir is not None else RESULTS_DIR
+ self._device_mac = os.environ.get('DEVICE_MAC', '')
+ self._ipv4_addr = os.environ.get('IPV4_ADDR', '')
+ self._ipv4_subnet = os.environ.get('IPV4_SUBNET', '')
+ self._ipv6_subnet = os.environ.get('IPV6_SUBNET', '')
+ self._add_logger(log_name=log_name,
+ module_name=module_name,
+ log_dir=log_dir)
+ self._config = self._read_config(
+ conf_file=conf_file if conf_file is not None else CONF_FILE)
self._device_ipv4_addr = None
self._device_ipv6_addr = None
def _add_logger(self, log_name, module_name, log_dir=None):
global LOGGER
- LOGGER = logger.get_logger(log_name, module_name, log_dir=log_dir)
+ LOGGER = logger.get_logger(name=log_name,
+ log_file=module_name,
+ log_dir=log_dir)
def generate_module_report(self):
pass
@@ -100,7 +110,7 @@ def run_tests(self):
result = getattr(self, test_method_name)(config=test['config'])
else:
result = getattr(self, test_method_name)()
- except Exception as e:
+ except Exception as e: # pylint: disable=W0718
LOGGER.error(f'An error occurred whilst running {test["name"]}')
LOGGER.error(e)
else:
@@ -136,13 +146,16 @@ def run_tests(self):
test['description'] = result[1]
else:
test['description'] = 'No description was provided for this test'
+
+ # Check if details were provided
+ if len(result)>2:
+ test['details'] = result[2]
else:
test['result'] = 'Error'
test['description'] = 'An error occured whilst running this test'
# Remove the steps to resolve if compliant already
- if (test['result'] == 'Compliant' and
- 'recommendations' in test):
+ if (test['result'] == 'Compliant' and 'recommendations' in test):
test.pop('recommendations')
test['end'] = datetime.now().isoformat()
@@ -153,7 +166,7 @@ def run_tests(self):
json_results = json.dumps({'results': tests}, indent=2)
self._write_results(json_results)
- def _read_config(self,conf_file=CONF_FILE):
+ def _read_config(self, conf_file=CONF_FILE):
with open(conf_file, encoding='utf-8') as f:
config = json.load(f)
return config
diff --git a/modules/test/base/python/src/util.py b/modules/test/base/python/src/util.py
index 0f54c4298..ba9c89d6d 100644
--- a/modules/test/base/python/src/util.py
+++ b/modules/test/base/python/src/util.py
@@ -19,13 +19,14 @@
LOGGER = logger.get_logger('util')
-# Runs a process at the os level
-# By default, returns the standard output and error output
-# If the caller sets optional output parameter to False,
-# will only return a boolean result indicating if it was
-# succesful in running the command. Failure is indicated
-# by any return code from the process other than zero.
def run_command(cmd, output=True):
+ """Runs a process at the os level
+ By default, returns the standard output and error output
+ If the caller sets optional output parameter to False,
+ will only return a boolean result indicating if it was
+ successful in running the command. Failure is indicated
+ by any return code from the process other than zero."""
+
success = False
process = subprocess.Popen(shlex.split(cmd),
stdout=subprocess.PIPE,
@@ -37,7 +38,10 @@ def run_command(cmd, output=True):
LOGGER.error('Error: ' + err_msg)
else:
success = True
+ LOGGER.debug('Command succeeded: ' + cmd)
if output:
- return stdout.strip().decode('utf-8'), stderr
+ out = stdout.strip().decode('utf-8')
+ LOGGER.debug('Command output: ' + out)
+ return out, stderr
else:
return success
diff --git a/modules/test/conn/python/src/connection_module.py b/modules/test/conn/python/src/connection_module.py
index cf8852ef0..34e129103 100644
--- a/modules/test/conn/python/src/connection_module.py
+++ b/modules/test/conn/python/src/connection_module.py
@@ -104,9 +104,8 @@ def _connection_switch_arp_inspection(self):
no_arp = False
# Check MAC address matches IP address
- if (arp_packet.hwsrc == self._device_mac and
- (arp_packet.psrc != self._device_ipv4_addr
- and arp_packet.psrc != '0.0.0.0')):
+ if (arp_packet.hwsrc == self._device_mac
+ and (arp_packet.psrc not in (self._device_ipv4_addr, '0.0.0.0'))):
LOGGER.info(f'Bad ARP packet detected for MAC: {self._device_mac}')
LOGGER.info(f'''ARP packet from IP {arp_packet.psrc}
does not match {self._device_ipv4_addr}''')
@@ -205,13 +204,11 @@ def _connection_single_ip(self):
LOGGER.info('Inspecting: ' + str(len(packets)) + ' packets')
for packet in packets:
if DHCP in packet:
- for option in packet[DHCP].options:
- # message-type, option 3 = DHCPREQUEST
- if self._get_dhcp_type(packet) == 3:
- mac_address = packet[Ether].src
- LOGGER.info('DHCPREQUEST detected MAC address: ' + mac_address)
- if not mac_address.startswith(TR_CONTAINER_MAC_PREFIX):
- mac_addresses.add(mac_address.upper())
+ if self._get_dhcp_type(packet) == 3:
+ mac_address = packet[Ether].src
+ LOGGER.info('DHCPREQUEST detected MAC address: ' + mac_address)
+ if not mac_address.startswith(TR_CONTAINER_MAC_PREFIX):
+ mac_addresses.add(mac_address.upper())
# Check if the device mac address is in the list of DHCPREQUESTs
result = self._device_mac.upper() in mac_addresses
@@ -637,4 +634,4 @@ def test_subnets(self, subnets):
LOGGER.error(traceback.format_exc())
result = {'result': False, 'details': 'Subnet test failed: ' + str(e)}
results.append(result)
- return results
\ No newline at end of file
+ return results
diff --git a/modules/test/dns/python/src/dns_module.py b/modules/test/dns/python/src/dns_module.py
index 02d89eb0a..e9550663d 100644
--- a/modules/test/dns/python/src/dns_module.py
+++ b/modules/test/dns/python/src/dns_module.py
@@ -33,17 +33,17 @@ def __init__(self,
log_dir=None,
conf_file=None,
results_dir=None,
- DNS_SERVER_CAPTURE_FILE=DNS_SERVER_CAPTURE_FILE,
- STARTUP_CAPTURE_FILE=STARTUP_CAPTURE_FILE,
- MONITOR_CAPTURE_FILE=MONITOR_CAPTURE_FILE):
+ dns_server_capture_file=DNS_SERVER_CAPTURE_FILE,
+ startup_capture_file=STARTUP_CAPTURE_FILE,
+ monitor_capture_file=MONITOR_CAPTURE_FILE):
super().__init__(module_name=module,
log_name=LOG_NAME,
log_dir=log_dir,
conf_file=conf_file,
results_dir=results_dir)
- self.dns_server_capture_file=DNS_SERVER_CAPTURE_FILE
- self.startup_capture_file=STARTUP_CAPTURE_FILE
- self.monitor_capture_file=MONITOR_CAPTURE_FILE
+ self.dns_server_capture_file=dns_server_capture_file
+ self.startup_capture_file=startup_capture_file
+ self.monitor_capture_file=monitor_capture_file
self._dns_server = '10.10.10.4'
global LOGGER
LOGGER = self._get_logger()
diff --git a/modules/test/ntp/python/src/ntp_module.py b/modules/test/ntp/python/src/ntp_module.py
index 952ae236f..23c5cfd13 100644
--- a/modules/test/ntp/python/src/ntp_module.py
+++ b/modules/test/ntp/python/src/ntp_module.py
@@ -157,9 +157,10 @@ def generate_module_report(self):
def extract_ntp_data(self):
ntp_data = []
- # Read the pcap file
- packets = rdpcap(self.ntp_server_capture_file) + rdpcap(
- self.startup_capture_file) + rdpcap(self.monitor_capture_file)
+ # Read the pcap files
+ packets = (rdpcap(self.startup_capture_file) +
+ rdpcap(self.monitor_capture_file) +
+ rdpcap(self.ntp_server_capture_file))
# Iterate through NTP packets
for packet in packets:
diff --git a/modules/test/protocol/python/src/protocol_module.py b/modules/test/protocol/python/src/protocol_module.py
index 0c9936524..932bc7702 100644
--- a/modules/test/protocol/python/src/protocol_module.py
+++ b/modules/test/protocol/python/src/protocol_module.py
@@ -80,7 +80,16 @@ def _protocol_valid_modbus(self, config):
LOGGER.info('Running protocol.valid_modbus')
# Extract basic device connection information
modbus = Modbus(log=LOGGER, device_ip=self._device_ipv4_addr, config=config)
- return modbus.validate_device()
+ results = modbus.validate_device()
+ # Determine results and return proper messaging and details
+ description = ''
+ if results[0] is None:
+ description = 'No modbus connection could be made'
+ elif results[0]:
+ description = 'Valid modbus communication detected'
+ else:
+ description = 'Failed to confirm valid modbus communication'
+ return results[0], description, results[1]
def get_local_ip(self, interface_name):
try:
diff --git a/modules/test/nmap/README.md b/modules/test/services/README.md
similarity index 100%
rename from modules/test/nmap/README.md
rename to modules/test/services/README.md
diff --git a/modules/test/nmap/bin/start_test_module b/modules/test/services/bin/start_test_module
similarity index 100%
rename from modules/test/nmap/bin/start_test_module
rename to modules/test/services/bin/start_test_module
diff --git a/modules/test/nmap/conf/module_config.json b/modules/test/services/conf/module_config.json
similarity index 95%
rename from modules/test/nmap/conf/module_config.json
rename to modules/test/services/conf/module_config.json
index 1c1115afe..efd07d74b 100644
--- a/modules/test/nmap/conf/module_config.json
+++ b/modules/test/services/conf/module_config.json
@@ -1,8 +1,8 @@
{
"config": {
"meta": {
- "name": "nmap",
- "display_name": "nmap",
+ "name": "services",
+ "display_name": "Services",
"description": "Scan for open ports using nmap"
},
"network": true,
diff --git a/modules/test/nmap/python/requirements.txt b/modules/test/services/python/requirements.txt
similarity index 100%
rename from modules/test/nmap/python/requirements.txt
rename to modules/test/services/python/requirements.txt
diff --git a/modules/test/nmap/python/src/run.py b/modules/test/services/python/src/run.py
similarity index 82%
rename from modules/test/nmap/python/src/run.py
rename to modules/test/services/python/src/run.py
index 2a85bb074..d8f933c1d 100644
--- a/modules/test/nmap/python/src/run.py
+++ b/modules/test/services/python/src/run.py
@@ -17,12 +17,12 @@
import signal
import sys
import logger
-from nmap_module import NmapModule
+from services_module import ServicesModule
-LOG_NAME = 'nmap_runner'
+LOG_NAME = 'services_runner'
LOGGER = logger.get_logger(LOG_NAME)
-class NmapModuleRunner:
+class ServicesModuleRunner:
"""Run the NMAP module tests."""
def __init__(self, module):
@@ -33,13 +33,13 @@ def __init__(self, module):
signal.signal(signal.SIGQUIT, self._handler)
self.add_logger(module)
- LOGGER.info('Starting nmap module')
+ LOGGER.info('Starting services module')
- self._test_module = NmapModule(module)
+ self._test_module = ServicesModule(module)
self._test_module.run_tests()
self._test_module.generate_module_report()
- LOGGER.info('nmap test module finished')
+ LOGGER.info('Services module finished')
def add_logger(self, module):
global LOGGER
@@ -56,7 +56,7 @@ def _handler(self, signum):
def run():
parser = argparse.ArgumentParser(
- description='Nmap Module Help',
+ description='Services Module Help',
formatter_class=argparse.ArgumentDefaultsHelpFormatter)
parser.add_argument(
@@ -68,7 +68,7 @@ def run():
# For some reason passing in the args from bash adds an extra
# space before the argument so we'll just strip out extra space
- NmapModuleRunner(args.module.strip())
+ ServicesModuleRunner(args.module.strip())
if __name__ == '__main__':
diff --git a/modules/test/nmap/python/src/nmap_module.py b/modules/test/services/python/src/services_module.py
similarity index 95%
rename from modules/test/nmap/python/src/nmap_module.py
rename to modules/test/services/python/src/services_module.py
index 06613c023..bfa232c87 100644
--- a/modules/test/nmap/python/src/nmap_module.py
+++ b/modules/test/services/python/src/services_module.py
@@ -11,7 +11,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.
-"""NMAP test module"""
+"""Services test module"""
import time
import util
import json
@@ -20,14 +20,14 @@
from test_module import TestModule
import os
-LOG_NAME = 'test_nmap'
-MODULE_REPORT_FILE_NAME = 'nmap_report.html'
-NMAP_SCAN_RESULTS_SCAN_FILE = 'nmap_scan_results.json'
+LOG_NAME = 'test_services'
+MODULE_REPORT_FILE_NAME = 'services_report.html'
+NMAP_SCAN_RESULTS_SCAN_FILE = 'services_scan_results.json'
LOGGER = None
-class NmapModule(TestModule):
- """NMAP Test module"""
+class ServicesModule(TestModule):
+ """Services Test module"""
def __init__(self,
module,
@@ -157,7 +157,7 @@ def generate_module_report(self):
return report_path
def _run_nmap(self):
- LOGGER.info('Running nmap module')
+ LOGGER.info('Running nmap')
# Run the monitor method asynchronously to keep this method non-blocking
self._tcp_scan_thread = threading.Thread(target=self._scan_tcp_ports)
diff --git a/modules/test/nmap/nmap.Dockerfile b/modules/test/services/services.Dockerfile
similarity index 91%
rename from modules/test/nmap/nmap.Dockerfile
rename to modules/test/services/services.Dockerfile
index ea90ee06f..3a89fc33c 100644
--- a/modules/test/nmap/nmap.Dockerfile
+++ b/modules/test/services/services.Dockerfile
@@ -12,10 +12,10 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-# Image name: test-run/nmap-test
+# Image name: test-run/services-test
FROM test-run/base-test:latest
-ARG MODULE_NAME=nmap
+ARG MODULE_NAME=services
ARG MODULE_DIR=modules/test/$MODULE_NAME
# Load the requirements file
diff --git a/modules/test/tls/bin/check_cert_chain_signature.sh b/modules/test/tls/bin/check_cert_chain_signature.sh
old mode 100644
new mode 100755
index 9bb62e7b7..fa7d595fb
--- a/modules/test/tls/bin/check_cert_chain_signature.sh
+++ b/modules/test/tls/bin/check_cert_chain_signature.sh
@@ -14,12 +14,12 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-INTERMEDIATE=$1
-DEVICE_CERT=$2
+INTERMEDIATE="$1"
+DEVICE_CERT="$2"
echo "ROOT: $ROOT_CERT"
echo "DEVICE_CERT: $DEVICE_CERT"
-response=$(openssl verify -untrusted $INTERMEDIATE $DEVICE_CERT)
+response=$(openssl verify -untrusted "$INTERMEDIATE" "$DEVICE_CERT")
echo "$response"
\ No newline at end of file
diff --git a/modules/test/tls/bin/check_cert_signature.sh b/modules/test/tls/bin/check_cert_signature.sh
old mode 100644
new mode 100755
index 37ea0f187..83d1a74e5
--- a/modules/test/tls/bin/check_cert_signature.sh
+++ b/modules/test/tls/bin/check_cert_signature.sh
@@ -14,12 +14,12 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-ROOT_CERT=$1
-DEVICE_CERT=$2
+ROOT_CERT="$1"
+DEVICE_CERT="$2"
echo "ROOT: $ROOT_CERT"
echo "DEVICE_CERT: $DEVICE_CERT"
-response=$(openssl verify -CAfile $ROOT_CERT $DEVICE_CERT)
+response=$(openssl verify -CAfile "$ROOT_CERT" "$DEVICE_CERT")
echo "$response"
diff --git a/modules/test/tls/bin/get_ciphers.sh b/modules/test/tls/bin/get_ciphers.sh
old mode 100644
new mode 100755
index 3896af388..053afd37f
--- a/modules/test/tls/bin/get_ciphers.sh
+++ b/modules/test/tls/bin/get_ciphers.sh
@@ -14,11 +14,11 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-CAPTURE_FILE=$1
-DST_IP=$2
-DST_PORT=$3
+CAPTURE_FILE="$1"
+DST_IP="$2"
+DST_PORT="$3"
TSHARK_FILTER="ssl.handshake.ciphersuites and ip.dst==$DST_IP and tcp.dstport==$DST_PORT"
-response=$(tshark -r $CAPTURE_FILE -Y "$TSHARK_FILTER" -Vx | grep 'Cipher Suite:' | awk '{$1=$1};1' | sed 's/Cipher Suite: //')
+response=$(tshark -r "$CAPTURE_FILE" -Y "$TSHARK_FILTER" -Vx | grep 'Cipher Suite:' | awk '{$1=$1};1' | sed 's/Cipher Suite: //')
echo "$response"
diff --git a/modules/test/tls/bin/get_client_hello_packets.sh b/modules/test/tls/bin/get_client_hello_packets.sh
old mode 100644
new mode 100755
index 03cfe903c..d563d11f2
--- a/modules/test/tls/bin/get_client_hello_packets.sh
+++ b/modules/test/tls/bin/get_client_hello_packets.sh
@@ -14,9 +14,9 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-CAPTURE_FILE=$1
-SRC_IP=$2
-TLS_VERSION=$3
+CAPTURE_FILE="$1"
+SRC_IP="$2"
+TLS_VERSION="$3"
TSHARK_OUTPUT="-T json -e ip.src -e tcp.dstport -e ip.dst"
TSHARK_FILTER="ssl.handshake.type==1 and ip.src==$SRC_IP"
@@ -27,7 +27,7 @@ elif [ $TLS_VERSION == '1.3' ];then
TSHARK_FILTER="$TSHARK_FILTER and (ssl.handshake.version==0x0304 or tls.handshake.extensions.supported_version==0x0304)"
fi
-response=$(tshark -r $CAPTURE_FILE $TSHARK_OUTPUT $TSHARK_FILTER)
+response=$(tshark -r "$CAPTURE_FILE" $TSHARK_OUTPUT $TSHARK_FILTER)
echo "$response"
\ No newline at end of file
diff --git a/modules/test/tls/bin/get_handshake_complete.sh b/modules/test/tls/bin/get_handshake_complete.sh
old mode 100644
new mode 100755
index a2a6dc222..9bf9c525d
--- a/modules/test/tls/bin/get_handshake_complete.sh
+++ b/modules/test/tls/bin/get_handshake_complete.sh
@@ -14,10 +14,10 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-CAPTURE_FILE=$1
-SRC_IP=$2
-DST_IP=$3
-TLS_VERSION=$4
+CAPTURE_FILE="$1"
+SRC_IP="$2"
+DST_IP="$3"
+TLS_VERSION="$4"
TSHARK_FILTER="ip.src==$SRC_IP and ip.dst==$DST_IP "
@@ -27,7 +27,7 @@ elif [ $TLS_VERSION == '1.2' ];then
TSHARK_FILTER=$TSHARK_FILTER "and ssl.handshake.type==2 and tls.handshake.extensions.supported_version==0x0304"
fi
-response=$(tshark -r $CAPTURE_FILE $TSHARK_FILTER)
+response=$(tshark -r "$CAPTURE_FILE" $TSHARK_FILTER)
echo "$response"
\ No newline at end of file
diff --git a/modules/test/tls/bin/get_non_tls_client_connections.sh b/modules/test/tls/bin/get_non_tls_client_connections.sh
old mode 100644
new mode 100755
index 08ed19090..2bfc3d635
--- a/modules/test/tls/bin/get_non_tls_client_connections.sh
+++ b/modules/test/tls/bin/get_non_tls_client_connections.sh
@@ -14,8 +14,8 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-CAPTURE_FILE=$1
-SRC_IP=$2
+CAPTURE_FILE="$1"
+SRC_IP="$2"
TSHARK_OUTPUT="-T json -e ip.src -e tcp.dstport -e ip.dst"
# Filter out TLS, DNS and NTP, ICMP (ping), braodcast and multicast packets
@@ -26,7 +26,7 @@ TSHARK_OUTPUT="-T json -e ip.src -e tcp.dstport -e ip.dst"
# - ICMP (ping) requests are not encrypted so we also need to ignore these
TSHARK_FILTER="ip.src == $SRC_IP and not tls and not dns and not ntp and not icmp and not(ip.dst == 224.0.0.0/4 or ip.dst == 255.255.255.255)"
-response=$(tshark -r $CAPTURE_FILE $TSHARK_OUTPUT $TSHARK_FILTER)
+response=$(tshark -r "$CAPTURE_FILE" $TSHARK_OUTPUT $TSHARK_FILTER)
echo "$response"
\ No newline at end of file
diff --git a/modules/test/tls/bin/get_tls_client_connections.sh b/modules/test/tls/bin/get_tls_client_connections.sh
old mode 100644
new mode 100755
index 2486a5f9a..f036c2154
--- a/modules/test/tls/bin/get_tls_client_connections.sh
+++ b/modules/test/tls/bin/get_tls_client_connections.sh
@@ -14,13 +14,13 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-CAPTURE_FILE=$1
-SRC_IP=$2
+CAPTURE_FILE="$1"
+SRC_IP="$2"
TSHARK_OUTPUT="-T json -e ip.src -e tcp.dstport -e ip.dst"
TSHARK_FILTER="ip.src == $SRC_IP and tls"
-response=$(tshark -r $CAPTURE_FILE $TSHARK_OUTPUT $TSHARK_FILTER)
+response=$(tshark -r "$CAPTURE_FILE" $TSHARK_OUTPUT $TSHARK_FILTER)
echo "$response"
\ No newline at end of file
diff --git a/modules/test/tls/bin/get_tls_packets.sh b/modules/test/tls/bin/get_tls_packets.sh
old mode 100644
new mode 100755
index e06a51571..e64d4e9fb
--- a/modules/test/tls/bin/get_tls_packets.sh
+++ b/modules/test/tls/bin/get_tls_packets.sh
@@ -15,9 +15,9 @@
# limitations under the License.
-CAPTURE_FILE=$1
-SRC_IP=$2
-TLS_VERSION=$3
+CAPTURE_FILE="$1"
+SRC_IP="$2"
+TLS_VERSION="$3"
TSHARK_OUTPUT="-T json -e ip.src -e tcp.dstport -e ip.dst"
# Handshakes will still report TLS version 1 even for TLS 1.2 connections
@@ -34,7 +34,7 @@ elif [ $TLS_VERSION == '1.3' ];then
TSHARK_FILTER="$TSHARK_FILTER and ssl.record.version==0x0304"
fi
-response=$(tshark -r $CAPTURE_FILE $TSHARK_OUTPUT $TSHARK_FILTER)
+response=$(tshark -r "$CAPTURE_FILE" $TSHARK_OUTPUT $TSHARK_FILTER)
echo "$response"
\ No newline at end of file
diff --git a/modules/test/tls/python/requirements.txt b/modules/test/tls/python/requirements.txt
index a8a7e2a30..f6d9cf8a7 100644
--- a/modules/test/tls/python/requirements.txt
+++ b/modules/test/tls/python/requirements.txt
@@ -1,5 +1,5 @@
-cryptography==2.8
-pyOpenSSL==19.0.0
+cryptography==42.0.4
+pyOpenSSL==24.0.0
lxml==5.1.0 # Requirement of pyshark but if upgraded automatically above 5.1 will cause a python crash
pyshark==0.6
-requests==2.28.2
\ No newline at end of file
+requests==2.31.0
diff --git a/modules/test/tls/python/src/tls_module.py b/modules/test/tls/python/src/tls_module.py
index 472d403b2..98d6f25b1 100644
--- a/modules/test/tls/python/src/tls_module.py
+++ b/modules/test/tls/python/src/tls_module.py
@@ -14,12 +14,9 @@
"""Baseline test module"""
from test_module import TestModule
from tls_util import TLSUtil
-import os
import pyshark
from cryptography import x509
from cryptography.hazmat.backends import default_backend
-from cryptography.hazmat.primitives import serialization
-from cryptography.hazmat.primitives.asymmetric import rsa, dsa, ec
LOG_NAME = 'test_tls'
MODULE_REPORT_FILE_NAME = 'tls_report.html'
@@ -27,7 +24,7 @@
MONITOR_CAPTURE_FILE = '/runtime/device/monitor.pcap'
TLS_CAPTURE_FILE = '/runtime/output/tls.pcap'
GATEWAY_CAPTURE_FILE = '/runtime/network/gateway.pcap'
-
+LOGGER = None
class TLSModule(TestModule):
"""An example testing module."""
@@ -54,144 +51,144 @@ def __init__(self,
# def generate_module_report(self):
- # html_content = 'TLS Module '
-
- # # List of capture files to scan
- # pcap_files = [
- # self.startup_capture_file, self.monitor_capture_file,
- # self.tls_capture_file
- # ]
- # certificates = self.extract_certificates_from_pcap(pcap_files,
- # self._device_mac)
- # if len(certificates) > 0:
-
- # # Add summary table
- # summary_table = '''
- #
- #
- #
- # Expiry
- # Length
- # Type
- # Port number
- # Signed by
- #
- #
- #
- # '''
-
- # # table_content = '''
- # #
- # #
- # #
- # # Expiry
- # # Length
- # # Type
- # # Port number
- # # Signed by
- # #
- # #
- # # '''
-
- # cert_tables = []
- # for cert_num, ((ip_address, port), cert) in enumerate(
- # certificates.items()):
-
- # # Extract certificate data
- # not_valid_before = cert.not_valid_before
- # not_valid_after = cert.not_valid_after
- # version_value = f'{cert.version.value + 1} ({hex(cert.version.value)})'
- # signature_alg_value = cert.signature_algorithm_oid._name # pylint: disable=W0212
- # not_before = str(not_valid_before)
- # not_after = str(not_valid_after)
- # public_key = cert.public_key()
- # signed_by = 'None'
- # if isinstance(public_key, rsa.RSAPublicKey):
- # public_key_type = 'RSA'
- # elif isinstance(public_key, dsa.DSAPublicKey):
- # public_key_type = 'DSA'
- # elif isinstance(public_key, ec.EllipticCurvePublicKey):
- # public_key_type = 'EC'
- # else:
- # public_key_type = 'Unknown'
- # # Calculate certificate length
- # cert_length = len(cert.public_bytes(
- # encoding=serialization.Encoding.DER))
-
- # # Generate the Certificate table
- # # cert_table = (f'| Property | Value |\n'
- # # f'|---|---|\n'
- # # f"| {'Version':<17} | {version_value:^25} |\n"
- # # f"| {'Signature Alg.':<17} | {signature_alg_value:^25} |\n"
- # # f"| {'Validity from':<17} | {not_before:^25} |\n"
- # # f"| {'Valid to':<17} | {not_after:^25} |")
-
- # # Generate the Subject table
- # subj_table = ('| Distinguished Name | Value |\n'
- # '|---|---|')
- # for val in cert.subject.rdns:
- # dn = val.rfc4514_string().split('=')
- # subj_table += f'\n| {dn[0]} | {dn[1]}'
-
- # # Generate the Issuer table
- # iss_table = ('| Distinguished Name | Value |\n'
- # '|---|---|')
- # for val in cert.issuer.rdns:
- # dn = val.rfc4514_string().split('=')
- # iss_table += f'\n| {dn[0]} | {dn[1]}'
- # if 'CN' in dn[0]:
- # signed_by = dn[1]
-
- # ext_table = None
- # # if cert.extensions:
- # # ext_table = ('| Extension | Value |\n'
- # # '|---|---|')
- # # for extension in cert.extensions:
- # # for extension_value in extension.value:
- # # ext_table += f'''\n| {extension.oid._name} |
- # # {extension_value.value}''' # pylint: disable=W0212
- # # cert_table = f'### Certificate\n{cert_table}'
- # # cert_table += f'\n\n### Subject\n{subj_table}'
- # # cert_table += f'\n\n### Issuer\n{iss_table}'
- # # if ext_table is not None:
- # # cert_table += f'\n\n### Extensions\n{ext_table}'
- # # cert_tables.append(cert_table)
-
- # summary_table += f'''
- #
- # {not_after}
- # {cert_length}
- # {public_key_type}
- # {port}
- # {signed_by}
- #
- # '''
-
- # summary_table += '''
- #
- #
- # '''
-
- # html_content += summary_table
-
- # else:
- # html_content += ('''
- #
- #
- # No TLS certificates found on the device
- #
''')
-
- # LOGGER.debug('Module report:\n' + html_content)
-
- # # Use os.path.join to create the complete file path
- # report_path = os.path.join(self._results_dir, MODULE_REPORT_FILE_NAME)
-
- # # Write the content to a file
- # with open(report_path, 'w', encoding='utf-8') as file:
- # file.write(html_content)
-
- # LOGGER.info('Module report generated at: ' + str(report_path))
- # return report_path
+ # html_content = 'TLS Module '
+
+ # # List of capture files to scan
+ # pcap_files = [
+ # self.startup_capture_file, self.monitor_capture_file,
+ # self.tls_capture_file
+ # ]
+ # certificates = self.extract_certificates_from_pcap(pcap_files,
+ # self._device_mac)
+ # if len(certificates) > 0:
+
+ # # Add summary table
+ # summary_table = '''
+ #
+ #
+ #
+ # Expiry
+ # Length
+ # Type
+ # Port number
+ # Signed by
+ #
+ #
+ #
+ # '''
+
+ # # table_content = '''
+ # #
+ # #
+ # #
+ # # Expiry
+ # # Length
+ # # Type
+ # # Port number
+ # # Signed by
+ # #
+ # #
+ # # '''
+
+ # cert_tables = []
+ # for cert_num, ((ip_address, port), cert) in enumerate(
+ # certificates.items()):
+
+ # # Extract certificate data
+ # not_valid_before = cert.not_valid_before
+ # not_valid_after = cert.not_valid_after
+ # version_value = f'{cert.version.value + 1} ({hex(cert.version.value)})'
+ # signature_alg_value = cert.signature_algorithm_oid._name # pylint: disable=W0212
+ # not_before = str(not_valid_before)
+ # not_after = str(not_valid_after)
+ # public_key = cert.public_key()
+ # signed_by = 'None'
+ # if isinstance(public_key, rsa.RSAPublicKey):
+ # public_key_type = 'RSA'
+ # elif isinstance(public_key, dsa.DSAPublicKey):
+ # public_key_type = 'DSA'
+ # elif isinstance(public_key, ec.EllipticCurvePublicKey):
+ # public_key_type = 'EC'
+ # else:
+ # public_key_type = 'Unknown'
+ # # Calculate certificate length
+ # cert_length = len(cert.public_bytes(
+ # encoding=serialization.Encoding.DER))
+
+ # # Generate the Certificate table
+ # # cert_table = (f'| Property | Value |\n'
+ # # f'|---|---|\n'
+ # # f"| {'Version':<17} | {version_value:^25} |\n"
+ # # f"| {'Signature Alg.':<17} | {signature_alg_value:^25} |\n"
+ # # f"| {'Validity from':<17} | {not_before:^25} |\n"
+ # # f"| {'Valid to':<17} | {not_after:^25} |")
+
+ # # Generate the Subject table
+ # subj_table = ('| Distinguished Name | Value |\n'
+ # '|---|---|')
+ # for val in cert.subject.rdns:
+ # dn = val.rfc4514_string().split('=')
+ # subj_table += f'\n| {dn[0]} | {dn[1]}'
+
+ # # Generate the Issuer table
+ # iss_table = ('| Distinguished Name | Value |\n'
+ # '|---|---|')
+ # for val in cert.issuer.rdns:
+ # dn = val.rfc4514_string().split('=')
+ # iss_table += f'\n| {dn[0]} | {dn[1]}'
+ # if 'CN' in dn[0]:
+ # signed_by = dn[1]
+
+ # ext_table = None
+ # # if cert.extensions:
+ # # ext_table = ('| Extension | Value |\n'
+ # # '|---|---|')
+ # # for extension in cert.extensions:
+ # # for extension_value in extension.value:
+ # # ext_table += f'''\n| {extension.oid._name} |
+ # # {extension_value.value}''' # pylint: disable=W0212
+ # # cert_table = f'### Certificate\n{cert_table}'
+ # # cert_table += f'\n\n### Subject\n{subj_table}'
+ # # cert_table += f'\n\n### Issuer\n{iss_table}'
+ # # if ext_table is not None:
+ # # cert_table += f'\n\n### Extensions\n{ext_table}'
+ # # cert_tables.append(cert_table)
+
+ # summary_table += f'''
+ #
+ # {not_after}
+ # {cert_length}
+ # {public_key_type}
+ # {port}
+ # {signed_by}
+ #
+ # '''
+
+ # summary_table += '''
+ #
+ #
+ # '''
+
+ # html_content += summary_table
+
+ # else:
+ # html_content += ('''
+ #
+ #
+ # No TLS certificates found on the device
+ #
''')
+
+ # LOGGER.debug('Module report:\n' + html_content)
+
+ # # Use os.path.join to create the complete file path
+ # report_path = os.path.join(self._results_dir, MODULE_REPORT_FILE_NAME)
+
+ # # Write the content to a file
+ # with open(report_path, 'w', encoding='utf-8') as file:
+ # file.write(html_content)
+
+ # LOGGER.info('Module report generated at: ' + str(report_path))
+ # return report_path
def extract_certificates_from_pcap(self, pcap_files, mac_address):
# Initialize a list to store packets
@@ -237,8 +234,18 @@ def _security_tls_v1_2_server(self):
self._device_ipv4_addr, tls_version='1.2')
tls_1_3_results = self._tls_util.validate_tls_server(
self._device_ipv4_addr, tls_version='1.3')
- return self._tls_util.process_tls_server_results(tls_1_2_results,
+ results = self._tls_util.process_tls_server_results(tls_1_2_results,
tls_1_3_results)
+ # Determine results and return proper messaging and details
+ description = ''
+ if results[0] is None:
+ description = 'TLS 1.2 certificate could not be validated'
+ elif results[0]:
+ description = 'TLS 1.2 certificate is valid'
+ else:
+ description = 'TLS 1.2 certificate is invalid'
+ return results[0], description,results[1]
+
else:
LOGGER.error('Could not resolve device IP address. Skipping')
return 'Error', 'Could not resolve device IP address'
@@ -248,8 +255,18 @@ def _security_tls_v1_3_server(self):
self._resolve_device_ip()
# If the ipv4 address wasn't resolved yet, try again
if self._device_ipv4_addr is not None:
- return self._tls_util.validate_tls_server(self._device_ipv4_addr,
+ results = self._tls_util.validate_tls_server(self._device_ipv4_addr,
tls_version='1.3')
+ # Determine results and return proper messaging and details
+ description = ''
+ if results[0] is None:
+ description = 'TLS 1.3 certificate could not be validated'
+ elif results[0]:
+ description = 'TLS 1.3 certificate is valid'
+ else:
+ description = 'TLS 1.3 certificate is invalid'
+ return results[0], description,results[1]
+
else:
LOGGER.error('Could not resolve device IP address. Skipping')
return 'Error', 'Could not resolve device IP address'
@@ -259,7 +276,16 @@ def _security_tls_v1_2_client(self):
self._resolve_device_ip()
# If the ipv4 address wasn't resolved yet, try again
if self._device_ipv4_addr is not None:
- return self._validate_tls_client(self._device_ipv4_addr, '1.2')
+ results = self._validate_tls_client(self._device_ipv4_addr, '1.2')
+ # Determine results and return proper messaging and details
+ description = ''
+ if results[0] is None:
+ description = 'No outbound connections were found'
+ elif results[0]:
+ description = 'TLS 1.2 client connections valid'
+ else:
+ description = 'TLS 1.2 client connections invalid'
+ return results[0], description, results[1]
else:
LOGGER.error('Could not resolve device IP address. Skipping')
return 'Error', 'Could not resolve device IP address'
@@ -269,7 +295,16 @@ def _security_tls_v1_3_client(self):
self._resolve_device_ip()
# If the ipv4 address wasn't resolved yet, try again
if self._device_ipv4_addr is not None:
- return self._validate_tls_client(self._device_ipv4_addr, '1.3')
+ results = self._validate_tls_client(self._device_ipv4_addr, '1.3')
+ # Determine results and return proper messaging and details
+ description = ''
+ if results[0] is None:
+ description = 'No outbound connections were found'
+ elif results[0]:
+ description = 'TLS 1.3 client connections valid'
+ else:
+ description = 'TLS 1.3 client connections invalid'
+ return results[0], description, results[1]
else:
LOGGER.error('Could not resolve device IP address. Skipping')
return 'Error', 'Could not resolve device IP address'
diff --git a/modules/test/tls/python/src/tls_util.py b/modules/test/tls/python/src/tls_util.py
index 874810e18..368108bdd 100644
--- a/modules/test/tls/python/src/tls_util.py
+++ b/modules/test/tls/python/src/tls_util.py
@@ -188,7 +188,7 @@ def validate_local_ca_signature(self, device_cert_path):
# Create the file path
root_cert_path = os.path.join(self._root_certs_dir, root_cert)
LOGGER.info('Checking root cert: ' + str(root_cert_path))
- args = f'{root_cert_path} {device_cert_path}'
+ args = f'"{root_cert_path}" "{device_cert_path}"'
command = f'{bin_file} {args}'
response = util.run_command(command)
if f'{device_cert_path}: OK' in str(response):
@@ -257,7 +257,7 @@ def validate_trusted_ca_signature_chain(self, device_cert_path,
# Use openssl script to validate the combined certificate
# against the available trusted CA's
- args = f'{intermediate_cert_path} {combined_cert_path}'
+ args = f'"{intermediate_cert_path}" "{combined_cert_path}"'
command = f'{bin_file} {args}'
response = util.run_command(command)
return combined_cert_name + ': OK' in str(response)
@@ -407,7 +407,7 @@ def write_cert_to_file(self, cert_name, cert):
def get_ciphers(self, capture_file, dst_ip, dst_port):
bin_file = self._bin_dir + '/get_ciphers.sh'
- args = f'{capture_file} {dst_ip} {dst_port}'
+ args = f'"{capture_file}" {dst_ip} {dst_port}'
command = f'{bin_file} {args}'
response = util.run_command(command)
ciphers = response[0].split('\n')
@@ -417,7 +417,7 @@ def get_hello_packets(self, capture_files, src_ip, tls_version):
combined_results = []
for capture_file in capture_files:
bin_file = self._bin_dir + '/get_client_hello_packets.sh'
- args = f'{capture_file} {src_ip} {tls_version}'
+ args = f'"{capture_file}" {src_ip} {tls_version}'
command = f'{bin_file} {args}'
response = util.run_command(command)
packets = response[0].strip()
@@ -431,7 +431,7 @@ def get_handshake_complete(self, capture_files, src_ip, dst_ip, tls_version):
combined_results = ''
for capture_file in capture_files:
bin_file = self._bin_dir + '/get_handshake_complete.sh'
- args = f'{capture_file} {src_ip} {dst_ip} {tls_version}'
+ args = f'"{capture_file}" {src_ip} {dst_ip} {tls_version}'
command = f'{bin_file} {args}'
response = util.run_command(command)
if len(response) > 0:
@@ -443,7 +443,7 @@ def get_non_tls_packetes(self, client_ip, capture_files):
combined_packets = []
for capture_file in capture_files:
bin_file = self._bin_dir + '/get_non_tls_client_connections.sh'
- args = f'{capture_file} {client_ip}'
+ args = f'"{capture_file}" {client_ip}'
command = f'{bin_file} {args}'
response = util.run_command(command)
if len(response) > 0:
@@ -456,7 +456,7 @@ def get_tls_client_connection_packetes(self, client_ip, capture_files):
combined_packets = []
for capture_file in capture_files:
bin_file = self._bin_dir + '/get_tls_client_connections.sh'
- args = f'{capture_file} {client_ip}'
+ args = f'"{capture_file}" {client_ip}'
command = f'{bin_file} {args}'
response = util.run_command(command)
packets = json.loads(response[0].strip())
@@ -471,7 +471,7 @@ def get_tls_packets(self, capture_files, src_ip, tls_version):
combined_results = []
for capture_file in capture_files:
bin_file = self._bin_dir + '/get_tls_packets.sh'
- args = f'{capture_file} {src_ip} {tls_version}'
+ args = f'"{capture_file}" {src_ip} {tls_version}'
command = f'{bin_file} {args}'
response = util.run_command(command)
packets = response[0].strip()
@@ -549,11 +549,15 @@ def get_non_tls_client_connection_ips(self, client_ip, capture_files):
non_tls_dst_ips = set() # Store unique destination IPs
for packet in packets:
- # Check if an IP address is within the specified subnet.
- dst_ip = ipaddress.ip_address(packet['_source']['layers']['ip.dst'][0])
- if not dst_ip in subnet_with_mask:
- non_tls_dst_ips.add(str(dst_ip))
-
+ # Check if packet contains TCP layer
+ if 'tcp' in packet['_source']['layers']:
+ tcp_flags = packet['_source']['layers']['tcp.flags']
+ if 'A' not in tcp_flags and 'S' not in tcp_flags:
+ # Packet is not ACK or SYN
+ dst_ip = ipaddress.ip_address(
+ packet['_source']['layers']['ip.dst'][0])
+ if not dst_ip in subnet_with_mask:
+ non_tls_dst_ips.add(str(dst_ip))
return non_tls_dst_ips
# Check if the device has made any outbound connections that don't
diff --git a/modules/ui/README.md b/modules/ui/README.md
index cef441386..7cb0cc2ae 100644
--- a/modules/ui/README.md
+++ b/modules/ui/README.md
@@ -1,4 +1,4 @@
-# TestRunUi
+# Testrun UI
This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 16.1.3.
diff --git a/modules/ui/package-lock.json b/modules/ui/package-lock.json
index 529f93bf5..3cf3333c5 100644
--- a/modules/ui/package-lock.json
+++ b/modules/ui/package-lock.json
@@ -52,15 +52,6 @@
"typescript": "~5.2.2"
}
},
- "node_modules/@aashutoshrathi/word-wrap": {
- "version": "1.2.6",
- "resolved": "https://registry.npmjs.org/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz",
- "integrity": "sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==",
- "dev": true,
- "engines": {
- "node": ">=0.10.0"
- }
- },
"node_modules/@ampproject/remapping": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz",
@@ -75,12 +66,12 @@
}
},
"node_modules/@angular-devkit/architect": {
- "version": "0.1703.2",
- "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1703.2.tgz",
- "integrity": "sha512-fT5gSzwDHOyGv8zF97t8rjeoYSGSxXjWWstl3rN1nXdO0qgJ5m6Sv0fupON+HltdXDCBLRH+2khNpqx/Fh0Qww==",
+ "version": "0.1703.6",
+ "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1703.6.tgz",
+ "integrity": "sha512-Ck501FD/QuOjeKVFs7hU92w8+Ffetv0d5Sq09XY2/uygo5c/thMzp9nkevaIWBxUSeU5RqYZizDrhFVgYzbbOw==",
"dev": true,
"dependencies": {
- "@angular-devkit/core": "17.3.2",
+ "@angular-devkit/core": "17.3.6",
"rxjs": "7.8.1"
},
"engines": {
@@ -90,15 +81,15 @@
}
},
"node_modules/@angular-devkit/build-angular": {
- "version": "17.3.2",
- "resolved": "https://registry.npmjs.org/@angular-devkit/build-angular/-/build-angular-17.3.2.tgz",
- "integrity": "sha512-muPCUyL0uHvRkLH4NLWiccER6P2vCm/Q5DDvqyN4XOzzY3tAHHLrKrpvY87sgd2oNJ6Ci8x7GPNcfzR5KELCnw==",
+ "version": "17.3.6",
+ "resolved": "https://registry.npmjs.org/@angular-devkit/build-angular/-/build-angular-17.3.6.tgz",
+ "integrity": "sha512-K4CEZvhQZUUOpmXPVoI1YBM8BARbIlqE6FZRxakmnr+YOtVTYE5s+Dr1wgja8hZIohNz6L7j167G9Aut7oPU/w==",
"dev": true,
"dependencies": {
"@ampproject/remapping": "2.3.0",
- "@angular-devkit/architect": "0.1703.2",
- "@angular-devkit/build-webpack": "0.1703.2",
- "@angular-devkit/core": "17.3.2",
+ "@angular-devkit/architect": "0.1703.6",
+ "@angular-devkit/build-webpack": "0.1703.6",
+ "@angular-devkit/core": "17.3.6",
"@babel/core": "7.24.0",
"@babel/generator": "7.23.6",
"@babel/helper-annotate-as-pure": "7.22.5",
@@ -109,7 +100,7 @@
"@babel/preset-env": "7.24.0",
"@babel/runtime": "7.24.0",
"@discoveryjs/json-ext": "0.5.7",
- "@ngtools/webpack": "17.3.2",
+ "@ngtools/webpack": "17.3.6",
"@vitejs/plugin-basic-ssl": "1.1.0",
"ansi-colors": "4.1.3",
"autoprefixer": "10.4.18",
@@ -150,8 +141,8 @@
"terser": "5.29.1",
"tree-kill": "1.2.2",
"tslib": "2.6.2",
- "undici": "6.7.1",
- "vite": "5.1.5",
+ "undici": "6.11.1",
+ "vite": "5.1.7",
"watchpack": "2.4.0",
"webpack": "5.90.3",
"webpack-dev-middleware": "6.1.2",
@@ -219,12 +210,12 @@
}
},
"node_modules/@angular-devkit/build-webpack": {
- "version": "0.1703.2",
- "resolved": "https://registry.npmjs.org/@angular-devkit/build-webpack/-/build-webpack-0.1703.2.tgz",
- "integrity": "sha512-w7rVFQcZK4iTCd/MLfQWIkDkwBOfAs++txNQyS9qYID8KvLs1V+oWYd2qDBRelRv1u3YtaCIS1pQx3GFKBC3OA==",
+ "version": "0.1703.6",
+ "resolved": "https://registry.npmjs.org/@angular-devkit/build-webpack/-/build-webpack-0.1703.6.tgz",
+ "integrity": "sha512-pJu0et2SiF0kfXenHSTtAART0omzbWpLgBfeUo4hBh4uwX5IaT+mRpYpr8gCXMq+qsjoQp3HobSU3lPDeBn+bg==",
"dev": true,
"dependencies": {
- "@angular-devkit/architect": "0.1703.2",
+ "@angular-devkit/architect": "0.1703.6",
"rxjs": "7.8.1"
},
"engines": {
@@ -238,9 +229,9 @@
}
},
"node_modules/@angular-devkit/core": {
- "version": "17.3.2",
- "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-17.3.2.tgz",
- "integrity": "sha512-1vxKo9+pdSwTOwqPDSYQh84gZYmCJo6OgR5+AZoGLGMZSeqvi9RG5RiUcOMLQYOnuYv0arlhlWxz0ZjyR8ApKw==",
+ "version": "17.3.6",
+ "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-17.3.6.tgz",
+ "integrity": "sha512-FVbkT9dEwHEvjnxr4mvMNSMg2bCFoGoP4X68xXU9dhLEUpC05opLvfbaR3Qh543eCJ5AstosBFVzB/krfIkOvA==",
"dev": true,
"dependencies": {
"ajv": "8.12.0",
@@ -438,9 +429,9 @@
}
},
"node_modules/@angular/animations": {
- "version": "17.3.1",
- "resolved": "https://registry.npmjs.org/@angular/animations/-/animations-17.3.1.tgz",
- "integrity": "sha512-2TZ0M5J0IizhHpb404DeqArlv8Ki9BFz5ZUuET2uFROpKW8IMDCht8fSrn/DKHpjB9lvzPUhNFaRxNWEY6klnA==",
+ "version": "17.3.7",
+ "resolved": "https://registry.npmjs.org/@angular/animations/-/animations-17.3.7.tgz",
+ "integrity": "sha512-ahenGALPPweeHgqtl9BMkGIAV4fUNI5kOWUrLNbKBfwIJN+aOBOYV1Jz6NKUQq6eYn/1ZYtm0f3lIkHIdtLKEw==",
"dependencies": {
"tslib": "^2.3.0"
},
@@ -448,13 +439,13 @@
"node": "^18.13.0 || >=20.9.0"
},
"peerDependencies": {
- "@angular/core": "17.3.1"
+ "@angular/core": "17.3.7"
}
},
"node_modules/@angular/cdk": {
- "version": "17.3.1",
- "resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-17.3.1.tgz",
- "integrity": "sha512-pHSN+KlCmdo2u9jY7Yxsry/ZK+EcjOYGzdwxXxcKragMzm7etY3BJiTl4N+qZRuV6cJlMj2PRkij8ABi/HQdEA==",
+ "version": "17.3.7",
+ "resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-17.3.7.tgz",
+ "integrity": "sha512-aFEh8tzKFOwini6aNEp57S54Ocp9T7YIJfBVMESptu2TCPdMTlJ1HJTg5XS8NcQO+vwi9cFPGVwGF1frOx4LXA==",
"dependencies": {
"tslib": "^2.3.0"
},
@@ -673,9 +664,9 @@
"dev": true
},
"node_modules/@angular/common": {
- "version": "17.3.1",
- "resolved": "https://registry.npmjs.org/@angular/common/-/common-17.3.1.tgz",
- "integrity": "sha512-HyUTJ4RxhE3bOmFRV6Fv2y01ixbrUb8Hd4MxPm8REbNMGKsWCfXhR3FfxFL18Sc03SAF+o0Md0wwekjFKTNKfQ==",
+ "version": "17.3.7",
+ "resolved": "https://registry.npmjs.org/@angular/common/-/common-17.3.7.tgz",
+ "integrity": "sha512-A7LRJu1vVCGGgrfZXjU+njz50SiU4weheKCar5PIUprcdIofS1IrHAJDqYh+kwXxkjXbZMOr/ijQY0+AESLEsw==",
"dependencies": {
"tslib": "^2.3.0"
},
@@ -683,14 +674,14 @@
"node": "^18.13.0 || >=20.9.0"
},
"peerDependencies": {
- "@angular/core": "17.3.1",
+ "@angular/core": "17.3.7",
"rxjs": "^6.5.3 || ^7.4.0"
}
},
"node_modules/@angular/compiler": {
- "version": "17.3.1",
- "resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-17.3.1.tgz",
- "integrity": "sha512-8qqlWPGZEyD2FY5losOW3Aocro+lFysPDzsf0LHgQUM6Ub1b+pq4jUOjH6w0vzaxG3TfxkgzOQ9aNdWtSV67Rg==",
+ "version": "17.3.7",
+ "resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-17.3.7.tgz",
+ "integrity": "sha512-AlKiqPoxnrpQ0hn13fIaQPSVodaVAIjBW4vpFyuKFqs2LBKg6iolwZ21s8rEI0KR2gXl+8ugj0/UZ6YADiVM5w==",
"dependencies": {
"tslib": "^2.3.0"
},
@@ -698,7 +689,7 @@
"node": "^18.13.0 || >=20.9.0"
},
"peerDependencies": {
- "@angular/core": "17.3.1"
+ "@angular/core": "17.3.7"
},
"peerDependenciesMeta": {
"@angular/core": {
@@ -707,9 +698,9 @@
}
},
"node_modules/@angular/compiler-cli": {
- "version": "17.3.1",
- "resolved": "https://registry.npmjs.org/@angular/compiler-cli/-/compiler-cli-17.3.1.tgz",
- "integrity": "sha512-xLV9KU+zOpe57/2rQ59ku21EaStNpLSlR9+qkDYf8JR09fB+W9vY3UYbpi5RjHxAFIZBM5D9SFQjjll8rch26g==",
+ "version": "17.3.7",
+ "resolved": "https://registry.npmjs.org/@angular/compiler-cli/-/compiler-cli-17.3.7.tgz",
+ "integrity": "sha512-vSg5IQZ9jGmvYjpbfH8KbH4Sl1IVeE+Mr1ogcxkGEsURSRvKo7EWc0K7LSEI9+gg0VLamMiP9EyCJdPxiJeLJQ==",
"dev": true,
"dependencies": {
"@babel/core": "7.23.9",
@@ -730,7 +721,7 @@
"node": "^18.13.0 || >=20.9.0"
},
"peerDependencies": {
- "@angular/compiler": "17.3.1",
+ "@angular/compiler": "17.3.7",
"typescript": ">=5.2 <5.5"
}
},
@@ -780,9 +771,9 @@
}
},
"node_modules/@angular/core": {
- "version": "17.3.1",
- "resolved": "https://registry.npmjs.org/@angular/core/-/core-17.3.1.tgz",
- "integrity": "sha512-Qf3/sgkXS1LHwOTtqAVYprySrn0YpPIZqerPc0tK+hyQfwAz5BQlpcBhbH8RWKlfCY8eO0cqo/j0+e8DQOgYfg==",
+ "version": "17.3.7",
+ "resolved": "https://registry.npmjs.org/@angular/core/-/core-17.3.7.tgz",
+ "integrity": "sha512-HWcrbxqnvIMSxFuQdN0KPt08bc87hqr0LKm89yuRTUwx/2sNJlNQUobk6aJj4trswGBttcRDT+GOS4DQP2Nr4g==",
"dependencies": {
"tslib": "^2.3.0"
},
@@ -795,9 +786,9 @@
}
},
"node_modules/@angular/forms": {
- "version": "17.3.1",
- "resolved": "https://registry.npmjs.org/@angular/forms/-/forms-17.3.1.tgz",
- "integrity": "sha512-HndsO90k67sFHzd+sII+rhAUksffBvquFuAUCc6QR9WVjILxVg2fY7oBidgS1gKNqu0mptPG0GvuORnaW/0gSg==",
+ "version": "17.3.7",
+ "resolved": "https://registry.npmjs.org/@angular/forms/-/forms-17.3.7.tgz",
+ "integrity": "sha512-FEhXh/VmT++XCoO8i7bBtzxG7Am/cE1zrr9aF+fWW+4jpWvJvVN1IaSiJxgBB+iPsOJ9lTBRwfRW3onlcDkhrw==",
"dependencies": {
"tslib": "^2.3.0"
},
@@ -805,16 +796,16 @@
"node": "^18.13.0 || >=20.9.0"
},
"peerDependencies": {
- "@angular/common": "17.3.1",
- "@angular/core": "17.3.1",
- "@angular/platform-browser": "17.3.1",
+ "@angular/common": "17.3.7",
+ "@angular/core": "17.3.7",
+ "@angular/platform-browser": "17.3.7",
"rxjs": "^6.5.3 || ^7.4.0"
}
},
"node_modules/@angular/material": {
- "version": "17.3.1",
- "resolved": "https://registry.npmjs.org/@angular/material/-/material-17.3.1.tgz",
- "integrity": "sha512-Md1OnO0/sQvK5GkTQyE4v1DAaMswXt1TnjjY07KG7cICTrUN8lc0a2P9dMjlSFXIhxC7PTlNH6plSZ1uspbU8Q==",
+ "version": "17.3.7",
+ "resolved": "https://registry.npmjs.org/@angular/material/-/material-17.3.7.tgz",
+ "integrity": "sha512-wjSKkk9KZE8QiBPkMd5axh5u/3pUSxoLKNO7OasFhEagMmSv5oYTLm40cErhtb4UdkSmbC19WuuluS6P3leoPA==",
"dependencies": {
"@material/animation": "15.0.0-canary.7f224ddd4.0",
"@material/auto-init": "15.0.0-canary.7f224ddd4.0",
@@ -867,7 +858,7 @@
},
"peerDependencies": {
"@angular/animations": "^17.0.0 || ^18.0.0",
- "@angular/cdk": "17.3.1",
+ "@angular/cdk": "17.3.7",
"@angular/common": "^17.0.0 || ^18.0.0",
"@angular/core": "^17.0.0 || ^18.0.0",
"@angular/forms": "^17.0.0 || ^18.0.0",
@@ -876,9 +867,9 @@
}
},
"node_modules/@angular/platform-browser": {
- "version": "17.3.1",
- "resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-17.3.1.tgz",
- "integrity": "sha512-8ABAL8PElSGzkIparVwifsU0NSu0DdqnWYw9YvLhhZQ6lOuWbG+dTUo/DXzmWhA6ezQWJGNakEZPJJytFIIy+A==",
+ "version": "17.3.7",
+ "resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-17.3.7.tgz",
+ "integrity": "sha512-Nn8ZMaftAvO9dEwribWdNv+QBHhYIBrRkv85G6et80AXfXoYAr/xcfnQECRFtZgPmANqHC5auv/xrmExQG+Yeg==",
"dependencies": {
"tslib": "^2.3.0"
},
@@ -886,9 +877,9 @@
"node": "^18.13.0 || >=20.9.0"
},
"peerDependencies": {
- "@angular/animations": "17.3.1",
- "@angular/common": "17.3.1",
- "@angular/core": "17.3.1"
+ "@angular/animations": "17.3.7",
+ "@angular/common": "17.3.7",
+ "@angular/core": "17.3.7"
},
"peerDependenciesMeta": {
"@angular/animations": {
@@ -897,9 +888,9 @@
}
},
"node_modules/@angular/platform-browser-dynamic": {
- "version": "17.3.1",
- "resolved": "https://registry.npmjs.org/@angular/platform-browser-dynamic/-/platform-browser-dynamic-17.3.1.tgz",
- "integrity": "sha512-ACW/npNaDxUNQtEomjjv/KIBY8jHEinePff5qosnAxLE0IpA4qE9eDp36zG35xoJqrPJPYjXbZCBRqqrzM7U7Q==",
+ "version": "17.3.7",
+ "resolved": "https://registry.npmjs.org/@angular/platform-browser-dynamic/-/platform-browser-dynamic-17.3.7.tgz",
+ "integrity": "sha512-9c2I4u0L1p2v1/lW8qy+WaNHisUWbyy6wqsv2v9FfCaSM49Lxymgo9LPFPC4qEG5ei5nE+eIQ2ocRiXXsf5QkQ==",
"dependencies": {
"tslib": "^2.3.0"
},
@@ -907,16 +898,16 @@
"node": "^18.13.0 || >=20.9.0"
},
"peerDependencies": {
- "@angular/common": "17.3.1",
- "@angular/compiler": "17.3.1",
- "@angular/core": "17.3.1",
- "@angular/platform-browser": "17.3.1"
+ "@angular/common": "17.3.7",
+ "@angular/compiler": "17.3.7",
+ "@angular/core": "17.3.7",
+ "@angular/platform-browser": "17.3.7"
}
},
"node_modules/@angular/router": {
- "version": "17.3.1",
- "resolved": "https://registry.npmjs.org/@angular/router/-/router-17.3.1.tgz",
- "integrity": "sha512-H6H7lY9i5Ppu0SFwwpeWqJbCFw8cILOj8Rd1+AGoCN5m3ivPtjD2Ltz62PI2zZkqx+WhQdk19l61Wm3oRqg70A==",
+ "version": "17.3.7",
+ "resolved": "https://registry.npmjs.org/@angular/router/-/router-17.3.7.tgz",
+ "integrity": "sha512-lMkuRrc1ZjP5JPDxNHqoAhB0uAnfPQ/q6mJrw1s8IZoVV6VyM+FxR5r13ajNcXWC38xy/YhBjpXPF1vBdxuLXg==",
"dependencies": {
"tslib": "^2.3.0"
},
@@ -924,9 +915,9 @@
"node": "^18.13.0 || >=20.9.0"
},
"peerDependencies": {
- "@angular/common": "17.3.1",
- "@angular/core": "17.3.1",
- "@angular/platform-browser": "17.3.1",
+ "@angular/common": "17.3.7",
+ "@angular/core": "17.3.7",
+ "@angular/platform-browser": "17.3.7",
"rxjs": "^6.5.3 || ^7.4.0"
}
},
@@ -944,9 +935,9 @@
}
},
"node_modules/@babel/compat-data": {
- "version": "7.24.1",
- "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.24.1.tgz",
- "integrity": "sha512-Pc65opHDliVpRHuKfzI+gSA4zcgr65O4cl64fFJIWEEh8JoHIHh0Oez1Eo8Arz8zq/JhgKodQaxEwUPRtZylVA==",
+ "version": "7.24.4",
+ "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.24.4.tgz",
+ "integrity": "sha512-vg8Gih2MLK+kOkHJp4gBEIkyaIi00jgWot2D9QOmmfLC8jINSOzmCLta6Bvz/JSBCqnegV0L80jhxkol5GWNfQ==",
"dev": true,
"engines": {
"node": ">=6.9.0"
@@ -1062,19 +1053,19 @@
}
},
"node_modules/@babel/helper-create-class-features-plugin": {
- "version": "7.24.1",
- "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.24.1.tgz",
- "integrity": "sha512-1yJa9dX9g//V6fDebXoEfEsxkZHk3Hcbm+zLhyu6qVgYFLvmTALTeV+jNU9e5RnYtioBrGEOdoI2joMSNQ/+aA==",
+ "version": "7.24.5",
+ "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.24.5.tgz",
+ "integrity": "sha512-uRc4Cv8UQWnE4NXlYTIIdM7wfFkOqlFztcC/gVXDKohKoVB3OyonfelUBaJzSwpBntZ2KYGF/9S7asCHsXwW6g==",
"dev": true,
"dependencies": {
"@babel/helper-annotate-as-pure": "^7.22.5",
"@babel/helper-environment-visitor": "^7.22.20",
"@babel/helper-function-name": "^7.23.0",
- "@babel/helper-member-expression-to-functions": "^7.23.0",
+ "@babel/helper-member-expression-to-functions": "^7.24.5",
"@babel/helper-optimise-call-expression": "^7.22.5",
"@babel/helper-replace-supers": "^7.24.1",
"@babel/helper-skip-transparent-expression-wrappers": "^7.22.5",
- "@babel/helper-split-export-declaration": "^7.22.6",
+ "@babel/helper-split-export-declaration": "^7.24.5",
"semver": "^6.3.1"
},
"engines": {
@@ -1084,6 +1075,18 @@
"@babel/core": "^7.0.0"
}
},
+ "node_modules/@babel/helper-create-class-features-plugin/node_modules/@babel/helper-split-export-declaration": {
+ "version": "7.24.5",
+ "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.24.5.tgz",
+ "integrity": "sha512-5CHncttXohrHk8GWOFCcCl4oRD9fKosWlIRgWm4ql9VYioKm52Mk2xsmoohvm7f3JoiLSM5ZgJuRaf5QZZYd3Q==",
+ "dev": true,
+ "dependencies": {
+ "@babel/types": "^7.24.5"
+ },
+ "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",
@@ -1120,9 +1123,9 @@
}
},
"node_modules/@babel/helper-define-polyfill-provider": {
- "version": "0.6.1",
- "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.1.tgz",
- "integrity": "sha512-o7SDgTJuvx5vLKD6SFvkydkSMBvahDKGiNJzG22IZYXhiqoe9efY7zocICBgzHV4IRg5wdgl2nEL/tulKIEIbA==",
+ "version": "0.6.2",
+ "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.2.tgz",
+ "integrity": "sha512-LV76g+C502biUK6AyZ3LK10vDpDyCzZnhZFXkH1L75zHPj68+qc8Zfpx2th+gzwA2MzyK+1g/3EPl62yFnVttQ==",
"dev": true,
"dependencies": {
"@babel/helper-compilation-targets": "^7.22.6",
@@ -1170,12 +1173,12 @@
}
},
"node_modules/@babel/helper-member-expression-to-functions": {
- "version": "7.23.0",
- "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.23.0.tgz",
- "integrity": "sha512-6gfrPwh7OuT6gZyJZvd6WbTfrqAo7vm4xCzAXOusKqq/vWdKXphTpj5klHKNmRUU6/QRGlBsyU9mAIPaWHlqJA==",
+ "version": "7.24.5",
+ "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.24.5.tgz",
+ "integrity": "sha512-4owRteeihKWKamtqg4JmWSsEZU445xpFRXPEwp44HbgbxdWlUV1b4Agg4lkA806Lil5XM/e+FJyS0vj5T6vmcA==",
"dev": true,
"dependencies": {
- "@babel/types": "^7.23.0"
+ "@babel/types": "^7.24.5"
},
"engines": {
"node": ">=6.9.0"
@@ -1194,16 +1197,16 @@
}
},
"node_modules/@babel/helper-module-transforms": {
- "version": "7.23.3",
- "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.23.3.tgz",
- "integrity": "sha512-7bBs4ED9OmswdfDzpz4MpWgSrV7FXlc3zIagvLFjS5H+Mk7Snr21vQ6QwrsoCGMfNC4e4LQPdoULEt4ykz0SRQ==",
+ "version": "7.24.5",
+ "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.24.5.tgz",
+ "integrity": "sha512-9GxeY8c2d2mdQUP1Dye0ks3VDyIMS98kt/llQ2nUId8IsWqTF0l1LkSX0/uP7l7MCDrzXS009Hyhe2gzTiGW8A==",
"dev": true,
"dependencies": {
"@babel/helper-environment-visitor": "^7.22.20",
- "@babel/helper-module-imports": "^7.22.15",
- "@babel/helper-simple-access": "^7.22.5",
- "@babel/helper-split-export-declaration": "^7.22.6",
- "@babel/helper-validator-identifier": "^7.22.20"
+ "@babel/helper-module-imports": "^7.24.3",
+ "@babel/helper-simple-access": "^7.24.5",
+ "@babel/helper-split-export-declaration": "^7.24.5",
+ "@babel/helper-validator-identifier": "^7.24.5"
},
"engines": {
"node": ">=6.9.0"
@@ -1212,6 +1215,18 @@
"@babel/core": "^7.0.0"
}
},
+ "node_modules/@babel/helper-module-transforms/node_modules/@babel/helper-split-export-declaration": {
+ "version": "7.24.5",
+ "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.24.5.tgz",
+ "integrity": "sha512-5CHncttXohrHk8GWOFCcCl4oRD9fKosWlIRgWm4ql9VYioKm52Mk2xsmoohvm7f3JoiLSM5ZgJuRaf5QZZYd3Q==",
+ "dev": true,
+ "dependencies": {
+ "@babel/types": "^7.24.5"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
"node_modules/@babel/helper-optimise-call-expression": {
"version": "7.22.5",
"resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.22.5.tgz",
@@ -1225,9 +1240,9 @@
}
},
"node_modules/@babel/helper-plugin-utils": {
- "version": "7.24.0",
- "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.0.tgz",
- "integrity": "sha512-9cUznXMG0+FxRuJfvL82QlTqIzhVW9sL0KjMPHhAOOvpQGL8QtdxnBKILjBqxlHyliz0yCa1G903ZXI/FuHy2w==",
+ "version": "7.24.5",
+ "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.5.tgz",
+ "integrity": "sha512-xjNLDopRzW2o6ba0gKbkZq5YWEBaK3PCyTOY1K2P/O07LGMhMqlMXPxwN4S5/RhWuCobT8z0jrlKGlYmeR1OhQ==",
"dev": true,
"engines": {
"node": ">=6.9.0"
@@ -1268,12 +1283,12 @@
}
},
"node_modules/@babel/helper-simple-access": {
- "version": "7.22.5",
- "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.22.5.tgz",
- "integrity": "sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w==",
+ "version": "7.24.5",
+ "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.24.5.tgz",
+ "integrity": "sha512-uH3Hmf5q5n7n8mz7arjUlDOCbttY/DW4DYhE6FUsjKJ/oYC1kQQUvwEQWxRwUpX9qQKRXeqLwWxrqilMrf32sQ==",
"dev": true,
"dependencies": {
- "@babel/types": "^7.22.5"
+ "@babel/types": "^7.24.5"
},
"engines": {
"node": ">=6.9.0"
@@ -1313,9 +1328,9 @@
}
},
"node_modules/@babel/helper-validator-identifier": {
- "version": "7.22.20",
- "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz",
- "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==",
+ "version": "7.24.5",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.5.tgz",
+ "integrity": "sha512-3q93SSKX2TWCG30M2G2kwaKeTYgEUp5Snjuj8qm729SObL6nbtUldAi37qbxkD5gg3xnBio+f9nqpSepGZMvxA==",
"dev": true,
"engines": {
"node": ">=6.9.0"
@@ -1331,40 +1346,40 @@
}
},
"node_modules/@babel/helper-wrap-function": {
- "version": "7.22.20",
- "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.22.20.tgz",
- "integrity": "sha512-pms/UwkOpnQe/PDAEdV/d7dVCoBbB+R4FvYoHGZz+4VPcg7RtYy2KP7S2lbuWM6FCSgob5wshfGESbC/hzNXZw==",
+ "version": "7.24.5",
+ "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.24.5.tgz",
+ "integrity": "sha512-/xxzuNvgRl4/HLNKvnFwdhdgN3cpLxgLROeLDl83Yx0AJ1SGvq1ak0OszTOjDfiB8Vx03eJbeDWh9r+jCCWttw==",
"dev": true,
"dependencies": {
- "@babel/helper-function-name": "^7.22.5",
- "@babel/template": "^7.22.15",
- "@babel/types": "^7.22.19"
+ "@babel/helper-function-name": "^7.23.0",
+ "@babel/template": "^7.24.0",
+ "@babel/types": "^7.24.5"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/helpers": {
- "version": "7.24.1",
- "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.24.1.tgz",
- "integrity": "sha512-BpU09QqEe6ZCHuIHFphEFgvNSrubve1FtyMton26ekZ85gRGi6LrTF7zArARp2YvyFxloeiRmtSCq5sjh1WqIg==",
+ "version": "7.24.5",
+ "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.24.5.tgz",
+ "integrity": "sha512-CiQmBMMpMQHwM5m01YnrM6imUG1ebgYJ+fAIW4FZe6m4qHTPaRHti+R8cggAwkdz4oXhtO4/K9JWlh+8hIfR2Q==",
"dev": true,
"dependencies": {
"@babel/template": "^7.24.0",
- "@babel/traverse": "^7.24.1",
- "@babel/types": "^7.24.0"
+ "@babel/traverse": "^7.24.5",
+ "@babel/types": "^7.24.5"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/highlight": {
- "version": "7.24.2",
- "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.2.tgz",
- "integrity": "sha512-Yac1ao4flkTxTteCDZLEvdxg2fZfz1v8M4QpaGypq/WPDqg3ijHYbDfs+LG5hvzSoqaSZ9/Z9lKSP3CjZjv+pA==",
+ "version": "7.24.5",
+ "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.5.tgz",
+ "integrity": "sha512-8lLmua6AVh/8SLJRRVD6V8p73Hir9w5mJrhE+IPpILG31KKlI9iz5zmBYKcWPS59qSfgP9RaSBQSHHE81WKuEw==",
"dev": true,
"dependencies": {
- "@babel/helper-validator-identifier": "^7.22.20",
+ "@babel/helper-validator-identifier": "^7.24.5",
"chalk": "^2.4.2",
"js-tokens": "^4.0.0",
"picocolors": "^1.0.0"
@@ -1374,9 +1389,9 @@
}
},
"node_modules/@babel/parser": {
- "version": "7.24.1",
- "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.1.tgz",
- "integrity": "sha512-Zo9c7N3xdOIQrNip7Lc9wvRPzlRtovHVE4lkz8WEDr7uYh/GMQhSiIgFxGIArRHYdJE5kxtZjAf8rT0xhdLCzg==",
+ "version": "7.24.5",
+ "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.5.tgz",
+ "integrity": "sha512-EOv5IK8arwh3LI47dz1b0tKUb/1uhHAnHJOrjgtQMIpu1uXd9mlFrJg9IUgGUgZ41Ch0K8REPTYpO7B76b4vJg==",
"dev": true,
"bin": {
"parser": "bin/babel-parser.js"
@@ -1746,12 +1761,12 @@
}
},
"node_modules/@babel/plugin-transform-block-scoping": {
- "version": "7.24.1",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.24.1.tgz",
- "integrity": "sha512-h71T2QQvDgM2SmT29UYU6ozjMlAt7s7CSs5Hvy8f8cf/GM/Z4a2zMfN+fjVGaieeCrXR3EdQl6C4gQG+OgmbKw==",
+ "version": "7.24.5",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.24.5.tgz",
+ "integrity": "sha512-sMfBc3OxghjC95BkYrYocHL3NaOplrcaunblzwXhGmlPwpmfsxr4vK+mBBt49r+S240vahmv+kUxkeKgs+haCw==",
"dev": true,
"dependencies": {
- "@babel/helper-plugin-utils": "^7.24.0"
+ "@babel/helper-plugin-utils": "^7.24.5"
},
"engines": {
"node": ">=6.9.0"
@@ -1777,12 +1792,12 @@
}
},
"node_modules/@babel/plugin-transform-class-static-block": {
- "version": "7.24.1",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.24.1.tgz",
- "integrity": "sha512-FUHlKCn6J3ERiu8Dv+4eoz7w8+kFLSyeVG4vDAikwADGjUCoHw/JHokyGtr8OR4UjpwPVivyF+h8Q5iv/JmrtA==",
+ "version": "7.24.4",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.24.4.tgz",
+ "integrity": "sha512-B8q7Pz870Hz/q9UgP8InNpY01CSLDSCyqX7zcRuv3FcPl87A2G17lASroHWaCtbdIcbYzOZ7kWmXFKbijMSmFg==",
"dev": true,
"dependencies": {
- "@babel/helper-create-class-features-plugin": "^7.24.1",
+ "@babel/helper-create-class-features-plugin": "^7.24.4",
"@babel/helper-plugin-utils": "^7.24.0",
"@babel/plugin-syntax-class-static-block": "^7.14.5"
},
@@ -1794,18 +1809,18 @@
}
},
"node_modules/@babel/plugin-transform-classes": {
- "version": "7.24.1",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.24.1.tgz",
- "integrity": "sha512-ZTIe3W7UejJd3/3R4p7ScyyOoafetUShSf4kCqV0O7F/RiHxVj/wRaRnQlrGwflvcehNA8M42HkAiEDYZu2F1Q==",
+ "version": "7.24.5",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.24.5.tgz",
+ "integrity": "sha512-gWkLP25DFj2dwe9Ck8uwMOpko4YsqyfZJrOmqqcegeDYEbp7rmn4U6UQZNj08UF6MaX39XenSpKRCvpDRBtZ7Q==",
"dev": true,
"dependencies": {
"@babel/helper-annotate-as-pure": "^7.22.5",
"@babel/helper-compilation-targets": "^7.23.6",
"@babel/helper-environment-visitor": "^7.22.20",
"@babel/helper-function-name": "^7.23.0",
- "@babel/helper-plugin-utils": "^7.24.0",
+ "@babel/helper-plugin-utils": "^7.24.5",
"@babel/helper-replace-supers": "^7.24.1",
- "@babel/helper-split-export-declaration": "^7.22.6",
+ "@babel/helper-split-export-declaration": "^7.24.5",
"globals": "^11.1.0"
},
"engines": {
@@ -1815,6 +1830,18 @@
"@babel/core": "^7.0.0-0"
}
},
+ "node_modules/@babel/plugin-transform-classes/node_modules/@babel/helper-split-export-declaration": {
+ "version": "7.24.5",
+ "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.24.5.tgz",
+ "integrity": "sha512-5CHncttXohrHk8GWOFCcCl4oRD9fKosWlIRgWm4ql9VYioKm52Mk2xsmoohvm7f3JoiLSM5ZgJuRaf5QZZYd3Q==",
+ "dev": true,
+ "dependencies": {
+ "@babel/types": "^7.24.5"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
"node_modules/@babel/plugin-transform-computed-properties": {
"version": "7.24.1",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.24.1.tgz",
@@ -1832,12 +1859,12 @@
}
},
"node_modules/@babel/plugin-transform-destructuring": {
- "version": "7.24.1",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.24.1.tgz",
- "integrity": "sha512-ow8jciWqNxR3RYbSNVuF4U2Jx130nwnBnhRw6N6h1bOejNkABmcI5X5oz29K4alWX7vf1C+o6gtKXikzRKkVdw==",
+ "version": "7.24.5",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.24.5.tgz",
+ "integrity": "sha512-SZuuLyfxvsm+Ah57I/i1HVjveBENYK9ue8MJ7qkc7ndoNjqquJiElzA7f5yaAXjyW2hKojosOTAQQRX50bPSVg==",
"dev": true,
"dependencies": {
- "@babel/helper-plugin-utils": "^7.24.0"
+ "@babel/helper-plugin-utils": "^7.24.5"
},
"engines": {
"node": ">=6.9.0"
@@ -2151,15 +2178,15 @@
}
},
"node_modules/@babel/plugin-transform-object-rest-spread": {
- "version": "7.24.1",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.24.1.tgz",
- "integrity": "sha512-XjD5f0YqOtebto4HGISLNfiNMTTs6tbkFf2TOqJlYKYmbo+mN9Dnpl4SRoofiziuOWMIyq3sZEUqLo3hLITFEA==",
+ "version": "7.24.5",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.24.5.tgz",
+ "integrity": "sha512-7EauQHszLGM3ay7a161tTQH7fj+3vVM/gThlz5HpFtnygTxjrlvoeq7MPVA1Vy9Q555OB8SnAOsMkLShNkkrHA==",
"dev": true,
"dependencies": {
"@babel/helper-compilation-targets": "^7.23.6",
- "@babel/helper-plugin-utils": "^7.24.0",
+ "@babel/helper-plugin-utils": "^7.24.5",
"@babel/plugin-syntax-object-rest-spread": "^7.8.3",
- "@babel/plugin-transform-parameters": "^7.24.1"
+ "@babel/plugin-transform-parameters": "^7.24.5"
},
"engines": {
"node": ">=6.9.0"
@@ -2201,12 +2228,12 @@
}
},
"node_modules/@babel/plugin-transform-optional-chaining": {
- "version": "7.24.1",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.24.1.tgz",
- "integrity": "sha512-n03wmDt+987qXwAgcBlnUUivrZBPZ8z1plL0YvgQalLm+ZE5BMhGm94jhxXtA1wzv1Cu2aaOv1BM9vbVttrzSg==",
+ "version": "7.24.5",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.24.5.tgz",
+ "integrity": "sha512-xWCkmwKT+ihmA6l7SSTpk8e4qQl/274iNbSKRRS8mpqFR32ksy36+a+LWY8OXCCEefF8WFlnOHVsaDI2231wBg==",
"dev": true,
"dependencies": {
- "@babel/helper-plugin-utils": "^7.24.0",
+ "@babel/helper-plugin-utils": "^7.24.5",
"@babel/helper-skip-transparent-expression-wrappers": "^7.22.5",
"@babel/plugin-syntax-optional-chaining": "^7.8.3"
},
@@ -2218,12 +2245,12 @@
}
},
"node_modules/@babel/plugin-transform-parameters": {
- "version": "7.24.1",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.24.1.tgz",
- "integrity": "sha512-8Jl6V24g+Uw5OGPeWNKrKqXPDw2YDjLc53ojwfMcKwlEoETKU9rU0mHUtcg9JntWI/QYzGAXNWEcVHZ+fR+XXg==",
+ "version": "7.24.5",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.24.5.tgz",
+ "integrity": "sha512-9Co00MqZ2aoky+4j2jhofErthm6QVLKbpQrvz20c3CH9KQCLHyNB+t2ya4/UrRpQGR+Wrwjg9foopoeSdnHOkA==",
"dev": true,
"dependencies": {
- "@babel/helper-plugin-utils": "^7.24.0"
+ "@babel/helper-plugin-utils": "^7.24.5"
},
"engines": {
"node": ">=6.9.0"
@@ -2249,14 +2276,14 @@
}
},
"node_modules/@babel/plugin-transform-private-property-in-object": {
- "version": "7.24.1",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.24.1.tgz",
- "integrity": "sha512-pTHxDVa0BpUbvAgX3Gat+7cSciXqUcY9j2VZKTbSB6+VQGpNgNO9ailxTGHSXlqOnX1Hcx1Enme2+yv7VqP9bg==",
+ "version": "7.24.5",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.24.5.tgz",
+ "integrity": "sha512-JM4MHZqnWR04jPMujQDTBVRnqxpLLpx2tkn7iPn+Hmsc0Gnb79yvRWOkvqFOx3Z7P7VxiRIR22c4eGSNj87OBQ==",
"dev": true,
"dependencies": {
"@babel/helper-annotate-as-pure": "^7.22.5",
- "@babel/helper-create-class-features-plugin": "^7.24.1",
- "@babel/helper-plugin-utils": "^7.24.0",
+ "@babel/helper-create-class-features-plugin": "^7.24.5",
+ "@babel/helper-plugin-utils": "^7.24.5",
"@babel/plugin-syntax-private-property-in-object": "^7.14.5"
},
"engines": {
@@ -2403,12 +2430,12 @@
}
},
"node_modules/@babel/plugin-transform-typeof-symbol": {
- "version": "7.24.1",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.24.1.tgz",
- "integrity": "sha512-CBfU4l/A+KruSUoW+vTQthwcAdwuqbpRNB8HQKlZABwHRhsdHZ9fezp4Sn18PeAlYxTNiLMlx4xUBV3AWfg1BA==",
+ "version": "7.24.5",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.24.5.tgz",
+ "integrity": "sha512-UTGnhYVZtTAjdwOTzT+sCyXmTn8AhaxOS/MjG9REclZ6ULHWF9KoCZur0HSGU7hk8PdBFKKbYe6+gqdXWz84Jg==",
"dev": true,
"dependencies": {
- "@babel/helper-plugin-utils": "^7.24.0"
+ "@babel/helper-plugin-utils": "^7.24.5"
},
"engines": {
"node": ">=6.9.0"
@@ -2630,19 +2657,19 @@
}
},
"node_modules/@babel/traverse": {
- "version": "7.24.1",
- "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.24.1.tgz",
- "integrity": "sha512-xuU6o9m68KeqZbQuDt2TcKSxUw/mrsvavlEqQ1leZ/B+C9tk6E4sRWy97WaXgvq5E+nU3cXMxv3WKOCanVMCmQ==",
+ "version": "7.24.5",
+ "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.24.5.tgz",
+ "integrity": "sha512-7aaBLeDQ4zYcUFDUD41lJc1fG8+5IU9DaNSJAgal866FGvmD5EbWQgnEC6kO1gGLsX0esNkfnJSndbTXA3r7UA==",
"dev": true,
"dependencies": {
- "@babel/code-frame": "^7.24.1",
- "@babel/generator": "^7.24.1",
+ "@babel/code-frame": "^7.24.2",
+ "@babel/generator": "^7.24.5",
"@babel/helper-environment-visitor": "^7.22.20",
"@babel/helper-function-name": "^7.23.0",
"@babel/helper-hoist-variables": "^7.22.5",
- "@babel/helper-split-export-declaration": "^7.22.6",
- "@babel/parser": "^7.24.1",
- "@babel/types": "^7.24.0",
+ "@babel/helper-split-export-declaration": "^7.24.5",
+ "@babel/parser": "^7.24.5",
+ "@babel/types": "^7.24.5",
"debug": "^4.3.1",
"globals": "^11.1.0"
},
@@ -2651,12 +2678,12 @@
}
},
"node_modules/@babel/traverse/node_modules/@babel/generator": {
- "version": "7.24.1",
- "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.24.1.tgz",
- "integrity": "sha512-DfCRfZsBcrPEHUfuBMgbJ1Ut01Y/itOs+hY2nFLgqsqXd52/iSiVq5TITtUasIUgm+IIKdY2/1I7auiQOEeC9A==",
+ "version": "7.24.5",
+ "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.24.5.tgz",
+ "integrity": "sha512-x32i4hEXvr+iI0NEoEfDKzlemF8AmtOP8CcrRaEcpzysWuoEb1KknpcvMsHKPONoKZiDuItklgWhB18xEhr9PA==",
"dev": true,
"dependencies": {
- "@babel/types": "^7.24.0",
+ "@babel/types": "^7.24.5",
"@jridgewell/gen-mapping": "^0.3.5",
"@jridgewell/trace-mapping": "^0.3.25",
"jsesc": "^2.5.1"
@@ -2665,14 +2692,26 @@
"node": ">=6.9.0"
}
},
+ "node_modules/@babel/traverse/node_modules/@babel/helper-split-export-declaration": {
+ "version": "7.24.5",
+ "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.24.5.tgz",
+ "integrity": "sha512-5CHncttXohrHk8GWOFCcCl4oRD9fKosWlIRgWm4ql9VYioKm52Mk2xsmoohvm7f3JoiLSM5ZgJuRaf5QZZYd3Q==",
+ "dev": true,
+ "dependencies": {
+ "@babel/types": "^7.24.5"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
"node_modules/@babel/types": {
- "version": "7.24.0",
- "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.24.0.tgz",
- "integrity": "sha512-+j7a5c253RfKh8iABBhywc8NSfP5LURe7Uh4qpsh6jc+aLJguvmIUBdjSdEMQv2bENrCR5MfRdjGo7vzS/ob7w==",
+ "version": "7.24.5",
+ "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.24.5.tgz",
+ "integrity": "sha512-6mQNsaLeXTw0nxYUYu+NSa4Hx4BlF1x1x8/PMFbiR+GBSr+2DkECc69b8hgy2frEodNcvPffeH8YfWd3LI6jhQ==",
"dev": true,
"dependencies": {
- "@babel/helper-string-parser": "^7.23.4",
- "@babel/helper-validator-identifier": "^7.22.20",
+ "@babel/helper-string-parser": "^7.24.1",
+ "@babel/helper-validator-identifier": "^7.24.5",
"to-fast-properties": "^2.0.0"
},
"engines": {
@@ -3260,9 +3299,9 @@
}
},
"node_modules/@humanwhocodes/object-schema": {
- "version": "2.0.2",
- "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.2.tgz",
- "integrity": "sha512-6EwiSjwWYP7pTckG6I5eyFANjPhmPjUX9JRLUSfNPC7FX7zK9gyZAfUEaECL6ALTpGX5AjnBq3C9XmVWPitNpw==",
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz",
+ "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==",
"dev": true
},
"node_modules/@isaacs/cliui": {
@@ -3457,9 +3496,9 @@
}
},
"node_modules/@leichtgewicht/ip-codec": {
- "version": "2.0.4",
- "resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.4.tgz",
- "integrity": "sha512-Hcv+nVC0kZnQ3tD9GVu5xSMR4VVYOteQIr/hwFPVEvPdlXqgGEuRjiheChHgdM+JyqdgNcmzZOX/tnl0JOiI7A==",
+ "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
},
"node_modules/@ljharb/through": {
@@ -4227,9 +4266,9 @@
}
},
"node_modules/@ngrx/component-store": {
- "version": "17.1.1",
- "resolved": "https://registry.npmjs.org/@ngrx/component-store/-/component-store-17.1.1.tgz",
- "integrity": "sha512-pknwettIC52JELk9PjhTgBBsY/WtdltB91jPW9c6hWCxW8X89ipV9Fe+alKBhqqL8pQiK/gPpWmbp0zPauUChQ==",
+ "version": "17.2.0",
+ "resolved": "https://registry.npmjs.org/@ngrx/component-store/-/component-store-17.2.0.tgz",
+ "integrity": "sha512-ywhyoZpkbVIY1t5zf7xfWLGkY0A/fQdMjPehHloDI6bRLrmbllBhQRazwZ+FAGIi2myx1+mGcmAc6FbtIikedA==",
"dependencies": {
"@ngrx/operators": "17.0.0-beta.0",
"tslib": "^2.0.0"
@@ -4240,16 +4279,16 @@
}
},
"node_modules/@ngrx/effects": {
- "version": "17.1.1",
- "resolved": "https://registry.npmjs.org/@ngrx/effects/-/effects-17.1.1.tgz",
- "integrity": "sha512-VDNVI70wfEwqoNliffAiMhsPry0CWKkifCLmfzr+SZEEdAaPEBr4FtRrrdcdq/ovmkcgoWukkH2MBljbCyHwtA==",
+ "version": "17.2.0",
+ "resolved": "https://registry.npmjs.org/@ngrx/effects/-/effects-17.2.0.tgz",
+ "integrity": "sha512-tXDJNsuBtbvI/7+vYnkDKKpUvLbopw1U5G6LoPnKNrbTPsPcUGmCqF5Su/ZoRN3BhXjt2j+eoeVdpBkxdxMRgg==",
"dependencies": {
"@ngrx/operators": "17.0.0-beta.0",
"tslib": "^2.0.0"
},
"peerDependencies": {
"@angular/core": "^17.0.0",
- "@ngrx/store": "17.1.1",
+ "@ngrx/store": "17.2.0",
"rxjs": "^6.5.3 || ^7.5.0"
}
},
@@ -4265,9 +4304,9 @@
}
},
"node_modules/@ngrx/store": {
- "version": "17.1.1",
- "resolved": "https://registry.npmjs.org/@ngrx/store/-/store-17.1.1.tgz",
- "integrity": "sha512-MGbKLTcl4uq2Uzx+qbMYNy6xW+JnkpRiznaGFX2/NFflq/zNZsjbxZrvn4/Z6ClVYIjj3uadjM1fupwMYMJxVA==",
+ "version": "17.2.0",
+ "resolved": "https://registry.npmjs.org/@ngrx/store/-/store-17.2.0.tgz",
+ "integrity": "sha512-7wKgZ59B/6yQSvvsU0DQXipDqpkAXv7LwcXLD5Ww7nvqN0fQoRPThMh4+Wv55DCJhE0bQc1NEMciLA47uRt7Wg==",
"dependencies": {
"tslib": "^2.0.0"
},
@@ -4277,9 +4316,9 @@
}
},
"node_modules/@ngtools/webpack": {
- "version": "17.3.2",
- "resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-17.3.2.tgz",
- "integrity": "sha512-E8zejFF4aJ8l2XcF+GgnE/1IqsZepnPT1xzulLB4LXtjVuXLFLoF9xkHQwxs7cJWWZsxd/SlNsCIcn/ezrYBcQ==",
+ "version": "17.3.6",
+ "resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-17.3.6.tgz",
+ "integrity": "sha512-equxbgh2DKzZtiFMoVf1KD4yJcH1q8lpqQ/GSPPQUvONcmHrr+yqdRUdaJ7oZCyCYmXF/nByBxtMKtJr6nKZVg==",
"dev": true,
"engines": {
"node": "^18.13.0 || >=20.9.0",
@@ -4328,25 +4367,25 @@
}
},
"node_modules/@npmcli/agent": {
- "version": "2.2.1",
- "resolved": "https://registry.npmjs.org/@npmcli/agent/-/agent-2.2.1.tgz",
- "integrity": "sha512-H4FrOVtNyWC8MUwL3UfjOsAihHvT1Pe8POj3JvjXhSTJipsZMtgUALCT4mGyYZNxymkUfOw3PUj6dE4QPp6osQ==",
+ "version": "2.2.2",
+ "resolved": "https://registry.npmjs.org/@npmcli/agent/-/agent-2.2.2.tgz",
+ "integrity": "sha512-OrcNPXdpSl9UX7qPVRWbmWMCSXrcDa2M9DvrbOTj7ao1S4PlqVFYv9/yLKMkrJKZ/V5A/kDBC690or307i26Og==",
"dev": true,
"dependencies": {
"agent-base": "^7.1.0",
"http-proxy-agent": "^7.0.0",
"https-proxy-agent": "^7.0.1",
"lru-cache": "^10.0.1",
- "socks-proxy-agent": "^8.0.1"
+ "socks-proxy-agent": "^8.0.3"
},
"engines": {
"node": "^16.14.0 || >=18.0.0"
}
},
"node_modules/@npmcli/agent/node_modules/lru-cache": {
- "version": "10.2.0",
- "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.2.0.tgz",
- "integrity": "sha512-2bIM8x+VAf6JT4bKAljS1qUWgMsqZRPGJS6FSahIMPVvctcNhyVp7AJu7quxOW9jwkryBReKZY5tY5JYv2n/7Q==",
+ "version": "10.2.2",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.2.2.tgz",
+ "integrity": "sha512-9hp3Vp2/hFQUiIwKo8XCeFVnrg8Pk3TYNPIR7tJADKi5YfcF7vEaK7avFHTlSy3kOKYaJQaalfEo6YuXdceBOQ==",
"dev": true,
"engines": {
"node": "14 || >=16.14"
@@ -4365,15 +4404,15 @@
}
},
"node_modules/@npmcli/git": {
- "version": "5.0.4",
- "resolved": "https://registry.npmjs.org/@npmcli/git/-/git-5.0.4.tgz",
- "integrity": "sha512-nr6/WezNzuYUppzXRaYu/W4aT5rLxdXqEFupbh6e/ovlYFQ8hpu1UUPV3Ir/YTl+74iXl2ZOMlGzudh9ZPUchQ==",
+ "version": "5.0.7",
+ "resolved": "https://registry.npmjs.org/@npmcli/git/-/git-5.0.7.tgz",
+ "integrity": "sha512-WaOVvto604d5IpdCRV2KjQu8PzkfE96d50CQGKgywXh2GxXmDeUO5EWcBC4V57uFyrNqx83+MewuJh3WTR3xPA==",
"dev": true,
"dependencies": {
"@npmcli/promise-spawn": "^7.0.0",
"lru-cache": "^10.0.1",
"npm-pick-manifest": "^9.0.0",
- "proc-log": "^3.0.0",
+ "proc-log": "^4.0.0",
"promise-inflight": "^1.0.1",
"promise-retry": "^2.0.1",
"semver": "^7.3.5",
@@ -4393,14 +4432,23 @@
}
},
"node_modules/@npmcli/git/node_modules/lru-cache": {
- "version": "10.2.0",
- "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.2.0.tgz",
- "integrity": "sha512-2bIM8x+VAf6JT4bKAljS1qUWgMsqZRPGJS6FSahIMPVvctcNhyVp7AJu7quxOW9jwkryBReKZY5tY5JYv2n/7Q==",
+ "version": "10.2.2",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.2.2.tgz",
+ "integrity": "sha512-9hp3Vp2/hFQUiIwKo8XCeFVnrg8Pk3TYNPIR7tJADKi5YfcF7vEaK7avFHTlSy3kOKYaJQaalfEo6YuXdceBOQ==",
"dev": true,
"engines": {
"node": "14 || >=16.14"
}
},
+ "node_modules/@npmcli/git/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==",
+ "dev": true,
+ "engines": {
+ "node": "^14.17.0 || ^16.13.0 || >=18.0.0"
+ }
+ },
"node_modules/@npmcli/git/node_modules/which": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/which/-/which-4.0.0.tgz",
@@ -4417,16 +4465,16 @@
}
},
"node_modules/@npmcli/installed-package-contents": {
- "version": "2.0.2",
- "resolved": "https://registry.npmjs.org/@npmcli/installed-package-contents/-/installed-package-contents-2.0.2.tgz",
- "integrity": "sha512-xACzLPhnfD51GKvTOOuNX2/V4G4mz9/1I2MfDoye9kBM3RYe5g2YbscsaGoTlaWqkxeiapBWyseULVKpSVHtKQ==",
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/@npmcli/installed-package-contents/-/installed-package-contents-2.1.0.tgz",
+ "integrity": "sha512-c8UuGLeZpm69BryRykLuKRyKFZYJsZSCT4aVY5ds4omyZqJ172ApzgfKJ5eV/r3HgLdUYgFVe54KSFVjKoe27w==",
"dev": true,
"dependencies": {
"npm-bundled": "^3.0.0",
"npm-normalize-package-bin": "^3.0.0"
},
"bin": {
- "installed-package-contents": "lib/index.js"
+ "installed-package-contents": "bin/index.js"
},
"engines": {
"node": "^14.17.0 || ^16.13.0 || >=18.0.0"
@@ -4442,9 +4490,9 @@
}
},
"node_modules/@npmcli/package-json": {
- "version": "5.0.0",
- "resolved": "https://registry.npmjs.org/@npmcli/package-json/-/package-json-5.0.0.tgz",
- "integrity": "sha512-OI2zdYBLhQ7kpNPaJxiflofYIpkNLi+lnGdzqUOfRmCF3r2l1nadcjtCYMJKv/Utm/ZtlffaUuTiAktPHbc17g==",
+ "version": "5.1.0",
+ "resolved": "https://registry.npmjs.org/@npmcli/package-json/-/package-json-5.1.0.tgz",
+ "integrity": "sha512-1aL4TuVrLS9sf8quCLerU3H9J4vtCtgu8VauYozrmEyU57i/EdKleCnsQ7vpnABIH6c9mnTxcH5sFkO3BlV8wQ==",
"dev": true,
"dependencies": {
"@npmcli/git": "^5.0.0",
@@ -4452,7 +4500,7 @@
"hosted-git-info": "^7.0.0",
"json-parse-even-better-errors": "^3.0.0",
"normalize-package-data": "^6.0.0",
- "proc-log": "^3.0.0",
+ "proc-log": "^4.0.0",
"semver": "^7.5.3"
},
"engines": {
@@ -4460,16 +4508,16 @@
}
},
"node_modules/@npmcli/package-json/node_modules/glob": {
- "version": "10.3.10",
- "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.10.tgz",
- "integrity": "sha512-fa46+tv1Ak0UPK1TOy/pZrIybNNt4HCv7SDzwyfiOZkvZLEbjsZkJBPtDHVshZjbecAoAGSC20MjLDG/qr679g==",
+ "version": "10.3.12",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.12.tgz",
+ "integrity": "sha512-TCNv8vJ+xz4QiqTpfOJA7HvYv+tNIRHKfUWw/q+v2jdgN4ebz+KY9tGx5J4rHP0o84mNP+ApH66HRX8us3Khqg==",
"dev": true,
"dependencies": {
"foreground-child": "^3.1.0",
- "jackspeak": "^2.3.5",
+ "jackspeak": "^2.3.6",
"minimatch": "^9.0.1",
- "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0",
- "path-scurry": "^1.10.1"
+ "minipass": "^7.0.4",
+ "path-scurry": "^1.10.2"
},
"bin": {
"glob": "dist/esm/bin.mjs"
@@ -4481,10 +4529,19 @@
"url": "https://github.com/sponsors/isaacs"
}
},
+ "node_modules/@npmcli/package-json/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==",
+ "dev": true,
+ "engines": {
+ "node": "^14.17.0 || ^16.13.0 || >=18.0.0"
+ }
+ },
"node_modules/@npmcli/promise-spawn": {
- "version": "7.0.1",
- "resolved": "https://registry.npmjs.org/@npmcli/promise-spawn/-/promise-spawn-7.0.1.tgz",
- "integrity": "sha512-P4KkF9jX3y+7yFUxgcUdDtLy+t4OlDGuEBLNs57AZsfSfg+uV6MLndqGpnl4831ggaEdXwR50XFoZP4VFtHolg==",
+ "version": "7.0.2",
+ "resolved": "https://registry.npmjs.org/@npmcli/promise-spawn/-/promise-spawn-7.0.2.tgz",
+ "integrity": "sha512-xhfYPXoV5Dy4UkY0D+v2KkwvnDfiA/8Mt3sWCGI/hM03NsYIH8ZaG6QzS9x7pje5vHZBZJ2v6VRFVTWACnqcmQ==",
"dev": true,
"dependencies": {
"which": "^4.0.0"
@@ -4517,6 +4574,15 @@
"node": "^16.13.0 || >=18.0.0"
}
},
+ "node_modules/@npmcli/redact": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@npmcli/redact/-/redact-1.1.0.tgz",
+ "integrity": "sha512-PfnWuOkQgu7gCbnSsAisaX7hKOdZ4wSAhAzH3/ph5dSGau52kCRrMMGbiSQLwyTZpgldkZ49b0brkOr1AzGBHQ==",
+ "dev": true,
+ "engines": {
+ "node": "^16.14.0 || >=18.0.0"
+ }
+ },
"node_modules/@npmcli/run-script": {
"version": "7.0.4",
"resolved": "https://registry.npmjs.org/@npmcli/run-script/-/run-script-7.0.4.tgz",
@@ -4813,9 +4879,9 @@
}
},
"node_modules/@rollup/rollup-android-arm-eabi": {
- "version": "4.13.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.13.0.tgz",
- "integrity": "sha512-5ZYPOuaAqEH/W3gYsRkxQATBW3Ii1MfaT4EQstTnLKViLi2gLSQmlmtTpGucNP3sXEpOiI5tdGhjdE111ekyEg==",
+ "version": "4.17.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.17.2.tgz",
+ "integrity": "sha512-NM0jFxY8bB8QLkoKxIQeObCaDlJKewVlIEkuyYKm5An1tdVZ966w2+MPQ2l8LBZLjR+SgyV+nRkTIunzOYBMLQ==",
"cpu": [
"arm"
],
@@ -4826,9 +4892,9 @@
]
},
"node_modules/@rollup/rollup-android-arm64": {
- "version": "4.13.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.13.0.tgz",
- "integrity": "sha512-BSbaCmn8ZadK3UAQdlauSvtaJjhlDEjS5hEVVIN3A4bbl3X+otyf/kOJV08bYiRxfejP3DXFzO2jz3G20107+Q==",
+ "version": "4.17.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.17.2.tgz",
+ "integrity": "sha512-yeX/Usk7daNIVwkq2uGoq2BYJKZY1JfyLTaHO/jaiSwi/lsf8fTFoQW/n6IdAsx5tx+iotu2zCJwz8MxI6D/Bw==",
"cpu": [
"arm64"
],
@@ -4839,9 +4905,9 @@
]
},
"node_modules/@rollup/rollup-darwin-arm64": {
- "version": "4.13.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.13.0.tgz",
- "integrity": "sha512-Ovf2evVaP6sW5Ut0GHyUSOqA6tVKfrTHddtmxGQc1CTQa1Cw3/KMCDEEICZBbyppcwnhMwcDce9ZRxdWRpVd6g==",
+ "version": "4.17.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.17.2.tgz",
+ "integrity": "sha512-kcMLpE6uCwls023+kknm71ug7MZOrtXo+y5p/tsg6jltpDtgQY1Eq5sGfHcQfb+lfuKwhBmEURDga9N0ol4YPw==",
"cpu": [
"arm64"
],
@@ -4852,9 +4918,9 @@
]
},
"node_modules/@rollup/rollup-darwin-x64": {
- "version": "4.13.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.13.0.tgz",
- "integrity": "sha512-U+Jcxm89UTK592vZ2J9st9ajRv/hrwHdnvyuJpa5A2ngGSVHypigidkQJP+YiGL6JODiUeMzkqQzbCG3At81Gg==",
+ "version": "4.17.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.17.2.tgz",
+ "integrity": "sha512-AtKwD0VEx0zWkL0ZjixEkp5tbNLzX+FCqGG1SvOu993HnSz4qDI6S4kGzubrEJAljpVkhRSlg5bzpV//E6ysTQ==",
"cpu": [
"x64"
],
@@ -4865,9 +4931,22 @@
]
},
"node_modules/@rollup/rollup-linux-arm-gnueabihf": {
- "version": "4.13.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.13.0.tgz",
- "integrity": "sha512-8wZidaUJUTIR5T4vRS22VkSMOVooG0F4N+JSwQXWSRiC6yfEsFMLTYRFHvby5mFFuExHa/yAp9juSphQQJAijQ==",
+ "version": "4.17.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.17.2.tgz",
+ "integrity": "sha512-3reX2fUHqN7sffBNqmEyMQVj/CKhIHZd4y631duy0hZqI8Qoqf6lTtmAKvJFYa6bhU95B1D0WgzHkmTg33In0A==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm-musleabihf": {
+ "version": "4.17.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.17.2.tgz",
+ "integrity": "sha512-uSqpsp91mheRgw96xtyAGP9FW5ChctTFEoXP0r5FAzj/3ZRv3Uxjtc7taRQSaQM/q85KEKjKsZuiZM3GyUivRg==",
"cpu": [
"arm"
],
@@ -4878,9 +4957,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm64-gnu": {
- "version": "4.13.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.13.0.tgz",
- "integrity": "sha512-Iu0Kno1vrD7zHQDxOmvweqLkAzjxEVqNhUIXBsZ8hu8Oak7/5VTPrxOEZXYC1nmrBVJp0ZcL2E7lSuuOVaE3+w==",
+ "version": "4.17.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.17.2.tgz",
+ "integrity": "sha512-EMMPHkiCRtE8Wdk3Qhtciq6BndLtstqZIroHiiGzB3C5LDJmIZcSzVtLRbwuXuUft1Cnv+9fxuDtDxz3k3EW2A==",
"cpu": [
"arm64"
],
@@ -4891,9 +4970,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm64-musl": {
- "version": "4.13.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.13.0.tgz",
- "integrity": "sha512-C31QrW47llgVyrRjIwiOwsHFcaIwmkKi3PCroQY5aVq4H0A5v/vVVAtFsI1nfBngtoRpeREvZOkIhmRwUKkAdw==",
+ "version": "4.17.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.17.2.tgz",
+ "integrity": "sha512-NMPylUUZ1i0z/xJUIx6VUhISZDRT+uTWpBcjdv0/zkp7b/bQDF+NfnfdzuTiB1G6HTodgoFa93hp0O1xl+/UbA==",
"cpu": [
"arm64"
],
@@ -4903,10 +4982,23 @@
"linux"
]
},
+ "node_modules/@rollup/rollup-linux-powerpc64le-gnu": {
+ "version": "4.17.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.17.2.tgz",
+ "integrity": "sha512-T19My13y8uYXPw/L/k0JYaX1fJKFT/PWdXiHr8mTbXWxjVF1t+8Xl31DgBBvEKclw+1b00Chg0hxE2O7bTG7GQ==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
"node_modules/@rollup/rollup-linux-riscv64-gnu": {
- "version": "4.13.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.13.0.tgz",
- "integrity": "sha512-Oq90dtMHvthFOPMl7pt7KmxzX7E71AfyIhh+cPhLY9oko97Zf2C9tt/XJD4RgxhaGeAraAXDtqxvKE1y/j35lA==",
+ "version": "4.17.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.17.2.tgz",
+ "integrity": "sha512-BOaNfthf3X3fOWAB+IJ9kxTgPmMqPPH5f5k2DcCsRrBIbWnaJCgX2ll77dV1TdSy9SaXTR5iDXRL8n7AnoP5cg==",
"cpu": [
"riscv64"
],
@@ -4916,10 +5008,23 @@
"linux"
]
},
+ "node_modules/@rollup/rollup-linux-s390x-gnu": {
+ "version": "4.17.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.17.2.tgz",
+ "integrity": "sha512-W0UP/x7bnn3xN2eYMql2T/+wpASLE5SjObXILTMPUBDB/Fg/FxC+gX4nvCfPBCbNhz51C+HcqQp2qQ4u25ok6g==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
"node_modules/@rollup/rollup-linux-x64-gnu": {
- "version": "4.13.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.13.0.tgz",
- "integrity": "sha512-yUD/8wMffnTKuiIsl6xU+4IA8UNhQ/f1sAnQebmE/lyQ8abjsVyDkyRkWop0kdMhKMprpNIhPmYlCxgHrPoXoA==",
+ "version": "4.17.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.17.2.tgz",
+ "integrity": "sha512-Hy7pLwByUOuyaFC6mAr7m+oMC+V7qyifzs/nW2OJfC8H4hbCzOX07Ov0VFk/zP3kBsELWNFi7rJtgbKYsav9QQ==",
"cpu": [
"x64"
],
@@ -4930,9 +5035,9 @@
]
},
"node_modules/@rollup/rollup-linux-x64-musl": {
- "version": "4.13.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.13.0.tgz",
- "integrity": "sha512-9RyNqoFNdF0vu/qqX63fKotBh43fJQeYC98hCaf89DYQpv+xu0D8QFSOS0biA7cGuqJFOc1bJ+m2rhhsKcw1hw==",
+ "version": "4.17.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.17.2.tgz",
+ "integrity": "sha512-h1+yTWeYbRdAyJ/jMiVw0l6fOOm/0D1vNLui9iPuqgRGnXA0u21gAqOyB5iHjlM9MMfNOm9RHCQ7zLIzT0x11Q==",
"cpu": [
"x64"
],
@@ -4943,9 +5048,9 @@
]
},
"node_modules/@rollup/rollup-win32-arm64-msvc": {
- "version": "4.13.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.13.0.tgz",
- "integrity": "sha512-46ue8ymtm/5PUU6pCvjlic0z82qWkxv54GTJZgHrQUuZnVH+tvvSP0LsozIDsCBFO4VjJ13N68wqrKSeScUKdA==",
+ "version": "4.17.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.17.2.tgz",
+ "integrity": "sha512-tmdtXMfKAjy5+IQsVtDiCfqbynAQE/TQRpWdVataHmhMb9DCoJxp9vLcCBjEQWMiUYxO1QprH/HbY9ragCEFLA==",
"cpu": [
"arm64"
],
@@ -4956,9 +5061,9 @@
]
},
"node_modules/@rollup/rollup-win32-ia32-msvc": {
- "version": "4.13.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.13.0.tgz",
- "integrity": "sha512-P5/MqLdLSlqxbeuJ3YDeX37srC8mCflSyTrUsgbU1c/U9j6l2g2GiIdYaGD9QjdMQPMSgYm7hgg0551wHyIluw==",
+ "version": "4.17.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.17.2.tgz",
+ "integrity": "sha512-7II/QCSTAHuE5vdZaQEwJq2ZACkBpQDOmQsE6D6XUbnBHW8IAhm4eTufL6msLJorzrHDFv3CF8oCA/hSIRuZeQ==",
"cpu": [
"ia32"
],
@@ -4969,9 +5074,9 @@
]
},
"node_modules/@rollup/rollup-win32-x64-msvc": {
- "version": "4.13.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.13.0.tgz",
- "integrity": "sha512-UKXUQNbO3DOhzLRwHSpa0HnhhCgNODvfoPWv2FCXme8N/ANFfhIPMGuOT+QuKd16+B5yxZ0HdpNlqPvTMS1qfw==",
+ "version": "4.17.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.17.2.tgz",
+ "integrity": "sha512-TGGO7v7qOq4CYmSBVEYpI1Y5xDuCEnbVC5Vth8mOsW0gDSzxNrVERPc790IGHsrT2dQSimgMr9Ub3Y1Jci5/8w==",
"cpu": [
"x64"
],
@@ -5043,12 +5148,12 @@
}
},
"node_modules/@sigstore/bundle": {
- "version": "2.2.0",
- "resolved": "https://registry.npmjs.org/@sigstore/bundle/-/bundle-2.2.0.tgz",
- "integrity": "sha512-5VI58qgNs76RDrwXNhpmyN/jKpq9evV/7f1XrcqcAfvxDl5SeVY/I5Rmfe96ULAV7/FK5dge9RBKGBJPhL1WsQ==",
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/@sigstore/bundle/-/bundle-2.3.1.tgz",
+ "integrity": "sha512-eqV17lO3EIFqCWK3969Rz+J8MYrRZKw9IBHpSo6DEcEX2c+uzDFOgHE9f2MnyDpfs48LFO4hXmk9KhQ74JzU1g==",
"dev": true,
"dependencies": {
- "@sigstore/protobuf-specs": "^0.3.0"
+ "@sigstore/protobuf-specs": "^0.3.1"
},
"engines": {
"node": "^16.14.0 || >=18.0.0"
@@ -5064,23 +5169,23 @@
}
},
"node_modules/@sigstore/protobuf-specs": {
- "version": "0.3.0",
- "resolved": "https://registry.npmjs.org/@sigstore/protobuf-specs/-/protobuf-specs-0.3.0.tgz",
- "integrity": "sha512-zxiQ66JFOjVvP9hbhGj/F/qNdsZfkGb/dVXSanNRNuAzMlr4MC95voPUBX8//ZNnmv3uSYzdfR/JSkrgvZTGxA==",
+ "version": "0.3.1",
+ "resolved": "https://registry.npmjs.org/@sigstore/protobuf-specs/-/protobuf-specs-0.3.1.tgz",
+ "integrity": "sha512-aIL8Z9NsMr3C64jyQzE0XlkEyBLpgEJJFDHLVVStkFV5Q3Il/r/YtY6NJWKQ4cy4AE7spP1IX5Jq7VCAxHHMfQ==",
"dev": true,
"engines": {
- "node": "^14.17.0 || ^16.13.0 || >=18.0.0"
+ "node": "^16.14.0 || >=18.0.0"
}
},
"node_modules/@sigstore/sign": {
- "version": "2.2.3",
- "resolved": "https://registry.npmjs.org/@sigstore/sign/-/sign-2.2.3.tgz",
- "integrity": "sha512-LqlA+ffyN02yC7RKszCdMTS6bldZnIodiox+IkT8B2f8oRYXCB3LQ9roXeiEL21m64CVH1wyveYAORfD65WoSw==",
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/@sigstore/sign/-/sign-2.3.0.tgz",
+ "integrity": "sha512-tsAyV6FC3R3pHmKS880IXcDJuiFJiKITO1jxR1qbplcsBkZLBmjrEw5GbC7ikD6f5RU1hr7WnmxB/2kKc1qUWQ==",
"dev": true,
"dependencies": {
- "@sigstore/bundle": "^2.2.0",
+ "@sigstore/bundle": "^2.3.0",
"@sigstore/core": "^1.0.0",
- "@sigstore/protobuf-specs": "^0.3.0",
+ "@sigstore/protobuf-specs": "^0.3.1",
"make-fetch-happen": "^13.0.0"
},
"engines": {
@@ -5101,14 +5206,14 @@
}
},
"node_modules/@sigstore/verify": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/@sigstore/verify/-/verify-1.1.1.tgz",
- "integrity": "sha512-BNANJms49rw9Q5J+fJjrDqOQSzjXDcOq/pgKDaVdDoIvQwqIfaoUriy+fQfh8sBX04hr4bkkrwu3EbhQqoQH7A==",
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/@sigstore/verify/-/verify-1.2.0.tgz",
+ "integrity": "sha512-hQF60nc9yab+Csi4AyoAmilGNfpXT+EXdBgFkP9OgPwIBPwyqVf7JAWPtmqrrrneTmAT6ojv7OlH1f6Ix5BG4Q==",
"dev": true,
"dependencies": {
- "@sigstore/bundle": "^2.2.0",
+ "@sigstore/bundle": "^2.3.1",
"@sigstore/core": "^1.1.0",
- "@sigstore/protobuf-specs": "^0.3.0"
+ "@sigstore/protobuf-specs": "^0.3.1"
},
"engines": {
"node": "^16.14.0 || >=18.0.0"
@@ -5121,9 +5226,9 @@
"dev": true
},
"node_modules/@socket.io/component-emitter": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.0.tgz",
- "integrity": "sha512-+9jVqKhRSpsc591z5vX+X5Yyw+he/HCB4iQ/RYxw35CEPaY1gnsNE43nf9n9AaYjAQrTiI/mOwKUKdUs9vf7Xg==",
+ "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
},
"node_modules/@tufjs/canonical-json": {
@@ -5136,18 +5241,33 @@
}
},
"node_modules/@tufjs/models": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/@tufjs/models/-/models-2.0.0.tgz",
- "integrity": "sha512-c8nj8BaOExmZKO2DXhDfegyhSGcG9E/mPN3U13L+/PsoWm1uaGiHHjxqSHQiasDBQwDA3aHuw9+9spYAP1qvvg==",
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/@tufjs/models/-/models-2.0.1.tgz",
+ "integrity": "sha512-92F7/SFyufn4DXsha9+QfKnN03JGqtMFMXgSHbZOo8JG59WkTni7UzAouNQDf7AuP9OAMxVOPQcqG3sB7w+kkg==",
"dev": true,
"dependencies": {
"@tufjs/canonical-json": "2.0.0",
- "minimatch": "^9.0.3"
+ "minimatch": "^9.0.4"
},
"engines": {
"node": "^16.14.0 || >=18.0.0"
}
},
+ "node_modules/@tufjs/models/node_modules/minimatch": {
+ "version": "9.0.4",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.4.tgz",
+ "integrity": "sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==",
+ "dev": true,
+ "dependencies": {
+ "brace-expansion": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=16 || 14 >=14.17"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
"node_modules/@types/body-parser": {
"version": "1.19.5",
"resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.5.tgz",
@@ -5202,9 +5322,9 @@
}
},
"node_modules/@types/eslint": {
- "version": "8.56.6",
- "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.56.6.tgz",
- "integrity": "sha512-ymwc+qb1XkjT/gfoQwxIeHZ6ixH23A+tCT2ADSA/DPVKzAjwYkTXBMCQ/f6fe4wEa85Lhp26VPeUxI7wMhAi7A==",
+ "version": "8.56.10",
+ "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.56.10.tgz",
+ "integrity": "sha512-Shavhk87gCtY2fhXDctcfS3e6FdxWkCx1iUZ9eEUbh7rTqlZT0/IzOkCOVt0fCjcFuZ9FPYfuezTBImfHCDBGQ==",
"dev": true,
"dependencies": {
"@types/estree": "*",
@@ -5240,9 +5360,9 @@
}
},
"node_modules/@types/express-serve-static-core": {
- "version": "4.17.43",
- "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.43.tgz",
- "integrity": "sha512-oaYtiBirUOPQGSWNGPWnzyAFJ0BP3cwvN4oWZQY+zUBwpVIGsKUkpBpSztp74drYcjavs7SKFZ4DX1V2QeN8rg==",
+ "version": "4.19.0",
+ "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.19.0.tgz",
+ "integrity": "sha512-bGyep3JqPCRry1wq+O5n7oiBgGWmeIJXPjXXCo8EK0u8duZGSYar7cGqd3ML2JUsLGeB7fmc06KYo9fLGWqPvQ==",
"dev": true,
"dependencies": {
"@types/node": "*",
@@ -5285,9 +5405,9 @@
"dev": true
},
"node_modules/@types/node": {
- "version": "20.11.30",
- "resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.30.tgz",
- "integrity": "sha512-dHM6ZxwlmuZaRmUPfv1p+KrdD1Dci04FbdEm/9wEMouFqxYoFl5aMkt0VMAUtYRQDyYvD41WJLukhq/ha3YuTw==",
+ "version": "20.12.10",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-20.12.10.tgz",
+ "integrity": "sha512-Eem5pH9pmWBHoGAT8Dr5fdc5rYA+4NAovdM4EktRPVAAiJhmWWfQrA0cFhAbOsQdSfIHjAud6YdkbL69+zSKjw==",
"dev": true,
"dependencies": {
"undici-types": "~5.26.4"
@@ -5303,9 +5423,9 @@
}
},
"node_modules/@types/qs": {
- "version": "6.9.14",
- "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.14.tgz",
- "integrity": "sha512-5khscbd3SwWMhFqylJBLQ0zIu7c1K6Vz0uBIt915BI3zV0q1nfjRQD3RqSBcPaO6PHEF4ov/t9y89fSiyThlPA==",
+ "version": "6.9.15",
+ "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.15.tgz",
+ "integrity": "sha512-uXHQKES6DQKKCLh441Xv/dwxOq1TVS3JPUMlEqoEglvlhR6Mxnlew/Xq/LRVHpLyk7iK3zODe1qYHIMltO7XGg==",
"dev": true
},
"node_modules/@types/range-parser": {
@@ -5346,14 +5466,14 @@
}
},
"node_modules/@types/serve-static": {
- "version": "1.15.5",
- "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.5.tgz",
- "integrity": "sha512-PDRk21MnK70hja/YF8AHfC7yIsiQHn1rcXx7ijCFBX/k+XQJhQT/gw3xekXKJvx+5SXaMMS8oqQy09Mzvz2TuQ==",
+ "version": "1.15.7",
+ "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.7.tgz",
+ "integrity": "sha512-W8Ym+h8nhuRwaKPaDw34QUkwsGi6Rc4yYqvKFo5rm2FUEhCFbzVWrxXUxuKK8TASjWsysJY0nsmNCGhCOIsrOw==",
"dev": true,
"dependencies": {
"@types/http-errors": "*",
- "@types/mime": "*",
- "@types/node": "*"
+ "@types/node": "*",
+ "@types/send": "*"
}
},
"node_modules/@types/sockjs": {
@@ -6042,9 +6162,9 @@
}
},
"node_modules/agent-base": {
- "version": "7.1.0",
- "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.0.tgz",
- "integrity": "sha512-o/zjMZRhJxny7OyEF+Op8X+efiELC7k7yOjMzgfzVqOzXqkBkWI79YoTdOtsuWd5BWhAGAuOY/Xa6xpiaWXiNg==",
+ "version": "7.1.1",
+ "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz",
+ "integrity": "sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==",
"dev": true,
"dependencies": {
"debug": "^4.3.4"
@@ -6329,13 +6449,13 @@
}
},
"node_modules/babel-plugin-polyfill-corejs2": {
- "version": "0.4.10",
- "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.10.tgz",
- "integrity": "sha512-rpIuu//y5OX6jVU+a5BCn1R5RSZYWAl2Nar76iwaOdycqb6JPxediskWFMMl7stfwNJR4b7eiQvh5fB5TEQJTQ==",
+ "version": "0.4.11",
+ "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.11.tgz",
+ "integrity": "sha512-sMEJ27L0gRHShOh5G54uAAPaiCOygY/5ratXuiyb2G46FmlSpc9eFCzYVyDiPxfNbwzA7mYahmjQc5q+CZQ09Q==",
"dev": true,
"dependencies": {
"@babel/compat-data": "^7.22.6",
- "@babel/helper-define-polyfill-provider": "^0.6.1",
+ "@babel/helper-define-polyfill-provider": "^0.6.2",
"semver": "^6.3.1"
},
"peerDependencies": {
@@ -6619,15 +6739,6 @@
"integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==",
"dev": true
},
- "node_modules/builtins": {
- "version": "5.0.1",
- "resolved": "https://registry.npmjs.org/builtins/-/builtins-5.0.1.tgz",
- "integrity": "sha512-qwVpFEHNfhYJIzNRBvd2C1kyo6jz3ZSMPyyuR47OPdiKWlbYnZNyDWuyR175qDnAJLiCo5fBBqPb3RiXgWlkOQ==",
- "dev": true,
- "dependencies": {
- "semver": "^7.0.0"
- }
- },
"node_modules/bytes": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
@@ -6638,9 +6749,9 @@
}
},
"node_modules/cacache": {
- "version": "18.0.2",
- "resolved": "https://registry.npmjs.org/cacache/-/cacache-18.0.2.tgz",
- "integrity": "sha512-r3NU8h/P+4lVUHfeRw1dtgQYar3DZMm4/cm2bZgOvrFC/su7budSOeqh52VJIC4U4iG1WWwV6vRW0znqBvxNuw==",
+ "version": "18.0.3",
+ "resolved": "https://registry.npmjs.org/cacache/-/cacache-18.0.3.tgz",
+ "integrity": "sha512-qXCd4rh6I07cnDqh8V48/94Tc/WSfj+o3Gn6NZ0aZovS255bUx8O13uKxRFd2eWG0xgsco7+YItQNPaa5E85hg==",
"dev": true,
"dependencies": {
"@npmcli/fs": "^3.1.0",
@@ -6661,16 +6772,16 @@
}
},
"node_modules/cacache/node_modules/glob": {
- "version": "10.3.10",
- "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.10.tgz",
- "integrity": "sha512-fa46+tv1Ak0UPK1TOy/pZrIybNNt4HCv7SDzwyfiOZkvZLEbjsZkJBPtDHVshZjbecAoAGSC20MjLDG/qr679g==",
+ "version": "10.3.12",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.12.tgz",
+ "integrity": "sha512-TCNv8vJ+xz4QiqTpfOJA7HvYv+tNIRHKfUWw/q+v2jdgN4ebz+KY9tGx5J4rHP0o84mNP+ApH66HRX8us3Khqg==",
"dev": true,
"dependencies": {
"foreground-child": "^3.1.0",
- "jackspeak": "^2.3.5",
+ "jackspeak": "^2.3.6",
"minimatch": "^9.0.1",
- "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0",
- "path-scurry": "^1.10.1"
+ "minipass": "^7.0.4",
+ "path-scurry": "^1.10.2"
},
"bin": {
"glob": "dist/esm/bin.mjs"
@@ -6683,9 +6794,9 @@
}
},
"node_modules/cacache/node_modules/lru-cache": {
- "version": "10.2.0",
- "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.2.0.tgz",
- "integrity": "sha512-2bIM8x+VAf6JT4bKAljS1qUWgMsqZRPGJS6FSahIMPVvctcNhyVp7AJu7quxOW9jwkryBReKZY5tY5JYv2n/7Q==",
+ "version": "10.2.2",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.2.2.tgz",
+ "integrity": "sha512-9hp3Vp2/hFQUiIwKo8XCeFVnrg8Pk3TYNPIR7tJADKi5YfcF7vEaK7avFHTlSy3kOKYaJQaalfEo6YuXdceBOQ==",
"dev": true,
"engines": {
"node": "14 || >=16.14"
@@ -6729,9 +6840,9 @@
}
},
"node_modules/caniuse-lite": {
- "version": "1.0.30001600",
- "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001600.tgz",
- "integrity": "sha512-+2S9/2JFhYmYaDpZvo0lKkfvuKIglrx68MwOBqMGHhQsNkLjB5xtc/TGoEPs+MxjSyN/72qer2g97nzR641mOQ==",
+ "version": "1.0.30001616",
+ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001616.tgz",
+ "integrity": "sha512-RHVYKov7IcdNjVHJFNY/78RdG4oGVjbayxv8u5IO74Wv7Hlq4PnJE6mo/OjFijjVFNy5ijnCt6H3IIo4t+wfEw==",
"dev": true,
"funding": [
{
@@ -7220,9 +7331,9 @@
}
},
"node_modules/core-js-compat": {
- "version": "3.36.1",
- "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.36.1.tgz",
- "integrity": "sha512-Dk997v9ZCt3X/npqzyGdTlq6t7lDBhZwGvV94PKzDArjp7BTRm7WlDAXYd/OWdeFHO8OChQYRJNJvUCqCbrtKA==",
+ "version": "3.37.0",
+ "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.37.0.tgz",
+ "integrity": "sha512-vYq4L+T8aS5UuFg4UwDhc7YNRWVeVZwltad9C/jV3R2LgVOpS9BDr7l/WL6BN0dbV3k1XejPTHqqEzJgsa0frA==",
"dev": true,
"dependencies": {
"browserslist": "^4.23.0"
@@ -7764,9 +7875,9 @@
"dev": true
},
"node_modules/ejs": {
- "version": "3.1.9",
- "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.9.tgz",
- "integrity": "sha512-rC+QVNMJWv+MtPgkt0y+0rVEIdbtxVADApW9JXrUVlzHetgcyczP/E7DJmWJ4fJCZF2cPcBk0laWO9ZHMG3DmQ==",
+ "version": "3.1.10",
+ "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz",
+ "integrity": "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==",
"dev": true,
"dependencies": {
"jake": "^10.8.5"
@@ -7779,9 +7890,9 @@
}
},
"node_modules/electron-to-chromium": {
- "version": "1.4.717",
- "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.717.tgz",
- "integrity": "sha512-6Fmg8QkkumNOwuZ/5mIbMU9WI3H2fmn5ajcVya64I5Yr5CcNmO7vcLt0Y7c96DCiMO5/9G+4sI2r6eEvdg1F7A==",
+ "version": "1.4.757",
+ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.757.tgz",
+ "integrity": "sha512-jftDaCknYSSt/+KKeXzH3LX5E2CvRLm75P3Hj+J/dv3CL0qUYcOt13d5FN1NiL5IJbbhzHrb3BomeG2tkSlZmw==",
"dev": true
},
"node_modules/emoji-regex": {
@@ -7972,9 +8083,9 @@
}
},
"node_modules/es-module-lexer": {
- "version": "1.5.0",
- "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.5.0.tgz",
- "integrity": "sha512-pqrTKmwEIgafsYZAGw9kszYzmagcE/n4dbgwGWLEXg7J4QFJVQRBld8j3Q3GNez79jzxZshq0bcT962QHOghjw==",
+ "version": "1.5.2",
+ "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.5.2.tgz",
+ "integrity": "sha512-l60ETUTmLqbVbVHv1J4/qj+M8nq7AwMzEcg3kmJDt9dCNrTk+yHcYFf/Kw75pMDwd9mPcIGCG5LcS20SxYRzFA==",
"dev": true
},
"node_modules/esbuild": {
@@ -9037,9 +9148,9 @@
}
},
"node_modules/fs-monkey": {
- "version": "1.0.5",
- "resolved": "https://registry.npmjs.org/fs-monkey/-/fs-monkey-1.0.5.tgz",
- "integrity": "sha512-8uMbBjrhzW76TYgEV27Y5E//W2f/lTFmx78P2w19FZSxarhI/798APGQyuGCwmkNxgwGRhrLfvWyLBvNtuOmew==",
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/fs-monkey/-/fs-monkey-1.0.6.tgz",
+ "integrity": "sha512-b1FMfwetIKymC0eioW7mTywihSQE4oLzQn1dB6rZB5fx/3NpNEdAWeCSMB+60/AeT0TCXsxzAlcYVEFCTAksWg==",
"dev": true
},
"node_modules/fs.realpath": {
@@ -9327,9 +9438,9 @@
}
},
"node_modules/hosted-git-info": {
- "version": "7.0.1",
- "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-7.0.1.tgz",
- "integrity": "sha512-+K84LB1DYwMHoHSgaOY/Jfhw3ucPmSET5v98Ke/HdNSw4a0UktWzyW1mjhjpuxxTqOOsfWT/7iVshHmVZ4IpOA==",
+ "version": "7.0.2",
+ "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-7.0.2.tgz",
+ "integrity": "sha512-puUZAUKT5m8Zzvs72XWy3HtvVbTWljRE66cP60bxJzAqf2DgICo7lYTY2IHUmLnNpjYvw5bvmoHvPc0QO2a62w==",
"dev": true,
"dependencies": {
"lru-cache": "^10.0.1"
@@ -9339,9 +9450,9 @@
}
},
"node_modules/hosted-git-info/node_modules/lru-cache": {
- "version": "10.2.0",
- "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.2.0.tgz",
- "integrity": "sha512-2bIM8x+VAf6JT4bKAljS1qUWgMsqZRPGJS6FSahIMPVvctcNhyVp7AJu7quxOW9jwkryBReKZY5tY5JYv2n/7Q==",
+ "version": "10.2.2",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.2.2.tgz",
+ "integrity": "sha512-9hp3Vp2/hFQUiIwKo8XCeFVnrg8Pk3TYNPIR7tJADKi5YfcF7vEaK7avFHTlSy3kOKYaJQaalfEo6YuXdceBOQ==",
"dev": true,
"engines": {
"node": "14 || >=16.14"
@@ -9600,9 +9711,9 @@
}
},
"node_modules/ignore-walk": {
- "version": "6.0.4",
- "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-6.0.4.tgz",
- "integrity": "sha512-t7sv42WkwFkyKbivUCglsQW5YWMskWtbEf4MNKX5u/CCWHKSPzN4FtBQGsQZgCLbxOzpVlcbWVK5KB3auIOjSw==",
+ "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==",
"dev": true,
"dependencies": {
"minimatch": "^9.0.0"
@@ -9756,9 +9867,9 @@
"dev": true
},
"node_modules/ipaddr.js": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.1.0.tgz",
- "integrity": "sha512-LlbxQ7xKzfBusov6UMi4MFpEg0m+mAm9xyNGEduwXMEDuf4WfzB/RZwMVYEd7IKGvh4IUkEXYxtAVu9T3OelJQ==",
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.2.0.tgz",
+ "integrity": "sha512-Ag3wB2o37wslZS19hZqorUnrnzSkpOVy+IiiDEiTqNubEYpYuHWIf6K4psgN2ZWKExS4xhVCrRVfb/wfW8fWJA==",
"dev": true,
"engines": {
"node": ">= 10"
@@ -10095,9 +10206,9 @@
}
},
"node_modules/jake": {
- "version": "10.8.7",
- "resolved": "https://registry.npmjs.org/jake/-/jake-10.8.7.tgz",
- "integrity": "sha512-ZDi3aP+fG/LchyBzUM804VjddnwfSfsdeYkwt8NcbKRvo4rFkjhs456iLFn3k2ZUWvNe4i48WACDbza8fhq2+w==",
+ "version": "10.9.1",
+ "resolved": "https://registry.npmjs.org/jake/-/jake-10.9.1.tgz",
+ "integrity": "sha512-61btcOHNnLnsOdtLgA5efqQWjnSi/vow5HbI7HMdKKWqvrKR1bLK3BPlJn9gcSaP2ewuamUSMB5XEy76KUIS2w==",
"dev": true,
"dependencies": {
"async": "^3.2.3",
@@ -10395,9 +10506,9 @@
"dev": true
},
"node_modules/json-parse-even-better-errors": {
- "version": "3.0.1",
- "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-3.0.1.tgz",
- "integrity": "sha512-aatBvbL26wVUCLmbWdCpeu9iF5wOyWpagiKkInA+kfws3sWdBrTnsvN2CKcyCYyUrc7rebNBlK6+kteg7ksecg==",
+ "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==",
"dev": true,
"engines": {
"node": "^14.17.0 || ^16.13.0 || >=18.0.0"
@@ -11140,9 +11251,9 @@
}
},
"node_modules/make-fetch-happen": {
- "version": "13.0.0",
- "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-13.0.0.tgz",
- "integrity": "sha512-7ThobcL8brtGo9CavByQrQi+23aIfgYU++wg4B87AIS8Rb2ZBt/MEaDqzA00Xwv/jUjAjYkLHjVolYuTLKda2A==",
+ "version": "13.0.1",
+ "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-13.0.1.tgz",
+ "integrity": "sha512-cKTUFc/rbKUd/9meOvgrpJ2WrNzymt6jfRDdwg5UCnVzv9dTpEj9JS5m3wtziXVCjluIXyL8pcaukYqezIzZQA==",
"dev": true,
"dependencies": {
"@npmcli/agent": "^2.0.0",
@@ -11154,6 +11265,7 @@
"minipass-flush": "^1.0.5",
"minipass-pipeline": "^1.2.4",
"negotiator": "^0.6.3",
+ "proc-log": "^4.2.0",
"promise-retry": "^2.0.1",
"ssri": "^10.0.0"
},
@@ -11161,6 +11273,15 @@
"node": "^16.14.0 || >=18.0.0"
}
},
+ "node_modules/make-fetch-happen/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==",
+ "dev": true,
+ "engines": {
+ "node": "^14.17.0 || ^16.13.0 || >=18.0.0"
+ }
+ },
"node_modules/media-typer": {
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
@@ -11330,9 +11451,9 @@
}
},
"node_modules/minipass": {
- "version": "7.0.4",
- "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.0.4.tgz",
- "integrity": "sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ==",
+ "version": "7.1.0",
+ "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.0.tgz",
+ "integrity": "sha512-oGZRv2OT1lO2UF1zUcwdTb3wqUwI0kBGTgt/T7OdSj6M6N5m3o5uPf0AIW6lVxGGoiWUR7e2AwTE+xiwK8WQig==",
"dev": true,
"engines": {
"node": ">=16 || 14 >=14.17"
@@ -11351,9 +11472,9 @@
}
},
"node_modules/minipass-fetch": {
- "version": "3.0.4",
- "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-3.0.4.tgz",
- "integrity": "sha512-jHAqnA728uUpIaFm7NWsCnqKT6UqZz7GcI/bDpPATuwYyKwJwW0remxSCxUlKiEty+eopHGa3oc8WxgQ1FFJqg==",
+ "version": "3.0.5",
+ "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-3.0.5.tgz",
+ "integrity": "sha512-2N8elDQAtSnFV0Dk7gt15KHsS0Fyz6CbYZ360h0WTYV1Ty46li3rAXVOQj1THMNLdmrD9Vt5pBPtWtVkpwGBqg==",
"dev": true,
"dependencies": {
"minipass": "^7.0.3",
@@ -11703,9 +11824,9 @@
}
},
"node_modules/node-gyp-build": {
- "version": "4.8.0",
- "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.0.tgz",
- "integrity": "sha512-u6fs2AEUljNho3EYTJNBfImO5QTo/J/1Etd+NVdCj7qWKUSN/bSLkZwhDv7I+w/MSC6qJ4cknepkAYykDdK8og==",
+ "version": "4.8.1",
+ "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.1.tgz",
+ "integrity": "sha512-OSs33Z9yWr148JZcbZd5WiAXhh/n9z8TxQcdMhIOlpN9AhWpLfvVFO73+m77bBABQMaY9XSvIa+qk0jlI7Gcaw==",
"dev": true,
"optional": true,
"bin": {
@@ -11715,16 +11836,16 @@
}
},
"node_modules/node-gyp/node_modules/glob": {
- "version": "10.3.10",
- "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.10.tgz",
- "integrity": "sha512-fa46+tv1Ak0UPK1TOy/pZrIybNNt4HCv7SDzwyfiOZkvZLEbjsZkJBPtDHVshZjbecAoAGSC20MjLDG/qr679g==",
+ "version": "10.3.12",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.12.tgz",
+ "integrity": "sha512-TCNv8vJ+xz4QiqTpfOJA7HvYv+tNIRHKfUWw/q+v2jdgN4ebz+KY9tGx5J4rHP0o84mNP+ApH66HRX8us3Khqg==",
"dev": true,
"dependencies": {
"foreground-child": "^3.1.0",
- "jackspeak": "^2.3.5",
+ "jackspeak": "^2.3.6",
"minimatch": "^9.0.1",
- "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0",
- "path-scurry": "^1.10.1"
+ "minipass": "^7.0.4",
+ "path-scurry": "^1.10.2"
},
"bin": {
"glob": "dist/esm/bin.mjs"
@@ -11773,9 +11894,9 @@
"dev": true
},
"node_modules/nopt": {
- "version": "7.2.0",
- "resolved": "https://registry.npmjs.org/nopt/-/nopt-7.2.0.tgz",
- "integrity": "sha512-CVDtwCdhYIvnAzFoJ6NJ6dX3oga9/HyciQDnG1vQDjSLMeKLJ4A93ZqYKDrgYSr1FBY5/hMYC+2VCi24pgpkGA==",
+ "version": "7.2.1",
+ "resolved": "https://registry.npmjs.org/nopt/-/nopt-7.2.1.tgz",
+ "integrity": "sha512-taM24ViiimT/XntxbPyJQzCG+p4EKOpgD3mxFwW38mGjVUrfERQOeY4EDHjdnptttfHuHQXFx+lTP08Q+mLa/w==",
"dev": true,
"dependencies": {
"abbrev": "^2.0.0"
@@ -11788,9 +11909,9 @@
}
},
"node_modules/normalize-package-data": {
- "version": "6.0.0",
- "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-6.0.0.tgz",
- "integrity": "sha512-UL7ELRVxYBHBgYEtZCXjxuD5vPxnmvMGq0jp/dGPKKrN7tfsBh2IY7TlJ15WWwdjRWD3RJbnsygUurTK3xkPkg==",
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-6.0.1.tgz",
+ "integrity": "sha512-6rvCfeRW+OEZagAB4lMLSNuTNYZWLVtKccK79VSTf//yTY5VOCgcpH80O+bZK8Neps7pUnd5G+QlMg1yV/2iZQ==",
"dev": true,
"dependencies": {
"hosted-git-info": "^7.0.0",
@@ -11821,9 +11942,9 @@
}
},
"node_modules/npm-bundled": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-3.0.0.tgz",
- "integrity": "sha512-Vq0eyEQy+elFpzsKjMss9kxqb9tG3YHg4dsyWuUENuzvSUWe1TCnW/vV9FkhvBk/brEDoDiVd+M1Btosa6ImdQ==",
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-3.0.1.tgz",
+ "integrity": "sha512-+AvaheE/ww1JEwRHOrn4WHNzOxGtVp+adrg2AeZS/7KuxGUYFuBta98wYpfHBbJp6Tg6j1NKSEVHNcfZzJHQwQ==",
"dev": true,
"dependencies": {
"npm-normalize-package-bin": "^3.0.0"
@@ -11896,23 +12017,33 @@
}
},
"node_modules/npm-registry-fetch": {
- "version": "16.1.0",
- "resolved": "https://registry.npmjs.org/npm-registry-fetch/-/npm-registry-fetch-16.1.0.tgz",
- "integrity": "sha512-PQCELXKt8Azvxnt5Y85GseQDJJlglTFM9L9U9gkv2y4e9s0k3GVDdOx3YoB6gm2Do0hlkzC39iCGXby+Wve1Bw==",
+ "version": "16.2.1",
+ "resolved": "https://registry.npmjs.org/npm-registry-fetch/-/npm-registry-fetch-16.2.1.tgz",
+ "integrity": "sha512-8l+7jxhim55S85fjiDGJ1rZXBWGtRLi1OSb4Z3BPLObPuIaeKRlPRiYMSHU4/81ck3t71Z+UwDDl47gcpmfQQA==",
"dev": true,
"dependencies": {
+ "@npmcli/redact": "^1.1.0",
"make-fetch-happen": "^13.0.0",
"minipass": "^7.0.2",
"minipass-fetch": "^3.0.0",
"minipass-json-stream": "^1.0.1",
"minizlib": "^2.1.2",
"npm-package-arg": "^11.0.0",
- "proc-log": "^3.0.0"
+ "proc-log": "^4.0.0"
},
"engines": {
"node": "^16.14.0 || >=18.0.0"
}
},
+ "node_modules/npm-registry-fetch/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==",
+ "dev": true,
+ "engines": {
+ "node": "^14.17.0 || ^16.13.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",
@@ -12261,17 +12392,17 @@
}
},
"node_modules/optionator": {
- "version": "0.9.3",
- "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz",
- "integrity": "sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==",
+ "version": "0.9.4",
+ "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz",
+ "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==",
"dev": true,
"dependencies": {
- "@aashutoshrathi/word-wrap": "^1.2.3",
"deep-is": "^0.1.3",
"fast-levenshtein": "^2.0.6",
"levn": "^0.4.1",
"prelude-ls": "^1.2.1",
- "type-check": "^0.4.0"
+ "type-check": "^0.4.0",
+ "word-wrap": "^1.2.5"
},
"engines": {
"node": ">= 0.8.0"
@@ -12616,12 +12747,12 @@
"dev": true
},
"node_modules/path-scurry": {
- "version": "1.10.1",
- "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.10.1.tgz",
- "integrity": "sha512-MkhCqzzBEpPvxxQ71Md0b1Kk51W01lrYvlMzSUaIzNsODdd7mqhiimSZlr+VegAz5Z6Vzt9Xg2ttE//XBhH3EQ==",
+ "version": "1.10.2",
+ "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.10.2.tgz",
+ "integrity": "sha512-7xTavNy5RQXnsjANvVvMkEjvloOinkAjv/Z6Ildz9v2RinZ4SBKTWFOVRbaF8p0vpHnyjV/UwNDdKuUv6M5qcA==",
"dev": true,
"dependencies": {
- "lru-cache": "^9.1.1 || ^10.0.0",
+ "lru-cache": "^10.2.0",
"minipass": "^5.0.0 || ^6.0.2 || ^7.0.0"
},
"engines": {
@@ -12632,9 +12763,9 @@
}
},
"node_modules/path-scurry/node_modules/lru-cache": {
- "version": "10.2.0",
- "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.2.0.tgz",
- "integrity": "sha512-2bIM8x+VAf6JT4bKAljS1qUWgMsqZRPGJS6FSahIMPVvctcNhyVp7AJu7quxOW9jwkryBReKZY5tY5JYv2n/7Q==",
+ "version": "10.2.2",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.2.2.tgz",
+ "integrity": "sha512-9hp3Vp2/hFQUiIwKo8XCeFVnrg8Pk3TYNPIR7tJADKi5YfcF7vEaK7avFHTlSy3kOKYaJQaalfEo6YuXdceBOQ==",
"dev": true,
"engines": {
"node": "14 || >=16.14"
@@ -12855,9 +12986,9 @@
"dev": true
},
"node_modules/postcss-modules-extract-imports": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.0.0.tgz",
- "integrity": "sha512-bdHleFnP3kZ4NYDhuGlVK+CMrQ/pqUm8bx/oGL93K6gVwiclvX5x0n76fYMKuIGKzlABOy13zsvqjb0f92TEXw==",
+ "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,
"engines": {
"node": "^10 || ^12 || >= 14"
@@ -12867,9 +12998,9 @@
}
},
"node_modules/postcss-modules-local-by-default": {
- "version": "4.0.4",
- "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.4.tgz",
- "integrity": "sha512-L4QzMnOdVwRm1Qb8m4x8jsZzKAaPAgrUF1r/hjDR2Xj7R+8Zsf97jAlSQzWtKx5YNiNGN8QxmPFIc/sh+RQl+Q==",
+ "version": "4.0.5",
+ "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.5.tgz",
+ "integrity": "sha512-6MieY7sIfTK0hYfafw1OMEG+2bg8Q1ocHCpoWLqOKj3JXlKu4G7btkmM/B7lFubYkYWmRSPLZi5chid63ZaZYw==",
"dev": true,
"dependencies": {
"icss-utils": "^5.0.0",
@@ -12884,9 +13015,9 @@
}
},
"node_modules/postcss-modules-scope": {
- "version": "3.1.1",
- "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-3.1.1.tgz",
- "integrity": "sha512-uZgqzdTleelWjzJY+Fhti6F3C9iF1JR/dODLs/JDefozYcKTBCdD8BIl6nNPbTbcLnGrk56hzwZC2DaGNvYjzA==",
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-3.2.0.tgz",
+ "integrity": "sha512-oq+g1ssrsZOsx9M96c5w8laRmvEu9C3adDSjI8oTcbfkrTE8hx/zfyobUoWIxaKPO8bt6S62kxpw5GqypEw1QQ==",
"dev": true,
"dependencies": {
"postcss-selector-parser": "^6.0.4"
@@ -13185,15 +13316,15 @@
}
},
"node_modules/react-is": {
- "version": "18.2.0",
- "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz",
- "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==",
+ "version": "18.3.1",
+ "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz",
+ "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==",
"dev": true
},
"node_modules/read-package-json": {
- "version": "7.0.0",
- "resolved": "https://registry.npmjs.org/read-package-json/-/read-package-json-7.0.0.tgz",
- "integrity": "sha512-uL4Z10OKV4p6vbdvIXB+OzhInYtIozl/VxUBPgNkBuUi2DeRonnuspmaVAMcrkmfjKGNmRndyQAbE7/AmzGwFg==",
+ "version": "7.0.1",
+ "resolved": "https://registry.npmjs.org/read-package-json/-/read-package-json-7.0.1.tgz",
+ "integrity": "sha512-8PcDiZ8DXUjLf687Ol4BR8Bpm2umR7vhoZOzNRt+uxD9GpBh/K+CAAALVIiYFknmvlmyg7hM7BSNUXPaCCqd0Q==",
"dev": true,
"dependencies": {
"glob": "^10.2.2",
@@ -13219,16 +13350,16 @@
}
},
"node_modules/read-package-json/node_modules/glob": {
- "version": "10.3.10",
- "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.10.tgz",
- "integrity": "sha512-fa46+tv1Ak0UPK1TOy/pZrIybNNt4HCv7SDzwyfiOZkvZLEbjsZkJBPtDHVshZjbecAoAGSC20MjLDG/qr679g==",
+ "version": "10.3.12",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.12.tgz",
+ "integrity": "sha512-TCNv8vJ+xz4QiqTpfOJA7HvYv+tNIRHKfUWw/q+v2jdgN4ebz+KY9tGx5J4rHP0o84mNP+ApH66HRX8us3Khqg==",
"dev": true,
"dependencies": {
"foreground-child": "^3.1.0",
- "jackspeak": "^2.3.5",
+ "jackspeak": "^2.3.6",
"minimatch": "^9.0.1",
- "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0",
- "path-scurry": "^1.10.1"
+ "minipass": "^7.0.4",
+ "path-scurry": "^1.10.2"
},
"bin": {
"glob": "dist/esm/bin.mjs"
@@ -13279,9 +13410,9 @@
}
},
"node_modules/reflect-metadata": {
- "version": "0.2.1",
- "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.2.1.tgz",
- "integrity": "sha512-i5lLI6iw9AU3Uu4szRNPPEkomnkjRTaVt9hy/bn5g/oSzekBSMeLZblcjP74AW0vBabqERLLIrz+gR8QYR54Tw==",
+ "version": "0.2.2",
+ "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.2.2.tgz",
+ "integrity": "sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==",
"dev": true
},
"node_modules/regenerate": {
@@ -13510,9 +13641,9 @@
}
},
"node_modules/rollup": {
- "version": "4.13.0",
- "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.13.0.tgz",
- "integrity": "sha512-3YegKemjoQnYKmsBlOHfMLVPPA5xLkQ8MHLLSw/fBrFaVkEayL51DilPpNNLq1exr98F2B1TzrV0FUlN3gWRPg==",
+ "version": "4.17.2",
+ "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.17.2.tgz",
+ "integrity": "sha512-/9ClTJPByC0U4zNLowV1tMBe8yMEAxewtR3cUNX5BoEpGH3dQEWpJLr6CLp0fPdYRF/fzVOgvDb1zXuakwF5kQ==",
"dev": true,
"dependencies": {
"@types/estree": "1.0.5"
@@ -13525,19 +13656,22 @@
"npm": ">=8.0.0"
},
"optionalDependencies": {
- "@rollup/rollup-android-arm-eabi": "4.13.0",
- "@rollup/rollup-android-arm64": "4.13.0",
- "@rollup/rollup-darwin-arm64": "4.13.0",
- "@rollup/rollup-darwin-x64": "4.13.0",
- "@rollup/rollup-linux-arm-gnueabihf": "4.13.0",
- "@rollup/rollup-linux-arm64-gnu": "4.13.0",
- "@rollup/rollup-linux-arm64-musl": "4.13.0",
- "@rollup/rollup-linux-riscv64-gnu": "4.13.0",
- "@rollup/rollup-linux-x64-gnu": "4.13.0",
- "@rollup/rollup-linux-x64-musl": "4.13.0",
- "@rollup/rollup-win32-arm64-msvc": "4.13.0",
- "@rollup/rollup-win32-ia32-msvc": "4.13.0",
- "@rollup/rollup-win32-x64-msvc": "4.13.0",
+ "@rollup/rollup-android-arm-eabi": "4.17.2",
+ "@rollup/rollup-android-arm64": "4.17.2",
+ "@rollup/rollup-darwin-arm64": "4.17.2",
+ "@rollup/rollup-darwin-x64": "4.17.2",
+ "@rollup/rollup-linux-arm-gnueabihf": "4.17.2",
+ "@rollup/rollup-linux-arm-musleabihf": "4.17.2",
+ "@rollup/rollup-linux-arm64-gnu": "4.17.2",
+ "@rollup/rollup-linux-arm64-musl": "4.17.2",
+ "@rollup/rollup-linux-powerpc64le-gnu": "4.17.2",
+ "@rollup/rollup-linux-riscv64-gnu": "4.17.2",
+ "@rollup/rollup-linux-s390x-gnu": "4.17.2",
+ "@rollup/rollup-linux-x64-gnu": "4.17.2",
+ "@rollup/rollup-linux-x64-musl": "4.17.2",
+ "@rollup/rollup-win32-arm64-msvc": "4.17.2",
+ "@rollup/rollup-win32-ia32-msvc": "4.17.2",
+ "@rollup/rollup-win32-x64-msvc": "4.17.2",
"fsevents": "~2.3.2"
}
},
@@ -13996,17 +14130,17 @@
"dev": true
},
"node_modules/sigstore": {
- "version": "2.2.2",
- "resolved": "https://registry.npmjs.org/sigstore/-/sigstore-2.2.2.tgz",
- "integrity": "sha512-2A3WvXkQurhuMgORgT60r6pOWiCOO5LlEqY2ADxGBDGVYLSo5HN0uLtb68YpVpuL/Vi8mLTe7+0Dx2Fq8lLqEg==",
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/sigstore/-/sigstore-2.3.0.tgz",
+ "integrity": "sha512-q+o8L2ebiWD1AxD17eglf1pFrl9jtW7FHa0ygqY6EKvibK8JHyq9Z26v9MZXeDiw+RbfOJ9j2v70M10Hd6E06A==",
"dev": true,
"dependencies": {
- "@sigstore/bundle": "^2.2.0",
+ "@sigstore/bundle": "^2.3.1",
"@sigstore/core": "^1.0.0",
- "@sigstore/protobuf-specs": "^0.3.0",
- "@sigstore/sign": "^2.2.3",
+ "@sigstore/protobuf-specs": "^0.3.1",
+ "@sigstore/sign": "^2.3.0",
"@sigstore/tuf": "^2.3.1",
- "@sigstore/verify": "^1.1.0"
+ "@sigstore/verify": "^1.2.0"
},
"engines": {
"node": "^16.14.0 || >=18.0.0"
@@ -14084,9 +14218,9 @@
}
},
"node_modules/socks": {
- "version": "2.8.1",
- "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.1.tgz",
- "integrity": "sha512-B6w7tkwNid7ToxjZ08rQMT8M9BJAf8DKx8Ft4NivzH0zBUfd6jldGcisJn/RLgxcX3FPNDdNQCUEMMT79b+oCQ==",
+ "version": "2.8.3",
+ "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.3.tgz",
+ "integrity": "sha512-l5x7VUUWbjVFbafGLxPWkYsHIhEvmF85tbIeFZWc8ZPtoMyybuEhL7Jye/ooC4/d48FgOjSJXgsF/AJPYCW8Zw==",
"dev": true,
"dependencies": {
"ip-address": "^9.0.5",
@@ -14098,12 +14232,12 @@
}
},
"node_modules/socks-proxy-agent": {
- "version": "8.0.2",
- "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.2.tgz",
- "integrity": "sha512-8zuqoLv1aP/66PHF5TqwJ7Czm3Yv32urJQHrVyhD7mmA6d61Zv8cIXQYPTWwmg6qlupnPvs/QKDmfa4P/qct2g==",
+ "version": "8.0.3",
+ "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.3.tgz",
+ "integrity": "sha512-VNegTZKhuGq5vSD6XNKlbqWhyt/40CgoEw8XxD6dhnm8Jq9IEa3nIa4HwnM8XOqU0CdB0BwWVXusqiFXfHB3+A==",
"dev": true,
"dependencies": {
- "agent-base": "^7.0.2",
+ "agent-base": "^7.1.1",
"debug": "^4.3.4",
"socks": "^2.7.1"
},
@@ -14249,9 +14383,9 @@
"dev": true
},
"node_modules/ssri": {
- "version": "10.0.5",
- "resolved": "https://registry.npmjs.org/ssri/-/ssri-10.0.5.tgz",
- "integrity": "sha512-bSf16tAFkGeRlUNDjXu8FzaMQt6g2HZJrun7mtMbIPOddxt3GLMSz5VWUWcqTJUPfLEaDIepGxv+bYQW49596A==",
+ "version": "10.0.6",
+ "resolved": "https://registry.npmjs.org/ssri/-/ssri-10.0.6.tgz",
+ "integrity": "sha512-MGrFH9Z4NP9Iyhqn16sDtBpRRNJ0Y2hNa6D65h736fVSaPCHr4DM4sWUNvVaSuC+0OBGhwsrydQwmgfg5LncqQ==",
"dev": true,
"dependencies": {
"minipass": "^7.0.3"
@@ -14805,14 +14939,14 @@
"integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q=="
},
"node_modules/tuf-js": {
- "version": "2.2.0",
- "resolved": "https://registry.npmjs.org/tuf-js/-/tuf-js-2.2.0.tgz",
- "integrity": "sha512-ZSDngmP1z6zw+FIkIBjvOp/II/mIub/O7Pp12j1WNsiCpg5R5wAc//i555bBQsE44O94btLt0xM/Zr2LQjwdCg==",
+ "version": "2.2.1",
+ "resolved": "https://registry.npmjs.org/tuf-js/-/tuf-js-2.2.1.tgz",
+ "integrity": "sha512-GwIJau9XaA8nLVbUXsN3IlFi7WmQ48gBUrl3FTkkL/XLu/POhBzfmX9hd33FNMX1qAsfl6ozO1iMmW9NC8YniA==",
"dev": true,
"dependencies": {
- "@tufjs/models": "2.0.0",
+ "@tufjs/models": "2.0.1",
"debug": "^4.3.4",
- "make-fetch-happen": "^13.0.0"
+ "make-fetch-happen": "^13.0.1"
},
"engines": {
"node": "^16.14.0 || >=18.0.0"
@@ -14898,9 +15032,9 @@
}
},
"node_modules/undici": {
- "version": "6.7.1",
- "resolved": "https://registry.npmjs.org/undici/-/undici-6.7.1.tgz",
- "integrity": "sha512-+Wtb9bAQw6HYWzCnxrPTMVEV3Q1QjYanI0E4q02ehReMuquQdLTEFEYbfs7hcImVYKcQkWSwT6buEmSVIiDDtQ==",
+ "version": "6.11.1",
+ "resolved": "https://registry.npmjs.org/undici/-/undici-6.11.1.tgz",
+ "integrity": "sha512-KyhzaLJnV1qa3BSHdj4AZ2ndqI0QWPxYzaIOio0WzcEJB9gvuysprJSLtpvc2D9mhR9jPDUk7xlJlZbH2KR5iw==",
"dev": true,
"engines": {
"node": ">=18.0"
@@ -14995,9 +15129,9 @@
}
},
"node_modules/update-browserslist-db": {
- "version": "1.0.13",
- "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz",
- "integrity": "sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg==",
+ "version": "1.0.15",
+ "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.15.tgz",
+ "integrity": "sha512-K9HWH62x3/EalU1U6sjSZiylm9C8tgq2mSvshZpqc7QE69RaA2qjhkW2HlNA0tFpEbtyFz7HTqbSdN4MSwUodA==",
"dev": true,
"funding": [
{
@@ -15014,7 +15148,7 @@
}
],
"dependencies": {
- "escalade": "^3.1.1",
+ "escalade": "^3.1.2",
"picocolors": "^1.0.0"
},
"bin": {
@@ -15068,13 +15202,10 @@
}
},
"node_modules/validate-npm-package-name": {
- "version": "5.0.0",
- "resolved": "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-5.0.0.tgz",
- "integrity": "sha512-YuKoXDAhBYxY7SfOKxHBDoSyENFeW5VvIIQp2TGQuit8gpK6MnWaQelBKxso72DoxTZfZdcP3W90LqpSkgPzLQ==",
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-5.0.1.tgz",
+ "integrity": "sha512-OljLrQ9SQdOUqTaQxqL5dEfZWrXExyyWsozYlAWFawPVNuD83igl7uJD2RTkNMbniIYgt8l81eCJGIdQF7avLQ==",
"dev": true,
- "dependencies": {
- "builtins": "^5.0.0"
- },
"engines": {
"node": "^14.17.0 || ^16.13.0 || >=18.0.0"
}
@@ -15089,9 +15220,9 @@
}
},
"node_modules/vite": {
- "version": "5.1.5",
- "resolved": "https://registry.npmjs.org/vite/-/vite-5.1.5.tgz",
- "integrity": "sha512-BdN1xh0Of/oQafhU+FvopafUp6WaYenLU/NFoL5WyJL++GxkNfieKzBhM24H3HVsPQrlAqB7iJYTHabzaRed5Q==",
+ "version": "5.1.7",
+ "resolved": "https://registry.npmjs.org/vite/-/vite-5.1.7.tgz",
+ "integrity": "sha512-sgnEEFTZYMui/sTlH1/XEnVNHMujOahPLGMxn1+5sIT45Xjng1Ec1K78jRP15dSmVgg5WBin9yO81j3o9OxofA==",
"dev": true,
"dependencies": {
"esbuild": "^0.19.3",
@@ -15787,9 +15918,9 @@
}
},
"node_modules/webpack-dev-server/node_modules/ws": {
- "version": "8.16.0",
- "resolved": "https://registry.npmjs.org/ws/-/ws-8.16.0.tgz",
- "integrity": "sha512-HS0c//TP7Ina87TfiPUz1rQzMhHrl/SG2guqRcTOIUYD2q8uhUdNHZYJUaQ8aTGPzCh+c6oawMKW35nFl1dxyQ==",
+ "version": "8.17.0",
+ "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.0.tgz",
+ "integrity": "sha512-uJq6108EgZMAl20KagGkzCKfMEjxmKvZHG7Tlq0Z6nOky7YF7aq4mOx6xK8TJ/i1LeK4Qus7INktacctDgY8Ow==",
"dev": true,
"engines": {
"node": ">=10.0.0"
@@ -15972,6 +16103,15 @@
"integrity": "sha512-CC1bOL87PIWSBhDcTrdeLo6eGT7mCFtrg0uIJtqJUFyK+eJnzl8A1niH56uu7KMa5XFrtiV+AQuHO3n7DsHnLQ==",
"dev": true
},
+ "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,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
"node_modules/wrap-ansi": {
"version": "6.2.0",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz",
@@ -16152,9 +16292,9 @@
}
},
"node_modules/zone.js": {
- "version": "0.14.4",
- "resolved": "https://registry.npmjs.org/zone.js/-/zone.js-0.14.4.tgz",
- "integrity": "sha512-NtTUvIlNELez7Q1DzKVIFZBzNb646boQMgpATo9z3Ftuu/gWvzxCW7jdjcUDoRGxRikrhVHB/zLXh1hxeJawvw==",
+ "version": "0.14.5",
+ "resolved": "https://registry.npmjs.org/zone.js/-/zone.js-0.14.5.tgz",
+ "integrity": "sha512-9XYWZzY6PhHOSdkYryNcMm7L8EK7a4q+GbTvxbIA2a9lMdRUpGuyaYvLDcg8D6bdn+JomSsbPcilVKg6SmUx6w==",
"dependencies": {
"tslib": "^2.3.0"
}
diff --git a/modules/ui/src/app/app.component.html b/modules/ui/src/app/app.component.html
index a89ee0b51..8b3a8f7dc 100644
--- a/modules/ui/src/app/app.component.html
+++ b/modules/ui/src/app/app.component.html
@@ -87,6 +87,9 @@ Testrun
(click)="openGeneralSettings(true)">
tune
+
+
diff --git a/modules/ui/src/app/app.component.scss b/modules/ui/src/app/app.component.scss
index d5589fabd..46db2a291 100644
--- a/modules/ui/src/app/app.component.scss
+++ b/modules/ui/src/app/app.component.scss
@@ -102,6 +102,7 @@ $nav-open-btn-width: 210px;
width: $nav-close-btn-width;
display: flex;
justify-content: flex-start;
+ padding-inline: 8px;
}
.app-sidebar-button:first-child {
diff --git a/modules/ui/src/app/app.component.spec.ts b/modules/ui/src/app/app.component.spec.ts
index 901c08795..859cc7d64 100644
--- a/modules/ui/src/app/app.component.spec.ts
+++ b/modules/ui/src/app/app.component.spec.ts
@@ -58,6 +58,7 @@ import {
selectInterfaces,
selectMenuOpened,
} from './store/selectors';
+import { MatIconTestingModule } from '@angular/material/icon/testing';
describe('AppComponent', () => {
let component: AppComponent;
@@ -111,6 +112,7 @@ describe('AppComponent', () => {
MatSidenavModule,
BypassComponent,
CalloutComponent,
+ MatIconTestingModule,
],
providers: [
{ provide: TestRunService, useValue: mockService },
@@ -141,6 +143,7 @@ describe('AppComponent', () => {
AppComponent,
FakeGeneralSettingsComponent,
FakeSpinnerComponent,
+ FakeShutdownAppComponent,
FakeVersionComponent,
],
});
@@ -655,6 +658,14 @@ class FakeGeneralSettingsComponent {
})
class FakeSpinnerComponent {}
+@Component({
+ selector: 'app-shutdown-app',
+ template: '
',
+})
+class FakeShutdownAppComponent {
+ @Input() disable!: boolean;
+}
+
@Component({
selector: 'app-version',
template: '
',
diff --git a/modules/ui/src/app/app.component.ts b/modules/ui/src/app/app.component.ts
index 55d6ab9dc..21605e630 100644
--- a/modules/ui/src/app/app.component.ts
+++ b/modules/ui/src/app/app.component.ts
@@ -44,6 +44,7 @@ import { GeneralSettingsComponent } from './pages/settings/general-settings.comp
import { AppStore } from './app.store';
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 TESTRUN_LOGO_URL = '/assets/icons/testrun_logo_small.svg';
const TESTRUN_LOGO_COLOR_URL = '/assets/icons/testrun_logo_color.svg';
@@ -94,6 +95,10 @@ export class AppComponent implements OnInit {
'devices',
this.domSanitizer.bypassSecurityTrustResourceUrl(DEVICES_LOGO_URL)
);
+ this.matIconRegistry.addSvgIcon(
+ 'device_run',
+ this.domSanitizer.bypassSecurityTrustResourceUrl(DEVICES_RUN_URL)
+ );
this.matIconRegistry.addSvgIcon(
'reports',
this.domSanitizer.bypassSecurityTrustResourceUrl(REPORTS_LOGO_URL)
@@ -173,4 +178,12 @@ export class AppComponent implements OnInit {
consentShown() {
this.appStore.setContent();
}
+
+ testrunInProgress(status?: string): boolean {
+ return (
+ status === StatusOfTestrun.InProgress ||
+ status === StatusOfTestrun.WaitingForDevice ||
+ status === StatusOfTestrun.Monitoring
+ );
+ }
}
diff --git a/modules/ui/src/app/app.module.ts b/modules/ui/src/app/app.module.ts
index 42ade0ddd..753aaadc4 100644
--- a/modules/ui/src/app/app.module.ts
+++ b/modules/ui/src/app/app.module.ts
@@ -44,6 +44,8 @@ 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';
@NgModule({
declarations: [AppComponent, GeneralSettingsComponent],
@@ -71,8 +73,10 @@ import { SettingsDropdownComponent } from './pages/settings/components/settings-
EffectsModule.forRoot([AppEffects]),
CdkTrapFocus,
SettingsDropdownComponent,
+ ShutdownAppComponent,
],
providers: [
+ WindowProvider,
{
provide: HTTP_INTERCEPTORS,
useClass: ErrorInterceptor,
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 0496d74fb..84117dd34 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
@@ -61,8 +61,6 @@
attr.aria-label="Start new testrun for device {{ label }}"
class="button-start"
mat-flat-button>
-
- play_arrow
+
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 be2d4980a..f613ec2f1 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
@@ -174,9 +174,8 @@ $border-radius: 12px;
.button-start-icon {
margin: 0;
- width: 24px;
+ width: 30px;
height: 24px;
- font-size: 24px;
}
.device-item {
diff --git a/modules/ui/src/app/components/shutdown-app-modal/shutdown-app-modal.component.html b/modules/ui/src/app/components/shutdown-app-modal/shutdown-app-modal.component.html
new file mode 100644
index 000000000..0614dfc28
--- /dev/null
+++ b/modules/ui/src/app/components/shutdown-app-modal/shutdown-app-modal.component.html
@@ -0,0 +1,38 @@
+
+{{ data.title }}
+
+ {{ data.content }}
+
+
+
+
+ Cancel
+
+
+ Stop Server & Quit
+
+
diff --git a/modules/ui/src/app/components/shutdown-app-modal/shutdown-app-modal.component.scss b/modules/ui/src/app/components/shutdown-app-modal/shutdown-app-modal.component.scss
new file mode 100644
index 000000000..85ddce69d
--- /dev/null
+++ b/modules/ui/src/app/components/shutdown-app-modal/shutdown-app-modal.component.scss
@@ -0,0 +1,44 @@
+/**
+ * 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 '../../../theming/colors';
+
+: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;
+}
diff --git a/modules/ui/src/app/components/shutdown-app-modal/shutdown-app-modal.component.spec.ts b/modules/ui/src/app/components/shutdown-app-modal/shutdown-app-modal.component.spec.ts
new file mode 100644
index 000000000..d7cd5abb6
--- /dev/null
+++ b/modules/ui/src/app/components/shutdown-app-modal/shutdown-app-modal.component.spec.ts
@@ -0,0 +1,100 @@
+/**
+ * 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 { ShutdownAppModalComponent } from './shutdown-app-modal.component';
+import {
+ MAT_DIALOG_DATA,
+ MatDialogModule,
+ MatDialogRef,
+} from '@angular/material/dialog';
+import { MatButtonModule } from '@angular/material/button';
+import { of } from 'rxjs';
+import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
+
+describe('ShutdownAppModalComponent', () => {
+ let component: ShutdownAppModalComponent;
+ let fixture: ComponentFixture;
+ let compiled: HTMLElement;
+
+ beforeEach(async () => {
+ await TestBed.configureTestingModule({
+ imports: [
+ ShutdownAppModalComponent,
+ MatDialogModule,
+ MatButtonModule,
+ BrowserAnimationsModule,
+ ],
+ providers: [
+ {
+ provide: MatDialogRef,
+ useValue: {
+ keydownEvents: () => of(new KeyboardEvent('keydown', { code: '' })),
+ close: () => ({}),
+ },
+ },
+ { provide: MAT_DIALOG_DATA, useValue: {} },
+ ],
+ }).compileComponents();
+
+ fixture = TestBed.createComponent(ShutdownAppModalComponent);
+ component = fixture.componentInstance;
+ component.data = {
+ title: 'title',
+ content: 'content',
+ };
+ compiled = fixture.nativeElement as HTMLElement;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ 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', () => {
+ 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'
+ ) as HTMLButtonElement;
+
+ confirmButton.click();
+
+ expect(closeSpy).toHaveBeenCalledWith(true);
+
+ closeSpy.calls.reset();
+ });
+});
diff --git a/modules/ui/src/app/components/shutdown-app-modal/shutdown-app-modal.component.ts b/modules/ui/src/app/components/shutdown-app-modal/shutdown-app-modal.component.ts
new file mode 100644
index 000000000..af42aabe9
--- /dev/null
+++ b/modules/ui/src/app/components/shutdown-app-modal/shutdown-app-modal.component.ts
@@ -0,0 +1,53 @@
+/**
+ * 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, Inject } from '@angular/core';
+import { EscapableDialogComponent } from '../escapable-dialog/escapable-dialog.component';
+import {
+ MAT_DIALOG_DATA,
+ MatDialogModule,
+ MatDialogRef,
+} from '@angular/material/dialog';
+import { MatButtonModule } from '@angular/material/button';
+
+interface DialogData {
+ title?: string;
+ content?: string;
+}
+
+@Component({
+ selector: 'app-shutdown-app-modal',
+ templateUrl: './shutdown-app-modal.component.html',
+ styleUrl: './shutdown-app-modal.component.scss',
+ standalone: true,
+ imports: [MatDialogModule, MatButtonModule],
+ changeDetection: ChangeDetectionStrategy.OnPush,
+})
+export class ShutdownAppModalComponent extends EscapableDialogComponent {
+ constructor(
+ public override dialogRef: MatDialogRef,
+ @Inject(MAT_DIALOG_DATA) public data: DialogData
+ ) {
+ super(dialogRef);
+ }
+
+ confirm() {
+ this.dialogRef.close(true);
+ }
+
+ cancel() {
+ this.dialogRef.close();
+ }
+}
diff --git a/modules/ui/src/app/components/shutdown-app/shutdown-app.component.html b/modules/ui/src/app/components/shutdown-app/shutdown-app.component.html
new file mode 100644
index 000000000..a13bd5118
--- /dev/null
+++ b/modules/ui/src/app/components/shutdown-app/shutdown-app.component.html
@@ -0,0 +1,23 @@
+
+
+ power_settings_new
+
diff --git a/modules/ui/src/app/components/shutdown-app/shutdown-app.component.scss b/modules/ui/src/app/components/shutdown-app/shutdown-app.component.scss
new file mode 100644
index 000000000..a424bf842
--- /dev/null
+++ b/modules/ui/src/app/components/shutdown-app/shutdown-app.component.scss
@@ -0,0 +1,28 @@
+/**
+ * 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.
+ */
+:host {
+ ::ng-deep.mat-mdc-icon-button .mat-mdc-button-persistent-ripple {
+ border-radius: inherit;
+ }
+}
+.shutdown-button {
+ border-radius: 20px;
+ padding: 0;
+ box-sizing: border-box;
+ height: 34px;
+ margin: 11px 2px 11px 0;
+ line-height: 50%;
+}
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
new file mode 100644
index 000000000..522234f5e
--- /dev/null
+++ b/modules/ui/src/app/components/shutdown-app/shutdown-app.component.spec.ts
@@ -0,0 +1,112 @@
+/**
+ * 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,
+ fakeAsync,
+ TestBed,
+ tick,
+} from '@angular/core/testing';
+
+import { ShutdownAppComponent } from './shutdown-app.component';
+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';
+
+describe('ShutdownAppComponent', () => {
+ let component: ShutdownAppComponent;
+ let compiled: HTMLElement;
+ let fixture: ComponentFixture;
+ let mockService: SpyObj;
+
+ const windowMock = {
+ location: {
+ reload: jasmine.createSpy('reload'),
+ },
+ };
+
+ beforeEach(async () => {
+ mockService = jasmine.createSpyObj(['shutdownTestrun']);
+
+ await TestBed.configureTestingModule({
+ imports: [ShutdownAppComponent, MatDialogModule, BrowserAnimationsModule],
+ providers: [
+ { provide: TestRunService, useValue: mockService },
+ {
+ provide: MatDialogRef,
+ useValue: {
+ close: () => ({}),
+ },
+ },
+ { provide: WINDOW, useValue: windowMock },
+ ],
+ }).compileComponents();
+
+ fixture = TestBed.createComponent(ShutdownAppComponent);
+ component = fixture.componentInstance;
+ compiled = fixture.nativeElement as HTMLElement;
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+
+ it('#openShutdownModal should call service shutdownTestrun after close', fakeAsync(() => {
+ mockService.shutdownTestrun.and.returnValue(of(false));
+ spyOn(component.dialog, 'open').and.returnValue({
+ afterClosed: () => of(true),
+ } as MatDialogRef);
+ tick();
+
+ component.openShutdownModal();
+
+ expect(mockService.shutdownTestrun).toHaveBeenCalled();
+ }));
+
+ it('#openShutdownModal should reload window after shutdownTestrun', fakeAsync(() => {
+ mockService.shutdownTestrun.and.returnValue(of(true));
+ spyOn(component.dialog, 'open').and.returnValue({
+ afterClosed: () => of(true),
+ } as MatDialogRef);
+ tick();
+
+ component.openShutdownModal();
+
+ expect(windowMock.location.reload).toHaveBeenCalled();
+ }));
+
+ it('shutdown button should be enable', () => {
+ const shutdownButton = compiled.querySelector(
+ '.shutdown-button'
+ ) as HTMLButtonElement;
+
+ expect(shutdownButton.disabled).toBeFalse();
+ });
+
+ it('shutdown button should be disable', () => {
+ component.disable = true;
+ fixture.detectChanges();
+
+ const shutdownButton = compiled.querySelector(
+ '.shutdown-button'
+ ) as HTMLButtonElement;
+
+ expect(shutdownButton.disabled).toBeTrue();
+ });
+});
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
new file mode 100644
index 000000000..99a9b06d0
--- /dev/null
+++ b/modules/ui/src/app/components/shutdown-app/shutdown-app.component.ts
@@ -0,0 +1,92 @@
+/**
+ * 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,
+ Inject,
+ Input,
+ OnDestroy,
+} 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';
+
+@Component({
+ selector: 'app-shutdown-app',
+ standalone: true,
+ imports: [CommonModule, MatButtonModule, MatIcon],
+ templateUrl: './shutdown-app.component.html',
+ styleUrl: './shutdown-app.component.scss',
+ changeDetection: ChangeDetectionStrategy.OnPush,
+})
+export class ShutdownAppComponent implements OnDestroy {
+ @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, {
+ ariaLabel: 'Shutdown Testrun',
+ data: {
+ title: 'Shutdown Testrun',
+ content: 'Do you want to stop Testrun?',
+ },
+ autoFocus: true,
+ hasBackdrop: true,
+ disableClose: true,
+ panelClass: 'shutdown-app-dialog',
+ });
+
+ dialogRef
+ ?.afterClosed()
+ .pipe(takeUntil(this.destroy$))
+ .subscribe(shutdownApp => {
+ if (shutdownApp) {
+ this.shutdownApp();
+ }
+ });
+ }
+
+ private shutdownApp(): void {
+ this.testRunService
+ .shutdownTestrun()
+ .pipe(takeUntil(this.destroy$))
+ .subscribe(success => {
+ if (success) {
+ this.reloadPage();
+ }
+ });
+ }
+
+ private reloadPage(): void {
+ this.window.location.reload();
+ }
+
+ ngOnDestroy() {
+ this.destroy$.next(true);
+ this.destroy$.unsubscribe();
+ }
+}
diff --git a/modules/ui/src/app/interceptors/error.interceptor.ts b/modules/ui/src/app/interceptors/error.interceptor.ts
index 8fe5d0c23..924cbde02 100644
--- a/modules/ui/src/app/interceptors/error.interceptor.ts
+++ b/modules/ui/src/app/interceptors/error.interceptor.ts
@@ -31,22 +31,27 @@ import {
import { NotificationService } from '../services/notification.service';
import { SYSTEM_STOP } from '../services/test-run.service';
+import { finalize } from 'rxjs/operators';
const DEFAULT_TIMEOUT_MS = 5000;
-const SYSTEM_STOP_TIMEOUT_MS = 10000;
+const SYSTEM_STOP_TIMEOUT_MS = 60 * 1000;
@Injectable()
export class ErrorInterceptor implements HttpInterceptor {
+ private isTestrunStop = false;
constructor(private notificationService: NotificationService) {}
-
intercept(
request: HttpRequest,
next: HttpHandler,
timeoutMs = DEFAULT_TIMEOUT_MS
): Observable> {
- const timeoutValue = request.url.includes(SYSTEM_STOP)
- ? SYSTEM_STOP_TIMEOUT_MS
- : timeoutMs;
+ const timeoutValue =
+ request.url.includes(SYSTEM_STOP) || this.isTestrunStop
+ ? SYSTEM_STOP_TIMEOUT_MS
+ : timeoutMs;
+ if (request.url.includes(SYSTEM_STOP)) {
+ this.isTestrunStop = true;
+ }
return next.handle(request).pipe(
timeout(timeoutValue),
catchError((error: HttpErrorResponse | TimeoutError) => {
@@ -66,6 +71,11 @@ export class ErrorInterceptor implements HttpInterceptor {
}
}
return throwError(error);
+ }),
+ finalize(() => {
+ if (request.url.includes(SYSTEM_STOP)) {
+ this.isTestrunStop = false;
+ }
})
);
}
diff --git a/modules/ui/src/app/mocks/progress.mock.ts b/modules/ui/src/app/mocks/progress.mock.ts
index 2911ea794..258e73c10 100644
--- a/modules/ui/src/app/mocks/progress.mock.ts
+++ b/modules/ui/src/app/mocks/progress.mock.ts
@@ -69,6 +69,7 @@ const PROGRESS_DATA_RESPONSE = (
) => {
return {
status,
+ mac_addr: '01:02:03:04:05:06',
device: {
manufacturer: 'Delta',
model: '03-DIN-CPU',
diff --git a/modules/ui/src/app/mocks/reports.mock.ts b/modules/ui/src/app/mocks/reports.mock.ts
index 9a5430b5c..0cfb39420 100644
--- a/modules/ui/src/app/mocks/reports.mock.ts
+++ b/modules/ui/src/app/mocks/reports.mock.ts
@@ -3,6 +3,7 @@ import { MatTableDataSource } from '@angular/material/table';
export const HISTORY = [
{
+ mac_addr: '01:02:03:04:05:06',
status: 'compliant',
device: {
manufacturer: 'Delta',
@@ -16,6 +17,7 @@ export const HISTORY = [
},
{
status: 'compliant',
+ mac_addr: '01:02:03:04:05:07',
device: {
manufacturer: 'Delta',
model: '03-DIN-SRC',
@@ -30,6 +32,7 @@ export const HISTORY = [
export const HISTORY_AFTER_REMOVE = [
{
+ mac_addr: '01:02:03:04:05:06',
status: 'compliant',
device: {
manufacturer: 'Delta',
@@ -49,6 +52,7 @@ export const HISTORY_AFTER_REMOVE = [
export const FORMATTED_HISTORY = [
{
status: 'compliant',
+ mac_addr: '01:02:03:04:05:06',
device: {
manufacturer: 'Delta',
model: '03-DIN-SRC',
@@ -64,6 +68,7 @@ export const FORMATTED_HISTORY = [
},
{
status: 'compliant',
+ mac_addr: '01:02:03:04:05:07',
device: {
manufacturer: 'Delta',
model: '03-DIN-SRC',
diff --git a/modules/ui/src/app/mocks/settings.mock.ts b/modules/ui/src/app/mocks/settings.mock.ts
index c5b3ddc74..baab9a2c0 100644
--- a/modules/ui/src/app/mocks/settings.mock.ts
+++ b/modules/ui/src/app/mocks/settings.mock.ts
@@ -53,7 +53,7 @@ export const MOCK_INTERFACE_VALUE: SystemInterfaces = {
};
export const MOCK_LOG_VALUE: SystemInterfaces = {
key: 'DEBUG',
- value: '',
+ value: 'Every event will be logged',
};
export const MOCK_PERIOD_VALUE: SystemInterfaces = {
diff --git a/modules/ui/src/app/model/testrun-status.ts b/modules/ui/src/app/model/testrun-status.ts
index 1492160c5..3f6805367 100644
--- a/modules/ui/src/app/model/testrun-status.ts
+++ b/modules/ui/src/app/model/testrun-status.ts
@@ -16,6 +16,7 @@
import { Device } from './device';
export interface TestrunStatus {
+ mac_addr: string;
status: string;
device: IDevice;
started: string | null;
@@ -61,6 +62,7 @@ export enum StatusOfTestrun {
SmartReady = 'Smart Ready', // used for Completed
Idle = 'Idle',
Monitoring = 'Monitoring',
+ Error = 'Error',
}
export enum StatusOfTestResult {
@@ -69,7 +71,7 @@ export enum StatusOfTestResult {
CompliantHigh = 'Compliant (High)',
SmartReady = 'Smart Ready',
NonCompliant = 'Non-Compliant', // device does not support feature but feature is required
- Skipped = 'Skipped',
+ NotPresent = 'Feature Not Present',
NotStarted = 'Not Started',
InProgress = 'In Progress',
Error = 'Error', // test failed to run
diff --git a/modules/ui/src/app/pages/devices/components/device-form/device-form.component.html b/modules/ui/src/app/pages/devices/components/device-form/device-form.component.html
index 11f87ff33..5978f6d22 100644
--- a/modules/ui/src/app/pages/devices/components/device-form/device-form.component.html
+++ b/modules/ui/src/app/pages/devices/components/device-form/device-form.component.html
@@ -31,7 +31,7 @@
role="alert"
aria-live="assertive">
Please, check. The manufacturer name must be a maximum of 64
+ >Please, check. The manufacturer name must be a maximum of 28
characters. Only letters, numbers, and accented letters are
permitted.
@@ -49,7 +49,7 @@
role="alert"
aria-live="assertive">
Please, check. The device model name must be a maximum of 64
+ >Please, check. The device model name must be a maximum of 28
characters. Only letters, numbers, and accented letters are
permitted.
diff --git a/modules/ui/src/app/pages/devices/components/device-form/device-form.component.spec.ts b/modules/ui/src/app/pages/devices/components/device-form/device-form.component.spec.ts
index 4ffbb402e..6ea72aa52 100644
--- a/modules/ui/src/app/pages/devices/components/device-form/device-form.component.spec.ts
+++ b/modules/ui/src/app/pages/devices/components/device-form/device-form.component.spec.ts
@@ -240,7 +240,7 @@ describe('DeviceFormComponent', () => {
expect(error).toBeTruthy();
expect(modelError).toContain(
- 'The device model name must be a maximum of 64 characters. Only letters, numbers, and accented letters are permitted.'
+ 'The device model name must be a maximum of 28 characters. Only letters, numbers, and accented letters are permitted.'
);
});
});
@@ -283,7 +283,7 @@ describe('DeviceFormComponent', () => {
expect(error).toBeTruthy();
expect(manufacturerError).toContain(
- 'The manufacturer name must be a maximum of 64 characters. Only letters, numbers, and accented letters are permitted.'
+ 'The manufacturer name must be a maximum of 28 characters. Only letters, numbers, and accented letters are permitted.'
);
});
});
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 5e54e7996..2b7b23ae1 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
@@ -23,15 +23,28 @@ import { Device } from '../../../../model/device';
*/
export class DeviceValidators {
readonly STRING_FORMAT_REGEXP = new RegExp(
+ "^([a-z0-9\\p{L}\\p{M}.',-_ ]{1,28})$",
+ 'u'
+ );
+
+ readonly FIRMWARE_FORMAT_REGEXP = new RegExp(
"^([a-z0-9\\p{L}\\p{M}.',-_ ]{1,64})$",
'u'
);
public deviceStringFormat(): ValidatorFn {
+ return this.stringFormat(this.STRING_FORMAT_REGEXP);
+ }
+
+ public firmwareStringFormat(): ValidatorFn {
+ return this.stringFormat(this.FIRMWARE_FORMAT_REGEXP);
+ }
+
+ private stringFormat(regExp: RegExp): ValidatorFn {
return (control: AbstractControl): ValidationErrors | null => {
const value = control.value?.trim();
if (value) {
- const result = this.STRING_FORMAT_REGEXP.test(value);
+ const result = regExp.test(value);
return !result ? { invalid_format: true } : null;
}
return null;
diff --git a/modules/ui/src/app/pages/devices/device-repository.component.html b/modules/ui/src/app/pages/devices/device-repository.component.html
index efd61fc2e..6dc585f5e 100644
--- a/modules/ui/src/app/pages/devices/device-repository.component.html
+++ b/modules/ui/src/app/pages/devices/device-repository.component.html
@@ -30,21 +30,21 @@ Devices
-
-
-
-
-
-
+
+
+
+
+
-
-
- add
- Add device
-
-
+
+
+ add
+ Add device
+
+
+
diff --git a/modules/ui/src/app/pages/devices/device-repository.component.spec.ts b/modules/ui/src/app/pages/devices/device-repository.component.spec.ts
index dc6279081..98c923d91 100644
--- a/modules/ui/src/app/pages/devices/device-repository.component.spec.ts
+++ b/modules/ui/src/app/pages/devices/device-repository.component.spec.ts
@@ -136,7 +136,7 @@ describe('DeviceRepositoryComponent', () => {
device: null,
title: 'Create device',
testModules: MOCK_TEST_MODULES,
- devices: [],
+ devices: [device, device, device],
},
autoFocus: true,
hasBackdrop: true,
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 f8fea286b..3d8ac231a 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
@@ -162,7 +162,7 @@ export class FilterDialogComponent
private createFilterForm() {
this.filterForm = this.fb.group({
deviceInfo: ['', [this.deviceValidators.deviceStringFormat()]],
- deviceFirmware: ['', [this.deviceValidators.deviceStringFormat()]],
+ deviceFirmware: ['', [this.deviceValidators.firmwareStringFormat()]],
results: new FormArray(this.resultList.map(() => new FormControl(false))),
});
}
diff --git a/modules/ui/src/app/pages/reports/history.component.scss b/modules/ui/src/app/pages/reports/history.component.scss
index cc4f56bd0..e8c8850d4 100644
--- a/modules/ui/src/app/pages/reports/history.component.scss
+++ b/modules/ui/src/app/pages/reports/history.component.scss
@@ -35,12 +35,10 @@
overflow-y: auto;
border-radius: 4px;
border: 1px solid $lighter-grey;
- height: -webkit-fit-content;
- height: -moz-fit-content;
- height: fit-content;
- max-height: -webkit-fill-available;
- max-height: -moz-available;
- max-height: stretch;
+ height: -webkit-max-content;
+ height: -moz-max-content;
+ height: max-content;
+ max-height: calc(100vh - 160px);
}
.history-content-empty,
@@ -48,6 +46,11 @@
height: 100%;
}
+.history-content {
+ container-type: inline-size;
+ container-name: history-content;
+}
+
.history-content table {
th {
font-weight: 700;
@@ -95,6 +98,27 @@
}
}
+@container history-content (width < 770px) {
+ th,
+ td {
+ padding: 0 8px;
+ }
+}
+
+@container history-content (width < 680px) {
+ th,
+ td {
+ padding: 0 4px;
+ }
+}
+
+@container history-content (width < 632px) {
+ th,
+ td {
+ padding: 0 2px;
+ }
+}
+
.results-content-empty,
.results-content-filter-empty {
display: flex;
diff --git a/modules/ui/src/app/pages/reports/history.component.ts b/modules/ui/src/app/pages/reports/history.component.ts
index f4ae52e0d..7f5dc01f1 100644
--- a/modules/ui/src/app/pages/reports/history.component.ts
+++ b/modules/ui/src/app/pages/reports/history.component.ts
@@ -164,7 +164,7 @@ export class HistoryComponent implements OnInit, OnDestroy {
removeDevice(data: TestrunStatus) {
this.store.deleteReport({
- mac_addr: data.device.mac_addr,
+ mac_addr: data.mac_addr,
started: data.started,
});
this.focusNextButton();
diff --git a/modules/ui/src/app/pages/reports/reports.store.ts b/modules/ui/src/app/pages/reports/reports.store.ts
index 23c63b86e..a457b358b 100644
--- a/modules/ui/src/app/pages/reports/reports.store.ts
+++ b/modules/ui/src/app/pages/reports/reports.store.ts
@@ -226,8 +226,7 @@ export class ReportsStore extends ComponentStore {
) {
const history = current;
const idx = history.findIndex(
- report =>
- report.device.mac_addr === mac_addr && report.started === started
+ report => report.mac_addr === mac_addr && report.started === started
);
if (typeof idx === 'number') {
history.splice(idx, 1);
diff --git a/modules/ui/src/app/pages/settings/settings.store.spec.ts b/modules/ui/src/app/pages/settings/settings.store.spec.ts
index 603c688b8..4e1fb5379 100644
--- a/modules/ui/src/app/pages/settings/settings.store.spec.ts
+++ b/modules/ui/src/app/pages/settings/settings.store.spec.ts
@@ -322,7 +322,7 @@ describe('SettingsStore', () => {
});
expect((form.get(FormKey.LOG_LEVEL) as FormControl).value).toEqual({
key: 'INFO',
- value: '',
+ value: 'Normal events and issues',
});
expect(
(form.get(FormKey.MONITOR_PERIOD) as FormControl).value
diff --git a/modules/ui/src/app/pages/settings/settings.store.ts b/modules/ui/src/app/pages/settings/settings.store.ts
index eededce2c..b98175c72 100644
--- a/modules/ui/src/app/pages/settings/settings.store.ts
+++ b/modules/ui/src/app/pages/settings/settings.store.ts
@@ -48,11 +48,11 @@ export const DEFAULT_INTERNET_OPTION = {
};
export const LOG_LEVELS = {
- DEBUG: '',
- INFO: '',
- WARNING: '',
- ERROR: '',
- CRITICAL: '',
+ DEBUG: 'Every event will be logged',
+ INFO: 'Normal events and issues',
+ WARNING: 'Warnings, errors, critical issues',
+ ERROR: 'Errors and critical problems',
+ CRITICAL: 'Critical problems',
};
export const MONITORING_PERIOD = {
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
new file mode 100644
index 000000000..491151ac2
--- /dev/null
+++ b/modules/ui/src/app/pages/testrun/components/download-options/download-options.component.html
@@ -0,0 +1,43 @@
+
+
+
+
+
+ lab_profile
+
+ {{ DownloadOption.PDF }}
+
+
+
+ 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
new file mode 100644
index 000000000..195a11839
--- /dev/null
+++ b/modules/ui/src/app/pages/testrun/components/download-options/download-options.component.scss
@@ -0,0 +1,91 @@
+/**
+ * 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 'node_modules/@angular/material/index' as mat;
+@import 'src/theming/colors';
+
+$option-width: 170px;
+$option-height: 36px;
+
+.download-options-field {
+ width: $option-width;
+ background: mat.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;
+ height: $option-height;
+ width: $option-width;
+ min-height: $option-height;
+ }
+
+ ::ng-deep.mat-mdc-select-placeholder {
+ color: mat.get-color-from-palette($color-primary, 700);
+ font-size: 14px;
+ font-style: normal;
+ font-weight: 500;
+ line-height: 20px;
+ letter-spacing: 0.25px;
+ }
+
+ ::ng-deep.mat-mdc-select-arrow {
+ color: mat.get-color-from-palette($color-primary, 700);
+ }
+
+ ::ng-deep .mat-mdc-text-field-wrapper .mdc-notched-outline > * {
+ border-color: mat.get-color-from-palette($color-primary, 50);
+ }
+
+ ::ng-deep
+ .mat-mdc-text-field-wrapper.mdc-text-field--focused
+ .mdc-notched-outline
+ > * {
+ border-color: #000000de;
+ }
+
+ &:has(.download-options-select[aria-expanded='true'])
+ ::ng-deep
+ .mat-mdc-text-field-wrapper.mdc-text-field--focused
+ .mdc-notched-outline
+ > * {
+ border: none;
+ }
+
+ ::ng-deep
+ .mat-mdc-text-field-wrapper.mdc-text-field--outlined:hover
+ .mdc-notched-outline
+ > * {
+ border: none;
+ }
+}
+
+.download-option {
+ ::ng-deep .mat-mdc-focus-indicator {
+ display: none;
+ }
+}
+
+.download-option {
+ min-height: 32px;
+ color: $grey-800;
+}
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
new file mode 100644
index 000000000..c28c453c1
--- /dev/null
+++ b/modules/ui/src/app/pages/testrun/components/download-options/download-options.component.spec.ts
@@ -0,0 +1,78 @@
+/**
+ * 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 {
+ DownloadOption,
+ DownloadOptionsComponent,
+} from './download-options.component';
+import { MOCK_PROGRESS_DATA_COMPLIANT } from '../../../../mocks/progress.mock';
+import { NoopAnimationsModule } from '@angular/platform-browser/animations';
+import { MatOptionSelectionChange } from '@angular/material/core';
+
+describe('DownloadOptionsComponent', () => {
+ let component: DownloadOptionsComponent;
+ let fixture: ComponentFixture;
+
+ beforeEach(async () => {
+ await TestBed.configureTestingModule({
+ imports: [DownloadOptionsComponent, NoopAnimationsModule],
+ }).compileComponents();
+
+ fixture = TestBed.createComponent(DownloadOptionsComponent);
+ component = fixture.componentInstance;
+ component.data = MOCK_PROGRESS_DATA_COMPLIANT;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+
+ it('#onSelected should call getReportTitle', () => {
+ const spyGetReportTitle = spyOn(component, 'getReportTitle');
+
+ const mockEvent = {
+ source: {},
+ isUserInput: true,
+ } as MatOptionSelectionChange;
+
+ component.onSelected(
+ mockEvent,
+ MOCK_PROGRESS_DATA_COMPLIANT,
+ DownloadOption.PDF
+ );
+
+ expect(spyGetReportTitle).toHaveBeenCalled();
+ });
+
+ it('#onSelected should call getZipLink when using for zip report', () => {
+ const spyGetZipLink = spyOn(component, 'getZipLink');
+
+ const mockEvent = {
+ source: {},
+ isUserInput: true,
+ } as MatOptionSelectionChange;
+
+ component.onSelected(
+ mockEvent,
+ MOCK_PROGRESS_DATA_COMPLIANT,
+ DownloadOption.ZIP
+ );
+
+ expect(spyGetZipLink).toHaveBeenCalled();
+ });
+});
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
new file mode 100644
index 000000000..847feb30e
--- /dev/null
+++ b/modules/ui/src/app/pages/testrun/components/download-options/download-options.component.ts
@@ -0,0 +1,86 @@
+/**
+ * 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, Input } 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 { TestrunStatus } from '../../../../model/testrun-status';
+import { MatOptionSelectionChange } from '@angular/material/core';
+
+export enum DownloadOption {
+ PDF = 'PDF Report',
+ ZIP = 'ZIP File',
+}
+@Component({
+ selector: 'app-download-options',
+ templateUrl: './download-options.component.html',
+ styleUrl: './download-options.component.scss',
+ standalone: true,
+ imports: [
+ CommonModule,
+ FormsModule,
+ MatIconModule,
+ MatFormFieldModule,
+ MatSelectModule,
+ ],
+ providers: [DatePipe],
+ changeDetection: ChangeDetectionStrategy.OnPush,
+})
+export class DownloadOptionsComponent {
+ @Input() data!: TestrunStatus;
+ DownloadOption = DownloadOption;
+ constructor(private datePipe: DatePipe) {}
+
+ onSelected(
+ event: MatOptionSelectionChange,
+ data: TestrunStatus,
+ type: string
+ ) {
+ if (event.isUserInput) {
+ this.createLink(data, type);
+ }
+ }
+
+ createLink(data: TestrunStatus, type: string) {
+ if (!data.report) {
+ return;
+ }
+ const link = document.createElement('a');
+ link.href =
+ type === DownloadOption.PDF ? data.report : this.getZipLink(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) {
+ return `${data.device.manufacturer} ${data.device.model} ${
+ data.device.firmware
+ } ${data.status} ${this.getFormattedDateString(data.started)}`
+ .replace(/ /g, '_')
+ .toLowerCase();
+ }
+
+ getFormattedDateString(date: string | null) {
+ return date ? this.datePipe.transform(date, 'd MMM y H:mm') : '';
+ }
+}
diff --git a/modules/ui/src/app/pages/testrun/components/progress-initiate-form/progress-initiate-form.component.ts b/modules/ui/src/app/pages/testrun/components/progress-initiate-form/progress-initiate-form.component.ts
index cf323ec11..754d0ea5b 100644
--- a/modules/ui/src/app/pages/testrun/components/progress-initiate-form/progress-initiate-form.component.ts
+++ b/modules/ui/src/app/pages/testrun/components/progress-initiate-form/progress-initiate-form.component.ts
@@ -149,7 +149,7 @@ export class ProgressInitiateFormComponent
private createInitiateForm() {
this.initiateForm = this.fb.group({
- firmware: ['', [this.deviceValidators.deviceStringFormat()]],
+ firmware: ['', [this.deviceValidators.firmwareStringFormat()]],
test_modules: new FormArray([]),
});
}
diff --git a/modules/ui/src/app/pages/testrun/components/progress-status-card/progress-status-card.component.html b/modules/ui/src/app/pages/testrun/components/progress-status-card/progress-status-card.component.html
index 971a58430..fb4a404d6 100644
--- a/modules/ui/src/app/pages/testrun/components/progress-status-card/progress-status-card.component.html
+++ b/modules/ui/src/app/pages/testrun/components/progress-status-card/progress-status-card.component.html
@@ -59,13 +59,24 @@
>
-
+
+
+
+
+
diff --git a/modules/ui/src/app/pages/testrun/components/progress-status-card/progress-status-card.component.spec.ts b/modules/ui/src/app/pages/testrun/components/progress-status-card/progress-status-card.component.spec.ts
index 9d7248656..de7341897 100644
--- a/modules/ui/src/app/pages/testrun/components/progress-status-card/progress-status-card.component.spec.ts
+++ b/modules/ui/src/app/pages/testrun/components/progress-status-card/progress-status-card.component.spec.ts
@@ -67,6 +67,11 @@ describe('ProgressStatusCardComponent', () => {
StatusOfTestrun.CompliantHigh,
];
+ const statusesForCompletedFailedClass = [
+ StatusOfTestrun.NonCompliant,
+ StatusOfTestrun.Error,
+ ];
+
statusesForProgressClass.forEach(testCase => {
it(`should have class "progress" if status "${testCase}"`, () => {
const expectedResult = { ...availableClasses, progress: true };
@@ -90,15 +95,17 @@ describe('ProgressStatusCardComponent', () => {
});
});
- it('should have class "completed-failed" if status "Non Compliant"', () => {
- const expectedResult = {
- ...availableClasses,
- 'completed-failed': true,
- };
+ statusesForCompletedFailedClass.forEach(testCase => {
+ it(`should have class "completed-failed" if status "${testCase}"`, () => {
+ const expectedResult = {
+ ...availableClasses,
+ 'completed-failed': true,
+ };
- const result = component.getClass(StatusOfTestrun.NonCompliant);
+ const result = component.getClass(testCase);
- expect(result).toEqual(expectedResult);
+ expect(result).toEqual(expectedResult);
+ });
});
it('should have class "canceled" if status "Cancelled"', () => {
@@ -311,6 +318,24 @@ describe('ProgressStatusCardComponent', () => {
});
});
+ describe('with available systemStatus$ data, as "In Progress" and finish date', () => {
+ beforeEach(() => {
+ component.systemStatus$ = of({
+ ...MOCK_PROGRESS_DATA_IN_PROGRESS,
+ finished: '2023-06-22T09:26:00.123Z',
+ });
+ fixture.detectChanges();
+ });
+
+ it('should not have progress card result', () => {
+ const progressCardResultEl = compiled.querySelector(
+ '.progress-card-result-text span'
+ );
+
+ expect(progressCardResultEl).toBeNull();
+ });
+ });
+
describe('with available systemStatus$ data, as Waiting for Device', () => {
beforeEach(() => {
component.systemStatus$ = of(MOCK_PROGRESS_DATA_WAITING_FOR_DEVICE);
diff --git a/modules/ui/src/app/pages/testrun/components/progress-status-card/progress-status-card.component.ts b/modules/ui/src/app/pages/testrun/components/progress-status-card/progress-status-card.component.ts
index 8105fdca6..14f7aec72 100644
--- a/modules/ui/src/app/pages/testrun/components/progress-status-card/progress-status-card.component.ts
+++ b/modules/ui/src/app/pages/testrun/components/progress-status-card/progress-status-card.component.ts
@@ -46,7 +46,9 @@ export class ProgressStatusCardComponent {
status === StatusOfTestrun.CompliantLimited ||
status === StatusOfTestrun.CompliantHigh ||
status === StatusOfTestrun.SmartReady,
- 'completed-failed': status === StatusOfTestrun.NonCompliant,
+ 'completed-failed':
+ status === StatusOfTestrun.NonCompliant ||
+ status === StatusOfTestrun.Error,
canceled:
status === StatusOfTestrun.Cancelled ||
status === StatusOfTestrun.Cancelling,
diff --git a/modules/ui/src/app/pages/testrun/progress.component.html b/modules/ui/src/app/pages/testrun/progress.component.html
index d2a28724e..d832c47ef 100644
--- a/modules/ui/src/app/pages/testrun/progress.component.html
+++ b/modules/ui/src/app/pages/testrun/progress.component.html
@@ -42,15 +42,9 @@
mat-flat-button>
Stop
-
-
- Download Report
-
-
+
diff --git a/modules/ui/src/app/pages/testrun/progress.component.spec.ts b/modules/ui/src/app/pages/testrun/progress.component.spec.ts
index ee1362fa1..22c4fcc52 100644
--- a/modules/ui/src/app/pages/testrun/progress.component.spec.ts
+++ b/modules/ui/src/app/pages/testrun/progress.component.spec.ts
@@ -46,7 +46,6 @@ import { IResult, TestrunStatus } from '../../model/testrun-status';
import { MatDialogModule, MatDialogRef } from '@angular/material/dialog';
import { BehaviorSubject } from 'rxjs/internal/BehaviorSubject';
import { ProgressInitiateFormComponent } from './components/progress-initiate-form/progress-initiate-form.component';
-import { DownloadReportComponent } from '../../components/download-report/download-report.component';
import { DeleteFormComponent } from '../../components/delete-form/delete-form.component';
import { SpinnerComponent } from '../../components/spinner/spinner.component';
import { LoaderService } from '../../services/loader.service';
@@ -54,7 +53,6 @@ import { FocusManagerService } from '../../services/focus-manager.service';
import { MockStore, provideMockStore } from '@ngrx/store/testing';
import { AppState } from '../../store/state';
import { selectDevices, selectHasDevices } from '../../store/selectors';
-import { DownloadReportPdfComponent } from '../../components/download-report-pdf/download-report-pdf.component';
describe('ProgressComponent', () => {
let component: ProgressComponent;
@@ -93,6 +91,7 @@ describe('ProgressComponent', () => {
ProgressComponent,
FakeProgressStatusCardComponent,
FakeProgressTableComponent,
+ FakeDownloadOptionsComponent,
],
providers: [
{ provide: TestRunService, useValue: testRunServiceMock },
@@ -114,7 +113,6 @@ describe('ProgressComponent', () => {
MatToolbarModule,
MatDialogModule,
SpinnerComponent,
- DownloadReportPdfComponent,
],
})
.overrideComponent(ProgressComponent, {
@@ -325,6 +323,7 @@ describe('ProgressComponent', () => {
ProgressComponent,
FakeProgressStatusCardComponent,
FakeProgressTableComponent,
+ FakeDownloadOptionsComponent,
],
providers: [
{ provide: TestRunService, useValue: testRunServiceMock },
@@ -345,9 +344,7 @@ describe('ProgressComponent', () => {
MatIconModule,
MatToolbarModule,
MatDialogModule,
- DownloadReportComponent,
SpinnerComponent,
- DownloadReportPdfComponent,
],
})
.overrideComponent(ProgressComponent, {
@@ -484,10 +481,10 @@ describe('ProgressComponent', () => {
expect(startBtn.disabled).toBeTrue();
});
- it('should not have "Download Report" button', () => {
- const reportBtn = compiled.querySelector('.report-button');
+ it('should not have "Download" options', () => {
+ const downloadComp = compiled.querySelector('app-download-options');
- expect(reportBtn).toBeNull();
+ expect(downloadComp).toBeNull();
});
});
@@ -524,21 +521,10 @@ describe('ProgressComponent', () => {
expect(startBtn.disabled).toBeFalse();
});
- it('should have "Download Report" button', () => {
- const reportBtn = compiled.querySelector('.report-button');
+ it('should have "Download" options', () => {
+ const downloadComp = compiled.querySelector('app-download-options');
- expect(reportBtn).not.toBeNull();
- });
-
- it('should have report link', () => {
- const link = compiled.querySelector(
- '.download-report-link'
- ) as HTMLAnchorElement;
-
- expect(link.href).toEqual('https://api.testrun.io/report.pdf');
- expect(link.download).toEqual(
- 'delta_03-din-cpu_1.2.2_compliant_22_jun_2023_9:20'
- );
+ expect(downloadComp).not.toBeNull();
});
});
@@ -557,10 +543,10 @@ describe('ProgressComponent', () => {
expect(startBtn.disabled).toBeFalse();
});
- it('should not have "Download Report" button', () => {
- const reportBtn = compiled.querySelector('.report-button');
+ it('should not have "Download" options', () => {
+ const downloadComp = compiled.querySelector('app-download-options');
- expect(reportBtn).toBeNull();
+ expect(downloadComp).toBeNull();
});
});
@@ -645,3 +631,11 @@ class FakeProgressStatusCardComponent {
class FakeProgressTableComponent {
@Input() dataSource$!: Observable;
}
+
+@Component({
+ selector: 'app-download-options',
+ template: '
',
+})
+class FakeDownloadOptionsComponent {
+ @Input() data!: TestrunStatus;
+}
diff --git a/modules/ui/src/app/pages/testrun/progress.module.ts b/modules/ui/src/app/pages/testrun/progress.module.ts
index c61eb4a4c..f46bf6199 100644
--- a/modules/ui/src/app/pages/testrun/progress.module.ts
+++ b/modules/ui/src/app/pages/testrun/progress.module.ts
@@ -34,7 +34,7 @@ import { DeviceTestsComponent } from '../../components/device-tests/device-tests
import { DownloadReportComponent } from '../../components/download-report/download-report.component';
import { SpinnerComponent } from '../../components/spinner/spinner.component';
import { CalloutComponent } from '../../components/callout/callout.component';
-import { DownloadReportPdfComponent } from '../../components/download-report-pdf/download-report-pdf.component';
+import { DownloadOptionsComponent } from './components/download-options/download-options.component';
@NgModule({
declarations: [
@@ -59,7 +59,7 @@ import { DownloadReportPdfComponent } from '../../components/download-report-pdf
DownloadReportComponent,
SpinnerComponent,
CalloutComponent,
- DownloadReportPdfComponent,
+ DownloadOptionsComponent,
],
})
export class ProgressModule {}
diff --git a/modules/ui/src/app/providers/window.provider.ts b/modules/ui/src/app/providers/window.provider.ts
new file mode 100644
index 000000000..3902e6eb9
--- /dev/null
+++ b/modules/ui/src/app/providers/window.provider.ts
@@ -0,0 +1,27 @@
+import { FactoryProvider, InjectionToken } from '@angular/core';
+
+/**
+ * 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.
+ */
+export const WINDOW = new InjectionToken('window');
+
+export const WindowProvider: FactoryProvider = {
+ provide: WINDOW,
+ useFactory: getWindow,
+};
+
+export function getWindow(): Window {
+ return window;
+}
diff --git a/modules/ui/src/app/services/test-run.service.spec.ts b/modules/ui/src/app/services/test-run.service.spec.ts
index f6a6294cf..2068c2b80 100644
--- a/modules/ui/src/app/services/test-run.service.spec.ts
+++ b/modules/ui/src/app/services/test-run.service.spec.ts
@@ -79,7 +79,7 @@ describe('TestRunService', () => {
},
{
displayName: 'Services',
- name: 'nmap',
+ name: 'services',
enabled: true,
},
{
@@ -200,6 +200,19 @@ describe('TestRunService', () => {
req.flush({});
});
+ it('#shutdownTestrun should have necessary request data', () => {
+ const apiUrl = 'http://localhost:8000/system/shutdown';
+
+ service.shutdownTestrun().subscribe(res => {
+ expect(res).toEqual(true);
+ });
+
+ const req = httpTestingController.expectOne(apiUrl);
+ expect(req.request.method).toBe('POST');
+ expect(req.request.body).toEqual({});
+ req.flush({});
+ });
+
describe('#startTestRun', () => {
it('should have necessary request data', () => {
const apiUrl = 'http://localhost:8000/system/start';
@@ -271,7 +284,7 @@ describe('TestRunService', () => {
];
const statusesForGreyRes = [
- StatusOfTestResult.Skipped,
+ StatusOfTestResult.NotPresent,
StatusOfTestResult.NotStarted,
];
diff --git a/modules/ui/src/app/services/test-run.service.ts b/modules/ui/src/app/services/test-run.service.ts
index 0ee0a03e9..72f578571 100644
--- a/modules/ui/src/app/services/test-run.service.ts
+++ b/modules/ui/src/app/services/test-run.service.ts
@@ -60,7 +60,7 @@ export class TestRunService {
},
{
displayName: 'Services',
- name: 'nmap',
+ name: 'services',
enabled: true,
},
{
@@ -118,14 +118,15 @@ export class TestRunService {
* @param isCancelling - indicates if status should be overridden with Cancelling value
*/
getSystemStatus(isCancelling?: boolean): void {
- this.http
- .get(`${API_URL}/system/status`)
- .subscribe((res: TestrunStatus) => {
+ this.http.get(`${API_URL}/system/status`).subscribe(
+ (res: TestrunStatus) => {
if (isCancelling && res.status !== StatusOfTestrun.Cancelled) {
res.status = StatusOfTestrun.Cancelling;
}
this.setSystemStatus(res);
- });
+ },
+ err => console.error('HTTP Error', err)
+ );
}
stopTestrun(): Observable {
@@ -134,6 +135,12 @@ export class TestRunService {
.pipe(map(() => true));
}
+ shutdownTestrun(): Observable {
+ return this.http
+ .post<{ success: string }>(`${API_URL}/system/shutdown`, {})
+ .pipe(map(() => true));
+ }
+
getTestModules(): TestModule[] {
return this.testModules;
}
@@ -184,7 +191,7 @@ export class TestRunService {
result === StatusOfTestResult.Info ||
result === StatusOfTestResult.InProgress,
grey:
- result === StatusOfTestResult.Skipped ||
+ result === StatusOfTestResult.NotPresent ||
result === StatusOfTestResult.NotStarted,
};
}
diff --git a/modules/ui/src/assets/icons/device_run.svg b/modules/ui/src/assets/icons/device_run.svg
new file mode 100644
index 000000000..ec3693133
--- /dev/null
+++ b/modules/ui/src/assets/icons/device_run.svg
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/modules/ui/src/styles.scss b/modules/ui/src/styles.scss
index dcb47d83d..778f7cedf 100644
--- a/modules/ui/src/styles.scss
+++ b/modules/ui/src/styles.scss
@@ -41,6 +41,15 @@ body {
background: $dark-grey;
}
+.button-start {
+ &:hover,
+ &:focus-visible {
+ .button-start-icon path {
+ fill: $white;
+ }
+ }
+}
+
.app-sidebar-button-active .mat-icon path {
fill: $white;
}
@@ -98,6 +107,7 @@ mat-hint {
.mdc-button__label {
position: relative;
left: -999px;
+ display: none;
}
}
@@ -105,6 +115,7 @@ mat-hint {
.app-sidebar-button {
.mdc-button__label {
left: 8px;
+ display: inline;
}
}
diff --git a/modules/ui/src/theming/colors.scss b/modules/ui/src/theming/colors.scss
index db007cf16..a54b7e9ab 100644
--- a/modules/ui/src/theming/colors.scss
+++ b/modules/ui/src/theming/colors.scss
@@ -23,6 +23,7 @@ $color-background-grey: #f8f9fa;
$dark-grey: #444746;
$grey-700: #5f6368;
$grey-800: #3c4043;
+$grey-900: #202124;
$light-grey: #bdc1c6;
$lighter-grey: #dadce0;
$green-50: #e6f4ea;
diff --git a/testing/pylint/test_pylint b/testing/pylint/test_pylint
index a330d54c3..9e9074aa7 100755
--- a/testing/pylint/test_pylint
+++ b/testing/pylint/test_pylint
@@ -14,14 +14,14 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-ERROR_LIMIT=0
+ERROR_LIMIT=78
sudo cmd/install
source venv/bin/activate
sudo pip3 install pylint==3.0.3
-files=$(find ./framework -path ./venv -prune -o -name '*.py' -print)
+files=$(find ./ -path ./venv -prune -o -name '*.py' -print)
OUT=pylint.out
diff --git a/testing/tests/test_tests.py b/testing/tests/test_tests.py
index a14afb2cb..895b63ec0 100644
--- a/testing/tests/test_tests.py
+++ b/testing/tests/test_tests.py
@@ -11,7 +11,6 @@
# 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.
-
""" Test assertions for CI testing of tests """
# Temporarily disabled because using Pytest fixtures
# TODO refactor fixtures to not trigger error
@@ -29,6 +28,7 @@
TEST_MATRIX = 'test_tests.json'
RESULTS_PATH = '/tmp/results/*.json'
+
#TODO add reason
@dataclass(frozen=True)
class TestResult:
@@ -73,19 +73,23 @@ def test_tests(results, test_matrix):
actual = set(collect_actual_results(results[tester]))
assert expected & actual == expected
-def test_list_tests(capsys, results, test_matrix):
- all_tests = set(itertools.chain.from_iterable(
- [collect_actual_results(results[x]) for x in results.keys()]))
- ci_pass = set([test
- for testers in test_matrix.values()
- for test, result in testers['expected_results'].items()
- if result == 'Compliant'])
-
- ci_fail = set([test
- for testers in test_matrix.values()
- for test, result in testers['expected_results'].items()
- if result == 'Non-Compliant'])
+def test_list_tests(capsys, results, test_matrix):
+ all_tests = set(
+ itertools.chain.from_iterable(
+ [collect_actual_results(results[x]) for x in results.keys()]))
+
+ ci_pass = set([
+ test for testers in test_matrix.values()
+ for test, result in testers['expected_results'].items()
+ if result == 'Compliant'
+ ])
+
+ ci_fail = set([
+ test for testers in test_matrix.values()
+ for test, result in testers['expected_results'].items()
+ if result == 'Non-Compliant'
+ ])
with capsys.disabled():
#TODO print matching the JSON schema for easy copy/paste
@@ -101,12 +105,15 @@ def test_list_tests(capsys, results, test_matrix):
for tester in test_matrix.keys():
print(f'\n{tester}:')
print(' expected results:')
- for test in collect_expected_results(test_matrix[tester]['expected_results']):
+ for test in collect_expected_results(
+ test_matrix[tester]['expected_results']):
print(f' {test.name}: {test.result}')
print(' actual results:')
for test in collect_actual_results(results[tester]):
if test.name in test_matrix[tester]['expected_results']:
- print(f' {test.name}: {test.result} (exp: {test_matrix[tester]["expected_results"][test.name]})')
+ print(
+ f' {test.name}: {test.result} (exp: {test_matrix[tester]["expected_results"][test.name]})'
+ )
else:
print(f' {test.name}: {test.result}')
diff --git a/testing/unit/dns/dns_module_test.py b/testing/unit/dns/dns_module_test.py
index 52ec80b4d..6c3dec74d 100644
--- a/testing/unit/dns/dns_module_test.py
+++ b/testing/unit/dns/dns_module_test.py
@@ -22,18 +22,19 @@
# Define the directories
TEST_FILES_DIR = 'testing/unit/' + MODULE
-OUTPUT_DIR = os.path.join(TEST_FILES_DIR,'output/')
-REPORTS_DIR = os.path.join(TEST_FILES_DIR,'reports/')
-CAPTURES_DIR = os.path.join(TEST_FILES_DIR,'captures/')
+OUTPUT_DIR = os.path.join(TEST_FILES_DIR, 'output/')
+REPORTS_DIR = os.path.join(TEST_FILES_DIR, 'reports/')
+CAPTURES_DIR = os.path.join(TEST_FILES_DIR, 'captures/')
-LOCAL_REPORT = os.path.join(REPORTS_DIR,'dns_report_local.html')
-LOCAL_REPORT_NO_DNS = os.path.join(REPORTS_DIR,'dns_report_local_no_dns.html')
+LOCAL_REPORT = os.path.join(REPORTS_DIR, 'dns_report_local.html')
+LOCAL_REPORT_NO_DNS = os.path.join(REPORTS_DIR, 'dns_report_local_no_dns.html')
CONF_FILE = 'modules/test/' + MODULE + '/conf/module_config.json'
# Define the capture files to be used for the test
-DNS_SERVER_CAPTURE_FILE = os.path.join(CAPTURES_DIR,'dns.pcap')
-STARTUP_CAPTURE_FILE = os.path.join(CAPTURES_DIR,'startup.pcap')
-MONITOR_CAPTURE_FILE = os.path.join(CAPTURES_DIR,'monitor.pcap')
+DNS_SERVER_CAPTURE_FILE = os.path.join(CAPTURES_DIR, 'dns.pcap')
+STARTUP_CAPTURE_FILE = os.path.join(CAPTURES_DIR, 'startup.pcap')
+MONITOR_CAPTURE_FILE = os.path.join(CAPTURES_DIR, 'monitor.pcap')
+
class TLSModuleTest(unittest.TestCase):
"""Contains and runs all the unit tests concerning DNS behaviors"""
@@ -49,9 +50,9 @@ def dns_module_report_test(self):
log_dir=OUTPUT_DIR,
conf_file=CONF_FILE,
results_dir=OUTPUT_DIR,
- DNS_SERVER_CAPTURE_FILE=DNS_SERVER_CAPTURE_FILE,
- STARTUP_CAPTURE_FILE=STARTUP_CAPTURE_FILE,
- MONITOR_CAPTURE_FILE=MONITOR_CAPTURE_FILE)
+ dns_server_capture_file=DNS_SERVER_CAPTURE_FILE,
+ startup_capture_file=STARTUP_CAPTURE_FILE,
+ monitor_capture_file=MONITOR_CAPTURE_FILE)
report_out_path = dns_module.generate_module_report()
@@ -61,7 +62,7 @@ def dns_module_report_test(self):
formatted_report = self.add_formatting(report_out)
# Write back the new formatted_report value
- out_report_path = os.path.join(OUTPUT_DIR,'dns_report_with_dns.html')
+ out_report_path = os.path.join(OUTPUT_DIR, 'dns_report_with_dns.html')
with open(out_report_path, 'w', encoding='utf-8') as file:
file.write(formatted_report)
@@ -105,9 +106,9 @@ def dns_module_report_no_dns_test(self):
log_dir=OUTPUT_DIR,
conf_file=CONF_FILE,
results_dir=OUTPUT_DIR,
- DNS_SERVER_CAPTURE_FILE=dns_server_cap_file,
- STARTUP_CAPTURE_FILE=startup_cap_file,
- MONITOR_CAPTURE_FILE=monitor_cap_file)
+ dns_server_capture_file=dns_server_cap_file,
+ startup_capture_file=startup_cap_file,
+ monitor_capture_file=monitor_cap_file)
report_out_path = dns_module.generate_module_report()
@@ -117,7 +118,7 @@ def dns_module_report_no_dns_test(self):
formatted_report = self.add_formatting(report_out)
# Write back the new formatted_report value
- out_report_path = os.path.join(OUTPUT_DIR,'dns_report_no_dns.html')
+ out_report_path = os.path.join(OUTPUT_DIR, 'dns_report_no_dns.html')
with open(out_report_path, 'w', encoding='utf-8') as file:
file.write(formatted_report)
@@ -127,8 +128,7 @@ def dns_module_report_no_dns_test(self):
self.assertEqual(report_out, report_local)
-
- def add_formatting(self,body):
+ def add_formatting(self, body):
return f'''
@@ -138,6 +138,7 @@ def add_formatting(self,body):