diff --git a/.github/workflows/testing.yml b/.github/workflows/testing.yml
index 4071860f5..dcc5a2cfe 100644
--- a/.github/workflows/testing.yml
+++ b/.github/workflows/testing.yml
@@ -7,12 +7,13 @@ on:
jobs:
testrun_baseline:
+ permissions: {}
name: Baseline
runs-on: ubuntu-20.04
timeout-minutes: 20
steps:
- name: Checkout source
- uses: actions/checkout@v2.3.4
+ uses: actions/checkout@v4.1.1
- name: Install dependencies
shell: bash {0}
run: cmd/prepare
@@ -28,13 +29,14 @@ jobs:
run: testing/baseline/test_baseline
testrun_tests:
+ permissions: {}
name: Tests
runs-on: ubuntu-20.04
needs: testrun_baseline
timeout-minutes: 45
steps:
- name: Checkout source
- uses: actions/checkout@v2.3.4
+ uses: actions/checkout@v4.1.1
- name: Install dependencies
shell: bash {0}
run: cmd/prepare
@@ -51,20 +53,21 @@ jobs:
if: ${{ always() }}
run: sudo tar --exclude-vcs -czf runtime.tgz /usr/local/testrun/runtime/
- name: Upload runtime results
- uses: actions/upload-artifact@v3
+ uses: actions/upload-artifact@v4
if: ${{ always() }}
with:
if-no-files-found: error
- name: runtime_${{ github.workflow }}_${{ github.run_id }}
+ name: runtime_tests_${{ github.run_id }}
path: runtime.tgz
testrun_api:
+ permissions: {}
name: API
runs-on: ubuntu-20.04
timeout-minutes: 40
steps:
- name: Checkout source
- uses: actions/checkout@v2.3.4
+ uses: actions/checkout@v4.1.1
- name: Install dependencies
shell: bash {0}
run: cmd/prepare
@@ -82,47 +85,50 @@ jobs:
if: ${{ always() }}
run: sudo tar --exclude-vcs -czf runtime.tgz /usr/local/testrun/runtime/ /usr/local/testrun/local/
- name: Upload runtime results
- uses: actions/upload-artifact@v3
+ uses: actions/upload-artifact@v4
if: ${{ always() }}
with:
if-no-files-found: error
- name: runtime_${{ github.workflow }}_${{ github.run_id }}
+ name: runtime_api_${{ github.run_id }}
path: runtime.tgz
pylint:
+ permissions: {}
name: Pylint
runs-on: ubuntu-22.04
timeout-minutes: 5
steps:
- name: Checkout source
- uses: actions/checkout@v2.3.4
+ uses: actions/checkout@v4.1.1
- name: Run pylint
shell: bash {0}
run: testing/pylint/test_pylint
testrun_package:
+ permissions: {}
name: Package
runs-on: ubuntu-22.04
timeout-minutes: 5
steps:
- name: Checkout source
- uses: actions/checkout@v2.3.4
+ uses: actions/checkout@v4.1.1
- name: Package Testrun
shell: bash {0}
run: cmd/package
- name: Archive package
- uses: actions/upload-artifact@v3
+ uses: actions/upload-artifact@v4
with:
- name: Testrun Installer
+ name: testrun_installer
path: testrun*.deb
testrun_ui:
+ permissions: {}
name: UI
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v3
+ - uses: actions/checkout@v4.1.1
- name: Install Node
- uses: actions/setup-node@v3
+ uses: actions/setup-node@v4
with:
node-version: 18.10.0
- name: Install Chromium Browser
diff --git a/.gitignore b/.gitignore
index 3f944ba34..bf3d0c7d1 100644
--- a/.gitignore
+++ b/.gitignore
@@ -6,4 +6,6 @@ pylint.out
__pycache__/
build/
testing/unit_test/temp/
-*.deb
\ No newline at end of file
+*.deb
+
+make/DEBIAN/postinst
\ No newline at end of file
diff --git a/cmd/install b/cmd/install
index 0b6ac92de..c4f53b905 100755
--- a/cmd/install
+++ b/cmd/install
@@ -28,6 +28,13 @@ pip3 install -r framework/requirements.txt
# Copy the default configuration
cp -n local/system.json.example local/system.json
+# Set file permissions
+# This does not work on GitHub actions
+if logname ; then
+ USER_NAME=$(logname)
+ sudo chown "$USER_NAME" local/system.json
+fi
+
deactivate
# Build docker images
diff --git a/cmd/package b/cmd/package
index 87e66d977..7084343d6 100755
--- a/cmd/package
+++ b/cmd/package
@@ -53,4 +53,4 @@ 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-1_amd64.deb
+mv make.deb testrun_1-1-1_amd64.deb
diff --git a/docs/get_started.md b/docs/get_started.md
index 70215ac62..b7165d930 100644
--- a/docs/get_started.md
+++ b/docs/get_started.md
@@ -49,9 +49,10 @@ However, to achieve a compliant test outcome, your device must be configured cor
- Connect one USB Ethernet adapter to the internet source (e.g., router or switch) using an ethernet cable.
- Connect the other USB Ethernet adapter directly to the IoT device you want to test using an ethernet cable.
- **NOTE: The device under test should be powered off until prompted**
-
- **NOTE: Both adapters should be disabled in the host system (IPv4, IPv6 and general). You can do this by going to Settings > Network**
+ Some things to remember:
+ - The device under test should be powered off until prompted
+ - Both adapters should be disabled in the host system (IPv4, IPv6 and general). You can do this by going to Settings > Network
+ - Struggling to identify the correct interfaces? See [this guide](network/identify_interfaces.md).
2. Start Testrun.
diff --git a/docs/network/README.md b/docs/network/README.md
index 2d66d3e6a..601ed1349 100644
--- a/docs/network/README.md
+++ b/docs/network/README.md
@@ -2,8 +2,9 @@
## Table of Contents
1) Network Overview (this page)
-2) [Addresses](addresses.md)
-3) [Add a new network service](add_new_service.md)
+2) [How to identify network interfaces](identify_interfaces.md)
+3) [Addresses](addresses.md)
+4) [Add a new network service](add_new_service.md)
Test Run provides several built-in network services that can be utilized for testing purposes. These services are already available and can be used without any additional configuration.
diff --git a/docs/network/identify_interfaces.md b/docs/network/identify_interfaces.md
new file mode 100644
index 000000000..6baddb6ca
--- /dev/null
+++ b/docs/network/identify_interfaces.md
@@ -0,0 +1,18 @@
+# Identifying network interfaces
+
+For Testrun to operate correctly, you must select the correct network interfaces within the settings panel of the user interface. There are 2 methods to identify the correct network interfaces:
+
+A) Find the printed MAC address on your interface
+
+Some USB network interfaces will have the MAC address printed on the interface itself. This will look something like: ```00:e0:4c:02:0f:a8```.
+
+Compare this printed MAC address against the MAC address provided in the settings panel in the user interface.
+
+B) Connect your interfaces one at a time
+
+ 1) Ensure both interfaces are disconnected from your PC and open the settings panel in the user interface.
+
+ 2) Connect your internet interface to your PC and refresh the settings panel. One interface, which was not previously present, should now be visibile.
+
+ 3) Repeat the previous step for the devices interface.
+
diff --git a/framework/python/src/api/api.py b/framework/python/src/api/api.py
index 305310e7e..ebd022acf 100644
--- a/framework/python/src/api/api.py
+++ b/framework/python/src/api/api.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.
-
+"""Provides Testrun data via REST API."""
from fastapi import FastAPI, APIRouter, Response, Request, status
from fastapi.responses import FileResponse
from fastapi.middleware.cors import CORSMiddleware
@@ -37,7 +37,8 @@
DEVICES_PATH = "/usr/local/testrun/local/devices"
DEFAULT_DEVICE_INTF = "enx123456789123"
-LATEST_RELEASE_CHECK = "https://api.github.com/repos/google/testrun/releases/latest"
+LATEST_RELEASE_CHECK = ("https://api.github.com/repos/google/" +
+ "testrun/releases/latest")
class Api:
"""Provide REST endpoints to manage Testrun"""
@@ -77,6 +78,9 @@ def __init__(self, test_run):
self.delete_device,
methods=["DELETE"])
self._router.add_api_route("/device", self.save_device, methods=["POST"])
+ self._router.add_api_route("/device/edit",
+ self.edit_device,
+ methods=["POST"])
# Allow all origins to access the API
origins = ["*"]
@@ -111,14 +115,17 @@ def stop(self):
async def get_sys_interfaces(self):
addrs = psutil.net_if_addrs()
- ifaces = []
- for iface in addrs:
+ ifaces = {}
+
+ # pylint: disable=consider-using-dict-items
+ for key in addrs.keys():
+ nic = addrs[key]
# Ignore any interfaces that are not ethernet
- if not (iface.startswith("en") or iface.startswith("eth")):
+ if not (key.startswith("en") or key.startswith("eth")):
continue
- ifaces.append(iface)
+ ifaces[key] = nic[0].address
return ifaces
@@ -240,32 +247,38 @@ async def get_version(self, response: Response):
json_response["installed_version"] = "v" + current_version
# Check latest version number from GitHub API
- version_check = requests.get(LATEST_RELEASE_CHECK, timeout=5)
+ try:
+ version_check = requests.get(LATEST_RELEASE_CHECK, timeout=5)
+
+ # 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")
+
+ # Extract version number from response, removing the leading 'v'
+ latest_version_no = version_check.json()["name"].strip("v")
+ LOGGER.debug(f"Latest version available is {latest_version_no}")
+
+ # Craft JSON response
+ json_response["latest_version"] = "v" + latest_version_no
+ json_response["latest_version_url"] = version_check.json()["html_url"]
+
+ # String comparison between current and latest version
+ if latest_version_no > current_version:
+ json_response["update_available"] = True
+ LOGGER.debug("An update is available")
+ else:
+ json_response["update_available"] = False
+ LOGGER.debug("The latest version is installed")
- # Check OK response was received
- if version_check.status_code != 200:
+ return json_response
+ except Exception as e:
response.status_code = 500
- LOGGER.error(version_check.content)
+ LOGGER.error("Failed to fetch latest version")
+ LOGGER.debug(e)
return self._generate_msg(False, "Failed to fetch latest version")
- # Extract version number from response, removing the leading 'v'
- latest_version_no = version_check.json()["name"].strip("v")
- LOGGER.debug(f"Latest version available is {latest_version_no}")
-
- # Craft JSON response
- json_response["latest_version"] = "v" + latest_version_no
- json_response["latest_version_url"] = version_check.json()["html_url"]
-
- # String comparison between current and latest version
- if latest_version_no > current_version:
- json_response["update_available"] = True
- LOGGER.debug("An update is available")
- else:
- json_response["update_available"] = False
- LOGGER.debug("The latest version is installed")
-
- return json_response
-
async def get_reports(self, request: Request):
LOGGER.debug("Received reports list request")
# Resolve the server IP from the request so we
@@ -389,8 +402,76 @@ async def save_device(self, request: Request, response: Response):
else:
- self._test_run.save_device(device, device_json)
- response.status_code = status.HTTP_200_OK
+ response.status_code = status.HTTP_409_CONFLICT
+ return self._generate_msg(False, "A device with that " +
+ "MAC address already exists")
+
+ return device.to_config_json()
+
+ # Catch JSON Decode error etc
+ except JSONDecodeError:
+ response.status_code = status.HTTP_400_BAD_REQUEST
+ return self._generate_msg(False, "Invalid JSON received")
+
+ async def edit_device(self, request: Request, response: Response):
+
+ LOGGER.debug("Received device edit request")
+
+ try:
+ req_raw = (await request.body()).decode("UTF-8")
+ req_json = json.loads(req_raw)
+
+ # Validate top level fields
+ if not (DEVICE_MAC_ADDR_KEY in req_json and
+ "device" in req_json):
+ response.status_code = status.HTTP_400_BAD_REQUEST
+ return self._generate_msg(False, "Invalid request received")
+
+ # Extract device information from request
+ device_json = req_json.get("device")
+
+ if not self._validate_device_json(device_json):
+ response.status_code = status.HTTP_400_BAD_REQUEST
+ return self._generate_msg(False, "Invalid request received")
+
+ # Get device from old MAC address
+ device = self._session.get_device(req_json.get(DEVICE_MAC_ADDR_KEY))
+
+ # Check if device exists
+ if device is None:
+ response.status_code = status.HTTP_404_NOT_FOUND
+ return self._generate_msg(False,
+ "A device with that MAC " +
+ "address could not be found")
+
+ if (self._session.get_target_device() == device and
+ self._session.get_status() not in [
+ "Cancelled",
+ "Compliant",
+ "Non-Compliant"]):
+ response.status_code = 403
+ return self._generate_msg(False, "Cannot edit this device whilst " +
+ "it is being tested")
+
+ # Check if a device exists with the new MAC address
+ check_new_device = self._session.get_device(
+ device_json.get(DEVICE_MAC_ADDR_KEY))
+
+ if not check_new_device is None and (device.mac_addr
+ != check_new_device.mac_addr):
+ response.status_code = status.HTTP_409_CONFLICT
+ return self._generate_msg(False,
+ "A device with that MAC address " +
+ "already exists")
+
+ # Update the device
+ device.mac_addr = device_json.get(DEVICE_MAC_ADDR_KEY).lower()
+ device.manufacturer = device_json.get(DEVICE_MANUFACTURER_KEY)
+ device.model = device_json.get(DEVICE_MODEL_KEY)
+ device.test_modules = device_json.get(DEVICE_TEST_MODULES_KEY)
+
+ self._test_run.save_device(device, device_json)
+ response.status_code = status.HTTP_200_OK
return device.to_config_json()
@@ -399,6 +480,7 @@ async def save_device(self, request: Request, response: Response):
response.status_code = status.HTTP_400_BAD_REQUEST
return self._generate_msg(False, "Invalid JSON received")
+
async def get_report(self, response: Response,
device_name, timestamp):
diff --git a/framework/python/src/common/session.py b/framework/python/src/common/session.py
index 70442e9ff..c9ce524c3 100644
--- a/framework/python/src/common/session.py
+++ b/framework/python/src/common/session.py
@@ -42,6 +42,7 @@ def __init__(self, config_file):
self._runtime_params = []
self._device_repository = []
self._total_tests = 0
+ self._report_url = None
self._version = None
self._load_version()
@@ -231,9 +232,16 @@ def add_total_tests(self, no_tests):
def get_total_tests(self):
return self._total_tests
+ def get_report_url(self):
+ return self._report_url
+
+ def set_report_url(self, url):
+ self._report_url = url
+
def reset(self):
self.set_status('Idle')
self.set_target_device(None)
+ self._report_url = None
self._total_tests = 0
self._results = []
self._started = None
@@ -241,8 +249,6 @@ def reset(self):
def to_json(self):
- # TODO: Add report URL
-
results = {
'total': self.get_total_tests(),
'results': self.get_test_results()
@@ -256,6 +262,9 @@ def to_json(self):
'tests': results
}
+ if self._report_url is not None:
+ session_json['report'] = self.get_report_url()
+
return session_json
def get_timezone(self):
diff --git a/framework/python/src/common/testreport.py b/framework/python/src/common/testreport.py
index 0ac4cb9a6..25df0e511 100644
--- a/framework/python/src/common/testreport.py
+++ b/framework/python/src/common/testreport.py
@@ -75,6 +75,12 @@ def get_duration(self):
def add_test(self, test):
self._results.append(test)
+ def set_report_url(self, url):
+ self._report_url = url
+
+ def get_report_url(self):
+ return self._report_url
+
def to_json(self):
report_json = {}
report_json['device'] = self._device
@@ -182,7 +188,7 @@ def generate_pages(self, json_data):
def generate_page(self, json_data, page_num, max_page):
# Placeholder until available in json report
- version = 'v1.1 (2023-12-15)'
+ version = 'v1.1.1 (2024-01-31)'
page = '
'
page += self.generate_header(json_data)
if page_num == 1:
diff --git a/framework/python/src/common/util.py b/framework/python/src/common/util.py
index 098c478d3..ebc3d3d70 100644
--- a/framework/python/src/common/util.py
+++ b/framework/python/src/common/util.py
@@ -32,21 +32,21 @@ def run_command(cmd, output=True):
by any return code from the process other than zero."""
success = False
- process = subprocess.Popen(shlex.split(cmd),
+ with subprocess.Popen(shlex.split(cmd),
stdout=subprocess.PIPE,
- stderr=subprocess.PIPE)
- stdout, stderr = process.communicate()
-
- if process.returncode != 0 and output:
- err_msg = f'{stderr.strip()}. Code: {process.returncode}'
- LOGGER.error('Command failed: ' + cmd)
- LOGGER.error('Error: ' + err_msg)
- else:
- success = True
- if output:
- return stdout.strip().decode('utf-8'), stderr
- else:
- return success
+ stderr=subprocess.PIPE) as process:
+ stdout, stderr = process.communicate()
+
+ if process.returncode != 0 and output:
+ err_msg = f'{stderr.strip()}. Code: {process.returncode}'
+ LOGGER.error('Command failed: ' + cmd)
+ LOGGER.error('Error: ' + err_msg)
+ else:
+ success = True
+ if output:
+ return stdout.strip().decode('utf-8'), stderr
+ else:
+ return success
def interface_exists(interface):
diff --git a/framework/python/src/core/testrun.py b/framework/python/src/core/testrun.py
index 48e31c721..f579eba26 100644
--- a/framework/python/src/core/testrun.py
+++ b/framework/python/src/core/testrun.py
@@ -36,6 +36,8 @@
from net_orc import network_orchestrator as net_orc
from test_orc import test_orchestrator as test_orc
+from docker.errors import ImageNotFound
+
# Locate parent directory
current_dir = os.path.dirname(os.path.realpath(__file__))
@@ -430,16 +432,22 @@ def start_ui(self):
client = docker.from_env()
- client.containers.run(
- image='test-run/ui',
- auto_remove=True,
- name='tr-ui',
- hostname='testrun.io',
- detach=True,
- ports={
- '80': 8080
- }
- )
+ try:
+ client.containers.run(
+ image='test-run/ui',
+ auto_remove=True,
+ name='tr-ui',
+ hostname='testrun.io',
+ detach=True,
+ ports={
+ '80': 8080
+ }
+ )
+ except ImageNotFound as ie:
+ LOGGER.error('An error occured whilst starting the UI. ' +
+ 'Please investigate and try again.')
+ LOGGER.error(ie)
+ sys.exit(1)
# TODO: Make port configurable
LOGGER.info('User interface is ready on http://localhost:8080')
diff --git a/framework/python/src/net_orc/network_validator.py b/framework/python/src/net_orc/network_validator.py
index 3866bd3ae..6673a1fdb 100644
--- a/framework/python/src/net_orc/network_validator.py
+++ b/framework/python/src/net_orc/network_validator.py
@@ -240,7 +240,8 @@ def _attach_device_to_network(self, device):
mac_addr = TR_CONTAINER_MAC_PREFIX + '10'
- util.run_command('ip link set dev ' + container_intf + ' address ' + mac_addr)
+ util.run_command('ip link set dev ' + container_intf +
+ ' address ' + mac_addr)
# Add bridge interface to device bridge
util.run_command('ovs-vsctl add-port ' + DEVICE_BRIDGE + ' ' + bridge_intf)
diff --git a/framework/python/src/test_orc/test_orchestrator.py b/framework/python/src/test_orc/test_orchestrator.py
index 21b8650bb..d9b25108d 100644
--- a/framework/python/src/test_orc/test_orchestrator.py
+++ b/framework/python/src/test_orc/test_orchestrator.py
@@ -115,6 +115,7 @@ def run_test_modules(self):
self._write_reports(report)
self._test_in_progress = False
self._timestamp_results(device)
+ self.get_session().set_report_url(report.get_report_url())
LOGGER.debug("Cleaning old test results...")
self._cleanup_old_test_results(device)
@@ -234,6 +235,7 @@ def _timestamp_results(self, device):
device.mac_addr.replace(":", "")
)
+ # Define the directory
completed_results_dir = os.path.join(
self._root_path,
LOCAL_DEVICE_REPORTS.replace("{device_folder}", device.device_folder),
@@ -246,6 +248,8 @@ def _timestamp_results(self, device):
shutil.copytree(cur_results_dir, completed_results_dir, dirs_exist_ok=True)
util.run_command(f"chown -R {self._host_user} '{completed_results_dir}'")
+ return completed_results_dir
+
def test_in_progress(self):
return self._test_in_progress
diff --git a/make/DEBIAN/control b/make/DEBIAN/control
index c5af91918..6fa63afeb 100644
--- a/make/DEBIAN/control
+++ b/make/DEBIAN/control
@@ -1,5 +1,5 @@
Package: Testrun
-Version: 1.1
+Version: 1.1.1
Architecture: amd64
Maintainer: Google
Homepage: https://github.com/google/testrun
diff --git a/make/DEBIAN/postinst b/make/DEBIAN/postinst
deleted file mode 100755
index 0b6ac92de..000000000
--- a/make/DEBIAN/postinst
+++ /dev/null
@@ -1,36 +0,0 @@
-#!/bin/bash -e
-
-# 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.
-
-echo Installing application dependencies
-
-TESTRUN_DIR=/usr/local/testrun
-cd $TESTRUN_DIR
-
-python3 -m venv venv
-
-source venv/bin/activate
-
-pip3 install -r framework/requirements.txt
-
-# Copy the default configuration
-cp -n local/system.json.example local/system.json
-
-deactivate
-
-# Build docker images
-sudo cmd/build
-
-echo Finished installing Testrun
diff --git a/modules/network/base/bin/capture b/modules/network/base/bin/capture
index 59ffb4118..224d4e0c5 100644
--- a/modules/network/base/bin/capture
+++ b/modules/network/base/bin/capture
@@ -38,7 +38,7 @@ fi
# Create the output directory and start the capture
mkdir -p $PCAP_DIR
chown $HOST_USER $PCAP_DIR
-tcpdump -i $INTERFACE -w $PCAP_DIR/$PCAP_FILE -Z $HOST_USER &
+tcpdump -U -i $INTERFACE -w $PCAP_DIR/$PCAP_FILE -Z $HOST_USER &
#Small pause to let the capture to start
sleep 1
\ No newline at end of file
diff --git a/modules/network/dhcp-1/dhcp-1.Dockerfile b/modules/network/dhcp-1/dhcp-1.Dockerfile
index 8fc2d0630..e50ed9a95 100644
--- a/modules/network/dhcp-1/dhcp-1.Dockerfile
+++ b/modules/network/dhcp-1/dhcp-1.Dockerfile
@@ -24,9 +24,6 @@ RUN apt-get update --fix-missing
# Install all necessary packages
RUN apt-get install -y wget
-# Update the oui.txt file from ieee
-RUN wget http://standards-oui.ieee.org/oui.txt -P /usr/local/etc/
-
# Install dhcp server
RUN apt-get install -y isc-dhcp-server radvd systemd
diff --git a/modules/network/dhcp-2/dhcp-2.Dockerfile b/modules/network/dhcp-2/dhcp-2.Dockerfile
index 58256813f..66ea857c3 100644
--- a/modules/network/dhcp-2/dhcp-2.Dockerfile
+++ b/modules/network/dhcp-2/dhcp-2.Dockerfile
@@ -18,15 +18,12 @@ FROM test-run/base:latest
ARG MODULE_NAME=dhcp-2
ARG MODULE_DIR=modules/network/$MODULE_NAME
-#Update and get all additional requirements not contained in the base image
+# Update and get all additional requirements not contained in the base image
RUN apt-get update --fix-missing
# Install all necessary packages
RUN apt-get install -y wget
-# Update the oui.txt file from ieee
-RUN wget http://standards-oui.ieee.org/oui.txt -P /usr/local/etc/
-
# Install dhcp server
RUN apt-get install -y isc-dhcp-server radvd systemd
diff --git a/modules/test/base/base.Dockerfile b/modules/test/base/base.Dockerfile
index 340d0e983..878273055 100644
--- a/modules/test/base/base.Dockerfile
+++ b/modules/test/base/base.Dockerfile
@@ -22,7 +22,7 @@ ARG COMMON_DIR=framework/python/src/common
RUN apt-get update
# Install common software
-RUN DEBIAN_FRONTEND=noninteractive apt-get install -yq net-tools iputils-ping tzdata tcpdump iproute2 jq python3 python3-pip dos2unix nmap --fix-missing
+RUN DEBIAN_FRONTEND=noninteractive apt-get install -yq net-tools iputils-ping tzdata tcpdump iproute2 jq python3 python3-pip dos2unix nmap wget --fix-missing
# Install common python modules
COPY $COMMON_DIR/ /testrun/python/src/common
@@ -50,5 +50,8 @@ ARG CONTAINER_PROTO_DIR=testrun/python/src/grpc_server/proto
COPY $NET_MODULE_DIR/dhcp-1/$NET_MODULE_PROTO_DIR $CONTAINER_PROTO_DIR/dhcp1/
COPY $NET_MODULE_DIR/dhcp-2/$NET_MODULE_PROTO_DIR $CONTAINER_PROTO_DIR/dhcp2/
+# Update the oui.txt file from ieee
+RUN wget https://standards-oui.ieee.org/oui/oui.txt -P /usr/local/etc/
+
# Start the test module
ENTRYPOINT [ "/testrun/bin/start" ]
\ No newline at end of file
diff --git a/modules/test/base/bin/capture b/modules/test/base/bin/capture
index 69fa916c3..180b447cf 100644
--- a/modules/test/base/bin/capture
+++ b/modules/test/base/bin/capture
@@ -27,7 +27,7 @@ INTERFACE=$2
# Create the output directory and start the capture
mkdir -p $PCAP_DIR
chown $HOST_USER $PCAP_DIR
-tcpdump -i $INTERFACE -w $PCAP_DIR/$PCAP_FILE -Z $HOST_USER &
+tcpdump -U -i $INTERFACE -w $PCAP_DIR/$PCAP_FILE -Z $HOST_USER &
# Small pause to let the capture to start
sleep 1
\ No newline at end of file
diff --git a/modules/test/conn/conn.Dockerfile b/modules/test/conn/conn.Dockerfile
index af1921d69..a9f523e44 100644
--- a/modules/test/conn/conn.Dockerfile
+++ b/modules/test/conn/conn.Dockerfile
@@ -23,13 +23,10 @@ ARG GRPC_PROTO_FILE="grpc.proto"
# Install all necessary packages
RUN apt-get install -y wget
-# Update the oui.txt file from ieee
-RUN wget http://standards-oui.ieee.org/oui.txt -P /usr/local/etc/
-
-#Load the requirements file
+# Load the requirements file
COPY $MODULE_DIR/python/requirements.txt /testrun/python
-#Install all python requirements for the module
+# Install all python requirements for the module
RUN pip3 install -r /testrun/python/requirements.txt
# Copy over all configuration files
diff --git a/modules/test/conn/python/requirements.txt b/modules/test/conn/python/requirements.txt
index c2275b3e0..7244e9e75 100644
--- a/modules/test/conn/python/requirements.txt
+++ b/modules/test/conn/python/requirements.txt
@@ -1,2 +1,3 @@
pyOpenSSL
-scapy
\ No newline at end of file
+scapy
+python-dateutil
\ No newline at end of file
diff --git a/modules/test/conn/python/src/connection_module.py b/modules/test/conn/python/src/connection_module.py
index 3fc0e6765..9429541de 100644
--- a/modules/test/conn/python/src/connection_module.py
+++ b/modules/test/conn/python/src/connection_module.py
@@ -14,7 +14,6 @@
"""Connection test module"""
import util
import time
-import datetime
import traceback
from scapy.all import rdpcap, DHCP, Ether, IPv6, ICMPv6ND_NS
from test_module import TestModule
@@ -85,7 +84,8 @@ def _connection_shared_address(self, config):
def _connection_dhcp_address(self):
LOGGER.info('Running connection.dhcp_address')
- lease = self._dhcp_util.get_cur_lease(mac_address=self._device_mac, timeout=self._lease_wait_time_sec)
+ lease = self._dhcp_util.get_cur_lease(mac_address=self._device_mac,
+ timeout=self._lease_wait_time_sec)
if lease is not None:
if 'ip' in lease:
ip_addr = lease['ip']
@@ -176,7 +176,7 @@ def _connection_target_ping(self):
else:
return False, 'Device does not respond to ping'
- def _connection_ipaddr_ip_change(self,config):
+ def _connection_ipaddr_ip_change(self, config):
result = None
LOGGER.info('Running connection.ipaddr.ip_change')
# Resolve the configured lease wait time
@@ -184,7 +184,8 @@ def _connection_ipaddr_ip_change(self,config):
self._lease_wait_time_sec = config['lease_wait_time_sec']
if self._dhcp_util.setup_single_dhcp_server():
- lease = self._dhcp_util.get_cur_lease(mac_address=self._device_mac, timeout=self._lease_wait_time_sec)
+ lease = self._dhcp_util.get_cur_lease(mac_address=self._device_mac,
+ timeout=self._lease_wait_time_sec)
if lease is not None:
LOGGER.info('Current device lease resolved')
LOGGER.debug(str(lease))
@@ -192,7 +193,8 @@ def _connection_ipaddr_ip_change(self,config):
ip_address = '10.10.10.30'
if self._dhcp_util.add_reserved_lease(lease['hostname'],
lease['hw_addr'], ip_address):
- self._dhcp_util.wait_for_lease_expire(lease)
+ self._dhcp_util.wait_for_lease_expire(lease,
+ self._lease_wait_time_sec)
LOGGER.info('Checking device accepted new ip')
for _ in range(5):
LOGGER.info('Pinging device at IP: ' + ip_address)
@@ -216,12 +218,13 @@ def _connection_ipaddr_ip_change(self,config):
self._dhcp_util.restore_failover_dhcp_server()
LOGGER.info('Waiting 30 seconds for reserved lease to expire')
time.sleep(30)
- self._dhcp_util.get_cur_lease(mac_address=self._device_mac,timeout=self._lease_wait_time_sec)
+ self._dhcp_util.get_cur_lease(mac_address=self._device_mac,
+ timeout=self._lease_wait_time_sec)
else:
result = None, 'Failed to configure network for test'
return result
- def _connection_ipaddr_dhcp_failover(self,config):
+ def _connection_ipaddr_dhcp_failover(self, config):
result = None
LOGGER.info('Running connection.ipaddr.dhcp_failover')
@@ -235,17 +238,20 @@ def _connection_ipaddr_dhcp_failover(self,config):
secondary_status = self._dhcp_util.get_dhcp_server_status(
dhcp_server_primary=False)
if primary_status and secondary_status:
- lease = self._dhcp_util.get_cur_lease(mac_address=self._device_mac, timeout=self._lease_wait_time_sec)
+ lease = self._dhcp_util.get_cur_lease(mac_address=self._device_mac,
+ timeout=self._lease_wait_time_sec)
if lease is not None:
LOGGER.info('Current device lease resolved')
if self._dhcp_util.is_lease_active(lease):
# Shutdown the primary server
if self._dhcp_util.stop_dhcp_server(dhcp_server_primary=True):
# Wait until the current lease is expired
- self._dhcp_util.wait_for_lease_expire(lease)
+ self._dhcp_util.wait_for_lease_expire(lease,
+ self._lease_wait_time_sec)
# Make sure the device has received a new lease from the
# secondary server
- if self._dhcp_util.get_cur_lease(mac_address=self._device_mac,timeout=self._lease_wait_time_sec):
+ if self._dhcp_util.get_cur_lease(mac_address=self._device_mac,
+ timeout=self._lease_wait_time_sec):
if self._dhcp_util.is_lease_active(lease):
result = True, ('Secondary DHCP server lease confirmed active '
'in device')
@@ -294,9 +300,8 @@ def _connection_ipv6_slaac(self):
return result
def _has_slaac_addres(self):
- packet_capture = (rdpcap(STARTUP_CAPTURE_FILE)
- + rdpcap(MONITOR_CAPTURE_FILE)
- + rdpcap(DHCP_CAPTURE_FILE))
+ packet_capture = (rdpcap(STARTUP_CAPTURE_FILE) +
+ rdpcap(MONITOR_CAPTURE_FILE) + rdpcap(DHCP_CAPTURE_FILE))
sends_ipv6 = False
for packet_number, packet in enumerate(packet_capture, start=1):
if IPv6 in packet and packet.src == self._device_mac:
@@ -366,7 +371,8 @@ def setup_single_dhcp_server(self):
if response.code == 200:
LOGGER.info('Secondary DHCP server stopped')
LOGGER.info('Configuring primary DHCP server')
- # Move primary DHCP server from failover into a single DHCP server config
+ # Move primary DHCP server from failover into
+ # a single DHCP server config
response = self.dhcp1_client.disable_failover()
if response.code == 200:
LOGGER.info('Primary DHCP server failover disabled')
@@ -410,7 +416,7 @@ def _run_subnet_test(self, config):
# Resolve the configured lease wait time
if 'lease_wait_time_sec' in config:
- self._lease_wait_time_sec = config['lease_wait_time_sec']
+ self._lease_wait_time_sec = config['lease_wait_time_sec']
response = self.dhcp1_client.get_dhcp_range()
cur_range = {}
@@ -428,12 +434,13 @@ def _run_subnet_test(self, config):
dhcp_setup = self.setup_single_dhcp_server()
if dhcp_setup[0]:
LOGGER.info(dhcp_setup[1])
- lease = self._dhcp_util.get_cur_lease(mac_address=self._device_mac, timeout=self._lease_wait_time_sec)
+ lease = self._dhcp_util.get_cur_lease(mac_address=self._device_mac,
+ timeout=self._lease_wait_time_sec)
if lease is not None:
if self._dhcp_util.is_lease_active(lease):
results = self.test_subnets(ranges)
else:
- LOGGER.info("Failed to confirm a valid active lease for the device")
+ LOGGER.info('Failed to confirm a valid active lease for the device')
return None, 'Failed to confirm a valid active lease for the device'
else:
LOGGER.error(dhcp_setup[1])
@@ -459,15 +466,18 @@ def _run_subnet_test(self, config):
self.restore_failover_dhcp_server(cur_range)
# Wait for the current lease to expire
- self._wait_for_lease_expire(self._dhcp_util.get_cur_lease(
- self._device_mac,timeout=self._lease_wait_time_sec))
+ lease = self._dhcp_util.get_cur_lease(mac_address=self._device_mac,
+ timeout=self._lease_wait_time_sec)
+ self._dhcp_util.wait_for_lease_expire(lease, self._lease_wait_time_sec)
# Wait for a new lease to be provided before exiting test
# to prevent other test modules from failing
LOGGER.info('Checking for new lease')
# Subnet changes tend to take longer to pick up so we'll allow
# for twice the lease wait time
- lease = self._dhcp_util.get_cur_lease(mac_address=self._device_mac,timeout=2*self._lease_wait_time_sec)
+ lease = self._dhcp_util.get_cur_lease(mac_address=self._device_mac,
+ timeout=2 *
+ self._lease_wait_time_sec)
if lease is not None:
LOGGER.info('Validating subnet for new lease...')
in_range = self.is_ip_in_range(lease['ip'], cur_range['start'],
@@ -482,47 +492,28 @@ def _run_subnet_test(self, config):
return final_result, final_result_details
def _test_subnet(self, subnet, lease):
- LOGGER.info("Testing subnet: " + str(subnet))
+ LOGGER.info('Testing subnet: ' + str(subnet))
if self._change_subnet(subnet):
- expiration = datetime.datetime.strptime(
- lease['expires'], '%Y-%m-%d %H:%M:%S')
- now_utc = datetime.datetime.now(
- datetime.timezone.utc).replace(tzinfo=None)
- time_to_expire = (expiration - now_utc).total_seconds()
- LOGGER.debug('Time until lease expiration: ' + str(time_to_expire))
- LOGGER.info('Waiting for current lease to expire: ' + str(expiration))
- if time_to_expire > 0:
- # Wait until the expiration time and add 5 seconds
- time.sleep(time_to_expire + 5)
- LOGGER.debug('Current lease expired. Checking for new lease')
- LOGGER.debug('Checking for new lease')
- # Subnet changes tend to take longer to pick up so we'll allow
- # for twice the lease wait time
- lease = self._dhcp_util.get_cur_lease(mac_address=self._device_mac,timeout=2*self._lease_wait_time_sec)
- if lease is not None:
- LOGGER.debug('New lease found: ' + str(lease))
- LOGGER.debug('Validating subnet for new lease...')
- in_range = self.is_ip_in_range(lease['ip'], subnet['start'],
- subnet['end'])
- LOGGER.info('Lease within subnet: ' + str(in_range))
- return in_range
- else:
- LOGGER.info('Device did not receive lease in subnet')
- return False
+ self._dhcp_util.wait_for_lease_expire(lease, self._lease_wait_time_sec)
+ LOGGER.debug('Checking for new lease')
+ # Subnet changes tend to take longer to pick up so we'll allow
+ # for twice the lease wait time
+ lease = self._dhcp_util.get_cur_lease(mac_address=self._device_mac,
+ timeout=2 *
+ self._lease_wait_time_sec)
+ if lease is not None:
+ LOGGER.debug('New lease found: ' + str(lease))
+ LOGGER.debug('Validating subnet for new lease...')
+ in_range = self.is_ip_in_range(lease['ip'], subnet['start'],
+ subnet['end'])
+ LOGGER.info('Lease within subnet: ' + str(in_range))
+ return in_range
+ else:
+ LOGGER.info('Device did not receive lease in subnet')
+ return False
else:
LOGGER.error('Failed to change subnet')
- def _wait_for_lease_expire(self, lease):
- expiration = datetime.datetime.strptime(
- lease['expires'], '%Y-%m-%d %H:%M:%S')
- time_to_expire = expiration - datetime.datetime.now()
- LOGGER.info('Time until lease expiration: ' + str(time_to_expire))
- LOGGER.info('Waiting for current lease to expire: ' + str(expiration))
- if time_to_expire.total_seconds() > 0:
- time.sleep(time_to_expire.total_seconds() +
- 5) # Wait until the expiration time and padd 5 seconds
- LOGGER.info('Current lease expired.')
-
def _change_subnet(self, subnet):
LOGGER.info('Changing subnet to: ' + str(subnet))
response = self.dhcp1_client.set_dhcp_range(subnet['start'], subnet['end'])
@@ -549,25 +540,25 @@ def test_subnets(self, subnets):
result = self._test_subnet(subnet, lease)
if result:
result = {
- 'result':
- True,
- 'details':
- 'Subnet ' + subnet['start'] + '-' + subnet['end'] + ' passed'
+ 'result':
+ True,
+ 'details':
+ 'Subnet ' + subnet['start'] + '-' + subnet['end'] + ' passed'
}
else:
result = {
- 'result':
- False,
- 'details':
- 'Subnet ' + subnet['start'] + '-' + subnet['end'] + ' failed'
+ 'result':
+ False,
+ 'details':
+ 'Subnet ' + subnet['start'] + '-' + subnet['end'] + ' failed'
}
else:
result = {
- 'result':
- None,
- 'details':
- 'Device does not have active lease, cannot test subnet change. ' +
- 'Subnet ' + subnet['start'] + '-' + subnet['end'] + ' skipped'
+ 'result':
+ None,
+ 'details':
+ 'Device does not have active lease, cannot test subnet change. ' +
+ 'Subnet ' + subnet['start'] + '-' + subnet['end'] + ' skipped'
}
except Exception as e: # pylint: disable=W0718
LOGGER.error('Subnet test failed: ' + str(e))
diff --git a/modules/test/conn/python/src/dhcp_util.py b/modules/test/conn/python/src/dhcp_util.py
index 6d063d62a..be5f0cac2 100644
--- a/modules/test/conn/python/src/dhcp_util.py
+++ b/modules/test/conn/python/src/dhcp_util.py
@@ -17,6 +17,7 @@
import time
from datetime import datetime
import util
+from dateutil import tz
LOG_NAME = 'dhcp_util'
LOGGER = None
@@ -125,17 +126,22 @@ def get_cur_lease(self, mac_address, timeout):
Retrieve the current lease for a given MAC address with retries.
Args:
- mac_address (str): The MAC address of the client whose lease is being queried.
- timeout (int): The maximum time (in seconds) to wait for a lease to be found.
+ mac_address (str): The MAC address of the client whose
+ lease is being queried.
+ timeout (int): The maximum time (in seconds) to wait
+ for a lease to be found.
Returns:
- str or None: The lease information as a string if found, or None if no lease is found within the timeout.
+ str or None: The lease information as a string if found,
+ or None if no lease is found within the timeout.
Note:
- This method will attempt to query both primary and secondary DHCP servers for the lease,
- with a 5-second pause between retries until the `timeout` is reached.
+ This method will attempt to query both primary and secondary
+ DHCP servers for the lease, with a 5-second pause between
+ retries until the `timeout` is reached.
"""
- LOGGER.info('Resolving current lease with max wait time of ' + str(timeout) + ' seconds')
+ LOGGER.info('Resolving current lease with max wait time of ' +
+ str(timeout) + ' seconds')
start_time = time.time()
while True:
@@ -146,24 +152,27 @@ def get_cur_lease(self, mac_address, timeout):
def _get_cur_lease(self, mac_address):
"""
- Retrieve the current lease for a given MAC address from both primary and secondary DHCP servers.
+ Retrieve the current lease for a given MAC address from both
+ primary and secondary DHCP servers.
Args:
- mac_address (str): The MAC address of the client whose lease is being queried.
+ mac_address (str): The MAC address of the client whose
+ lease is being queried.
Returns:
- str or None: The lease information as a string if found, or None if no lease is found.
+ str or None: The lease information as a string if found,
+ or None if no lease is found.
"""
primary = False
lease = self._get_cur_lease_from_server(mac_address=mac_address,
dhcp_server_primary=True)
if lease is not None:
- primary=True
+ primary = True
else:
lease = self._get_cur_lease_from_server(mac_address=mac_address,
dhcp_server_primary=False)
if lease is not None:
- lease['primary']=primary
+ lease['primary'] = primary
log_msg = 'DHCP lease resolved from '
log_msg += 'primary' if lease['primary'] else 'secondary'
log_msg += ' server'
@@ -176,7 +185,8 @@ def _get_cur_lease_from_server(self, mac_address, dhcp_server_primary=True):
# Check if the server is online first, old lease files can still return
# lease information that is no longer valid when a dhcp server is shutdown
if self.get_dhcp_server_status(dhcp_server_primary):
- response = self.get_dhcp_client(dhcp_server_primary).get_lease(mac_address)
+ response = self.get_dhcp_client(dhcp_server_primary).get_lease(
+ mac_address)
if response.code == 200:
lease_resp = eval(response.message) # pylint: disable=W0123
if lease_resp: # Check if non-empty lease
@@ -245,12 +255,29 @@ def setup_single_dhcp_server(self):
LOGGER.error('Failed to stop secondary DHCP server')
return False
- def wait_for_lease_expire(self, lease):
- expiration = datetime.strptime(lease['expires'], '%Y-%m-%d %H:%M:%S')
- time_to_expire = expiration - datetime.now()
- LOGGER.info('Time until lease expiration: ' + str(time_to_expire))
+ def wait_for_lease_expire(self, lease, max_wait_time=30):
+ expiration_utc = datetime.strptime(lease['expires'], '%Y-%m-%d %H:%M:%S')
+ # lease information stored in UTC so we need to convert to local time
+ expiration = self.utc_to_local(expiration_utc)
+ time_to_expire = expiration - datetime.now(tz=tz.tzlocal())
+ # Wait until the expiration time and padd 5 seconds
+ # If wait time is longer than max_wait_time, only wait
+ # for the max wait time
+ wait_time = min(max_wait_time,
+ time_to_expire.total_seconds() +
+ 5) if time_to_expire.total_seconds() > 0 else 0
+ LOGGER.info('Time until lease expiration: ' + str(wait_time))
LOGGER.info('Waiting for current lease to expire: ' + str(expiration))
- if time_to_expire.total_seconds() > 0:
- time.sleep(time_to_expire.total_seconds() +
- 5) # Wait until the expiration time and padd 5 seconds
- LOGGER.info('Current lease expired.')
+ if wait_time > 0:
+ time.sleep(wait_time)
+ LOGGER.info('Current lease expired.')
+
+ # Convert from a UTC datetime to the local time zone
+ def utc_to_local(self, utc_datetime):
+ # Set the time zone for the UTC datetime
+ utc = utc_datetime.replace(tzinfo=tz.tzutc())
+
+ # Convert to local time zone
+ local_datetime = utc.astimezone(tz.tzlocal())
+
+ return local_datetime
diff --git a/modules/test/ntp/python/src/ntp_module.py b/modules/test/ntp/python/src/ntp_module.py
index 0e7de00ec..40089ad44 100644
--- a/modules/test/ntp/python/src/ntp_module.py
+++ b/modules/test/ntp/python/src/ntp_module.py
@@ -36,7 +36,9 @@ def __init__(self, module):
def _ntp_network_ntp_support(self):
LOGGER.info('Running ntp.network.ntp_support')
result = None
- packet_capture = rdpcap(STARTUP_CAPTURE_FILE) + rdpcap(MONITOR_CAPTURE_FILE)
+ packet_capture = (rdpcap(STARTUP_CAPTURE_FILE) +
+ rdpcap(MONITOR_CAPTURE_FILE) +
+ rdpcap(NTP_SERVER_CAPTURE_FILE))
device_sends_ntp4 = False
device_sends_ntp3 = False
@@ -71,7 +73,9 @@ def _ntp_network_ntp_support(self):
def _ntp_network_ntp_dhcp(self):
LOGGER.info('Running ntp.network.ntp_dhcp')
result = None
- packet_capture = rdpcap(STARTUP_CAPTURE_FILE) + rdpcap(MONITOR_CAPTURE_FILE)
+ packet_capture = (rdpcap(STARTUP_CAPTURE_FILE) +
+ rdpcap(MONITOR_CAPTURE_FILE) +
+ rdpcap(NTP_SERVER_CAPTURE_FILE))
device_sends_ntp = False
ntp_to_local = False
diff --git a/modules/ui/package-lock.json b/modules/ui/package-lock.json
index 8e90d3a71..f5a5b5e2f 100644
--- a/modules/ui/package-lock.json
+++ b/modules/ui/package-lock.json
@@ -8,44 +8,44 @@
"name": "test-run-ui",
"version": "0.0.0",
"dependencies": {
- "@angular/animations": "^16.2.10",
- "@angular/cdk": "^16.2.9",
- "@angular/common": "^16.2.10",
- "@angular/compiler": "^16.2.10",
- "@angular/core": "^16.2.10",
- "@angular/forms": "^16.2.10",
- "@angular/material": "^16.2.9",
- "@angular/platform-browser": "^16.2.10",
- "@angular/platform-browser-dynamic": "^16.2.10",
- "@angular/router": "^16.2.10",
- "ngx-mask": "^16.3.9",
+ "@angular/animations": "^16.2.12",
+ "@angular/cdk": "^16.2.13",
+ "@angular/common": "^16.2.12",
+ "@angular/compiler": "^16.2.12",
+ "@angular/core": "^16.2.12",
+ "@angular/forms": "^16.2.12",
+ "@angular/material": "^16.2.13",
+ "@angular/platform-browser": "^16.2.12",
+ "@angular/platform-browser-dynamic": "^16.2.12",
+ "@angular/router": "^16.2.12",
+ "ngx-mask": "^16.4.2",
"rxjs": "~7.8.0",
"tslib": "^2.6.2",
"zone.js": "~0.13.3"
},
"devDependencies": {
- "@angular-devkit/build-angular": "^16.2.6",
+ "@angular-devkit/build-angular": "^16.2.11",
"@angular-eslint/builder": "16.2.0",
"@angular-eslint/eslint-plugin": "16.2.0",
"@angular-eslint/eslint-plugin-template": "16.2.0",
"@angular-eslint/schematics": "16.2.0",
"@angular-eslint/template-parser": "16.2.0",
"@angular/cli": "~16.1.8",
- "@angular/compiler-cli": "^16.2.10",
+ "@angular/compiler-cli": "^16.2.12",
"@types/jasmine": "~4.3.6",
"@typescript-eslint/eslint-plugin": "5.62.0",
"@typescript-eslint/parser": "5.62.0",
- "eslint": "^8.49.0",
- "eslint-config-prettier": "^9.0.0",
- "eslint-plugin-prettier": "^5.0.1",
+ "eslint": "^8.56.0",
+ "eslint-config-prettier": "^9.1.0",
+ "eslint-plugin-prettier": "^5.1.3",
"jasmine-core": "~4.6.0",
"karma": "~6.4.0",
"karma-chrome-launcher": "~3.2.0",
"karma-coverage": "~2.2.0",
"karma-jasmine": "~5.1.0",
"karma-jasmine-html-reporter": "~2.1.0",
- "prettier": "^3.0.3",
- "prettier-eslint": "^16.1.1",
+ "prettier": "^3.1.1",
+ "prettier-eslint": "^16.2.0",
"typescript": "~5.1.3"
}
},
@@ -72,12 +72,12 @@
}
},
"node_modules/@angular-devkit/architect": {
- "version": "0.1602.6",
- "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1602.6.tgz",
- "integrity": "sha512-b1NNV3yNg6Rt86ms20bJIroWUI8ihaEwv5k+EoijEXLoMs4eNs5PhqL+QE8rTj+q9pa1gSrWf2blXor2JGwf1g==",
+ "version": "0.1602.11",
+ "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1602.11.tgz",
+ "integrity": "sha512-qC1tPL/82gxqCS1z9pTpLn5NQH6uqbV6UNjbkFEQpTwEyWEK6VLChAJsybHHfbpssPS2HWf31VoUzX7RqDjoQQ==",
"dev": true,
"dependencies": {
- "@angular-devkit/core": "16.2.6",
+ "@angular-devkit/core": "16.2.11",
"rxjs": "7.8.1"
},
"engines": {
@@ -87,15 +87,15 @@
}
},
"node_modules/@angular-devkit/build-angular": {
- "version": "16.2.6",
- "resolved": "https://registry.npmjs.org/@angular-devkit/build-angular/-/build-angular-16.2.6.tgz",
- "integrity": "sha512-QdU/q77K1P8CPEEZGxw1QqLcnA9ofboDWS7vcLRBmFmk2zydtLTApbK0P8GNDRbnmROOKkoaLo+xUTDJz9gvPA==",
+ "version": "16.2.11",
+ "resolved": "https://registry.npmjs.org/@angular-devkit/build-angular/-/build-angular-16.2.11.tgz",
+ "integrity": "sha512-yNzUiAeg1WHMsFG9IBg4S/7dsMcEAMYQ1I360ib80c0T/IwRb8pHhOokrl5Mu8zfNqZ/dxH4ItKY1uIMDmuMGQ==",
"dev": true,
"dependencies": {
"@ampproject/remapping": "2.2.1",
- "@angular-devkit/architect": "0.1602.6",
- "@angular-devkit/build-webpack": "0.1602.6",
- "@angular-devkit/core": "16.2.6",
+ "@angular-devkit/architect": "0.1602.11",
+ "@angular-devkit/build-webpack": "0.1602.11",
+ "@angular-devkit/core": "16.2.11",
"@babel/core": "7.22.9",
"@babel/generator": "7.22.9",
"@babel/helper-annotate-as-pure": "7.22.5",
@@ -107,7 +107,7 @@
"@babel/runtime": "7.22.6",
"@babel/template": "7.22.5",
"@discoveryjs/json-ext": "0.5.7",
- "@ngtools/webpack": "16.2.6",
+ "@ngtools/webpack": "16.2.11",
"@vitejs/plugin-basic-ssl": "1.0.1",
"ansi-colors": "4.1.3",
"autoprefixer": "10.4.14",
@@ -150,7 +150,7 @@
"text-table": "0.2.0",
"tree-kill": "1.2.2",
"tslib": "2.6.1",
- "vite": "4.4.7",
+ "vite": "4.5.1",
"webpack": "5.88.2",
"webpack-dev-middleware": "6.1.1",
"webpack-dev-server": "4.15.1",
@@ -215,12 +215,12 @@
"dev": true
},
"node_modules/@angular-devkit/build-webpack": {
- "version": "0.1602.6",
- "resolved": "https://registry.npmjs.org/@angular-devkit/build-webpack/-/build-webpack-0.1602.6.tgz",
- "integrity": "sha512-BJPR6xdq7gRJ6bVWnZ81xHyH75j7lyLbegCXbvUNaM8TWVBkwWsSdqr2NQ717dNLLn5umg58SFpU/pWMq6CxMQ==",
+ "version": "0.1602.11",
+ "resolved": "https://registry.npmjs.org/@angular-devkit/build-webpack/-/build-webpack-0.1602.11.tgz",
+ "integrity": "sha512-2Au6xRMxNugFkXP0LS1TwNE5gAfGW4g6yxC9P5j5p3kdGDnAVaZRTOKB9dg73i3uXtJHUMciYOThV0b78XRxwA==",
"dev": true,
"dependencies": {
- "@angular-devkit/architect": "0.1602.6",
+ "@angular-devkit/architect": "0.1602.11",
"rxjs": "7.8.1"
},
"engines": {
@@ -234,9 +234,9 @@
}
},
"node_modules/@angular-devkit/core": {
- "version": "16.2.6",
- "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-16.2.6.tgz",
- "integrity": "sha512-iez/8NYXQT6fqVQLlKmZUIRkFUEZ88ACKbTwD4lBmk0+hXW+bQBxI7JOnE3C4zkcM2YeuTXIYsC5SebTKYiR4Q==",
+ "version": "16.2.11",
+ "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-16.2.11.tgz",
+ "integrity": "sha512-u3cEQHqhSMWyAFIaPdRukCJwEUJt7Fy3C02gTlTeCB4F/OnftVFIm2e5vmCqMo9rgbfdvjWj9V+7wWiCpMrzAQ==",
"dev": true,
"dependencies": {
"ajv": "8.12.0",
@@ -443,9 +443,9 @@
}
},
"node_modules/@angular/animations": {
- "version": "16.2.10",
- "resolved": "https://registry.npmjs.org/@angular/animations/-/animations-16.2.10.tgz",
- "integrity": "sha512-UudunZoyFWWNpuWkwiBxC3cleLCVJGHIfMgypFwC35YjtiIlRJ0r4nVkc96Rq1xd4mT71Dbk1kQHc8urB8A7aw==",
+ "version": "16.2.12",
+ "resolved": "https://registry.npmjs.org/@angular/animations/-/animations-16.2.12.tgz",
+ "integrity": "sha512-MD0ElviEfAJY8qMOd6/jjSSvtqER2RDAi0lxe6EtUacC1DHCYkaPrKW4vLqY+tmZBg1yf+6n+uS77pXcHHcA3w==",
"dependencies": {
"tslib": "^2.3.0"
},
@@ -453,13 +453,13 @@
"node": "^16.14.0 || >=18.10.0"
},
"peerDependencies": {
- "@angular/core": "16.2.10"
+ "@angular/core": "16.2.12"
}
},
"node_modules/@angular/cdk": {
- "version": "16.2.9",
- "resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-16.2.9.tgz",
- "integrity": "sha512-TrLV68YpddUx3t2rs8W29CPk8YkgNGA8PKHwjB4Xvo1yaEH5XUnsw3MQCh42Ee7FKseaqzFgG85USZXAK0IB0A==",
+ "version": "16.2.13",
+ "resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-16.2.13.tgz",
+ "integrity": "sha512-8kn2X2yesvgfIbCUNoS9EDjooIx9LwEglYBbD89Y/do8EeN/CC3Tn02gqSrEfgMhYBLBJmHXbfOhbDDvcvOCeg==",
"dependencies": {
"tslib": "^2.3.0"
},
@@ -581,9 +581,9 @@
"dev": true
},
"node_modules/@angular/common": {
- "version": "16.2.10",
- "resolved": "https://registry.npmjs.org/@angular/common/-/common-16.2.10.tgz",
- "integrity": "sha512-cLth66aboInNcWFjDBRmK30jC5KN10nKDDcv4U/r3TDTBpKOtnmTjNFFr7dmjfUmVhHFy/66piBMfpjZI93Rxg==",
+ "version": "16.2.12",
+ "resolved": "https://registry.npmjs.org/@angular/common/-/common-16.2.12.tgz",
+ "integrity": "sha512-B+WY/cT2VgEaz9HfJitBmgdk4I333XG/ybC98CMC4Wz8E49T8yzivmmxXB3OD6qvjcOB6ftuicl6WBqLbZNg2w==",
"dependencies": {
"tslib": "^2.3.0"
},
@@ -591,14 +591,14 @@
"node": "^16.14.0 || >=18.10.0"
},
"peerDependencies": {
- "@angular/core": "16.2.10",
+ "@angular/core": "16.2.12",
"rxjs": "^6.5.3 || ^7.4.0"
}
},
"node_modules/@angular/compiler": {
- "version": "16.2.10",
- "resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-16.2.10.tgz",
- "integrity": "sha512-ty6SfqkZlV2bLU/SSi3wmxrEFgPrK+WVslCNIr3FlTnCBdqpIbadHN2QB3A1d9XaNc7c4Tq5DQKh34cwMwNbuw==",
+ "version": "16.2.12",
+ "resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-16.2.12.tgz",
+ "integrity": "sha512-6SMXUgSVekGM7R6l1Z9rCtUGtlg58GFmgbpMCsGf+VXxP468Njw8rjT2YZkf5aEPxEuRpSHhDYjqz7n14cwCXQ==",
"dependencies": {
"tslib": "^2.3.0"
},
@@ -606,7 +606,7 @@
"node": "^16.14.0 || >=18.10.0"
},
"peerDependencies": {
- "@angular/core": "16.2.10"
+ "@angular/core": "16.2.12"
},
"peerDependenciesMeta": {
"@angular/core": {
@@ -615,9 +615,9 @@
}
},
"node_modules/@angular/compiler-cli": {
- "version": "16.2.10",
- "resolved": "https://registry.npmjs.org/@angular/compiler-cli/-/compiler-cli-16.2.10.tgz",
- "integrity": "sha512-swgmtm4R23vQV9nJTXdDEFpOyIw3kz80mdT9qo3VId/2rqenOK253JsFypoqEj/fKzjV9gwXtTbmrMlhVyuyxw==",
+ "version": "16.2.12",
+ "resolved": "https://registry.npmjs.org/@angular/compiler-cli/-/compiler-cli-16.2.12.tgz",
+ "integrity": "sha512-pWSrr152562ujh6lsFZR8NfNc5Ljj+zSTQO44DsuB0tZjwEpnRcjJEgzuhGXr+CoiBf+jTSPZKemtSktDk5aaA==",
"dev": true,
"dependencies": {
"@babel/core": "7.23.2",
@@ -638,7 +638,7 @@
"node": "^16.14.0 || >=18.10.0"
},
"peerDependencies": {
- "@angular/compiler": "16.2.10",
+ "@angular/compiler": "16.2.12",
"typescript": ">=4.9.3 <5.2"
}
},
@@ -688,12 +688,12 @@
}
},
"node_modules/@angular/compiler-cli/node_modules/@babel/generator": {
- "version": "7.23.0",
- "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.0.tgz",
- "integrity": "sha512-lN85QRR+5IbYrMWM6Y4pE/noaQtg4pNiqeNGX60eqOfo6gtEj6uw/JagelB8vVztSd7R6M5n1+PQkDbHbBRU4g==",
+ "version": "7.23.6",
+ "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.6.tgz",
+ "integrity": "sha512-qrSfCYxYQB5owCmGLbl8XRpX1ytXlpueOb0N0UmQwA073KZxejgQTzAmJezxvpwQD9uGtK2shHdi55QT+MbjIw==",
"dev": true,
"dependencies": {
- "@babel/types": "^7.23.0",
+ "@babel/types": "^7.23.6",
"@jridgewell/gen-mapping": "^0.3.2",
"@jridgewell/trace-mapping": "^0.3.17",
"jsesc": "^2.5.1"
@@ -717,9 +717,9 @@
}
},
"node_modules/@angular/core": {
- "version": "16.2.10",
- "resolved": "https://registry.npmjs.org/@angular/core/-/core-16.2.10.tgz",
- "integrity": "sha512-0XTsPjNflFhOl2CfNEdGeDOklG2t+m/D3g10Y7hg9dBjC1dURUEqTmM4d6J7JNbBURrP+/iP7uLsn3WRSipGUw==",
+ "version": "16.2.12",
+ "resolved": "https://registry.npmjs.org/@angular/core/-/core-16.2.12.tgz",
+ "integrity": "sha512-GLLlDeke/NjroaLYOks0uyzFVo6HyLl7VOm0K1QpLXnYvW63W9Ql/T3yguRZa7tRkOAeFZ3jw+1wnBD4O8MoUA==",
"dependencies": {
"tslib": "^2.3.0"
},
@@ -732,9 +732,9 @@
}
},
"node_modules/@angular/forms": {
- "version": "16.2.10",
- "resolved": "https://registry.npmjs.org/@angular/forms/-/forms-16.2.10.tgz",
- "integrity": "sha512-TZliEtSWIL1UzY8kjed4QcMawWS8gk/H60KVgzCh83NGE0wd1OGv20Z5OR7O8j07dxB9vaxY7CQz/8eCz5KaNQ==",
+ "version": "16.2.12",
+ "resolved": "https://registry.npmjs.org/@angular/forms/-/forms-16.2.12.tgz",
+ "integrity": "sha512-1Eao89hlBgLR3v8tU91vccn21BBKL06WWxl7zLpQmG6Hun+2jrThgOE4Pf3os4fkkbH4Apj0tWL2fNIWe/blbw==",
"dependencies": {
"tslib": "^2.3.0"
},
@@ -742,16 +742,16 @@
"node": "^16.14.0 || >=18.10.0"
},
"peerDependencies": {
- "@angular/common": "16.2.10",
- "@angular/core": "16.2.10",
- "@angular/platform-browser": "16.2.10",
+ "@angular/common": "16.2.12",
+ "@angular/core": "16.2.12",
+ "@angular/platform-browser": "16.2.12",
"rxjs": "^6.5.3 || ^7.4.0"
}
},
"node_modules/@angular/material": {
- "version": "16.2.9",
- "resolved": "https://registry.npmjs.org/@angular/material/-/material-16.2.9.tgz",
- "integrity": "sha512-ppEVvB5+TAqYxEiWCOt56TJbKayuJXPO5gAIaoIgaj7a77A3iuJRBZD/TLldqUxqCI6T5pwuTVzdeDU4tTHGug==",
+ "version": "16.2.13",
+ "resolved": "https://registry.npmjs.org/@angular/material/-/material-16.2.13.tgz",
+ "integrity": "sha512-7gP9KlaVZGpCeJlwXlD7puTiafAHYAbgYoODEQLbPCiAL/woFFDvM+DCdos7lmCBMyt6+10bkrPvz8cVfyTfQg==",
"dependencies": {
"@material/animation": "15.0.0-canary.bc9ae6c9c.0",
"@material/auto-init": "15.0.0-canary.bc9ae6c9c.0",
@@ -804,7 +804,7 @@
},
"peerDependencies": {
"@angular/animations": "^16.0.0 || ^17.0.0",
- "@angular/cdk": "16.2.9",
+ "@angular/cdk": "16.2.13",
"@angular/common": "^16.0.0 || ^17.0.0",
"@angular/core": "^16.0.0 || ^17.0.0",
"@angular/forms": "^16.0.0 || ^17.0.0",
@@ -813,9 +813,9 @@
}
},
"node_modules/@angular/platform-browser": {
- "version": "16.2.10",
- "resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-16.2.10.tgz",
- "integrity": "sha512-TOZiK7ji550F8G39Ri255NnK1+2Xlr74RiElJdQct4TzfN0lqNf2KRDFFNwDohkP/78FUzcP4qBxs+Nf8M7OuQ==",
+ "version": "16.2.12",
+ "resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-16.2.12.tgz",
+ "integrity": "sha512-NnH7ju1iirmVEsUq432DTm0nZBGQsBrU40M3ZeVHMQ2subnGiyUs3QyzDz8+VWLL/T5xTxWLt9BkDn65vgzlIQ==",
"dependencies": {
"tslib": "^2.3.0"
},
@@ -823,9 +823,9 @@
"node": "^16.14.0 || >=18.10.0"
},
"peerDependencies": {
- "@angular/animations": "16.2.10",
- "@angular/common": "16.2.10",
- "@angular/core": "16.2.10"
+ "@angular/animations": "16.2.12",
+ "@angular/common": "16.2.12",
+ "@angular/core": "16.2.12"
},
"peerDependenciesMeta": {
"@angular/animations": {
@@ -834,9 +834,9 @@
}
},
"node_modules/@angular/platform-browser-dynamic": {
- "version": "16.2.10",
- "resolved": "https://registry.npmjs.org/@angular/platform-browser-dynamic/-/platform-browser-dynamic-16.2.10.tgz",
- "integrity": "sha512-YVmhAjOmsp2SWRonv6Mr/qXuKroCiew9asd1IlAZ//wqcml9ZrNAcX3WlDa8ZqdmOplQb0LuvvirfNB/6Is/jg==",
+ "version": "16.2.12",
+ "resolved": "https://registry.npmjs.org/@angular/platform-browser-dynamic/-/platform-browser-dynamic-16.2.12.tgz",
+ "integrity": "sha512-ya54jerNgreCVAR278wZavwjrUWImMr2F8yM5n9HBvsMBbFaAQ83anwbOEiHEF2BlR+gJiEBLfpuPRMw20pHqw==",
"dependencies": {
"tslib": "^2.3.0"
},
@@ -844,16 +844,16 @@
"node": "^16.14.0 || >=18.10.0"
},
"peerDependencies": {
- "@angular/common": "16.2.10",
- "@angular/compiler": "16.2.10",
- "@angular/core": "16.2.10",
- "@angular/platform-browser": "16.2.10"
+ "@angular/common": "16.2.12",
+ "@angular/compiler": "16.2.12",
+ "@angular/core": "16.2.12",
+ "@angular/platform-browser": "16.2.12"
}
},
"node_modules/@angular/router": {
- "version": "16.2.10",
- "resolved": "https://registry.npmjs.org/@angular/router/-/router-16.2.10.tgz",
- "integrity": "sha512-ndiq2NkGZ8hTsyL/KK8qsiR3UA0NjOFIn1jtGXOKtHryXZ6vSTtkhtkE4h4+G6/QNTL1IKtocFhOQt/xsc7DUA==",
+ "version": "16.2.12",
+ "resolved": "https://registry.npmjs.org/@angular/router/-/router-16.2.12.tgz",
+ "integrity": "sha512-aU6QnYSza005V9P3W6PpkieL56O0IHps96DjqI1RS8yOJUl3THmokqYN4Fm5+HXy4f390FN9i6ftadYQDKeWmA==",
"dependencies": {
"tslib": "^2.3.0"
},
@@ -861,9 +861,9 @@
"node": "^16.14.0 || >=18.10.0"
},
"peerDependencies": {
- "@angular/common": "16.2.10",
- "@angular/core": "16.2.10",
- "@angular/platform-browser": "16.2.10",
+ "@angular/common": "16.2.12",
+ "@angular/core": "16.2.12",
+ "@angular/platform-browser": "16.2.12",
"rxjs": "^6.5.3 || ^7.4.0"
}
},
@@ -1255,9 +1255,9 @@
}
},
"node_modules/@babel/helper-string-parser": {
- "version": "7.22.5",
- "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz",
- "integrity": "sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw==",
+ "version": "7.23.4",
+ "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.23.4.tgz",
+ "integrity": "sha512-803gmbQdqwdf4olxrX4AJyFBV/RTr3rSmOj0rKwesmzlfhYNDEs+/iOcznzpNWlJlIlTJC2QfPFcHB6DlzdVLQ==",
"dev": true,
"engines": {
"node": ">=6.9.0"
@@ -2667,12 +2667,12 @@
}
},
"node_modules/@babel/types": {
- "version": "7.23.0",
- "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.0.tgz",
- "integrity": "sha512-0oIyUfKoI3mSqMvsxBdclDwxXKXAUA8v/apZbc+iSyARYou1o8ZGDxbUYyLFoW2arqS2jDGqJuZvv1d/io1axg==",
+ "version": "7.23.6",
+ "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.6.tgz",
+ "integrity": "sha512-+uarb83brBzPKN38NX1MkB6vb6+mwvR6amUulqAE7ccQw1pEl+bCia9TbdG1lsnFP7lZySvUn37CHyXQdfTwzg==",
"dev": true,
"dependencies": {
- "@babel/helper-string-parser": "^7.22.5",
+ "@babel/helper-string-parser": "^7.23.4",
"@babel/helper-validator-identifier": "^7.22.20",
"to-fast-properties": "^2.0.0"
},
@@ -2698,246 +2698,6 @@
"node": ">=10.0.0"
}
},
- "node_modules/@esbuild/android-arm": {
- "version": "0.18.17",
- "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.18.17.tgz",
- "integrity": "sha512-wHsmJG/dnL3OkpAcwbgoBTTMHVi4Uyou3F5mf58ZtmUyIKfcdA7TROav/6tCzET4A3QW2Q2FC+eFneMU+iyOxg==",
- "cpu": [
- "arm"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "android"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@esbuild/android-arm64": {
- "version": "0.18.17",
- "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.18.17.tgz",
- "integrity": "sha512-9np+YYdNDed5+Jgr1TdWBsozZ85U1Oa3xW0c7TWqH0y2aGghXtZsuT8nYRbzOMcl0bXZXjOGbksoTtVOlWrRZg==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "android"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@esbuild/android-x64": {
- "version": "0.18.17",
- "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.18.17.tgz",
- "integrity": "sha512-O+FeWB/+xya0aLg23hHEM2E3hbfwZzjqumKMSIqcHbNvDa+dza2D0yLuymRBQQnC34CWrsJUXyH2MG5VnLd6uw==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "android"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@esbuild/darwin-arm64": {
- "version": "0.18.17",
- "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.18.17.tgz",
- "integrity": "sha512-M9uJ9VSB1oli2BE/dJs3zVr9kcCBBsE883prage1NWz6pBS++1oNn/7soPNS3+1DGj0FrkSvnED4Bmlu1VAE9g==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "darwin"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@esbuild/darwin-x64": {
- "version": "0.18.17",
- "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.18.17.tgz",
- "integrity": "sha512-XDre+J5YeIJDMfp3n0279DFNrGCXlxOuGsWIkRb1NThMZ0BsrWXoTg23Jer7fEXQ9Ye5QjrvXpxnhzl3bHtk0g==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "darwin"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@esbuild/freebsd-arm64": {
- "version": "0.18.17",
- "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.18.17.tgz",
- "integrity": "sha512-cjTzGa3QlNfERa0+ptykyxs5A6FEUQQF0MuilYXYBGdBxD3vxJcKnzDlhDCa1VAJCmAxed6mYhA2KaJIbtiNuQ==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "freebsd"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@esbuild/freebsd-x64": {
- "version": "0.18.17",
- "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.18.17.tgz",
- "integrity": "sha512-sOxEvR8d7V7Kw8QqzxWc7bFfnWnGdaFBut1dRUYtu+EIRXefBc/eIsiUiShnW0hM3FmQ5Zf27suDuHsKgZ5QrA==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "freebsd"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@esbuild/linux-arm": {
- "version": "0.18.17",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.18.17.tgz",
- "integrity": "sha512-2d3Lw6wkwgSLC2fIvXKoMNGVaeY8qdN0IC3rfuVxJp89CRfA3e3VqWifGDfuakPmp90+ZirmTfye1n4ncjv2lg==",
- "cpu": [
- "arm"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@esbuild/linux-arm64": {
- "version": "0.18.17",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.18.17.tgz",
- "integrity": "sha512-c9w3tE7qA3CYWjT+M3BMbwMt+0JYOp3vCMKgVBrCl1nwjAlOMYzEo+gG7QaZ9AtqZFj5MbUc885wuBBmu6aADQ==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@esbuild/linux-ia32": {
- "version": "0.18.17",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.18.17.tgz",
- "integrity": "sha512-1DS9F966pn5pPnqXYz16dQqWIB0dmDfAQZd6jSSpiT9eX1NzKh07J6VKR3AoXXXEk6CqZMojiVDSZi1SlmKVdg==",
- "cpu": [
- "ia32"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@esbuild/linux-loong64": {
- "version": "0.18.17",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.18.17.tgz",
- "integrity": "sha512-EvLsxCk6ZF0fpCB6w6eOI2Fc8KW5N6sHlIovNe8uOFObL2O+Mr0bflPHyHwLT6rwMg9r77WOAWb2FqCQrVnwFg==",
- "cpu": [
- "loong64"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@esbuild/linux-mips64el": {
- "version": "0.18.17",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.18.17.tgz",
- "integrity": "sha512-e0bIdHA5p6l+lwqTE36NAW5hHtw2tNRmHlGBygZC14QObsA3bD4C6sXLJjvnDIjSKhW1/0S3eDy+QmX/uZWEYQ==",
- "cpu": [
- "mips64el"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@esbuild/linux-ppc64": {
- "version": "0.18.17",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.18.17.tgz",
- "integrity": "sha512-BAAilJ0M5O2uMxHYGjFKn4nJKF6fNCdP1E0o5t5fvMYYzeIqy2JdAP88Az5LHt9qBoUa4tDaRpfWt21ep5/WqQ==",
- "cpu": [
- "ppc64"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@esbuild/linux-riscv64": {
- "version": "0.18.17",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.18.17.tgz",
- "integrity": "sha512-Wh/HW2MPnC3b8BqRSIme/9Zhab36PPH+3zam5pqGRH4pE+4xTrVLx2+XdGp6fVS3L2x+DrsIcsbMleex8fbE6g==",
- "cpu": [
- "riscv64"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@esbuild/linux-s390x": {
- "version": "0.18.17",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.18.17.tgz",
- "integrity": "sha512-j/34jAl3ul3PNcK3pfI0NSlBANduT2UO5kZ7FCaK33XFv3chDhICLY8wJJWIhiQ+YNdQ9dxqQctRg2bvrMlYgg==",
- "cpu": [
- "s390x"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=12"
- }
- },
"node_modules/@esbuild/linux-x64": {
"version": "0.18.17",
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.18.17.tgz",
@@ -2954,102 +2714,6 @@
"node": ">=12"
}
},
- "node_modules/@esbuild/netbsd-x64": {
- "version": "0.18.17",
- "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.18.17.tgz",
- "integrity": "sha512-/jGlhWR7Sj9JPZHzXyyMZ1RFMkNPjC6QIAan0sDOtIo2TYk3tZn5UDrkE0XgsTQCxWTTOcMPf9p6Rh2hXtl5TQ==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "netbsd"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@esbuild/openbsd-x64": {
- "version": "0.18.17",
- "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.18.17.tgz",
- "integrity": "sha512-rSEeYaGgyGGf4qZM2NonMhMOP/5EHp4u9ehFiBrg7stH6BYEEjlkVREuDEcQ0LfIl53OXLxNbfuIj7mr5m29TA==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "openbsd"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@esbuild/sunos-x64": {
- "version": "0.18.17",
- "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.18.17.tgz",
- "integrity": "sha512-Y7ZBbkLqlSgn4+zot4KUNYst0bFoO68tRgI6mY2FIM+b7ZbyNVtNbDP5y8qlu4/knZZ73fgJDlXID+ohY5zt5g==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "sunos"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@esbuild/win32-arm64": {
- "version": "0.18.17",
- "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.18.17.tgz",
- "integrity": "sha512-bwPmTJsEQcbZk26oYpc4c/8PvTY3J5/QK8jM19DVlEsAB41M39aWovWoHtNm78sd6ip6prilxeHosPADXtEJFw==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "win32"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@esbuild/win32-ia32": {
- "version": "0.18.17",
- "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.18.17.tgz",
- "integrity": "sha512-H/XaPtPKli2MhW+3CQueo6Ni3Avggi6hP/YvgkEe1aSaxw+AeO8MFjq8DlgfTd9Iz4Yih3QCZI6YLMoyccnPRg==",
- "cpu": [
- "ia32"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "win32"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@esbuild/win32-x64": {
- "version": "0.18.17",
- "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.18.17.tgz",
- "integrity": "sha512-fGEb8f2BSA3CW7riJVurug65ACLuQAzKq0SSqkY2b2yHHH0MzDfbLyKIGzHwOI/gkHcxM/leuSW6D5w/LMNitA==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "win32"
- ],
- "engines": {
- "node": ">=12"
- }
- },
"node_modules/@eslint-community/eslint-utils": {
"version": "4.4.0",
"resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz",
@@ -3075,9 +2739,9 @@
}
},
"node_modules/@eslint/eslintrc": {
- "version": "2.1.2",
- "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.2.tgz",
- "integrity": "sha512-+wvgpDsrB1YqAMdEUCcnTlpfVBH7Vqn6A/NT3D8WVXFIaKMlErPIZT3oCIAVCOtarRpMtelZLqJeU3t7WY6X6g==",
+ "version": "2.1.4",
+ "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz",
+ "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==",
"dev": true,
"dependencies": {
"ajv": "^6.12.4",
@@ -3120,9 +2784,9 @@
"dev": true
},
"node_modules/@eslint/eslintrc/node_modules/globals": {
- "version": "13.23.0",
- "resolved": "https://registry.npmjs.org/globals/-/globals-13.23.0.tgz",
- "integrity": "sha512-XAmF0RjlrjY23MA51q3HltdlGxUpXPvg0GioKiD9X6HD28iMjo2dKC8Vqwm7lne4GNr78+RHTfliktR6ZH09wA==",
+ "version": "13.24.0",
+ "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz",
+ "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==",
"dev": true,
"dependencies": {
"type-fest": "^0.20.2"
@@ -3165,9 +2829,9 @@
}
},
"node_modules/@eslint/js": {
- "version": "8.52.0",
- "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.52.0.tgz",
- "integrity": "sha512-mjZVbpaeMZludF2fsWLD0Z9gCref1Tk4i9+wddjRvpUNqqcndPkBD09N/Mapey0b3jaXbLm2kICwFv2E64QinA==",
+ "version": "8.56.0",
+ "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.56.0.tgz",
+ "integrity": "sha512-gMsVel9D7f2HLkBma9VbtzZRehRogVRfbr++f06nL2vnCGCNlzOD+/MUov/F4p8myyAHspEhVobgjpX64q5m6A==",
"dev": true,
"engines": {
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
@@ -4156,9 +3820,9 @@
}
},
"node_modules/@ngtools/webpack": {
- "version": "16.2.6",
- "resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-16.2.6.tgz",
- "integrity": "sha512-d8ZlZL6dOtWmHdjG9PTGBkdiJMcsXD2tp6WeFRVvTEuvCI3XvKsUXBvJDE+mZOhzn5pUEYt+1TR5DHjDZbME3w==",
+ "version": "16.2.11",
+ "resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-16.2.11.tgz",
+ "integrity": "sha512-4ndXJ4s94ZsryVGSDk/waIDrUqXqdGWftoOEn81Zu+nkL9ncI/G1fNUlSJ5OqeKmMLxMFouoy+BuJfvT+gEgnQ==",
"dev": true,
"engines": {
"node": "^16.14.0 || >=18.10.0",
@@ -4414,174 +4078,46 @@
"resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz",
"integrity": "sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==",
"dev": true,
- "dependencies": {
- "rimraf": "^3.0.0"
- },
- "engines": {
- "node": ">=8.17.0"
- }
- },
- "node_modules/@nx/devkit/node_modules/yallist": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
- "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
- "dev": true
- },
- "node_modules/@nx/nx-darwin-arm64": {
- "version": "16.5.1",
- "resolved": "https://registry.npmjs.org/@nx/nx-darwin-arm64/-/nx-darwin-arm64-16.5.1.tgz",
- "integrity": "sha512-q98TFI4B/9N9PmKUr1jcbtD4yAFs1HfYd9jUXXTQOlfO9SbDjnrYJgZ4Fp9rMNfrBhgIQ4x1qx0AukZccKmH9Q==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "darwin"
- ],
- "engines": {
- "node": ">= 10"
- }
- },
- "node_modules/@nx/nx-darwin-x64": {
- "version": "16.5.1",
- "resolved": "https://registry.npmjs.org/@nx/nx-darwin-x64/-/nx-darwin-x64-16.5.1.tgz",
- "integrity": "sha512-j9HmL1l8k7EVJ3eOM5y8COF93gqrydpxCDoz23ZEtsY+JHY77VAiRQsmqBgEx9GGA2dXi9VEdS67B0+1vKariw==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "darwin"
- ],
- "engines": {
- "node": ">= 10"
- }
- },
- "node_modules/@nx/nx-freebsd-x64": {
- "version": "16.5.1",
- "resolved": "https://registry.npmjs.org/@nx/nx-freebsd-x64/-/nx-freebsd-x64-16.5.1.tgz",
- "integrity": "sha512-CXSPT01aVS869tvCCF2tZ7LnCa8l41wJ3mTVtWBkjmRde68E5Up093hklRMyXb3kfiDYlfIKWGwrV4r0eH6x1A==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "freebsd"
- ],
- "engines": {
- "node": ">= 10"
- }
- },
- "node_modules/@nx/nx-linux-arm-gnueabihf": {
- "version": "16.5.1",
- "resolved": "https://registry.npmjs.org/@nx/nx-linux-arm-gnueabihf/-/nx-linux-arm-gnueabihf-16.5.1.tgz",
- "integrity": "sha512-BhrumqJSZCWFfLFUKl4CAUwR0Y0G2H5EfFVGKivVecEQbb+INAek1aa6c89evg2/OvetQYsJ+51QknskwqvLsA==",
- "cpu": [
- "arm"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">= 10"
- }
- },
- "node_modules/@nx/nx-linux-arm64-gnu": {
- "version": "16.5.1",
- "resolved": "https://registry.npmjs.org/@nx/nx-linux-arm64-gnu/-/nx-linux-arm64-gnu-16.5.1.tgz",
- "integrity": "sha512-x7MsSG0W+X43WVv7JhiSq2eKvH2suNKdlUHEG09Yt0vm3z0bhtym1UCMUg3IUAK7jy9hhLeDaFVFkC6zo+H/XQ==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">= 10"
- }
- },
- "node_modules/@nx/nx-linux-arm64-musl": {
- "version": "16.5.1",
- "resolved": "https://registry.npmjs.org/@nx/nx-linux-arm64-musl/-/nx-linux-arm64-musl-16.5.1.tgz",
- "integrity": "sha512-J+/v/mFjOm74I0PNtH5Ka+fDd+/dWbKhpcZ2R1/6b9agzZk+Ff/SrwJcSYFXXWKbPX+uQ4RcJoytT06Zs3s0ow==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">= 10"
- }
- },
- "node_modules/@nx/nx-linux-x64-gnu": {
- "version": "16.5.1",
- "resolved": "https://registry.npmjs.org/@nx/nx-linux-x64-gnu/-/nx-linux-x64-gnu-16.5.1.tgz",
- "integrity": "sha512-igooWJ5YxQ94Zft7IqgL+Lw0qHaY15Btw4gfK756g/YTYLZEt4tTvR1y6RnK/wdpE3sa68bFTLVBNCGTyiTiDQ==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">= 10"
- }
- },
- "node_modules/@nx/nx-linux-x64-musl": {
- "version": "16.5.1",
- "resolved": "https://registry.npmjs.org/@nx/nx-linux-x64-musl/-/nx-linux-x64-musl-16.5.1.tgz",
- "integrity": "sha512-zF/exnPqFYbrLAduGhTmZ7zNEyADid2bzNQiIjJkh8Y6NpDwrQIwVIyvIxqynsjMrIs51kBH+8TUjKjj2Jgf5A==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "linux"
- ],
+ "dependencies": {
+ "rimraf": "^3.0.0"
+ },
"engines": {
- "node": ">= 10"
+ "node": ">=8.17.0"
}
},
- "node_modules/@nx/nx-win32-arm64-msvc": {
+ "node_modules/@nx/devkit/node_modules/yallist": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
+ "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
+ "dev": true
+ },
+ "node_modules/@nx/nx-linux-x64-gnu": {
"version": "16.5.1",
- "resolved": "https://registry.npmjs.org/@nx/nx-win32-arm64-msvc/-/nx-win32-arm64-msvc-16.5.1.tgz",
- "integrity": "sha512-qtqiLS9Y9TYyAbbpq58kRoOroko4ZXg5oWVqIWFHoxc5bGPweQSJCROEqd1AOl2ZDC6BxfuVHfhDDop1kK05WA==",
+ "resolved": "https://registry.npmjs.org/@nx/nx-linux-x64-gnu/-/nx-linux-x64-gnu-16.5.1.tgz",
+ "integrity": "sha512-igooWJ5YxQ94Zft7IqgL+Lw0qHaY15Btw4gfK756g/YTYLZEt4tTvR1y6RnK/wdpE3sa68bFTLVBNCGTyiTiDQ==",
"cpu": [
- "arm64"
+ "x64"
],
"dev": true,
"optional": true,
"os": [
- "win32"
+ "linux"
],
"engines": {
"node": ">= 10"
}
},
- "node_modules/@nx/nx-win32-x64-msvc": {
+ "node_modules/@nx/nx-linux-x64-musl": {
"version": "16.5.1",
- "resolved": "https://registry.npmjs.org/@nx/nx-win32-x64-msvc/-/nx-win32-x64-msvc-16.5.1.tgz",
- "integrity": "sha512-kUJBLakK7iyA9WfsGGQBVennA4jwf5XIgm0lu35oMOphtZIluvzItMt0EYBmylEROpmpEIhHq0P6J9FA+WH0Rg==",
+ "resolved": "https://registry.npmjs.org/@nx/nx-linux-x64-musl/-/nx-linux-x64-musl-16.5.1.tgz",
+ "integrity": "sha512-zF/exnPqFYbrLAduGhTmZ7zNEyADid2bzNQiIjJkh8Y6NpDwrQIwVIyvIxqynsjMrIs51kBH+8TUjKjj2Jgf5A==",
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
- "win32"
+ "linux"
],
"engines": {
"node": ">= 10"
@@ -4615,19 +4151,11 @@
"node": ">=14"
}
},
- "node_modules/@pkgr/utils": {
- "version": "2.4.2",
- "resolved": "https://registry.npmjs.org/@pkgr/utils/-/utils-2.4.2.tgz",
- "integrity": "sha512-POgTXhjrTfbTV63DiFXav4lBHiICLKKwDeaKn9Nphwj7WH6m0hMMCaJkMyRWjgtPFyRKRVoMXXjczsTQRDEhYw==",
+ "node_modules/@pkgr/core": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.1.0.tgz",
+ "integrity": "sha512-Zwq5OCzuwJC2jwqmpEQt7Ds1DTi6BWSwoGkbb1n9pO3hzb35BoJELx7c0T23iDkBGkh2e7tvOtjF3tr3OaQHDQ==",
"dev": true,
- "dependencies": {
- "cross-spawn": "^7.0.3",
- "fast-glob": "^3.3.0",
- "is-glob": "^4.0.3",
- "open": "^9.1.0",
- "picocolors": "^1.0.0",
- "tslib": "^2.6.0"
- },
"engines": {
"node": "^12.20.0 || ^14.18.0 || >=16.0.0"
},
@@ -4635,36 +4163,6 @@
"url": "https://opencollective.com/unts"
}
},
- "node_modules/@pkgr/utils/node_modules/define-lazy-prop": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz",
- "integrity": "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==",
- "dev": true,
- "engines": {
- "node": ">=12"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/@pkgr/utils/node_modules/open": {
- "version": "9.1.0",
- "resolved": "https://registry.npmjs.org/open/-/open-9.1.0.tgz",
- "integrity": "sha512-OS+QTnw1/4vrf+9hh1jc1jnYjzSG4ttTBB8UxOwAnInG3Uo4ssetzC1ihqaIHjLJnA5GGlRl6QlZXOTQhRBUvg==",
- "dev": true,
- "dependencies": {
- "default-browser": "^4.0.0",
- "define-lazy-prop": "^3.0.0",
- "is-inside-container": "^1.0.0",
- "is-wsl": "^2.2.0"
- },
- "engines": {
- "node": ">=14.16"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
"node_modules/@schematics/angular": {
"version": "16.1.8",
"resolved": "https://registry.npmjs.org/@schematics/angular/-/angular-16.1.8.tgz",
@@ -4823,9 +4321,9 @@
}
},
"node_modules/@types/body-parser": {
- "version": "1.19.4",
- "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.4.tgz",
- "integrity": "sha512-N7UDG0/xiPQa2D/XrVJXjkWbpqHCd2sBaB32ggRF2l83RhPfamgKGF8gwwqyksS95qUS5ZYF9aF+lLPRlwI2UA==",
+ "version": "1.19.5",
+ "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.5.tgz",
+ "integrity": "sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==",
"dev": true,
"dependencies": {
"@types/connect": "*",
@@ -4833,27 +4331,27 @@
}
},
"node_modules/@types/bonjour": {
- "version": "3.5.12",
- "resolved": "https://registry.npmjs.org/@types/bonjour/-/bonjour-3.5.12.tgz",
- "integrity": "sha512-ky0kWSqXVxSqgqJvPIkgFkcn4C8MnRog308Ou8xBBIVo39OmUFy+jqNe0nPwLCDFxUpmT9EvT91YzOJgkDRcFg==",
+ "version": "3.5.13",
+ "resolved": "https://registry.npmjs.org/@types/bonjour/-/bonjour-3.5.13.tgz",
+ "integrity": "sha512-z9fJ5Im06zvUL548KvYNecEVlA7cVDkGUi6kZusb04mpyEFKCIZJvloCcmpmLaIahDpOQGHaHmG6imtPMmPXGQ==",
"dev": true,
"dependencies": {
"@types/node": "*"
}
},
"node_modules/@types/connect": {
- "version": "3.4.37",
- "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.37.tgz",
- "integrity": "sha512-zBUSRqkfZ59OcwXon4HVxhx5oWCJmc0OtBTK05M+p0dYjgN6iTwIL2T/WbsQZrEsdnwaF9cWQ+azOnpPvIqY3Q==",
+ "version": "3.4.38",
+ "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz",
+ "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==",
"dev": true,
"dependencies": {
"@types/node": "*"
}
},
"node_modules/@types/connect-history-api-fallback": {
- "version": "1.5.2",
- "resolved": "https://registry.npmjs.org/@types/connect-history-api-fallback/-/connect-history-api-fallback-1.5.2.tgz",
- "integrity": "sha512-gX2j9x+NzSh4zOhnRPSdPPmTepS4DfxES0AvIFv3jGv5QyeAJf6u6dY5/BAoAJU9Qq1uTvwOku8SSC2GnCRl6Q==",
+ "version": "1.5.4",
+ "resolved": "https://registry.npmjs.org/@types/connect-history-api-fallback/-/connect-history-api-fallback-1.5.4.tgz",
+ "integrity": "sha512-n6Cr2xS1h4uAulPRdlw6Jl6s1oG8KrVilPN2yUITEs+K48EzMJJ3W1xy8K5eWuFvjp3R74AOIGSmp2UfBJ8HFw==",
"dev": true,
"dependencies": {
"@types/express-serve-static-core": "*",
@@ -4902,9 +4400,9 @@
"dev": true
},
"node_modules/@types/express": {
- "version": "4.17.20",
- "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.20.tgz",
- "integrity": "sha512-rOaqlkgEvOW495xErXMsmyX3WKBInbhG5eqojXYi3cGUaLoRDlXa5d52fkfWZT963AZ3v2eZ4MbKE6WpDAGVsw==",
+ "version": "4.17.21",
+ "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.21.tgz",
+ "integrity": "sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ==",
"dev": true,
"dependencies": {
"@types/body-parser": "*",
@@ -4914,9 +4412,9 @@
}
},
"node_modules/@types/express-serve-static-core": {
- "version": "4.17.38",
- "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.38.tgz",
- "integrity": "sha512-hXOtc0tuDHZPFwwhuBJXPbjemWtXnJjbvuuyNH2Y5Z6in+iXc63c4eXYDc7GGGqHy+iwYqAJMdaItqdnbcBKmg==",
+ "version": "4.17.41",
+ "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.41.tgz",
+ "integrity": "sha512-OaJ7XLaelTgrvlZD8/aa0vvvxZdUmlCn6MtWeB7TkiKW70BQLc9XEPpDLPdbo52ZhXUCrznlWdCHWxJWtdyajA==",
"dev": true,
"dependencies": {
"@types/node": "*",
@@ -4926,15 +4424,15 @@
}
},
"node_modules/@types/http-errors": {
- "version": "2.0.3",
- "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.3.tgz",
- "integrity": "sha512-pP0P/9BnCj1OVvQR2lF41EkDG/lWWnDyA203b/4Fmi2eTyORnBtcDoKDwjWQthELrBvWkMOrvSOnZ8OVlW6tXA==",
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.4.tgz",
+ "integrity": "sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==",
"dev": true
},
"node_modules/@types/http-proxy": {
- "version": "1.17.13",
- "resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.13.tgz",
- "integrity": "sha512-GkhdWcMNiR5QSQRYnJ+/oXzu0+7JJEPC8vkWXK351BkhjraZF+1W13CUYARUvX9+NqIU2n6YHA4iwywsc/M6Sw==",
+ "version": "1.17.14",
+ "resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.14.tgz",
+ "integrity": "sha512-SSrD0c1OQzlFX7pGu1eXxSEjemej64aaNPRhhVYUGqXh0BtldAAx37MG8btcumvpgKyZp1F5Gn3JkktdxiFv6w==",
"dev": true,
"dependencies": {
"@types/node": "*"
@@ -4953,9 +4451,9 @@
"dev": true
},
"node_modules/@types/mime": {
- "version": "1.3.4",
- "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.4.tgz",
- "integrity": "sha512-1Gjee59G25MrQGk8bsNvC6fxNiRgUlGn2wlhGf95a59DrprnnHk80FIMMFG9XHMdrfsuA119ht06QPDXA1Z7tw==",
+ "version": "1.3.5",
+ "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz",
+ "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==",
"dev": true
},
"node_modules/@types/node": {
@@ -4967,16 +4465,25 @@
"undici-types": "~5.25.1"
}
},
+ "node_modules/@types/node-forge": {
+ "version": "1.3.11",
+ "resolved": "https://registry.npmjs.org/@types/node-forge/-/node-forge-1.3.11.tgz",
+ "integrity": "sha512-FQx220y22OKNTqaByeBGqHWYz4cl94tpcxeFdvBo3wjG6XPBuZ0BNgNZRV5J5TFmmcsJ4IzsLkmGRiQbnYsBEQ==",
+ "dev": true,
+ "dependencies": {
+ "@types/node": "*"
+ }
+ },
"node_modules/@types/qs": {
- "version": "6.9.9",
- "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.9.tgz",
- "integrity": "sha512-wYLxw35euwqGvTDx6zfY1vokBFnsK0HNrzc6xNHchxfO2hpuRg74GbkEW7e3sSmPvj0TjCDT1VCa6OtHXnubsg==",
+ "version": "6.9.11",
+ "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.11.tgz",
+ "integrity": "sha512-oGk0gmhnEJK4Yyk+oI7EfXsLayXatCWPHary1MtcmbAifkobT9cM9yutG/hZKIseOU0MqbIwQ/u2nn/Gb+ltuQ==",
"dev": true
},
"node_modules/@types/range-parser": {
- "version": "1.2.6",
- "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.6.tgz",
- "integrity": "sha512-+0autS93xyXizIYiyL02FCY8N+KkKPhILhcUSA276HxzreZ16kl+cmwvV2qAM/PuCCwPXzOXOWhiPcw20uSFcA==",
+ "version": "1.2.7",
+ "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz",
+ "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==",
"dev": true
},
"node_modules/@types/retry": {
@@ -4992,9 +4499,9 @@
"dev": true
},
"node_modules/@types/send": {
- "version": "0.17.3",
- "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.3.tgz",
- "integrity": "sha512-/7fKxvKUoETxjFUsuFlPB9YndePpxxRAOfGC/yJdc9kTjTeP5kRCTzfnE8kPUKCeyiyIZu0YQ76s50hCedI1ug==",
+ "version": "0.17.4",
+ "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.4.tgz",
+ "integrity": "sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA==",
"dev": true,
"dependencies": {
"@types/mime": "^1",
@@ -5002,18 +4509,18 @@
}
},
"node_modules/@types/serve-index": {
- "version": "1.9.3",
- "resolved": "https://registry.npmjs.org/@types/serve-index/-/serve-index-1.9.3.tgz",
- "integrity": "sha512-4KG+yMEuvDPRrYq5fyVm/I2uqAJSAwZK9VSa+Zf+zUq9/oxSSvy3kkIqyL+jjStv6UCVi8/Aho0NHtB1Fwosrg==",
+ "version": "1.9.4",
+ "resolved": "https://registry.npmjs.org/@types/serve-index/-/serve-index-1.9.4.tgz",
+ "integrity": "sha512-qLpGZ/c2fhSs5gnYsQxtDEq3Oy8SXPClIXkW5ghvAvsNuVSA8k+gCONcUCS/UjLEYvYps+e8uBtfgXgvhwfNug==",
"dev": true,
"dependencies": {
"@types/express": "*"
}
},
"node_modules/@types/serve-static": {
- "version": "1.15.4",
- "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.4.tgz",
- "integrity": "sha512-aqqNfs1XTF0HDrFdlY//+SGUxmdSUbjeRXb5iaZc3x0/vMbYmdw9qvOgHWOyyLFxSSRnUuP5+724zBgfw8/WAw==",
+ "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==",
"dev": true,
"dependencies": {
"@types/http-errors": "*",
@@ -5022,18 +4529,18 @@
}
},
"node_modules/@types/sockjs": {
- "version": "0.3.35",
- "resolved": "https://registry.npmjs.org/@types/sockjs/-/sockjs-0.3.35.tgz",
- "integrity": "sha512-tIF57KB+ZvOBpAQwSaACfEu7htponHXaFzP7RfKYgsOS0NoYnn+9+jzp7bbq4fWerizI3dTB4NfAZoyeQKWJLw==",
+ "version": "0.3.36",
+ "resolved": "https://registry.npmjs.org/@types/sockjs/-/sockjs-0.3.36.tgz",
+ "integrity": "sha512-MK9V6NzAS1+Ud7JV9lJLFqW85VbC9dq3LmwZCuBe4wBDgKC0Kj/jd8Xl+nSviU+Qc3+m7umHHyHg//2KSa0a0Q==",
"dev": true,
"dependencies": {
"@types/node": "*"
}
},
"node_modules/@types/ws": {
- "version": "8.5.8",
- "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.8.tgz",
- "integrity": "sha512-flUksGIQCnJd6sZ1l5dqCEG/ksaoAg/eUwiLAGTJQcfgvZJKF++Ta4bJA6A5aPSJmsr+xlseHn4KLgVlNnvPTg==",
+ "version": "8.5.10",
+ "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.10.tgz",
+ "integrity": "sha512-vmQSUcfalpIq0R9q7uTo2lXs6eGIpt9wtnLdMv9LVpIjCA/+ufZRozlVoVelIYixx1ugCBKDhn89vnsEGOCx9A==",
"dev": true,
"dependencies": {
"@types/node": "*"
@@ -5865,9 +5372,9 @@
}
},
"node_modules/array-flatten": {
- "version": "2.1.2",
- "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-2.1.2.tgz",
- "integrity": "sha512-hNfzcOV8W4NdualtqBFPyVO+54DSJuZGY9qT4pRroB6S9e3iiido2ISIC5h9R2sPJ8H3FHCIiEnsv1lPXO3KtQ==",
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
+ "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==",
"dev": true
},
"node_modules/array-union": {
@@ -5925,12 +5432,12 @@
}
},
"node_modules/axios": {
- "version": "1.5.1",
- "resolved": "https://registry.npmjs.org/axios/-/axios-1.5.1.tgz",
- "integrity": "sha512-Q28iYCWzNHjAm+yEAot5QaAMxhMghWLFVf7rRdwhUI+c2jix2DUXjAHXVi+s1ibs3mjPO/cCgbA++3BjD0vP/A==",
+ "version": "1.6.5",
+ "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.5.tgz",
+ "integrity": "sha512-Ii012v05KEVuUoFWmMW/UQv9aRIc3ZwkWDcM+h5Il8izZCtRVpDUfwpoFf7eOtajT3QiGR4yDUx7lPqHJULgbg==",
"dev": true,
"dependencies": {
- "follow-redirects": "^1.15.0",
+ "follow-redirects": "^1.15.4",
"form-data": "^4.0.0",
"proxy-from-env": "^1.1.0"
}
@@ -6080,15 +5587,6 @@
"integrity": "sha512-x+VAiMRL6UPkx+kudNvxTl6hB2XNNCG2r+7wixVfIYwu/2HKRXimwQyaumLjMveWvT2Hkd/cAJw+QBMfJ/EKVw==",
"dev": true
},
- "node_modules/big-integer": {
- "version": "1.6.51",
- "resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.51.tgz",
- "integrity": "sha512-GPEid2Y9QU1Exl1rpO9B2IPJGHPSupF5GnVIP0blYvNOMer2bTvSWs1jGOUg04hTmu67nmLsQ9TBo1puaotBHg==",
- "dev": true,
- "engines": {
- "node": ">=0.6"
- }
- },
"node_modules/big.js": {
"version": "5.2.2",
"resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz",
@@ -6158,13 +5656,11 @@
"dev": true
},
"node_modules/bonjour-service": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/bonjour-service/-/bonjour-service-1.1.1.tgz",
- "integrity": "sha512-Z/5lQRMOG9k7W+FkeGTNjh7htqn/2LMnfOvBZ8pynNZCM9MwkQkI3zeI4oz09uWdcgmgHugVvBqxGg4VQJ5PCg==",
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/bonjour-service/-/bonjour-service-1.2.1.tgz",
+ "integrity": "sha512-oSzCS2zV14bh2kji6vNe7vrpJYCHGvcZnlffFQ1MEoX/WOeQ/teD8SYWKR942OI3INjq8OMNJlbPK5LLLUxFDw==",
"dev": true,
"dependencies": {
- "array-flatten": "^2.1.2",
- "dns-equal": "^1.0.0",
"fast-deep-equal": "^3.1.3",
"multicast-dns": "^7.2.5"
}
@@ -6175,18 +5671,6 @@
"integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==",
"dev": true
},
- "node_modules/bplist-parser": {
- "version": "0.2.0",
- "resolved": "https://registry.npmjs.org/bplist-parser/-/bplist-parser-0.2.0.tgz",
- "integrity": "sha512-z0M+byMThzQmD9NILRniCUXYsYpjwnlO8N5uCFaCqIOpqRsJCrQL9NK3JsD67CN5a08nF5oIL2bD6loTdHOuKw==",
- "dev": true,
- "dependencies": {
- "big-integer": "^1.6.44"
- },
- "engines": {
- "node": ">= 5.10.0"
- }
- },
"node_modules/brace-expansion": {
"version": "1.1.11",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
@@ -6286,21 +5770,6 @@
"semver": "^7.0.0"
}
},
- "node_modules/bundle-name": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/bundle-name/-/bundle-name-3.0.0.tgz",
- "integrity": "sha512-PKA4BeSvBpQKQ8iPOGCSiell+N8P+Tf1DlwqmYhpe2gAhKPHn8EYOxVT+ShuGmhg8lN8XiSlS80yiExKXrURlw==",
- "dev": true,
- "dependencies": {
- "run-applescript": "^5.0.0"
- },
- "engines": {
- "node": ">=12"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
"node_modules/bytes": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
@@ -7194,150 +6663,6 @@
"integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==",
"dev": true
},
- "node_modules/default-browser": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/default-browser/-/default-browser-4.0.0.tgz",
- "integrity": "sha512-wX5pXO1+BrhMkSbROFsyxUm0i/cJEScyNhA4PPxc41ICuv05ZZB/MX28s8aZx6xjmatvebIapF6hLEKEcpneUA==",
- "dev": true,
- "dependencies": {
- "bundle-name": "^3.0.0",
- "default-browser-id": "^3.0.0",
- "execa": "^7.1.1",
- "titleize": "^3.0.0"
- },
- "engines": {
- "node": ">=14.16"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/default-browser-id": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/default-browser-id/-/default-browser-id-3.0.0.tgz",
- "integrity": "sha512-OZ1y3y0SqSICtE8DE4S8YOE9UZOJ8wO16fKWVP5J1Qz42kV9jcnMVFrEE/noXb/ss3Q4pZIH79kxofzyNNtUNA==",
- "dev": true,
- "dependencies": {
- "bplist-parser": "^0.2.0",
- "untildify": "^4.0.0"
- },
- "engines": {
- "node": ">=12"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/default-browser/node_modules/execa": {
- "version": "7.2.0",
- "resolved": "https://registry.npmjs.org/execa/-/execa-7.2.0.tgz",
- "integrity": "sha512-UduyVP7TLB5IcAQl+OzLyLcS/l32W/GLg+AhHJ+ow40FOk2U3SAllPwR44v4vmdFwIWqpdwxxpQbF1n5ta9seA==",
- "dev": true,
- "dependencies": {
- "cross-spawn": "^7.0.3",
- "get-stream": "^6.0.1",
- "human-signals": "^4.3.0",
- "is-stream": "^3.0.0",
- "merge-stream": "^2.0.0",
- "npm-run-path": "^5.1.0",
- "onetime": "^6.0.0",
- "signal-exit": "^3.0.7",
- "strip-final-newline": "^3.0.0"
- },
- "engines": {
- "node": "^14.18.0 || ^16.14.0 || >=18.0.0"
- },
- "funding": {
- "url": "https://github.com/sindresorhus/execa?sponsor=1"
- }
- },
- "node_modules/default-browser/node_modules/human-signals": {
- "version": "4.3.1",
- "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-4.3.1.tgz",
- "integrity": "sha512-nZXjEF2nbo7lIw3mgYjItAfgQXog3OjJogSbKa2CQIIvSGWcKgeJnQlNXip6NglNzYH45nSRiEVimMvYL8DDqQ==",
- "dev": true,
- "engines": {
- "node": ">=14.18.0"
- }
- },
- "node_modules/default-browser/node_modules/is-stream": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz",
- "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==",
- "dev": true,
- "engines": {
- "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/default-browser/node_modules/mimic-fn": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz",
- "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==",
- "dev": true,
- "engines": {
- "node": ">=12"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/default-browser/node_modules/npm-run-path": {
- "version": "5.1.0",
- "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.1.0.tgz",
- "integrity": "sha512-sJOdmRGrY2sjNTRMbSvluQqg+8X7ZK61yvzBEIDhz4f8z1TZFYABsqjjCBd/0PUNE9M6QDgHJXQkGUEm7Q+l9Q==",
- "dev": true,
- "dependencies": {
- "path-key": "^4.0.0"
- },
- "engines": {
- "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/default-browser/node_modules/onetime": {
- "version": "6.0.0",
- "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz",
- "integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==",
- "dev": true,
- "dependencies": {
- "mimic-fn": "^4.0.0"
- },
- "engines": {
- "node": ">=12"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/default-browser/node_modules/path-key": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz",
- "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==",
- "dev": true,
- "engines": {
- "node": ">=12"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/default-browser/node_modules/strip-final-newline": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz",
- "integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==",
- "dev": true,
- "engines": {
- "node": ">=12"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
"node_modules/default-gateway": {
"version": "6.0.3",
"resolved": "https://registry.npmjs.org/default-gateway/-/default-gateway-6.0.3.tgz",
@@ -7444,12 +6769,6 @@
"integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==",
"dev": true
},
- "node_modules/dns-equal": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/dns-equal/-/dns-equal-1.0.0.tgz",
- "integrity": "sha512-z+paD6YUQsk+AbGCEM4PrOXSss5gd66QfcVBFTKR/HpFL9jCqikS94HYwKww6fQyO7IxrIIyUu+g0Ka9tUS2Cg==",
- "dev": true
- },
"node_modules/dns-packet": {
"version": "5.6.1",
"resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-5.6.1.tgz",
@@ -7908,15 +7227,15 @@
}
},
"node_modules/eslint": {
- "version": "8.52.0",
- "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.52.0.tgz",
- "integrity": "sha512-zh/JHnaixqHZsolRB/w9/02akBk9EPrOs9JwcTP2ek7yL5bVvXuRariiaAjjoJ5DvuwQ1WAE/HsMz+w17YgBCg==",
+ "version": "8.56.0",
+ "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.56.0.tgz",
+ "integrity": "sha512-Go19xM6T9puCOWntie1/P997aXxFsOi37JIHRWI514Hc6ZnaHGKY9xFhrU65RT6CcBEzZoGG1e6Nq+DT04ZtZQ==",
"dev": true,
"dependencies": {
"@eslint-community/eslint-utils": "^4.2.0",
"@eslint-community/regexpp": "^4.6.1",
- "@eslint/eslintrc": "^2.1.2",
- "@eslint/js": "8.52.0",
+ "@eslint/eslintrc": "^2.1.4",
+ "@eslint/js": "8.56.0",
"@humanwhocodes/config-array": "^0.11.13",
"@humanwhocodes/module-importer": "^1.0.1",
"@nodelib/fs.walk": "^1.2.8",
@@ -7963,9 +7282,9 @@
}
},
"node_modules/eslint-config-prettier": {
- "version": "9.0.0",
- "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-9.0.0.tgz",
- "integrity": "sha512-IcJsTkJae2S35pRsRAwoCE+925rJJStOdkKnLVgtE+tEpqU0EVVM7OqrwxqgptKdX29NUwC82I5pXsGFIgSevw==",
+ "version": "9.1.0",
+ "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-9.1.0.tgz",
+ "integrity": "sha512-NSWl5BFQWEPi1j4TjVNItzYV7dZXZ+wP6I6ZhrBGpChQhZRUaElihE9uRRkcbRnNb76UMKDF3r+WTmNcGPKsqw==",
"dev": true,
"bin": {
"eslint-config-prettier": "bin/cli.js"
@@ -7975,23 +7294,24 @@
}
},
"node_modules/eslint-plugin-prettier": {
- "version": "5.0.1",
- "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.0.1.tgz",
- "integrity": "sha512-m3u5RnR56asrwV/lDC4GHorlW75DsFfmUcjfCYylTUs85dBRnB7VM6xG8eCMJdeDRnppzmxZVf1GEPJvl1JmNg==",
+ "version": "5.1.3",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.1.3.tgz",
+ "integrity": "sha512-C9GCVAs4Eq7ZC/XFQHITLiHJxQngdtraXaM+LoUFoFp/lHNl2Zn8f3WQbe9HvTBBQ9YnKFB0/2Ajdqwo5D1EAw==",
"dev": true,
"dependencies": {
"prettier-linter-helpers": "^1.0.0",
- "synckit": "^0.8.5"
+ "synckit": "^0.8.6"
},
"engines": {
"node": "^14.18.0 || >=16.0.0"
},
"funding": {
- "url": "https://opencollective.com/prettier"
+ "url": "https://opencollective.com/eslint-plugin-prettier"
},
"peerDependencies": {
"@types/eslint": ">=8.0.0",
"eslint": ">=8.0.0",
+ "eslint-config-prettier": "*",
"prettier": ">=3.0.0"
},
"peerDependenciesMeta": {
@@ -8460,12 +7780,6 @@
"node": ">= 0.10.0"
}
},
- "node_modules/express/node_modules/array-flatten": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
- "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==",
- "dev": true
- },
"node_modules/express/node_modules/body-parser": {
"version": "1.20.1",
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz",
@@ -8810,9 +8124,9 @@
"dev": true
},
"node_modules/follow-redirects": {
- "version": "1.15.3",
- "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.3.tgz",
- "integrity": "sha512-1VzOtuEM8pC9SFU1E+8KfTjZyMztRsgEfwQl44z8A25uy13jSzTj6dyK2Df52iV0vgHCfBwLhDWevLn95w5v6Q==",
+ "version": "1.15.5",
+ "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.5.tgz",
+ "integrity": "sha512-vSFWUON1B+yAw1VN4xMfxgn5fTUiaOzAJCKBwIIgT/+7CuGy9+r+5gITvP62j3RmaD5Ph65UaERdOSRGUzZtgw==",
"dev": true,
"funding": [
{
@@ -9837,39 +9151,6 @@
"node": ">=0.10.0"
}
},
- "node_modules/is-inside-container": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz",
- "integrity": "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==",
- "dev": true,
- "dependencies": {
- "is-docker": "^3.0.0"
- },
- "bin": {
- "is-inside-container": "cli.js"
- },
- "engines": {
- "node": ">=14.16"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/is-inside-container/node_modules/is-docker": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz",
- "integrity": "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==",
- "dev": true,
- "bin": {
- "is-docker": "cli.js"
- },
- "engines": {
- "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
"node_modules/is-interactive": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz",
@@ -11560,9 +10841,9 @@
"dev": true
},
"node_modules/ngx-mask": {
- "version": "16.3.9",
- "resolved": "https://registry.npmjs.org/ngx-mask/-/ngx-mask-16.3.9.tgz",
- "integrity": "sha512-cptsvlI4OLI8Tpj23ZgSQDKz5jksyWGzAuEEn5pd58cq2oFGeeHZS2i1SQQi8kp+a+Dh/2RvDsfFmDWmI5Ln9w==",
+ "version": "16.4.2",
+ "resolved": "https://registry.npmjs.org/ngx-mask/-/ngx-mask-16.4.2.tgz",
+ "integrity": "sha512-mQjcsTpctGu6HYKLf6/gjEUvW65D+46xvPIMYz0BDZXqHXrqKVluHXR3KF++TNOfdLLXwW6SvuHWd91NZN/C1A==",
"dependencies": {
"tslib": "^2.3.0"
},
@@ -12909,9 +12190,9 @@
}
},
"node_modules/prettier": {
- "version": "3.0.3",
- "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.0.3.tgz",
- "integrity": "sha512-L/4pUDMxcNa8R/EthV08Zt42WBO4h1rarVtK0K+QJG0X187OLo7l699jWw0GKuwzkPQ//jMFA/8Xm6Fh3J/DAg==",
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.1.1.tgz",
+ "integrity": "sha512-22UbSzg8luF4UuZtzgiUOfcGM8s4tjBv6dJRT7j275NXsy2jb4aJa4NNveul5x4eqlF1wuhuR2RElK71RvmVaw==",
"dev": true,
"bin": {
"prettier": "bin/prettier.cjs"
@@ -12924,9 +12205,9 @@
}
},
"node_modules/prettier-eslint": {
- "version": "16.1.1",
- "resolved": "https://registry.npmjs.org/prettier-eslint/-/prettier-eslint-16.1.1.tgz",
- "integrity": "sha512-SbtugbH80njB9QOPqb8C+W40Rvhr6iD0wrJTxk1Zx10rkY7KdjtSwHpf/WfiI3REboaXbvIOJXGiua3maIt0Sw==",
+ "version": "16.2.0",
+ "resolved": "https://registry.npmjs.org/prettier-eslint/-/prettier-eslint-16.2.0.tgz",
+ "integrity": "sha512-GDTSKc62VaLceiaI/qMaKo2oco2CIWtbj4Zr6ckhbTgcBL/uR0d9jkMzh9OtBIT/Z7iBoCB4OHj/aJ5YuNgAuA==",
"dev": true,
"dependencies": {
"@typescript-eslint/parser": "^6.7.5",
@@ -13432,9 +12713,9 @@
}
},
"node_modules/reflect-metadata": {
- "version": "0.1.13",
- "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.1.13.tgz",
- "integrity": "sha512-Ts1Y/anZELhSsjMcU605fU9RE4Oi3p5ORujwbIKXfWa+0Zxs510Qrmrce5/Jowq3cHSZSJqBjypxmHarc+vEWg==",
+ "version": "0.1.14",
+ "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.1.14.tgz",
+ "integrity": "sha512-ZhYeb6nRaXCfhnndflDK8qI6ZQ/YcWZCISRAWICW9XYqMUwjZM9Z0DveWX/ABN01oxSHwVxKQmxeYZSsm0jh5A==",
"dev": true
},
"node_modules/regenerate": {
@@ -13678,21 +12959,6 @@
"fsevents": "~2.3.2"
}
},
- "node_modules/run-applescript": {
- "version": "5.0.0",
- "resolved": "https://registry.npmjs.org/run-applescript/-/run-applescript-5.0.0.tgz",
- "integrity": "sha512-XcT5rBksx1QdIhlFOCtgZkB99ZEouFZ1E2Kc2LHqNW13U3/74YGdkQRmThTwxy4QIyookibDKYZOPqX//6BlAg==",
- "dev": true,
- "dependencies": {
- "execa": "^5.0.0"
- },
- "engines": {
- "node": ">=12"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
"node_modules/run-async": {
"version": "2.4.1",
"resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz",
@@ -13863,11 +13129,12 @@
"dev": true
},
"node_modules/selfsigned": {
- "version": "2.1.1",
- "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-2.1.1.tgz",
- "integrity": "sha512-GSL3aowiF7wa/WtSFwnUrludWFoNhftq8bUkH9pkzjpN2XSPOAYEgg6e0sS9s0rZwgJzJiQRPU18A6clnoW5wQ==",
+ "version": "2.4.1",
+ "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-2.4.1.tgz",
+ "integrity": "sha512-th5B4L2U+eGLq1TVh7zNRGBapioSORUeymIydxgFpwww9d2qyKvtuPU2jJuHvYAwwqi2Y596QBL3eEqcPEYL8Q==",
"dev": true,
"dependencies": {
+ "@types/node-forge": "^1.3.0",
"node-forge": "^1"
},
"engines": {
@@ -14613,13 +13880,13 @@
"dev": true
},
"node_modules/synckit": {
- "version": "0.8.5",
- "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.8.5.tgz",
- "integrity": "sha512-L1dapNV6vu2s/4Sputv8xGsCdAVlb5nRDMFU/E27D44l5U6cw1g0dGd45uLc+OXjNMmF4ntiMdCimzcjFKQI8Q==",
+ "version": "0.8.8",
+ "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.8.8.tgz",
+ "integrity": "sha512-HwOKAP7Wc5aRGYdKH+dw0PRRpbO841v2DENBtjnR5HFWoiNByAl7vrx3p0G/rCyYXQsrxqtX48TImFtPcIHSpQ==",
"dev": true,
"dependencies": {
- "@pkgr/utils": "^2.3.1",
- "tslib": "^2.5.0"
+ "@pkgr/core": "^0.1.0",
+ "tslib": "^2.6.2"
},
"engines": {
"node": "^14.18.0 || >=16.0.0"
@@ -14845,18 +14112,6 @@
"integrity": "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==",
"dev": true
},
- "node_modules/titleize": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/titleize/-/titleize-3.0.0.tgz",
- "integrity": "sha512-KxVu8EYHDPBdUYdKZdKtU2aj2XfEx9AfjXxE/Aj0vT06w2icA09Vus1rh6eSu1y01akYg6BjIK/hxyLJINoMLQ==",
- "dev": true,
- "engines": {
- "node": ">=12"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
"node_modules/tmp": {
"version": "0.0.33",
"resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz",
@@ -15177,15 +14432,6 @@
"node": ">= 0.8"
}
},
- "node_modules/untildify": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/untildify/-/untildify-4.0.0.tgz",
- "integrity": "sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw==",
- "dev": true,
- "engines": {
- "node": ">=8"
- }
- },
"node_modules/update-browserslist-db": {
"version": "1.0.13",
"resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz",
@@ -15297,14 +14543,14 @@
}
},
"node_modules/vite": {
- "version": "4.4.7",
- "resolved": "https://registry.npmjs.org/vite/-/vite-4.4.7.tgz",
- "integrity": "sha512-6pYf9QJ1mHylfVh39HpuSfMPojPSKVxZvnclX1K1FyZ1PXDOcLBibdq5t1qxJSnL63ca8Wf4zts6mD8u8oc9Fw==",
+ "version": "4.5.1",
+ "resolved": "https://registry.npmjs.org/vite/-/vite-4.5.1.tgz",
+ "integrity": "sha512-AXXFaAJ8yebyqzoNB9fu2pHoo/nWX+xZlaRwoeYUxEqBO+Zj4msE5G+BhGBll9lYEKv9Hfks52PAF2X7qDYXQA==",
"dev": true,
"dependencies": {
"esbuild": "^0.18.10",
- "postcss": "^8.4.26",
- "rollup": "^3.25.2"
+ "postcss": "^8.4.27",
+ "rollup": "^3.27.1"
},
"bin": {
"vite": "bin/vite.js"
@@ -15620,9 +14866,9 @@
}
},
"node_modules/webpack-dev-server/node_modules/ws": {
- "version": "8.14.2",
- "resolved": "https://registry.npmjs.org/ws/-/ws-8.14.2.tgz",
- "integrity": "sha512-wEBG1ftX4jcglPxgFCMJmZ2PLtSbJ2Peg6TmpJFTbe9GZYOQCDPdMYu/Tm0/bGZkw8paZnJY45J4K2PZrLYq8g==",
+ "version": "8.16.0",
+ "resolved": "https://registry.npmjs.org/ws/-/ws-8.16.0.tgz",
+ "integrity": "sha512-HS0c//TP7Ina87TfiPUz1rQzMhHrl/SG2guqRcTOIUYD2q8uhUdNHZYJUaQ8aTGPzCh+c6oawMKW35nFl1dxyQ==",
"dev": true,
"engines": {
"node": ">=10.0.0"
diff --git a/modules/ui/package.json b/modules/ui/package.json
index c7ce21ec7..46f775272 100644
--- a/modules/ui/package.json
+++ b/modules/ui/package.json
@@ -17,44 +17,44 @@
},
"private": true,
"dependencies": {
- "@angular/animations": "^16.2.10",
- "@angular/cdk": "^16.2.9",
- "@angular/common": "^16.2.10",
- "@angular/compiler": "^16.2.10",
- "@angular/core": "^16.2.10",
- "@angular/forms": "^16.2.10",
- "@angular/material": "^16.2.9",
- "@angular/platform-browser": "^16.2.10",
- "@angular/platform-browser-dynamic": "^16.2.10",
- "@angular/router": "^16.2.10",
- "ngx-mask": "^16.3.9",
+ "@angular/animations": "^16.2.12",
+ "@angular/cdk": "^16.2.13",
+ "@angular/common": "^16.2.12",
+ "@angular/compiler": "^16.2.12",
+ "@angular/core": "^16.2.12",
+ "@angular/forms": "^16.2.12",
+ "@angular/material": "^16.2.13",
+ "@angular/platform-browser": "^16.2.12",
+ "@angular/platform-browser-dynamic": "^16.2.12",
+ "@angular/router": "^16.2.12",
+ "ngx-mask": "^16.4.2",
"rxjs": "~7.8.0",
"tslib": "^2.6.2",
"zone.js": "~0.13.3"
},
"devDependencies": {
- "@angular-devkit/build-angular": "^16.2.6",
+ "@angular-devkit/build-angular": "^16.2.11",
"@angular-eslint/builder": "16.2.0",
"@angular-eslint/eslint-plugin": "16.2.0",
"@angular-eslint/eslint-plugin-template": "16.2.0",
"@angular-eslint/schematics": "16.2.0",
"@angular-eslint/template-parser": "16.2.0",
"@angular/cli": "~16.1.8",
- "@angular/compiler-cli": "^16.2.10",
+ "@angular/compiler-cli": "^16.2.12",
"@types/jasmine": "~4.3.6",
"@typescript-eslint/eslint-plugin": "5.62.0",
"@typescript-eslint/parser": "5.62.0",
- "eslint": "^8.49.0",
- "eslint-config-prettier": "^9.0.0",
- "eslint-plugin-prettier": "^5.0.1",
+ "eslint": "^8.56.0",
+ "eslint-config-prettier": "^9.1.0",
+ "eslint-plugin-prettier": "^5.1.3",
"jasmine-core": "~4.6.0",
"karma": "~6.4.0",
"karma-chrome-launcher": "~3.2.0",
"karma-coverage": "~2.2.0",
"karma-jasmine": "~5.1.0",
"karma-jasmine-html-reporter": "~2.1.0",
- "prettier": "^3.0.3",
- "prettier-eslint": "^16.1.1",
+ "prettier": "^3.1.1",
+ "prettier-eslint": "^16.2.0",
"typescript": "~5.1.3"
}
}
diff --git a/modules/ui/src/app/app-routing.module.ts b/modules/ui/src/app/app-routing.module.ts
index 59be3e59d..f9c349b7f 100644
--- a/modules/ui/src/app/app-routing.module.ts
+++ b/modules/ui/src/app/app-routing.module.ts
@@ -21,6 +21,7 @@ const routes: Routes = [
path: 'testrun',
loadChildren: () =>
import('./progress/progress.module').then(m => m.ProgressModule),
+ title: 'Testrun',
},
{
path: 'devices',
@@ -28,11 +29,13 @@ const routes: Routes = [
import('./device-repository/device-repository.module').then(
m => m.DeviceRepositoryModule
),
+ title: 'Testrun - Devices',
},
{
path: 'reports',
loadChildren: () =>
import('./history/history.module').then(m => m.HistoryModule),
+ title: 'Testrun - Reports',
},
{
path: '',
diff --git a/modules/ui/src/app/app.component.html b/modules/ui/src/app/app.component.html
index 9b78ec850..e6e351a99 100644
--- a/modules/ui/src/app/app.component.html
+++ b/modules/ui/src/app/app.component.html
@@ -64,7 +64,10 @@
(keydown.tab)="skipToNavigation($event)">
menu
-
+
Testrun
@@ -74,7 +77,7 @@ Testrun
class="app-toolbar-button app-toolbar-button-general-settings"
mat-icon-button
aria-label="Connection settings"
- (click)="openGeneralSettings()">
+ (click)="openGeneralSettings(true)">
tune
@@ -84,8 +87,17 @@ Testrun
*ngIf="
(hasConnectionSetting$ | async) !== true && isConnectionSettingsLoaded
">
- Step 1: To perform a device test, please, select ports in Connection
- settings.
+ Step 1: To perform a device test, please, select ports in
+ Connection settings.
Testrun
(click)="navigateToDeviceRepository()"
(keydown.enter)="navigateToDeviceRepository()"
(keydown.space)="navigateToDeviceRepository()"
+ aria-label="The Create a Device link redirects to the Devices page and opens the dialogue there."
tabindex="0"
- role="button"
+ role="link"
class="message-link"
>Create a Device
@@ -121,8 +134,9 @@ Testrun
(click)="navigateToRuntime()"
(keydown.enter)="navigateToRuntime()"
(keydown.space)="navigateToRuntime()"
+ aria-label="The Testrun link redirects to the Testrun page and opens the dialogue there."
tabindex="0"
- role="button"
+ role="link"
class="message-link"
>Testrun
@@ -139,7 +153,6 @@ Testrun
diff --git a/modules/ui/src/app/app.component.scss b/modules/ui/src/app/app.component.scss
index 6060205f1..9eb33e232 100644
--- a/modules/ui/src/app/app.component.scss
+++ b/modules/ui/src/app/app.component.scss
@@ -74,10 +74,6 @@ $nav-open-btn-width: 210px;
line-height: 20px;
letter-spacing: 0.25px;
}
-
- app-version {
- padding: 0 16px;
- }
}
.app-sidebar {
@@ -132,7 +128,7 @@ $nav-open-btn-width: 210px;
.app-sidebar-button > .mat-icon {
margin: 0 11px;
min-width: 24px;
- line-height: 18px;
+ line-height: 18px !important;
}
.app-sidebar-button-active {
@@ -159,6 +155,7 @@ $nav-open-btn-width: 210px;
.logo-link .mat-icon {
width: 36px;
height: 23px;
+ line-height: 18px !important;
}
.main-heading {
@@ -206,4 +203,7 @@ app-version {
margin-top: auto;
margin-bottom: 16px;
max-width: 100%;
+ width: $nav-close-width;
+ display: flex;
+ justify-content: center;
}
diff --git a/modules/ui/src/app/app.component.spec.ts b/modules/ui/src/app/app.component.spec.ts
index 68bcc31c5..093ad3f35 100644
--- a/modules/ui/src/app/app.component.spec.ts
+++ b/modules/ui/src/app/app.component.spec.ts
@@ -38,7 +38,6 @@ import { AppRoutingModule } from './app-routing.module';
import { of } from 'rxjs/internal/observable/of';
import SpyObj = jasmine.SpyObj;
import { BypassComponent } from './components/bypass/bypass.component';
-import { VersionComponent } from './components/version/version.component';
import { CalloutComponent } from './components/callout/callout.component';
import {
MOCK_PROGRESS_DATA_IDLE,
@@ -46,6 +45,7 @@ import {
} from './mocks/progress.mock';
import { LoaderService } from './services/loader.service';
import { Routes } from './model/routes';
+import { StateService } from './services/state.service';
describe('AppComponent', () => {
let component: AppComponent;
@@ -54,6 +54,7 @@ describe('AppComponent', () => {
let router: Router;
let mockService: SpyObj;
let mockLoaderService: SpyObj;
+ let mockStateService: SpyObj;
const enterKeyEvent = new KeyboardEvent('keydown', {
key: 'Enter',
@@ -75,19 +76,22 @@ describe('AppComponent', () => {
'getSystemStatus',
'fetchHistory',
'getSystemInterfaces',
- 'getVersion',
- 'fetchVersion',
'setIsOpenAddDevice',
'systemStatus$',
'isTestrunStarted$',
'hasConnectionSetting$',
+ 'setIsOpenStartTestrun',
]);
mockLoaderService = jasmine.createSpyObj(['setLoading']);
+ mockStateService = jasmine.createSpyObj('mockStateService', [
+ 'focusFirstElementInMain',
+ ]);
+
mockService.getDevices.and.returnValue(
new BehaviorSubject([device])
);
- mockService.getSystemInterfaces.and.returnValue(of([]));
+ mockService.getSystemInterfaces.and.returnValue(of({}));
(mockService.systemStatus$ as unknown) = of({});
mockService.isTestrunStarted$ = of(true);
mockService.hasConnectionSetting$ = of(true);
@@ -103,17 +107,18 @@ describe('AppComponent', () => {
MatToolbarModule,
MatSidenavModule,
BypassComponent,
- VersionComponent,
CalloutComponent,
],
providers: [
{ provide: TestRunService, useValue: mockService },
{ provide: LoaderService, useValue: mockLoaderService },
+ { provide: StateService, useValue: mockStateService },
],
declarations: [
AppComponent,
FakeGeneralSettingsComponent,
FakeSpinnerComponent,
+ FakeVersionComponent,
],
});
@@ -228,6 +233,21 @@ describe('AppComponent', () => {
});
}));
+ it('should call focusFirstElementInMain if settingsDrawer opened not from toggleBtn', fakeAsync(() => {
+ spyOn(component.settingsDrawer, 'close').and.returnValue(
+ Promise.resolve('close')
+ );
+
+ component.openGeneralSettings(false);
+ tick();
+ component.closeSetting();
+ flush();
+
+ component.settingsDrawer.close().then(() => {
+ expect(mockStateService.focusFirstElementInMain).toHaveBeenCalled();
+ });
+ }));
+
it('should call settingsDrawer open on openSetting', fakeAsync(() => {
spyOn(component.settingsDrawer, 'open');
@@ -324,177 +344,220 @@ describe('AppComponent', () => {
expect(version).toBeTruthy();
});
- describe('with no connection settings', () => {
- beforeEach(() => {
- mockService.hasConnectionSetting$ = of(false);
- component.ngOnInit();
- fixture.detectChanges();
- });
+ describe('Callout component visibility', () => {
+ describe('with no connection settings', () => {
+ beforeEach(() => {
+ mockService.hasConnectionSetting$ = of(false);
+ component.ngOnInit();
+ fixture.detectChanges();
+ });
- it('should have callout component with "Step 1" text', () => {
- const callout = compiled.querySelector('app-callout');
- const calloutContent = callout?.innerHTML.trim();
+ it('should have callout component with "Step 1" text', () => {
+ const callout = compiled.querySelector('app-callout');
+ const calloutContent = callout?.innerHTML.trim();
- expect(callout).toBeTruthy();
- expect(calloutContent).toContain('Step 1');
- });
- });
+ expect(callout).toBeTruthy();
+ expect(calloutContent).toContain('Step 1');
+ });
- describe('with system status as "Idle"', () => {
- beforeEach(() => {
- mockService.hasConnectionSetting$ = of(true);
- mockService.getDevices.and.returnValue(
- new BehaviorSubject([device])
- );
- mockService.systemStatus$ = of(MOCK_PROGRESS_DATA_IDLE);
- mockService.isTestrunStarted$ = of(false);
- component.ngOnInit();
- fixture.detectChanges();
- });
+ it('should have callout content with "Connection settings" link ', () => {
+ const calloutLinkEl = compiled.querySelector(
+ '.message-link'
+ ) as HTMLAnchorElement;
+ const calloutLinkContent = calloutLinkEl.innerHTML.trim();
- it('should have callout component with "Step 3" text', () => {
- const callout = compiled.querySelector('app-callout');
- const calloutContent = callout?.innerHTML.trim();
+ expect(calloutLinkEl).toBeTruthy();
+ expect(calloutLinkContent).toContain('Connection settings');
+ });
- expect(callout).toBeTruthy();
- expect(calloutContent).toContain('Step 3');
- });
- });
+ keyboardCases.forEach(testCase => {
+ it(`should call openSetting on keydown ${testCase.name} "Connection settings" link`, fakeAsync(() => {
+ const spyOpenSetting = spyOn(component, 'openSetting');
+ const calloutLinkEl = compiled.querySelector(
+ '.message-link'
+ ) as HTMLAnchorElement;
- describe('with no devices setted', () => {
- beforeEach(() => {
- mockService.getDevices.and.returnValue(
- new BehaviorSubject(null)
- );
- component.ngOnInit();
- fixture.detectChanges();
- });
+ calloutLinkEl.dispatchEvent(testCase.event);
+ flush();
- it('should have callout component', () => {
- const callout = compiled.querySelector('app-callout');
+ expect(spyOpenSetting).toHaveBeenCalled();
+ }));
+ });
+ });
- expect(callout).toBeTruthy();
+ describe('with system status as "Idle"', () => {
+ beforeEach(() => {
+ mockService.hasConnectionSetting$ = of(true);
+ mockService.getDevices.and.returnValue(
+ new BehaviorSubject([device])
+ );
+ mockService.systemStatus$ = of(MOCK_PROGRESS_DATA_IDLE);
+ mockService.isTestrunStarted$ = of(false);
+ component.ngOnInit();
+ fixture.detectChanges();
+ });
+
+ it('should have callout component with "Step 3" text', () => {
+ const callout = compiled.querySelector('app-callout');
+ const calloutContent = callout?.innerHTML.trim();
+
+ expect(callout).toBeTruthy();
+ expect(calloutContent).toContain('Step 3');
+ });
});
- it('should have callout component with "Step 2" text', () => {
- const callout = compiled.querySelector('app-callout');
- const calloutContent = callout?.innerHTML.trim();
+ describe('with no devices setted', () => {
+ beforeEach(() => {
+ mockService.getDevices.and.returnValue(
+ new BehaviorSubject(null)
+ );
+ component.ngOnInit();
+ fixture.detectChanges();
+ });
- expect(callout).toBeTruthy();
- expect(calloutContent).toContain('Step 2');
- });
+ it('should have callout component', () => {
+ const callout = compiled.querySelector('app-callout');
- it('should have callout content with "Create a Device" link ', () => {
- const calloutLinkEl = compiled.querySelector(
- '.message-link'
- ) as HTMLAnchorElement;
- const calloutLinkContent = calloutLinkEl.innerHTML.trim();
+ expect(callout).toBeTruthy();
+ });
- expect(calloutLinkEl).toBeTruthy();
- expect(calloutLinkContent).toContain('Create a Device');
- });
+ it('should have callout component with "Step 2" text', () => {
+ const callout = compiled.querySelector('app-callout');
+ const calloutContent = callout?.innerHTML.trim();
- keyboardCases.forEach(testCase => {
- it(`should navigate to the device-repository on keydown ${testCase.name} "Create a Device" link`, fakeAsync(() => {
+ expect(callout).toBeTruthy();
+ expect(calloutContent).toContain('Step 2');
+ });
+
+ it('should have callout content with "Create a Device" link ', () => {
const calloutLinkEl = compiled.querySelector(
'.message-link'
) as HTMLAnchorElement;
+ const calloutLinkContent = calloutLinkEl.innerHTML.trim();
- calloutLinkEl.dispatchEvent(testCase.event);
- flush();
-
- expect(router.url).toBe(Routes.Devices);
- }));
- });
-
- it('should navigate to the device-repository on click "Create a Device" link', fakeAsync(() => {
- const calloutLinkEl = compiled.querySelector(
- '.message-link'
- ) as HTMLAnchorElement;
+ expect(calloutLinkEl).toBeTruthy();
+ expect(calloutLinkContent).toContain('Create a Device');
+ });
- calloutLinkEl.click();
- flush();
-
- expect(router.url).toBe(Routes.Devices);
- expect(mockService.setIsOpenAddDevice).toHaveBeenCalledWith(true);
- }));
- });
+ keyboardCases.forEach(testCase => {
+ it(`should navigate to the device-repository on keydown ${testCase.name} "Create a Device" link`, fakeAsync(() => {
+ const calloutLinkEl = compiled.querySelector(
+ '.message-link'
+ ) as HTMLAnchorElement;
- describe('with devices setted but without systemStatus data', () => {
- beforeEach(() => {
- mockService.getDevices.and.returnValue(
- new BehaviorSubject([device])
- );
- mockService.isTestrunStarted$ = of(false);
- component.ngOnInit();
- fixture.detectChanges();
- });
+ calloutLinkEl.dispatchEvent(testCase.event);
+ flush();
- it('should have callout component with "Step 3" text', () => {
- const callout = compiled.querySelector('app-callout');
- const calloutContent = callout?.innerHTML.trim();
+ expect(router.url).toBe(Routes.Devices);
+ }));
+ });
- expect(callout).toBeTruthy();
- expect(calloutContent).toContain('Step 3');
- });
+ it('should navigate to the device-repository on click "Create a Device" link', fakeAsync(() => {
+ const calloutLinkEl = compiled.querySelector(
+ '.message-link'
+ ) as HTMLAnchorElement;
- it('should have callout component with "Testrun" link', () => {
- const callout = compiled.querySelector('app-callout');
- const calloutLinkEl = compiled.querySelector(
- '.message-link'
- ) as HTMLAnchorElement;
- const calloutLinkContent = calloutLinkEl.innerHTML.trim();
+ calloutLinkEl.click();
+ flush();
- expect(callout).toBeTruthy();
- expect(calloutLinkContent).toContain('Testrun');
+ expect(router.url).toBe(Routes.Devices);
+ expect(mockService.setIsOpenAddDevice).toHaveBeenCalledWith(true);
+ }));
});
- keyboardCases.forEach(testCase => {
- it(`should navigate to the runtime on keydown ${testCase.name} "Run the Test" link`, fakeAsync(() => {
+ describe('with devices setted but without systemStatus data', () => {
+ beforeEach(() => {
+ mockService.getDevices.and.returnValue(
+ new BehaviorSubject([device])
+ );
+ mockService.isTestrunStarted$ = of(false);
+ component.ngOnInit();
+ fixture.detectChanges();
+ });
+
+ it('should have callout component with "Step 3" text', () => {
+ const callout = compiled.querySelector('app-callout');
+ const calloutContent = callout?.innerHTML.trim();
+
+ expect(callout).toBeTruthy();
+ expect(calloutContent).toContain('Step 3');
+ });
+
+ it('should have callout component with "Testrun" link', () => {
+ const callout = compiled.querySelector('app-callout');
const calloutLinkEl = compiled.querySelector(
'.message-link'
) as HTMLAnchorElement;
+ const calloutLinkContent = calloutLinkEl.innerHTML.trim();
- calloutLinkEl.dispatchEvent(testCase.event);
- flush();
+ expect(callout).toBeTruthy();
+ expect(calloutLinkContent).toContain('Testrun');
+ });
- expect(router.url).toBe(Routes.Testrun);
- }));
- });
- });
+ keyboardCases.forEach(testCase => {
+ it(`should navigate to the runtime on keydown ${testCase.name} "Run the Test" link`, fakeAsync(() => {
+ const calloutLinkEl = compiled.querySelector(
+ '.message-link'
+ ) as HTMLAnchorElement;
- describe('with devices setted, without systemStatus data, but run the tests ', () => {
- beforeEach(() => {
- mockService.getDevices.and.returnValue(
- new BehaviorSubject([device])
- );
- mockService.isTestrunStarted$ = of(true);
- component.ngOnInit();
- fixture.detectChanges();
+ calloutLinkEl.dispatchEvent(testCase.event);
+ flush();
+
+ expect(router.url).toBe(Routes.Testrun);
+ }));
+ });
});
- it('should not have callout component', () => {
- const callout = compiled.querySelector('app-callout');
+ describe('with devices setted, without systemStatus data, but run the tests ', () => {
+ beforeEach(() => {
+ mockService.getDevices.and.returnValue(
+ new BehaviorSubject([device])
+ );
+ mockService.isTestrunStarted$ = of(true);
+ component.ngOnInit();
+ fixture.detectChanges();
+ });
+
+ it('should not have callout component', () => {
+ const callout = compiled.querySelector('app-callout');
+
+ expect(callout).toBeNull();
+ });
+ });
- expect(callout).toBeNull();
+ describe('with devices setted and systemStatus data ', () => {
+ beforeEach(() => {
+ mockService.getDevices.and.returnValue(
+ new BehaviorSubject([device])
+ );
+ mockService.systemStatus$ = of(MOCK_PROGRESS_DATA_IN_PROGRESS);
+ component.ngOnInit();
+ fixture.detectChanges();
+ });
+
+ it('should not have callout component', () => {
+ const callout = compiled.querySelector('app-callout');
+
+ expect(callout).toBeNull();
+ });
});
});
- describe('with devices setted and systemStatus data ', () => {
- beforeEach(() => {
- mockService.getDevices.and.returnValue(
- new BehaviorSubject([device])
- );
- mockService.systemStatus$ = of(MOCK_PROGRESS_DATA_IN_PROGRESS);
- component.ngOnInit();
- fixture.detectChanges();
- });
+ it('should not call toggleSettingsBtn focus on closeSetting when device length is 0', async () => {
+ mockService.getDevices.and.returnValue(
+ new BehaviorSubject([])
+ );
+ component.ngOnInit();
+ fixture.detectChanges();
- it('should not have callout component', () => {
- const callout = compiled.querySelector('app-callout');
+ spyOn(component.settingsDrawer, 'close').and.returnValue(
+ Promise.resolve('close')
+ );
+ const spyToggle = spyOn(component.toggleSettingsBtn, 'focus');
- expect(callout).toBeNull();
- });
+ await component.closeSetting();
+
+ expect(spyToggle).toHaveBeenCalledTimes(0);
});
});
@@ -505,7 +568,6 @@ describe('AppComponent', () => {
class FakeGeneralSettingsComponent {
@Input() interfaces = [];
@Output() closeSettingEvent = new EventEmitter();
- @Output() openSettingEvent = new EventEmitter();
@Output() reloadInterfacesEvent = new EventEmitter();
}
@@ -514,3 +576,9 @@ class FakeGeneralSettingsComponent {
template: '',
})
class FakeSpinnerComponent {}
+
+@Component({
+ selector: 'app-version',
+ template: '',
+})
+class FakeVersionComponent {}
diff --git a/modules/ui/src/app/app.component.ts b/modules/ui/src/app/app.component.ts
index 41eb73ab2..287d8ba20 100644
--- a/modules/ui/src/app/app.component.ts
+++ b/modules/ui/src/app/app.component.ts
@@ -23,7 +23,7 @@ import {
import { MatIconRegistry } from '@angular/material/icon';
import { DomSanitizer } from '@angular/platform-browser';
import { MatDrawer } from '@angular/material/sidenav';
-import { TestRunService } from './services/test-run.service';
+import { SystemInterfaces, TestRunService } from './services/test-run.service';
import { Observable } from 'rxjs/internal/Observable';
import { Device } from './model/device';
import { take } from 'rxjs';
@@ -34,6 +34,7 @@ import { CalloutType } from './model/callout-type';
import { tap } from 'rxjs/internal/operators/tap';
import { shareReplay } from 'rxjs/internal/operators/shareReplay';
import { Routes } from './model/routes';
+import { StateService } from './services/state.service';
const DEVICES_LOGO_URL = '/assets/icons/devices.svg';
const REPORTS_LOGO_URL = '/assets/icons/reports.svg';
@@ -52,13 +53,14 @@ export class AppComponent implements OnInit {
systemStatus$!: Observable;
isTestrunStarted$!: Observable;
hasConnectionSetting$!: Observable;
- interfaces: string[] = [];
+ interfaces: SystemInterfaces = {};
isDevicesLoaded = false;
isStatusLoaded = false;
isConnectionSettingsLoaded = false;
public readonly StatusOfTestrun = StatusOfTestrun;
public readonly Routes = Routes;
-
+ private devicesLength = 0;
+ private openedSettingFromToggleBtn = true;
@ViewChild('settingsDrawer') public settingsDrawer!: MatDrawer;
@ViewChild('toggleSettingsBtn') public toggleSettingsBtn!: HTMLButtonElement;
@ViewChild('navigation') public navigation!: ElementRef;
@@ -69,6 +71,7 @@ export class AppComponent implements OnInit {
private domSanitizer: DomSanitizer,
private testRunService: TestRunService,
private readonly loaderService: LoaderService,
+ private readonly state: StateService,
private route: Router
) {
this.testRunService.fetchDevices();
@@ -99,7 +102,10 @@ export class AppComponent implements OnInit {
this.devices$ = this.testRunService.getDevices().pipe(
tap(result => {
if (result !== null) {
+ this.devicesLength = result.length;
this.isDevicesLoaded = true;
+ } else {
+ this.devicesLength = 0;
}
}),
shareReplay({ refCount: true, bufferSize: 1 })
@@ -128,16 +134,23 @@ export class AppComponent implements OnInit {
navigateToRuntime(): void {
this.route.navigate([Routes.Testrun]);
+ this.testRunService.setIsOpenStartTestrun(true);
}
async closeSetting(): Promise {
- return await this.settingsDrawer
- .close()
- .then(() => this.toggleSettingsBtn.focus());
+ return await this.settingsDrawer.close().then(() => {
+ if (this.devicesLength > 0) {
+ this.toggleSettingsBtn.focus();
+ } // else device create window will be opened
+
+ if (!this.openedSettingFromToggleBtn) {
+ this.state.focusFirstElementInMain();
+ }
+ });
}
async openSetting(): Promise {
- return await this.openGeneralSettings();
+ return await this.openGeneralSettings(false);
}
reloadInterfaces(): void {
@@ -169,7 +182,8 @@ export class AppComponent implements OnInit {
}
}
- async openGeneralSettings() {
+ async openGeneralSettings(openSettingFromToggleBtn: boolean) {
+ this.openedSettingFromToggleBtn = openSettingFromToggleBtn;
this.getSystemInterfaces();
await this.settingsDrawer.open();
}
diff --git a/modules/ui/src/app/app.module.ts b/modules/ui/src/app/app.module.ts
index 67d4c3464..e130fda29 100644
--- a/modules/ui/src/app/app.module.ts
+++ b/modules/ui/src/app/app.module.ts
@@ -30,6 +30,8 @@ import { GeneralSettingsComponent } from './components/general-settings/general-
import { ReactiveFormsModule } from '@angular/forms';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatSnackBarModule } from '@angular/material/snack-bar';
+import { MatInputModule } from '@angular/material/input';
+import { MatSelectModule } from '@angular/material/select';
import { ErrorInterceptor } from './interceptors/error.interceptor';
import { LoadingInterceptor } from './interceptors/loading.interceptor';
import { SpinnerComponent } from './components/spinner/spinner.component';
@@ -49,6 +51,8 @@ import { CalloutComponent } from './components/callout/callout.component';
MatSidenavModule,
MatButtonToggleModule,
MatRadioModule,
+ MatInputModule,
+ MatSelectModule,
HttpClientModule,
ReactiveFormsModule,
MatFormFieldModule,
diff --git a/modules/ui/src/app/components/bypass/bypass.component.spec.ts b/modules/ui/src/app/components/bypass/bypass.component.spec.ts
index ab8919049..93430f5c4 100644
--- a/modules/ui/src/app/components/bypass/bypass.component.spec.ts
+++ b/modules/ui/src/app/components/bypass/bypass.component.spec.ts
@@ -17,6 +17,8 @@ import { ComponentFixture, TestBed } from '@angular/core/testing';
import { BypassComponent } from './bypass.component';
import { Component } from '@angular/core';
+import { StateService } from '../../services/state.service';
+import SpyObj = jasmine.SpyObj;
@Component({
selector: 'app-test-bypass',
@@ -29,11 +31,14 @@ class TestBypassComponent {}
describe('BypassComponent', () => {
let component: TestBypassComponent;
let fixture: ComponentFixture;
+ let mockService: SpyObj;
beforeEach(() => {
+ mockService = jasmine.createSpyObj(['focusFirstElementInMain']);
TestBed.configureTestingModule({
imports: [BypassComponent],
declarations: [TestBypassComponent],
+ providers: [{ provide: StateService, useValue: mockService }],
});
fixture = TestBed.createComponent(TestBypassComponent);
component = fixture.componentInstance;
@@ -49,13 +54,10 @@ describe('BypassComponent', () => {
const button = fixture.nativeElement.querySelector(
'.navigation-bypass-button'
) as HTMLButtonElement;
- const testButton = fixture.nativeElement.querySelector(
- '#test-button'
- ) as HTMLButtonElement;
button?.click();
- expect(document.activeElement).toBe(testButton);
+ expect(mockService.focusFirstElementInMain).toHaveBeenCalled();
});
});
});
diff --git a/modules/ui/src/app/components/bypass/bypass.component.ts b/modules/ui/src/app/components/bypass/bypass.component.ts
index 218a0b3c5..ad29f4544 100644
--- a/modules/ui/src/app/components/bypass/bypass.component.ts
+++ b/modules/ui/src/app/components/bypass/bypass.component.ts
@@ -16,6 +16,7 @@
import { Component } from '@angular/core';
import { CommonModule } from '@angular/common';
import { MatButtonModule } from '@angular/material/button';
+import { StateService } from '../../services/state.service';
@Component({
selector: 'app-bypass',
@@ -25,13 +26,9 @@ import { MatButtonModule } from '@angular/material/button';
styleUrls: ['./bypass.component.scss'],
})
export class BypassComponent {
+ constructor(private readonly state: StateService) {}
skipToMainContent(event: Event) {
event.preventDefault();
- const firstControl: HTMLElement | null = window.document.querySelector(
- '#main button:not(disabled), #main table'
- );
- if (firstControl) {
- firstControl.focus();
- }
+ this.state.focusFirstElementInMain();
}
}
diff --git a/modules/ui/src/app/components/callout/callout.component.scss b/modules/ui/src/app/components/callout/callout.component.scss
index dc7d78e5d..d6b6f26c5 100644
--- a/modules/ui/src/app/components/callout/callout.component.scss
+++ b/modules/ui/src/app/components/callout/callout.component.scss
@@ -32,7 +32,7 @@
min-height: 48px;
padding: 6px 24px;
border-radius: 8px;
- align-items: flex-start;
+ align-items: center;
gap: 16px;
}
diff --git a/modules/ui/src/app/components/delete-form/delete-form.component.html b/modules/ui/src/app/components/delete-form/delete-form.component.html
index cafeba26c..e63e204b8 100644
--- a/modules/ui/src/app/components/delete-form/delete-form.component.html
+++ b/modules/ui/src/app/components/delete-form/delete-form.component.html
@@ -19,10 +19,20 @@
-
diff --git a/modules/ui/src/app/components/delete-report/delete-report.component.spec.ts b/modules/ui/src/app/components/delete-report/delete-report.component.spec.ts
index 766bf8b4e..11e9c2a2e 100644
--- a/modules/ui/src/app/components/delete-report/delete-report.component.spec.ts
+++ b/modules/ui/src/app/components/delete-report/delete-report.component.spec.ts
@@ -55,6 +55,7 @@ describe('DeleteReportComponent', () => {
});
it('#deleteReport should open delete dialog', () => {
+ const deviceRemovedSpy = spyOn(component.deviceRemoved, 'emit');
spyOn(component.dialog, 'open').and.returnValue({
afterClosed: () => of(true),
} as MatDialogRef);
@@ -71,6 +72,7 @@ describe('DeleteReportComponent', () => {
'01:02:03:04:05:06',
'2023-06-22T09:20:00.123Z'
);
+ expect(deviceRemovedSpy).toHaveBeenCalled();
});
});
diff --git a/modules/ui/src/app/components/delete-report/delete-report.component.ts b/modules/ui/src/app/components/delete-report/delete-report.component.ts
index 16d82ac52..4f76b1859 100644
--- a/modules/ui/src/app/components/delete-report/delete-report.component.ts
+++ b/modules/ui/src/app/components/delete-report/delete-report.component.ts
@@ -13,7 +13,13 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-import { ChangeDetectionStrategy, Component, OnDestroy } from '@angular/core';
+import {
+ ChangeDetectionStrategy,
+ Component,
+ EventEmitter,
+ OnDestroy,
+ Output,
+} from '@angular/core';
import { CommonModule, DatePipe } from '@angular/common';
import { MatButtonModule } from '@angular/material/button';
import { ReportActionComponent } from '../report-action/report-action.component';
@@ -36,6 +42,7 @@ export class DeleteReportComponent
extends ReportActionComponent
implements OnDestroy
{
+ @Output() deviceRemoved = new EventEmitter();
private destroy$: Subject = new Subject();
constructor(
private testRunService: TestRunService,
@@ -72,6 +79,7 @@ export class DeleteReportComponent
this.testRunService
.deleteReport(this.data.device.mac_addr, this.data.started || '')
.subscribe(() => {
+ this.deviceRemoved.emit();
this.testRunService.removeReport(
this.data.device.mac_addr,
this.data.started || ''
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 19683b6f1..7fc675c86 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
@@ -19,12 +19,12 @@
[attr.aria-label]="label"
class="device-item"
type="button">
-
- {{ device.model }}
-
{{ device.manufacturer }}
+
+ {{ device.model }}
+
{{ device.mac_addr }}
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 4e9e88b49..b7025ff76 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
@@ -32,17 +32,17 @@ $border-radius: 12px;
grid-row-gap: 4px;
font-family: 'Open Sans', sans-serif;
grid-template-areas:
- 'name name icon'
- 'manufacturer address icon';
+ 'manufacturer manufacturer icon'
+ 'name address icon';
&:hover {
cursor: pointer;
}
}
-.item-name {
+.item-manufacturer {
padding: 0 16px;
- grid-area: name;
+ grid-area: manufacturer;
justify-self: start;
align-self: end;
color: #1f1f1f;
@@ -57,9 +57,9 @@ $border-radius: 12px;
text-align: start;
}
-.item-manufacturer {
+.item-name {
padding: 0 16px;
- grid-area: manufacturer;
+ grid-area: name;
justify-self: start;
color: $grey-800;
font-size: 14px;
diff --git a/modules/ui/src/app/components/device-item/device-item.component.ts b/modules/ui/src/app/components/device-item/device-item.component.ts
index a72d2516d..dfb161089 100644
--- a/modules/ui/src/app/components/device-item/device-item.component.ts
+++ b/modules/ui/src/app/components/device-item/device-item.component.ts
@@ -32,6 +32,6 @@ export class DeviceItemComponent {
}
get label() {
- return `${this.device.model} ${this.device.manufacturer} ${this.device.mac_addr}`;
+ return `${this.device.manufacturer} ${this.device.model} ${this.device.mac_addr}`;
}
}
diff --git a/modules/ui/src/app/components/filter-chips/filter-chips.component.html b/modules/ui/src/app/components/filter-chips/filter-chips.component.html
index 512862023..07cf43aa0 100644
--- a/modules/ui/src/app/components/filter-chips/filter-chips.component.html
+++ b/modules/ui/src/app/components/filter-chips/filter-chips.component.html
@@ -20,11 +20,16 @@
class="filter-chip"
*ngIf="!isValueEmpty(item.value)"
(removed)="clearFilter(item.key)">
- Device
+ Device contains "{{ item.value }}"
- Firmware
+ Firmware contains "{{ item.value }}"
+
+
+ {{ item.value }}
- {{ item.value }}
+
+ Clear all filters
+
-Clear all filters
diff --git a/modules/ui/src/app/components/filter-chips/filter-chips.component.scss b/modules/ui/src/app/components/filter-chips/filter-chips.component.scss
index 3b3eeddb0..5dc1c5ede 100644
--- a/modules/ui/src/app/components/filter-chips/filter-chips.component.scss
+++ b/modules/ui/src/app/components/filter-chips/filter-chips.component.scss
@@ -23,7 +23,7 @@
display: flex;
align-items: center;
justify-content: center;
- padding: 0 16px;
+ padding: 0 8px;
cursor: pointer;
color: $primary;
font-weight: 400;
@@ -31,6 +31,8 @@
margin: 4px 0 4px 8px;
border-radius: 16px;
flex-shrink: 0;
+ background: $white;
+ font-family: Roboto, sans-serif;
}
.filter-chip.mat-mdc-chip {
@@ -58,3 +60,17 @@
padding-right: 0;
border-radius: 8px;
}
+
+.mat-mdc-standard-chip:not(
+ .mdc-evolution-chip--disabled
+ ).filter-chip-clear-all {
+ background-color: $white;
+
+ & ::ng-deep .mat-mdc-chip-action {
+ padding: 0;
+ }
+
+ & ::ng-deep .mat-mdc-chip-focus-overlay {
+ display: none;
+ }
+}
diff --git a/modules/ui/src/app/components/filter-dialog/filter-dialog.component.html b/modules/ui/src/app/components/filter-dialog/filter-dialog.component.html
index b422d051a..459ad20d6 100644
--- a/modules/ui/src/app/components/filter-dialog/filter-dialog.component.html
+++ b/modules/ui/src/app/components/filter-dialog/filter-dialog.component.html
@@ -21,7 +21,11 @@
appearance="outline"
class="text-field"
*ngIf="data.filter === FilterName.DeviceInfo">
-
+
+ matInput
+ aria-label="device firmware" />
-
- If a port is missing from this list, you can
-
- Refresh
-
- the Connection settings
-
-
- Both interfaces must have different values
-
-
diff --git a/modules/ui/src/app/components/general-settings/general-settings.component.scss b/modules/ui/src/app/components/general-settings/general-settings.component.scss
index 84d49e514..01e13dcef 100644
--- a/modules/ui/src/app/components/general-settings/general-settings.component.scss
+++ b/modules/ui/src/app/components/general-settings/general-settings.component.scss
@@ -56,20 +56,25 @@
}
.setting-drawer-content {
- padding: 11px 16px 16px;
+ padding: 11px 16px 8px 16px;
overflow: hidden;
+ flex: 1;
form {
display: grid;
- grid-template-rows: repeat(4, auto);
+ grid-template-rows: repeat(7, auto) 1fr;
height: 100%;
}
+
+ .setting-drawer-content-form-empty {
+ grid-template-rows: repeat(2, auto) 1fr;
+ }
}
.error-message-container {
display: block;
margin-top: auto;
- padding: 0 16px;
+ padding-bottom: 8px;
}
.error-message-container + .setting-drawer-footer {
@@ -79,11 +84,6 @@
.setting-form-label {
font-size: 18px;
color: $dark-grey;
-
- &.device-label {
- display: inline-block;
- padding-top: 16px;
- }
}
:host:has(.two-ports-message) .internet-label {
@@ -95,38 +95,56 @@
font-size: 14px;
line-height: 20px;
letter-spacing: 0.2px;
- margin: 10px 0 0;
+ margin: 10px 0;
color: $dark-grey;
}
-.setting-radio-group {
- display: flex;
- flex-direction: column;
- margin-left: -10px;
- align-items: flex-start;
- overflow: scroll;
+.setting-option-value {
+ padding: 14px 16px;
}
-.setting-radio-button {
- padding: 8px 0;
+.option-value {
+ margin: 0;
+ font-family: Roboto;
+ font-size: 14px;
+ font-style: normal;
+ font-weight: 400;
+ line-height: 20px;
+ letter-spacing: 0.2px;
- ::ng-deep .mdc-form-field > label {
+ &.top {
+ color: #3c4043;
+ }
+
+ &.bottom {
+ color: #5f6368;
+ }
+}
+
+.setting-field {
+ ::ng-deep .mat-mdc-form-field-infix {
+ min-height: 76px;
+ display: flex;
+ align-items: center;
+ }
+
+ ::ng-deep .mat-mdc-floating-label {
font-family: Roboto;
- font-size: 16px;
+ font-size: 14px;
font-style: normal;
font-weight: 400;
- line-height: 24px;
- letter-spacing: 0.1px;
- color: $grey-800;
- max-width: 240px;
- overflow: hidden;
- text-overflow: ellipsis;
+ line-height: 20px;
+ letter-spacing: 0.2px;
+ }
+
+ ::ng-deep .mat-mdc-floating-label:not(.mdc-floating-label--float-above) {
+ top: 35px;
}
}
.message {
margin: 0;
- padding: 0 16px 16px;
+ padding: 16px 0 0 0;
color: $grey-800;
font-family: $font-secondary;
font-size: 14px;
@@ -135,11 +153,11 @@
}
.setting-drawer-footer {
+ padding: 0 8px;
margin-top: auto;
display: flex;
flex-shrink: 0;
justify-content: flex-end;
- padding: 16px 24px 8px 24px;
.close-button,
.save-button {
@@ -153,7 +171,7 @@
.close-button {
margin-right: 10px;
&:enabled {
- color: $secondary;
+ color: $primary;
}
}
}
diff --git a/modules/ui/src/app/components/general-settings/general-settings.component.spec.ts b/modules/ui/src/app/components/general-settings/general-settings.component.spec.ts
index 6448b80a1..e9b82ddca 100644
--- a/modules/ui/src/app/components/general-settings/general-settings.component.spec.ts
+++ b/modules/ui/src/app/components/general-settings/general-settings.component.spec.ts
@@ -13,15 +13,13 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-import {
- ComponentFixture,
- fakeAsync,
- TestBed,
- tick,
-} from '@angular/core/testing';
+import { ComponentFixture, TestBed } from '@angular/core/testing';
import { GeneralSettingsComponent } from './general-settings.component';
-import { TestRunService } from '../../services/test-run.service';
+import {
+ SystemInterfaces,
+ TestRunService,
+} from '../../services/test-run.service';
import { of } from 'rxjs';
import { SystemConfig } from '../../model/setting';
import { MatRadioModule } from '@angular/material/radio';
@@ -30,6 +28,11 @@ import { MatButtonModule } from '@angular/material/button';
import { MatIcon, MatIconModule } from '@angular/material/icon';
import { MatIconTestingModule } from '@angular/material/icon/testing';
import { Component, Input } from '@angular/core';
+import { LiveAnnouncer } from '@angular/cdk/a11y';
+import SpyObj = jasmine.SpyObj;
+import { MatInputModule } from '@angular/material/input';
+import { MatSelectModule } from '@angular/material/select';
+import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
const MOCK_SYSTEM_CONFIG_EMPTY: SystemConfig = {
network: {
@@ -40,21 +43,21 @@ const MOCK_SYSTEM_CONFIG_EMPTY: SystemConfig = {
const MOCK_SYSTEM_CONFIG_WITH_DATA: SystemConfig = {
network: {
- device_intf: 'mockDeviceValue',
- internet_intf: 'mockInternetValue',
+ device_intf: 'mockDeviceKey',
+ internet_intf: 'mockInternetKey',
},
};
-const MOCK_SYSTEM_CONFIG_WITH_ONE_SETTING: SystemConfig = {
- network: {
- device_intf: 'mockDeviceValue',
- },
+const MOCK_INTERFACES: SystemInterfaces = {
+ mockDeviceKey: 'mockDeviceValue',
+ mockInternetKey: 'mockInternetValue',
};
describe('GeneralSettingsComponent', () => {
let component: GeneralSettingsComponent;
let fixture: ComponentFixture;
- let testRunServiceMock: jasmine.SpyObj;
+ let testRunServiceMock: SpyObj;
+ let mockLiveAnnouncer: SpyObj;
let compiled: HTMLElement;
beforeEach(async () => {
@@ -63,19 +66,22 @@ describe('GeneralSettingsComponent', () => {
'getSystemConfig',
'setSystemConfig',
'createSystemConfig',
- 'setIsOpenAddDevice',
'hasConnectionSetting$',
'setHasConnectionSetting',
+ 'systemConfig$',
]);
- testRunServiceMock.getSystemInterfaces.and.returnValue(of([]));
+ testRunServiceMock.getSystemInterfaces.and.returnValue(of({}));
testRunServiceMock.getSystemConfig.and.returnValue(
of(MOCK_SYSTEM_CONFIG_EMPTY)
);
testRunServiceMock.createSystemConfig.and.returnValue(
of(MOCK_SYSTEM_CONFIG_WITH_DATA)
);
+ testRunServiceMock.systemConfig$ = of(MOCK_SYSTEM_CONFIG_EMPTY);
testRunServiceMock.hasConnectionSetting$ = of(true);
+ mockLiveAnnouncer = jasmine.createSpyObj(['announce']);
+
await TestBed.configureTestingModule({
declarations: [
GeneralSettingsComponent,
@@ -83,13 +89,19 @@ describe('GeneralSettingsComponent', () => {
FakeSpinnerComponent,
FakeCalloutComponent,
],
- providers: [{ provide: TestRunService, useValue: testRunServiceMock }],
+ providers: [
+ { provide: TestRunService, useValue: testRunServiceMock },
+ { provide: LiveAnnouncer, useValue: mockLiveAnnouncer },
+ ],
imports: [
+ BrowserAnimationsModule,
MatButtonModule,
MatIconModule,
MatRadioModule,
ReactiveFormsModule,
MatIconTestingModule,
+ MatInputModule,
+ MatSelectModule,
],
}).compileComponents();
@@ -107,14 +119,15 @@ describe('GeneralSettingsComponent', () => {
testRunServiceMock.getSystemConfig.and.returnValue(
of(MOCK_SYSTEM_CONFIG_WITH_DATA)
);
+ component.interfaces = { mockDeviceKey: 'mockDeviceValue' };
+
+ const expectedDevice = { key: 'mockDeviceKey', value: 'mockDeviceValue' };
component.ngOnInit();
- expect(component.deviceControl.value).toBe(
- MOCK_SYSTEM_CONFIG_WITH_DATA?.network?.device_intf
- );
- expect(component.internetControl.value).toBe(
- MOCK_SYSTEM_CONFIG_WITH_DATA?.network?.internet_intf
+ expect(component.deviceControl.value).toEqual(expectedDevice);
+ expect(component.internetControl.value).toEqual(
+ component.defaultInternetOption
);
});
@@ -126,76 +139,51 @@ describe('GeneralSettingsComponent', () => {
expect(component.reloadInterfacesEvent.emit).toHaveBeenCalled();
});
- describe('#openSetting', () => {
- it('should call openSetting if device and internet data are unavailable', () => {
- spyOn(component.openSettingEvent, 'emit');
-
- component.ngOnInit();
-
- expect(component.openSettingEvent.emit).toHaveBeenCalled();
- });
-
- it('should call openSetting if not systemConfig data', fakeAsync(() => {
- spyOn(component.openSettingEvent, 'emit');
- testRunServiceMock.getSystemConfig.and.returnValue(of({}));
- tick();
-
- component.ngOnInit();
-
- expect(component.openSettingEvent.emit).toHaveBeenCalled();
- }));
-
- it('should call openSetting if only one setting available', fakeAsync(() => {
- spyOn(component.openSettingEvent, 'emit');
- testRunServiceMock.getSystemConfig.and.returnValue(
- of(MOCK_SYSTEM_CONFIG_WITH_ONE_SETTING)
- );
- tick();
-
- component.ngOnInit();
-
- expect(component.openSettingEvent.emit).toHaveBeenCalled();
- }));
-
- it('should not call openSetting if device and internet data are available', fakeAsync(() => {
- spyOn(component.openSettingEvent, 'emit');
- testRunServiceMock.getSystemConfig.and.returnValue(
- of(MOCK_SYSTEM_CONFIG_WITH_DATA)
- );
- tick();
-
- component.ngOnInit();
-
- expect(component.openSettingEvent.emit).not.toHaveBeenCalled();
- }));
- });
-
describe('#closeSetting', () => {
beforeEach(() => {
testRunServiceMock.systemConfig$ = of(MOCK_SYSTEM_CONFIG_WITH_DATA);
+ component.interfaces = MOCK_INTERFACES;
});
it('should emit closeSettingEvent', () => {
spyOn(component.closeSettingEvent, 'emit');
- component.closeSetting();
+ component.closeSetting('Message');
expect(component.closeSettingEvent.emit).toHaveBeenCalled();
});
+ it('should call liveAnnouncer with provided message', () => {
+ const mockMessage = 'mock event';
+
+ component.closeSetting(mockMessage);
+
+ expect(mockLiveAnnouncer.announce).toHaveBeenCalledWith(
+ `The ${mockMessage} finished. The connection setting panel is closed.`
+ );
+ });
+
it('should call reset settingForm', () => {
spyOn(component.settingForm, 'reset');
- component.closeSetting();
+ component.closeSetting('Message');
expect(component.settingForm.reset).toHaveBeenCalled();
});
it('should set value of settingForm on setSystemSetting', () => {
- component.closeSetting();
+ component.closeSetting('Message');
+
+ const expectedDevice = { key: 'mockDeviceKey', value: 'mockDeviceValue' };
+ const expectedInternet = {
+ key: 'mockInternetKey',
+ value: 'mockInternetValue',
+ };
+
+ expect(component.settingForm.value.device_intf).toEqual(expectedDevice);
- expect(component.settingForm.value).toEqual(
- MOCK_SYSTEM_CONFIG_WITH_DATA.network
+ expect(component.settingForm.value.internet_intf).toEqual(
+ expectedInternet
);
});
});
@@ -218,38 +206,31 @@ describe('GeneralSettingsComponent', () => {
});
it('should call createSystemConfig when setting form valid', () => {
- component.deviceControl.setValue(
- MOCK_SYSTEM_CONFIG_WITH_DATA?.network?.device_intf
- );
- component.internetControl.setValue(
- MOCK_SYSTEM_CONFIG_WITH_DATA?.network?.internet_intf
- );
+ const expectedResult = {
+ network: {
+ device_intf: 'mockDeviceKey',
+ internet_intf: '',
+ },
+ };
+
+ component.deviceControl.setValue({
+ key: 'mockDeviceKey',
+ value: 'mockDeviceValue',
+ });
+ component.internetControl.setValue(component.defaultInternetOption);
component.saveSetting();
expect(component.settingForm.invalid).toBeFalse();
expect(testRunServiceMock.createSystemConfig).toHaveBeenCalledWith(
- MOCK_SYSTEM_CONFIG_WITH_DATA
- );
- });
-
- it('should setIsOpenAddDevice as true on first save setting', () => {
- component.deviceControl.setValue(
- MOCK_SYSTEM_CONFIG_WITH_DATA.network?.device_intf
- );
- component.internetControl.setValue(
- MOCK_SYSTEM_CONFIG_WITH_DATA.network?.internet_intf
+ expectedResult
);
-
- component.saveSetting();
-
- expect(testRunServiceMock.setIsOpenAddDevice).toHaveBeenCalledWith(true);
});
});
describe('with no intefaces data', () => {
beforeEach(() => {
- component.interfaces = [];
+ component.interfaces = {};
fixture.detectChanges();
});
@@ -270,7 +251,7 @@ describe('GeneralSettingsComponent', () => {
describe('with intefaces lenght less then two', () => {
beforeEach(() => {
- component.interfaces = ['mockDeviceValue'];
+ component.interfaces = { mockDeviceValue: 'mockDeviceValue' };
testRunServiceMock.systemConfig$ = of(MOCK_SYSTEM_CONFIG_WITH_DATA);
testRunServiceMock.getSystemConfig.and.returnValue(
of(MOCK_SYSTEM_CONFIG_WITH_DATA)
@@ -301,9 +282,12 @@ describe('GeneralSettingsComponent', () => {
});
});
- describe('with intefaces lenght more then one', () => {
+ describe('with interfaces length more then one', () => {
beforeEach(() => {
- component.interfaces = ['mockDeviceValue', 'mockInternetValue'];
+ component.interfaces = {
+ mockDeviceValue: 'mockDeviceValue',
+ mockInterfaceValue: 'mockInterfaceValue',
+ };
testRunServiceMock.systemConfig$ = of(MOCK_SYSTEM_CONFIG_WITH_DATA);
testRunServiceMock.getSystemConfig.and.returnValue(
of(MOCK_SYSTEM_CONFIG_WITH_DATA)
diff --git a/modules/ui/src/app/components/general-settings/general-settings.component.ts b/modules/ui/src/app/components/general-settings/general-settings.component.ts
index 1d19d0e56..5f34ff98c 100644
--- a/modules/ui/src/app/components/general-settings/general-settings.component.ts
+++ b/modules/ui/src/app/components/general-settings/general-settings.component.ts
@@ -21,18 +21,19 @@ import {
OnInit,
Output,
} from '@angular/core';
-import {
- FormBuilder,
- FormControl,
- FormGroup,
- Validators,
-} from '@angular/forms';
+import { FormBuilder, FormControl, FormGroup } from '@angular/forms';
import { Subject, takeUntil, tap } from 'rxjs';
-import { TestRunService } from '../../services/test-run.service';
+import {
+ SystemInterfaces,
+ TestRunService,
+} from '../../services/test-run.service';
import { OnlyDifferentValuesValidator } from './only-different-values.validator';
import { CalloutType } from '../../model/callout-type';
import { Observable } from 'rxjs/internal/Observable';
import { shareReplay } from 'rxjs/internal/operators/shareReplay';
+import { LiveAnnouncer } from '@angular/cdk/a11y';
+import { EventType } from '../../model/event-type';
+import { SettingOption } from '../../model/setting';
@Component({
selector: 'app-general-settings',
@@ -40,22 +41,34 @@ import { shareReplay } from 'rxjs/internal/operators/shareReplay';
styleUrls: ['./general-settings.component.scss'],
})
export class GeneralSettingsComponent implements OnInit, OnDestroy {
- @Input() interfaces: string[] = [];
+ private _interfaces: SystemInterfaces = {};
+ @Input()
+ get interfaces(): SystemInterfaces {
+ return this._interfaces;
+ }
+ set interfaces(value: SystemInterfaces) {
+ this._interfaces = value;
+ this.setSystemSetting();
+ }
@Output() closeSettingEvent = new EventEmitter();
- @Output() openSettingEvent = new EventEmitter();
@Output() reloadInterfacesEvent = new EventEmitter();
public readonly CalloutType = CalloutType;
+ public readonly EventType = EventType;
public settingForm!: FormGroup;
public isSubmitting = false;
+ public defaultInternetOption = {
+ key: '',
+ value: 'Not specified',
+ };
hasConnectionSetting$!: Observable;
private destroy$: Subject = new Subject();
get deviceControl(): FormControl {
- return this.settingForm.get('device_intf') as FormControl;
+ return this.settingForm?.get('device_intf') as FormControl;
}
get internetControl(): FormControl {
- return this.settingForm.get('internet_intf') as FormControl;
+ return this.settingForm?.get('internet_intf') as FormControl;
}
get isFormValues(): boolean {
@@ -67,12 +80,13 @@ export class GeneralSettingsComponent implements OnInit, OnDestroy {
}
get isLessThanTwoInterfaces(): boolean {
- return !this.interfaces.length || this.interfaces?.length < 2;
+ return Object.keys(this.interfaces).length < 2;
}
constructor(
private readonly testRunService: TestRunService,
private readonly fb: FormBuilder,
+ private liveAnnouncer: LiveAnnouncer,
private readonly onlyDifferentValuesValidator: OnlyDifferentValuesValidator
) {}
@@ -90,9 +104,12 @@ export class GeneralSettingsComponent implements OnInit, OnDestroy {
this.reloadInterfacesEvent.emit();
}
- closeSetting(): void {
+ closeSetting(message: string): void {
this.resetForm();
this.closeSettingEvent.emit();
+ this.liveAnnouncer.announce(
+ `The ${message} finished. The connection setting panel is closed.`
+ );
this.setSystemSetting();
}
@@ -108,8 +125,8 @@ export class GeneralSettingsComponent implements OnInit, OnDestroy {
private createSettingForm(): FormGroup {
return (this.settingForm = this.fb.group(
{
- device_intf: ['', Validators.required],
- internet_intf: ['', Validators.required],
+ device_intf: [''],
+ internet_intf: [this.defaultInternetOption],
},
{
validators: [this.onlyDifferentValuesValidator.onlyDifferentSetting()],
@@ -129,23 +146,40 @@ export class GeneralSettingsComponent implements OnInit, OnDestroy {
this.testRunService.setHasConnectionSetting(true);
} else {
this.testRunService.setHasConnectionSetting(false);
- this.openSetting();
}
this.setDefaultFormValues(device_intf, internet_intf);
} else {
this.testRunService.setHasConnectionSetting(false);
- this.openSetting();
}
this.testRunService.setSystemConfig(config);
});
}
+ compare(c1: SettingOption, c2: SettingOption): boolean {
+ return c1 && c2 && c1.key === c2.key && c1.value === c2.value;
+ }
+
private setDefaultFormValues(
device: string | undefined,
internet: string | undefined
): void {
- this.deviceControl.setValue(device);
- this.internetControl.setValue(internet);
+ if (device && this.interfaces[device]) {
+ const deviceData = this.transformValueToObj(device);
+ this.deviceControl.setValue(deviceData);
+ }
+ if (internet && this.interfaces[internet]) {
+ const interneData = this.transformValueToObj(internet);
+ this.internetControl.setValue(interneData);
+ } else {
+ this.internetControl.setValue(this.defaultInternetOption);
+ }
+ }
+
+ private transformValueToObj(value: string): SettingOption {
+ return {
+ key: value,
+ value: this.interfaces[value],
+ };
}
private cleanFormErrorMessage(): void {
@@ -161,8 +195,8 @@ export class GeneralSettingsComponent implements OnInit, OnDestroy {
const { device_intf, internet_intf } = this.settingForm.value;
const data = {
network: {
- device_intf,
- internet_intf,
+ device_intf: device_intf.key,
+ internet_intf: internet_intf.key,
},
};
@@ -170,26 +204,21 @@ export class GeneralSettingsComponent implements OnInit, OnDestroy {
.createSystemConfig(data)
.pipe(takeUntil(this.destroy$))
.subscribe(() => {
- this.closeSetting();
+ this.closeSetting(EventType.Save);
this.testRunService.setSystemConfig(data);
- this.testRunService.setIsOpenAddDevice(true);
this.testRunService.setHasConnectionSetting(true);
});
}
- private openSetting(): void {
- this.openSettingEvent.emit();
- }
-
private setSystemSetting(): void {
this.testRunService.systemConfig$
.pipe(takeUntil(this.destroy$))
.subscribe(config => {
if (config?.network) {
const { device_intf, internet_intf } = config.network;
- if (device_intf && internet_intf) {
- this.setDefaultFormValues(device_intf, internet_intf);
- }
+ this.setDefaultFormValues(device_intf, internet_intf);
+ } else {
+ this.internetControl?.setValue(this.defaultInternetOption);
}
});
}
diff --git a/modules/ui/src/app/components/general-settings/only-different-values.validator.ts b/modules/ui/src/app/components/general-settings/only-different-values.validator.ts
index dea0c50dc..351e2bc06 100644
--- a/modules/ui/src/app/components/general-settings/only-different-values.validator.ts
+++ b/modules/ui/src/app/components/general-settings/only-different-values.validator.ts
@@ -39,7 +39,7 @@ export class OnlyDifferentValuesValidator {
return null;
}
- if (deviceControlValue === internetControlValue) {
+ if (deviceControlValue.key === internetControlValue.key) {
return { hasSameValues: true };
}
return null;
diff --git a/modules/ui/src/app/components/spinner/spinner.component.scss b/modules/ui/src/app/components/spinner/spinner.component.scss
index 5358b9874..788f2c676 100644
--- a/modules/ui/src/app/components/spinner/spinner.component.scss
+++ b/modules/ui/src/app/components/spinner/spinner.component.scss
@@ -9,7 +9,7 @@
top: 0;
bottom: 0;
background-color: rgba(255, 255, 255, 0.7);
- z-index: 9999;
+ z-index: 2;
display: flex;
align-items: center;
justify-content: center;
diff --git a/modules/ui/src/app/components/version/update-dialog/update-dialog.component.html b/modules/ui/src/app/components/version/update-dialog/update-dialog.component.html
index 61b507282..b319f9b97 100644
--- a/modules/ui/src/app/components/version/update-dialog/update-dialog.component.html
+++ b/modules/ui/src/app/components/version/update-dialog/update-dialog.component.html
@@ -25,14 +25,21 @@
-
+
Ignore
Download
diff --git a/modules/ui/src/app/components/version/version.component.html b/modules/ui/src/app/components/version/version.component.html
index 7d396e169..2420dbf5e 100644
--- a/modules/ui/src/app/components/version/version.component.html
+++ b/modules/ui/src/app/components/version/version.component.html
@@ -29,7 +29,7 @@
version?.installed_version +
' New version is available. Click here to update'
"
- (click)="openUpdateWindow()">
+ (click)="openUpdateWindow(version)">
{{ version?.installed_version }}
{
describe('update is not available', () => {
beforeEach(() => {
versionBehaviorSubject$.next(VERSION);
+ mockService.getVersion.and.returnValue(versionBehaviorSubject$);
fixture.detectChanges();
});
@@ -64,6 +65,7 @@ describe('VersionComponent', () => {
describe('update is available', () => {
beforeEach(() => {
versionBehaviorSubject$.next(NEW_VERSION);
+ mockService.getVersion.and.returnValue(versionBehaviorSubject$);
fixture.detectChanges();
});
diff --git a/modules/ui/src/app/components/version/version.component.ts b/modules/ui/src/app/components/version/version.component.ts
index e102ecaf6..5adb12bd1 100644
--- a/modules/ui/src/app/components/version/version.component.ts
+++ b/modules/ui/src/app/components/version/version.component.ts
@@ -13,14 +13,18 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-import { Component, OnInit } from '@angular/core';
+import { Component, OnDestroy, OnInit } from '@angular/core';
import { CommonModule } from '@angular/common';
import { TestRunService } from '../../services/test-run.service';
import { Version } from '../../model/version';
-import { BehaviorSubject } from 'rxjs/internal/BehaviorSubject';
import { MatButtonModule } from '@angular/material/button';
import { MatDialog, MatDialogModule } from '@angular/material/dialog';
import { UpdateDialogComponent } from './update-dialog/update-dialog.component';
+import { tap } from 'rxjs/internal/operators/tap';
+
+import { Observable } from 'rxjs/internal/Observable';
+import { Subject } from 'rxjs/internal/Subject';
+import { takeUntil } from 'rxjs/internal/operators/takeUntil';
@Component({
selector: 'app-version',
@@ -29,28 +33,47 @@ import { UpdateDialogComponent } from './update-dialog/update-dialog.component';
templateUrl: './version.component.html',
styleUrls: ['./version.component.scss'],
})
-export class VersionComponent implements OnInit {
- version$: BehaviorSubject;
+export class VersionComponent implements OnInit, OnDestroy {
+ version$!: Observable;
+ private destroy$: Subject = new Subject();
+
+ private isDialogClosed = false;
constructor(
private testRunService: TestRunService,
public dialog: MatDialog
- ) {
- this.version$ = testRunService.getVersion();
- }
+ ) {}
ngOnInit() {
this.testRunService.fetchVersion();
+
+ this.version$ = this.testRunService.getVersion().pipe(
+ tap(version => {
+ if (version?.update_available && !this.isDialogClosed) {
+ this.openUpdateWindow(version);
+ }
+ })
+ );
}
- openUpdateWindow() {
- this.dialog.open(UpdateDialogComponent, {
+ openUpdateWindow(version: Version) {
+ const dialogRef = this.dialog.open(UpdateDialogComponent, {
ariaLabel: 'Update version',
- data: this.version$.value,
+ data: version,
autoFocus: true,
hasBackdrop: true,
disableClose: true,
panelClass: 'version-update-dialog',
});
+
+ dialogRef
+ ?.afterClosed()
+ .pipe(takeUntil(this.destroy$))
+ .subscribe(() => (this.isDialogClosed = true));
+ }
+
+ ngOnDestroy() {
+ this.destroy$.next(true);
+ this.destroy$.unsubscribe();
}
}
diff --git a/modules/ui/src/app/device-repository/device-form/device-form.component.html b/modules/ui/src/app/device-repository/device-form/device-form.component.html
index ef888451a..e8ce4ac98 100644
--- a/modules/ui/src/app/device-repository/device-form/device-form.component.html
+++ b/modules/ui/src/app/device-repository/device-form/device-form.component.html
@@ -13,7 +13,11 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-
diff --git a/modules/ui/src/app/device-repository/device-repository.component.spec.ts b/modules/ui/src/app/device-repository/device-repository.component.spec.ts
index e348729ee..3bcc131f3 100644
--- a/modules/ui/src/app/device-repository/device-repository.component.spec.ts
+++ b/modules/ui/src/app/device-repository/device-repository.component.spec.ts
@@ -30,6 +30,7 @@ import { BehaviorSubject } from 'rxjs/internal/BehaviorSubject';
import { device } from '../mocks/device.mock';
import { DeleteFormComponent } from '../components/delete-form/delete-form.component';
import SpyObj = jasmine.SpyObj;
+import { StateService } from '../services/state.service';
describe('DeviceRepositoryComponent', () => {
let component: DeviceRepositoryComponent;
@@ -37,6 +38,11 @@ describe('DeviceRepositoryComponent', () => {
let compiled: HTMLElement;
let mockService: SpyObj;
+ const stateServiceMock: jasmine.SpyObj = jasmine.createSpyObj(
+ 'stateServiceMock',
+ ['focusFirstElementInMain']
+ );
+
beforeEach(() => {
mockService = jasmine.createSpyObj([
'getDevices',
@@ -67,7 +73,10 @@ describe('DeviceRepositoryComponent', () => {
]);
TestBed.configureTestingModule({
imports: [DeviceRepositoryModule, BrowserAnimationsModule],
- providers: [{ provide: TestRunService, useValue: mockService }],
+ providers: [
+ { provide: TestRunService, useValue: mockService },
+ { provide: StateService, useValue: stateServiceMock },
+ ],
declarations: [DeviceRepositoryComponent],
});
fixture = TestBed.createComponent(DeviceRepositoryComponent);
@@ -120,28 +129,61 @@ describe('DeviceRepositoryComponent', () => {
expect(item.length).toEqual(3);
}));
- it('should open device dialog on item click', () => {
- const openSpy = spyOn(component.dialog, 'open').and.returnValue({
- afterClosed: () => of(true),
- } as MatDialogRef);
+ it('should add device-item-selected class for selected device', fakeAsync(() => {
+ component.selectedDevice = device;
fixture.detectChanges();
+ const item = compiled.querySelector('app-device-item');
+
+ expect(item?.classList.contains('device-item-selected')).toBeTrue();
+ }));
- component.openDialog(device);
-
- expect(openSpy).toHaveBeenCalled();
- expect(openSpy).toHaveBeenCalledWith(DeviceFormComponent, {
- ariaLabel: 'Edit device',
- data: {
- device: device,
- title: 'Edit device',
- },
- autoFocus: true,
- hasBackdrop: true,
- disableClose: true,
- panelClass: 'device-form-dialog',
+ describe('#openDialog', () => {
+ it('should open device dialog on item click', () => {
+ const openSpy = spyOn(component.dialog, 'open').and.returnValue({
+ afterClosed: () => of(true),
+ } as MatDialogRef);
+ fixture.detectChanges();
+
+ component.openDialog(device);
+
+ expect(openSpy).toHaveBeenCalled();
+ expect(openSpy).toHaveBeenCalledWith(DeviceFormComponent, {
+ ariaLabel: 'Edit device',
+ data: {
+ device: device,
+ title: 'Edit device',
+ },
+ autoFocus: true,
+ hasBackdrop: true,
+ disableClose: true,
+ panelClass: 'device-form-dialog',
+ });
+
+ openSpy.calls.reset();
});
- openSpy.calls.reset();
+ it('should open device dialog with delete-button focus element', () => {
+ const openSpy = spyOn(component.dialog, 'open').and.returnValue({
+ afterClosed: () => of(true),
+ } as MatDialogRef);
+ fixture.detectChanges();
+
+ component.openDialog(device, true);
+
+ expect(openSpy).toHaveBeenCalledWith(DeviceFormComponent, {
+ ariaLabel: 'Edit device',
+ data: {
+ device: device,
+ title: 'Edit device',
+ },
+ autoFocus: '.delete-button',
+ hasBackdrop: true,
+ disableClose: true,
+ panelClass: 'device-form-dialog',
+ });
+
+ openSpy.calls.reset();
+ });
});
});
@@ -226,6 +268,20 @@ describe('DeviceRepositoryComponent', () => {
});
describe('delete device dialog', () => {
+ beforeEach(() => {
+ mockService.getDevices.and.returnValue(
+ new BehaviorSubject([device, device, device])
+ );
+ component.ngOnInit();
+ });
+
+ it('should show device item', fakeAsync(() => {
+ fixture.detectChanges();
+ const item = compiled.querySelectorAll('app-device-item');
+
+ expect(item.length).toEqual(3);
+ }));
+
it('should delete device when dialog return true', () => {
spyOn(component.dialog, 'open').and.returnValue({
afterClosed: () => of(true),
@@ -239,6 +295,40 @@ describe('DeviceRepositoryComponent', () => {
expect(mockService.removeDevice).toHaveBeenCalledWith(device);
});
+ it('should focus next device when dialog return true and next device is exist', fakeAsync(() => {
+ spyOn(component.dialog, 'open').and.returnValue({
+ afterClosed: () => of(true),
+ } as MatDialogRef);
+ component.selectedDevice = device;
+ mockService.deleteDevice.and.returnValue(of(true));
+ mockService.removeDevice.and.callThrough();
+ fixture.detectChanges();
+ const deviceButton = compiled.querySelector(
+ '.device-item-selected + app-device-item button'
+ ) as HTMLButtonElement;
+ const buttonFocusSpy = spyOn(deviceButton, 'focus');
+
+ component.openDeleteDialog(device);
+
+ expect(buttonFocusSpy).toHaveBeenCalled();
+ }));
+
+ it('should focus add button when dialog return true and next device is not exist', fakeAsync(() => {
+ spyOn(component.dialog, 'open').and.returnValue({
+ afterClosed: () => of(true),
+ } as MatDialogRef);
+ component.selectedDevice = null;
+ mockService.deleteDevice.and.returnValue(of(true));
+ mockService.removeDevice.and.callThrough();
+ fixture.detectChanges();
+
+ component.openDeleteDialog(device);
+
+ expect(
+ document.activeElement?.classList.contains('device-add-button')
+ ).toBeTrue();
+ }));
+
it('should open device dialog when dialog return null', () => {
const openDeleteDialogSpy = spyOn(component, 'openDialog');
spyOn(component.dialog, 'open').and.returnValue({
@@ -247,7 +337,7 @@ describe('DeviceRepositoryComponent', () => {
component.openDeleteDialog(device);
- expect(openDeleteDialogSpy).toHaveBeenCalledWith(device);
+ expect(openDeleteDialogSpy).toHaveBeenCalledWith(device, true);
});
});
});
diff --git a/modules/ui/src/app/device-repository/device-repository.component.ts b/modules/ui/src/app/device-repository/device-repository.component.ts
index 0578ce2ef..ef569b55d 100644
--- a/modules/ui/src/app/device-repository/device-repository.component.ts
+++ b/modules/ui/src/app/device-repository/device-repository.component.ts
@@ -13,7 +13,13 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-import { Component, OnDestroy, OnInit } from '@angular/core';
+import {
+ Component,
+ ElementRef,
+ OnDestroy,
+ OnInit,
+ ChangeDetectorRef,
+} from '@angular/core';
import { MatDialog } from '@angular/material/dialog';
import { Observable } from 'rxjs/internal/Observable';
import { Device } from '../model/device';
@@ -26,6 +32,8 @@ import {
import { Subject, takeUntil } from 'rxjs';
import { DeleteFormComponent } from '../components/delete-form/delete-form.component';
import { combineLatest } from 'rxjs/internal/observable/combineLatest';
+import { StateService } from '../services/state.service';
+import { timer } from 'rxjs/internal/observable/timer';
@Component({
selector: 'app-device-repository',
@@ -35,10 +43,14 @@ import { combineLatest } from 'rxjs/internal/observable/combineLatest';
export class DeviceRepositoryComponent implements OnInit, OnDestroy {
devices$!: Observable;
private destroy$: Subject = new Subject();
+ selectedDevice: Device | undefined | null;
constructor(
private testRunService: TestRunService,
- public dialog: MatDialog
+ private readonly state: StateService,
+ public dialog: MatDialog,
+ private element: ElementRef,
+ private readonly changeDetectorRef: ChangeDetectorRef
) {}
ngOnInit(): void {
@@ -58,14 +70,14 @@ export class DeviceRepositoryComponent implements OnInit, OnDestroy {
this.destroy$.unsubscribe();
}
- openDialog(selectedDevice?: Device): void {
+ openDialog(selectedDevice?: Device, focusDeleteButton = false): void {
const dialogRef = this.dialog.open(DeviceFormComponent, {
ariaLabel: selectedDevice ? 'Edit device' : 'Create device',
data: {
device: selectedDevice || null,
title: selectedDevice ? 'Edit device' : 'Create device',
},
- autoFocus: true,
+ autoFocus: focusDeleteButton ? '.delete-button' : true,
hasBackdrop: true,
disableClose: true,
panelClass: 'device-form-dialog',
@@ -75,6 +87,7 @@ export class DeviceRepositoryComponent implements OnInit, OnDestroy {
?.afterClosed()
.pipe(takeUntil(this.destroy$))
.subscribe((response: FormResponse) => {
+ this.selectedDevice = null;
if (!response) return;
if (
@@ -83,6 +96,11 @@ export class DeviceRepositoryComponent implements OnInit, OnDestroy {
!selectedDevice
) {
this.testRunService.addDevice(response.device);
+ timer(10)
+ .pipe(takeUntil(this.destroy$))
+ .subscribe(() => {
+ this.state.focusFirstElementInMain();
+ });
}
if (
response.action === FormAction.Save &&
@@ -92,6 +110,7 @@ export class DeviceRepositoryComponent implements OnInit, OnDestroy {
this.testRunService.updateDevice(selectedDevice, response.device);
}
if (response.action === FormAction.Delete && selectedDevice) {
+ this.selectedDevice = selectedDevice;
this.openDeleteDialog(selectedDevice);
}
});
@@ -120,10 +139,29 @@ export class DeviceRepositoryComponent implements OnInit, OnDestroy {
if (deleteDevice) {
this.testRunService.deleteDevice(device).subscribe(() => {
this.testRunService.removeDevice(device);
+ this.focusNextButton();
+ this.selectedDevice = null;
});
} else {
- this.openDialog(device);
+ this.openDialog(device, true);
+ this.selectedDevice = null;
}
});
}
+
+ private focusNextButton() {
+ // Try to focus next device item, if exitst
+ const next = this.element.nativeElement.querySelector(
+ '.device-item-selected + app-device-item button'
+ );
+ if (next) {
+ next.focus();
+ } else {
+ this.changeDetectorRef.detectChanges();
+ // If next device item doest not exist, add device button should be focused
+ const addButton =
+ this.element.nativeElement.querySelector('.device-add-button');
+ addButton?.focus();
+ }
+ }
}
diff --git a/modules/ui/src/app/history/history.component.html b/modules/ui/src/app/history/history.component.html
index 25fdff3b5..f2c992c82 100644
--- a/modules/ui/src/app/history/history.component.html
+++ b/modules/ui/src/app/history/history.component.html
@@ -25,6 +25,7 @@ Reports
@@ -132,7 +133,10 @@ Reports
file_download
-
+
@@ -177,7 +181,12 @@ Reports
-
+
diff --git a/modules/ui/src/app/history/history.component.scss b/modules/ui/src/app/history/history.component.scss
index d74335db4..af0f8ffed 100644
--- a/modules/ui/src/app/history/history.component.scss
+++ b/modules/ui/src/app/history/history.component.scss
@@ -27,7 +27,7 @@
padding-left: 32px;
gap: 10px;
background: $white;
- height: 89px;
+ height: 72px;
}
.history-content {
diff --git a/modules/ui/src/app/history/history.component.spec.ts b/modules/ui/src/app/history/history.component.spec.ts
index 57b83267a..87ee2ab21 100644
--- a/modules/ui/src/app/history/history.component.spec.ts
+++ b/modules/ui/src/app/history/history.component.spec.ts
@@ -43,6 +43,18 @@ const history = [
started: '2023-06-23T10:11:00.123Z',
finished: '2023-06-23T10:17:10.123Z',
},
+ {
+ status: 'compliant',
+ device: {
+ manufacturer: 'Delta',
+ model: '03-DIN-SRC',
+ mac_addr: '01:02:03:04:05:07',
+ firmware: '1.2.3',
+ },
+ report: 'https://api.testrun.io/report.pdf',
+ started: '2023-07-23T10:11:00.123Z',
+ finished: '2023-07-23T10:17:10.123Z',
+ },
] as TestrunStatus[];
describe('HistoryComponent', () => {
@@ -214,6 +226,47 @@ describe('HistoryComponent', () => {
mockFilteredData
);
});
+
+ it('should return true if filters has no values', () => {
+ component.filteredValues = {
+ deviceInfo: '',
+ deviceFirmware: '',
+ results: [],
+ dateRange: { start: '', end: '' },
+ };
+
+ expect(component.isFiltersEmpty()).toBeTrue();
+ });
+
+ describe('#focusNextButton', () => {
+ beforeEach(() => {
+ fixture.detectChanges();
+ });
+
+ it('should focus next active element if exist', () => {
+ const row = window.document.querySelector('tbody tr') as HTMLElement;
+ row.classList.add('report-selected');
+ const nextButton = window.document.querySelector(
+ '.report-selected + tr a'
+ ) as HTMLButtonElement;
+ const buttonFocusSpy = spyOn(nextButton, 'focus');
+
+ component.focusNextButton();
+
+ expect(buttonFocusSpy).toHaveBeenCalled();
+ });
+
+ it('should focus navigation button if next active element does not exist', () => {
+ const button = document.createElement('BUTTON');
+ button.classList.add('app-sidebar-button-reports');
+ document.querySelector('body')?.appendChild(button);
+ const buttonFocusSpy = spyOn(button, 'focus');
+
+ component.focusNextButton();
+
+ expect(buttonFocusSpy).toHaveBeenCalled();
+ });
+ });
});
describe('DOM tests', () => {
@@ -312,6 +365,15 @@ describe('HistoryComponent', () => {
expect(emptyMessage).toBeTruthy();
});
+
+ it('should select row on row click', () => {
+ component.selectedRow = null;
+ const row = window.document.querySelector('tbody tr') as HTMLElement;
+
+ row.click();
+
+ expect(component.selectedRow).toBeTruthy();
+ });
});
});
});
diff --git a/modules/ui/src/app/history/history.component.ts b/modules/ui/src/app/history/history.component.ts
index ebcfb3bda..bb4b54b5e 100644
--- a/modules/ui/src/app/history/history.component.ts
+++ b/modules/ui/src/app/history/history.component.ts
@@ -30,7 +30,7 @@ import {
import { DatePipe } from '@angular/common';
import { MatSort, Sort } from '@angular/material/sort';
import { Subject, takeUntil } from 'rxjs';
-import { MatTableDataSource } from '@angular/material/table';
+import { MatRow, MatTableDataSource } from '@angular/material/table';
import { FilterDialogComponent } from '../components/filter-dialog/filter-dialog.component';
import { MatDialog } from '@angular/material/dialog';
import { tap } from 'rxjs/internal/operators/tap';
@@ -67,6 +67,7 @@ export class HistoryComponent implements OnInit, OnDestroy {
private destroy$: Subject = new Subject();
@ViewChild(MatSort, { static: false }) sort!: MatSort;
dataLoaded = false;
+ selectedRow: MatRow | null = null;
constructor(
private testRunService: TestRunService,
private datePipe: DatePipe,
@@ -285,8 +286,35 @@ export class HistoryComponent implements OnInit, OnDestroy {
}
isFiltersEmpty() {
- return Object.values(this.filteredValues).every(
- value => value.length === 0
- );
+ return Object.values(this.filteredValues).every(value => {
+ if (value.start === '') {
+ return value.end.length === 0;
+ }
+ return value.length === 0;
+ });
+ }
+
+ selectRow(row: MatRow) {
+ this.selectedRow = row;
+ }
+
+ trackByStarted(index: number, item: TestrunStatus) {
+ return item.started;
+ }
+
+ focusNextButton() {
+ // Try to focus next interactive element, if exists
+ const next = window.document.querySelector(
+ '.report-selected + tr a'
+ ) as HTMLButtonElement;
+ if (next) {
+ next.focus();
+ } else {
+ // If next interactive element doest not exist, add menu reports button should be focused
+ const menuButton = window.document.querySelector(
+ '.app-sidebar-button-reports'
+ ) as HTMLButtonElement;
+ menuButton?.focus();
+ }
}
}
diff --git a/modules/ui/src/app/progress/progress-breadcrumbs/progress-breadcrumbs.component.ts b/modules/ui/src/app/model/event-type.ts
similarity index 57%
rename from modules/ui/src/app/progress/progress-breadcrumbs/progress-breadcrumbs.component.ts
rename to modules/ui/src/app/model/event-type.ts
index 4109c2c57..617fe8a49 100644
--- a/modules/ui/src/app/progress/progress-breadcrumbs/progress-breadcrumbs.component.ts
+++ b/modules/ui/src/app/model/event-type.ts
@@ -13,15 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-import { ChangeDetectionStrategy, Component, Input } from '@angular/core';
-import { Observable } from 'rxjs/internal/Observable';
-
-@Component({
- selector: 'app-progress-breadcrumbs',
- templateUrl: './progress-breadcrumbs.component.html',
- styleUrls: ['./progress-breadcrumbs.component.scss'],
- changeDetection: ChangeDetectionStrategy.OnPush,
-})
-export class ProgressBreadcrumbsComponent {
- @Input() breadcrumbs$!: Observable;
+export enum EventType {
+ Close = 'Close event',
+ Save = 'Save event',
}
diff --git a/modules/ui/src/app/model/setting.ts b/modules/ui/src/app/model/setting.ts
index 5d9c768e0..2fd7e7875 100644
--- a/modules/ui/src/app/model/setting.ts
+++ b/modules/ui/src/app/model/setting.ts
@@ -19,3 +19,8 @@ export interface SystemConfig {
internet_intf?: string;
};
}
+
+export interface SettingOption {
+ key: string;
+ value: string;
+}
diff --git a/modules/ui/src/app/progress/progress-breadcrumbs/progress-breadcrumbs.component.html b/modules/ui/src/app/progress/progress-breadcrumbs/progress-breadcrumbs.component.html
deleted file mode 100644
index 0e28c84f7..000000000
--- a/modules/ui/src/app/progress/progress-breadcrumbs/progress-breadcrumbs.component.html
+++ /dev/null
@@ -1,26 +0,0 @@
-
-
- -
- home
-
- -
- keyboard_arrow_right
- {{ breadcrumb }}
-
-
diff --git a/modules/ui/src/app/progress/progress-breadcrumbs/progress-breadcrumbs.component.scss b/modules/ui/src/app/progress/progress-breadcrumbs/progress-breadcrumbs.component.scss
deleted file mode 100644
index 8abbab97b..000000000
--- a/modules/ui/src/app/progress/progress-breadcrumbs/progress-breadcrumbs.component.scss
+++ /dev/null
@@ -1,67 +0,0 @@
-/**
- * Copyright 2023 Google LLC
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * https://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-@use '@angular/material' as mat;
-@import '../../../theming/colors';
-
-:host {
- width: 100%;
- overflow: hidden;
-}
-
-ul {
- display: flex;
- list-style: none;
- margin: 0;
- padding: 0 8px;
-
- .breadcrumb-item {
- display: flex;
- flex: 0 1 auto;
- max-width: 25%;
- align-items: center;
- justify-content: center;
- gap: 8px;
- margin-right: 16px;
-
- &.first {
- margin-right: 6px;
- flex-shrink: 0;
- }
- }
-
- .icon-home {
- color: $dark-grey;
- flex-shrink: 0;
- }
-
- .icon {
- color: $secondary;
- flex-shrink: 0;
- }
-
- .breadcrumb-text {
- color: mat.get-color-from-palette($color-primary, 600);
- text-align: center;
- /* font-family: SF Pro Text; */
- font-size: 12px;
- font-weight: 700;
- line-height: 16px;
- letter-spacing: 0.3px;
- white-space: nowrap;
- overflow: hidden;
- text-overflow: ellipsis;
- }
-}
diff --git a/modules/ui/src/app/progress/progress-breadcrumbs/progress-breadcrumbs.component.spec.ts b/modules/ui/src/app/progress/progress-breadcrumbs/progress-breadcrumbs.component.spec.ts
deleted file mode 100644
index 8dde292e5..000000000
--- a/modules/ui/src/app/progress/progress-breadcrumbs/progress-breadcrumbs.component.spec.ts
+++ /dev/null
@@ -1,38 +0,0 @@
-/**
- * Copyright 2023 Google LLC
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * https://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-import { ComponentFixture, TestBed } from '@angular/core/testing';
-
-import { ProgressBreadcrumbsComponent } from './progress-breadcrumbs.component';
-import { MatIconModule } from '@angular/material/icon';
-
-describe('ProgressBreadcrumbsComponent', () => {
- let component: ProgressBreadcrumbsComponent;
- let fixture: ComponentFixture;
-
- beforeEach(() => {
- TestBed.configureTestingModule({
- declarations: [ProgressBreadcrumbsComponent],
- imports: [MatIconModule],
- });
- fixture = TestBed.createComponent(ProgressBreadcrumbsComponent);
- component = fixture.componentInstance;
- fixture.detectChanges();
- });
-
- it('should create', () => {
- expect(component).toBeTruthy();
- });
-});
diff --git a/modules/ui/src/app/progress/progress-initiate-form/progress-initiate-form.component.spec.ts b/modules/ui/src/app/progress/progress-initiate-form/progress-initiate-form.component.spec.ts
index 121e51465..604c05914 100644
--- a/modules/ui/src/app/progress/progress-initiate-form/progress-initiate-form.component.spec.ts
+++ b/modules/ui/src/app/progress/progress-initiate-form/progress-initiate-form.component.spec.ts
@@ -43,6 +43,7 @@ describe('ProgressInitiateFormComponent', () => {
'systemStatus$',
'getSystemStatus',
'fetchVersion',
+ 'setIsOpenStartTestrun',
]);
testRunServiceMock.getTestModules.and.returnValue([
{
@@ -109,6 +110,14 @@ describe('ProgressInitiateFormComponent', () => {
expect(component.dialogRef.close).toHaveBeenCalled();
});
+ it('should call setIsOpenAddDevice on cancel', () => {
+ component.cancel();
+
+ expect(testRunServiceMock.setIsOpenStartTestrun).toHaveBeenCalledWith(
+ false
+ );
+ });
+
it('should set devices$ value', () => {
component.ngOnInit();
diff --git a/modules/ui/src/app/progress/progress-initiate-form/progress-initiate-form.component.ts b/modules/ui/src/app/progress/progress-initiate-form/progress-initiate-form.component.ts
index b9f9de250..bfb08f3e3 100644
--- a/modules/ui/src/app/progress/progress-initiate-form/progress-initiate-form.component.ts
+++ b/modules/ui/src/app/progress/progress-initiate-form/progress-initiate-form.component.ts
@@ -67,6 +67,7 @@ export class ProgressInitiateFormComponent
}
cancel(): void {
+ this.testRunService.setIsOpenStartTestrun(false);
this.dialogRef.close();
}
diff --git a/modules/ui/src/app/progress/progress-status-card/progress-status-card.component.scss b/modules/ui/src/app/progress/progress-status-card/progress-status-card.component.scss
index 1d929bbbb..74f337d0f 100644
--- a/modules/ui/src/app/progress/progress-status-card/progress-status-card.component.scss
+++ b/modules/ui/src/app/progress/progress-status-card/progress-status-card.component.scss
@@ -17,7 +17,8 @@
@import '../../../theming/colors';
:host {
- height: 152px;
+ height: auto;
+ min-height: 152px;
}
.progress-card {
@@ -26,6 +27,7 @@
justify-content: space-between;
width: 295px;
height: 100%;
+ min-height: 152px;
box-sizing: border-box;
padding: 16px 32px;
diff --git a/modules/ui/src/app/progress/progress-table/progress-table.component.html b/modules/ui/src/app/progress/progress-table/progress-table.component.html
index 178d2cdb6..9a28e7824 100644
--- a/modules/ui/src/app/progress/progress-table/progress-table.component.html
+++ b/modules/ui/src/app/progress/progress-table/progress-table.component.html
@@ -24,7 +24,7 @@
- {{ element.name }}
+ {{ element.name }}
|
@@ -33,7 +33,7 @@
Description
- {{ element.description }}
+ {{ element.description }}
|
@@ -48,7 +48,7 @@
- {{ element.result }}
+ {{ element.result }}
diff --git a/modules/ui/src/app/progress/progress.component.html b/modules/ui/src/app/progress/progress.component.html
index 75b6d57b2..66d1016ed 100644
--- a/modules/ui/src/app/progress/progress.component.html
+++ b/modules/ui/src/app/progress/progress.component.html
@@ -26,14 +26,12 @@