From d3d047fa4571ca462ed9c60ecc724a52fb08cc85 Mon Sep 17 00:00:00 2001 From: Jacob Boddey Date: Wed, 17 Jan 2024 11:58:00 +0000 Subject: [PATCH] Add edit device endpoint --- cmd/package | 2 +- framework/python/src/api/api.py | 77 +- framework/python/src/common/testreport.py | 2 +- make/DEBIAN/control | 2 +- modules/ui/package-lock.json | 1248 ++++------------- modules/ui/package.json | 36 +- modules/ui/src/app/app-routing.module.ts | 3 + modules/ui/src/app/app.component.html | 17 +- modules/ui/src/app/app.component.scss | 10 +- modules/ui/src/app/app.component.spec.ts | 320 +++-- modules/ui/src/app/app.component.ts | 18 +- .../bypass/bypass.component.spec.ts | 10 +- .../app/components/bypass/bypass.component.ts | 9 +- .../components/callout/callout.component.scss | 2 +- .../delete-form/delete-form.component.html | 14 +- .../delete-report.component.spec.ts | 2 + .../delete-report/delete-report.component.ts | 10 +- .../device-item/device-item.component.html | 6 +- .../device-item/device-item.component.scss | 12 +- .../filter-chips/filter-chips.component.html | 33 +- .../filter-chips/filter-chips.component.scss | 18 +- .../filter-dialog.component.html | 9 +- .../general-settings.component.html | 173 +-- .../general-settings.component.scss | 17 +- .../general-settings.component.spec.ts | 112 +- .../general-settings.component.ts | 28 +- .../components/spinner/spinner.component.scss | 2 +- .../update-dialog.component.html | 11 +- .../components/version/version.component.html | 2 +- .../version/version.component.spec.ts | 2 + .../components/version/version.component.ts | 43 +- .../device-form/device-form.component.html | 2 +- .../device-form/device-form.component.scss | 8 +- .../device-form/device-form.component.spec.ts | 27 +- .../device-form/device-form.component.ts | 14 +- .../device-form/device.validators.ts | 2 +- .../device-repository.component.html | 1 + .../device-repository.component.spec.ts | 119 +- .../device-repository.component.ts | 40 +- .../ui/src/app/history/history.component.html | 13 +- .../ui/src/app/history/history.component.scss | 2 +- .../src/app/history/history.component.spec.ts | 62 + .../ui/src/app/history/history.component.ts | 36 +- .../event-type.ts} | 14 +- .../progress-breadcrumbs.component.html | 26 - .../progress-breadcrumbs.component.scss | 67 - .../progress-breadcrumbs.component.spec.ts | 38 - .../progress-initiate-form.component.spec.ts | 9 + .../progress-initiate-form.component.ts | 1 + .../progress-status-card.component.scss | 4 +- .../progress-table.component.html | 6 +- .../src/app/progress/progress.component.html | 8 +- .../src/app/progress/progress.component.scss | 8 +- .../app/progress/progress.component.spec.ts | 82 +- .../ui/src/app/progress/progress.component.ts | 58 +- .../ui/src/app/progress/progress.module.ts | 2 - .../ui/src/app/services/state.service.spec.ts | 39 + modules/ui/src/app/services/state.service.ts | 17 + .../src/app/services/test-run.service.spec.ts | 98 +- .../ui/src/app/services/test-run.service.ts | 48 +- modules/ui/src/index.html | 23 + modules/ui/src/theming/theme.scss | 2 +- 62 files changed, 1429 insertions(+), 1697 deletions(-) rename modules/ui/src/app/{progress/progress-breadcrumbs/progress-breadcrumbs.component.ts => model/event-type.ts} (57%) delete mode 100644 modules/ui/src/app/progress/progress-breadcrumbs/progress-breadcrumbs.component.html delete mode 100644 modules/ui/src/app/progress/progress-breadcrumbs/progress-breadcrumbs.component.scss delete mode 100644 modules/ui/src/app/progress/progress-breadcrumbs/progress-breadcrumbs.component.spec.ts create mode 100644 modules/ui/src/app/services/state.service.spec.ts create mode 100644 modules/ui/src/app/services/state.service.ts diff --git a/cmd/package b/cmd/package index 87e66d977..988d4092e 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-beta_amd64.deb diff --git a/framework/python/src/api/api.py b/framework/python/src/api/api.py index 305310e7e..3b4ca5467 100644 --- a/framework/python/src/api/api.py +++ b/framework/python/src/api/api.py @@ -77,6 +77,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 = ["*"] @@ -362,6 +365,7 @@ async def delete_device(self, request: Request, response: Response): "the device") async def save_device(self, request: Request, response: Response): + LOGGER.debug("Received device post request") try: @@ -389,8 +393,9 @@ 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() @@ -399,6 +404,74 @@ 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 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() + + # 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 get_report(self, response: Response, device_name, timestamp): diff --git a/framework/python/src/common/testreport.py b/framework/python/src/common/testreport.py index 0ac4cb9a6..f522c7b25 100644 --- a/framework/python/src/common/testreport.py +++ b/framework/python/src/common/testreport.py @@ -182,7 +182,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-beta (2024-01-17)' page = '
' page += self.generate_header(json_data) if page_num == 1: diff --git a/make/DEBIAN/control b/make/DEBIAN/control index c5af91918..071073aa9 100644 --- a/make/DEBIAN/control +++ b/make/DEBIAN/control @@ -1,5 +1,5 @@ Package: Testrun -Version: 1.1 +Version: 1.1.1-beta Architecture: amd64 Maintainer: Google Homepage: https://github.com/google/testrun 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..86281676f 100644 --- a/modules/ui/src/app/app.component.html +++ b/modules/ui/src/app/app.component.html @@ -86,6 +86,16 @@

Testrun

"> Step 1: To perform a device test, please, select ports in Connection settings. + 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 menu there." tabindex="0" - role="button" + role="link" class="message-link" >Create a Device @@ -121,8 +132,9 @@

Testrun

(click)="navigateToRuntime()" (keydown.enter)="navigateToRuntime()" (keydown.space)="navigateToRuntime()" + aria-label="The Testrun link redirects to the Testrun page and opens a menu there." tabindex="0" - role="button" + role="link" class="message-link" >Testrun @@ -139,7 +151,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..69e921323 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, @@ -75,19 +74,18 @@ describe('AppComponent', () => { 'getSystemStatus', 'fetchHistory', 'getSystemInterfaces', - 'getVersion', - 'fetchVersion', 'setIsOpenAddDevice', 'systemStatus$', 'isTestrunStarted$', 'hasConnectionSetting$', + 'setIsOpenStartTestrun', ]); mockLoaderService = jasmine.createSpyObj(['setLoading']); 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,7 +101,6 @@ describe('AppComponent', () => { MatToolbarModule, MatSidenavModule, BypassComponent, - VersionComponent, CalloutComponent, ], providers: [ @@ -114,6 +111,7 @@ describe('AppComponent', () => { AppComponent, FakeGeneralSettingsComponent, FakeSpinnerComponent, + FakeVersionComponent, ], }); @@ -324,177 +322,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; - - calloutLinkEl.click(); - flush(); + expect(calloutLinkEl).toBeTruthy(); + expect(calloutLinkContent).toContain('Create a Device'); + }); - 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(); + + spyOn(component.settingsDrawer, 'close').and.returnValue( + Promise.resolve('close') + ); + const spyToggle = spyOn(component.toggleSettingsBtn, 'focus'); - it('should not have callout component', () => { - const callout = compiled.querySelector('app-callout'); + await component.closeSetting(); - expect(callout).toBeNull(); - }); + expect(spyToggle).toHaveBeenCalledTimes(0); }); }); @@ -505,7 +546,6 @@ describe('AppComponent', () => { class FakeGeneralSettingsComponent { @Input() interfaces = []; @Output() closeSettingEvent = new EventEmitter(); - @Output() openSettingEvent = new EventEmitter(); @Output() reloadInterfacesEvent = new EventEmitter(); } @@ -514,3 +554,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..11f39be81 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'; @@ -52,13 +52,13 @@ 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; @ViewChild('settingsDrawer') public settingsDrawer!: MatDrawer; @ViewChild('toggleSettingsBtn') public toggleSettingsBtn!: HTMLButtonElement; @ViewChild('navigation') public navigation!: ElementRef; @@ -99,7 +99,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,12 +131,15 @@ 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 + }); } async openSetting(): Promise { 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/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 }}
- - Warning! Testrun requires two ports to operate correctly. - - -

- Choose one of the following ports on your Laptop or Desktop -

- - - {{ interface }} - - - -

- Choose one of the options where you’ll connect IoT device for testing + [formGroup]="settingForm" + [class.setting-drawer-content-form-empty]=" + (interfaces | keyvalue).length === 0 + "> + + + Warning! Testrun requires two ports to operate correctly. + + +

+ Choose one of the following ports on your Laptop or Desktop +

+ + + {{ interface.value }} + + + +

+ Choose one of the options where you’ll connect IoT device for testing +

+ + + {{ interface.value }} + + + +

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

- - - {{ interface }} - - + + Both interfaces must have different values + +
@@ -83,37 +123,4 @@

Connection settings

-

- 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..e7fb547b2 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 { @@ -126,7 +131,7 @@ .message { margin: 0; - padding: 0 16px 16px; + padding: 16px 0 0 0; color: $grey-800; font-family: $font-secondary; font-size: 14px; @@ -135,11 +140,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 +158,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..b67a1d64d 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,12 +13,7 @@ * 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'; @@ -30,6 +25,8 @@ 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; const MOCK_SYSTEM_CONFIG_EMPTY: SystemConfig = { network: { @@ -45,16 +42,11 @@ const MOCK_SYSTEM_CONFIG_WITH_DATA: SystemConfig = { }, }; -const MOCK_SYSTEM_CONFIG_WITH_ONE_SETTING: SystemConfig = { - network: { - device_intf: 'mockDeviceValue', - }, -}; - describe('GeneralSettingsComponent', () => { let component: GeneralSettingsComponent; let fixture: ComponentFixture; - let testRunServiceMock: jasmine.SpyObj; + let testRunServiceMock: SpyObj; + let mockLiveAnnouncer: SpyObj; let compiled: HTMLElement; beforeEach(async () => { @@ -63,11 +55,10 @@ describe('GeneralSettingsComponent', () => { 'getSystemConfig', 'setSystemConfig', 'createSystemConfig', - 'setIsOpenAddDevice', 'hasConnectionSetting$', 'setHasConnectionSetting', ]); - testRunServiceMock.getSystemInterfaces.and.returnValue(of([])); + testRunServiceMock.getSystemInterfaces.and.returnValue(of({})); testRunServiceMock.getSystemConfig.and.returnValue( of(MOCK_SYSTEM_CONFIG_EMPTY) ); @@ -76,6 +67,8 @@ describe('GeneralSettingsComponent', () => { ); testRunServiceMock.hasConnectionSetting$ = of(true); + mockLiveAnnouncer = jasmine.createSpyObj(['announce']); + await TestBed.configureTestingModule({ declarations: [ GeneralSettingsComponent, @@ -83,7 +76,10 @@ describe('GeneralSettingsComponent', () => { FakeSpinnerComponent, FakeCalloutComponent, ], - providers: [{ provide: TestRunService, useValue: testRunServiceMock }], + providers: [ + { provide: TestRunService, useValue: testRunServiceMock }, + { provide: LiveAnnouncer, useValue: mockLiveAnnouncer }, + ], imports: [ MatButtonModule, MatIconModule, @@ -126,50 +122,6 @@ 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); @@ -178,21 +130,31 @@ describe('GeneralSettingsComponent', () => { 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'); expect(component.settingForm.value).toEqual( MOCK_SYSTEM_CONFIG_WITH_DATA.network @@ -232,24 +194,11 @@ describe('GeneralSettingsComponent', () => { 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 - ); - - component.saveSetting(); - - expect(testRunServiceMock.setIsOpenAddDevice).toHaveBeenCalledWith(true); - }); }); describe('with no intefaces data', () => { beforeEach(() => { - component.interfaces = []; + component.interfaces = {}; fixture.detectChanges(); }); @@ -270,7 +219,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 +250,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..b98ba35fc 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 @@ -28,11 +28,16 @@ import { Validators, } 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'; @Component({ selector: 'app-general-settings', @@ -40,11 +45,11 @@ import { shareReplay } from 'rxjs/internal/operators/shareReplay'; styleUrls: ['./general-settings.component.scss'], }) export class GeneralSettingsComponent implements OnInit, OnDestroy { - @Input() interfaces: string[] = []; + @Input() interfaces: SystemInterfaces = {}; @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; hasConnectionSetting$!: Observable; @@ -67,12 +72,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 +96,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(); } @@ -129,12 +138,10 @@ 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); }); @@ -170,17 +177,12 @@ 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$)) 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 @@

- 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..be653726f 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 @@ -79,7 +79,7 @@ ', +}) +class DummyComponent {} + +describe('StateService', () => { + let service: StateService; + let fixture: ComponentFixture; + + beforeEach(() => { + TestBed.configureTestingModule({ + declarations: [DummyComponent], + }); + service = TestBed.inject(StateService); + + fixture = TestBed.createComponent(DummyComponent); + + fixture.detectChanges(); + }); + + it('should be created', () => { + expect(service).toBeTruthy(); + }); + + it('should focus element', () => { + const testButton = fixture.nativeElement.querySelector( + '#test-button' + ) as HTMLButtonElement; + + service.focusFirstElementInMain(); + + expect(document.activeElement).toBe(testButton); + }); +}); diff --git a/modules/ui/src/app/services/state.service.ts b/modules/ui/src/app/services/state.service.ts new file mode 100644 index 000000000..509fbd897 --- /dev/null +++ b/modules/ui/src/app/services/state.service.ts @@ -0,0 +1,17 @@ +import { Injectable } from '@angular/core'; + +@Injectable({ + providedIn: 'root', +}) +export class StateService { + focusFirstElementInMain() { + const firstControl: HTMLElement | null = window.document.querySelector( + '#main button:not([disabled="true"]), ' + + '#main a:not([disabled="true"]), #main table' + ); + + if (firstControl) { + firstControl.focus(); + } + } +} diff --git a/modules/ui/src/app/services/test-run.service.spec.ts b/modules/ui/src/app/services/test-run.service.spec.ts index 68b841487..5cb091fed 100644 --- a/modules/ui/src/app/services/test-run.service.spec.ts +++ b/modules/ui/src/app/services/test-run.service.spec.ts @@ -20,7 +20,7 @@ import { import { fakeAsync, getTestBed, TestBed, tick } from '@angular/core/testing'; import { Device, TestModule } from '../model/device'; -import { TestRunService } from './test-run.service'; +import { SystemInterfaces, TestRunService } from './test-run.service'; import { SystemConfig } from '../model/setting'; import { MOCK_PROGRESS_DATA_CANCELLING, @@ -82,11 +82,6 @@ describe('TestRunService', () => { name: 'nmap', enabled: true, }, - { - displayName: 'Security', - name: 'security', - enabled: true, - }, { displayName: 'TLS', name: 'tls', @@ -165,7 +160,10 @@ describe('TestRunService', () => { it('getSystemInterfaces should return array of interfaces', () => { const apiUrl = 'http://localhost:8000/system/interfaces'; - const mockSystemInterfaces: string[] = ['mockValue', 'mockValue']; + const mockSystemInterfaces: SystemInterfaces = { + mockValue1: 'mockValue1', + mockValue2: 'mockValue2', + }; service.getSystemInterfaces().subscribe(res => { expect(res).toEqual(mockSystemInterfaces); @@ -245,32 +243,50 @@ describe('TestRunService', () => { }); }); - it('getHistory should return reports', () => { - let result: TestrunStatus[] | null = null; + describe('getHistory', () => { + it('should return reports', () => { + let result: TestrunStatus[] | null = null; + + const reports = [ + { + status: 'Completed', + device: device, + report: 'https://api.testrun.io/report.pdf', + started: '2023-06-22T10:11:00.123Z', + finished: '2023-06-22T10:17:00.123Z', + }, + ] as TestrunStatus[]; + + service.getHistory().subscribe(res => { + expect(res).toEqual(result); + }); - const reports = [ - { - status: 'Completed', - device: device, - report: 'https://api.testrun.io/report.pdf', - started: '2023-06-22T10:11:00.123Z', - finished: '2023-06-22T10:17:00.123Z', - }, - ] as TestrunStatus[]; + result = reports; + service.fetchHistory(); + const req = httpTestingController.expectOne( + 'http://localhost:8000/reports' + ); - service.getHistory().subscribe(res => { - expect(res).toEqual(result); + expect(req.request.method).toBe('GET'); + + req.flush(reports); }); - result = reports; - service.fetchHistory(); - const req = httpTestingController.expectOne( - 'http://localhost:8000/reports' - ); + it('should return [] when error happens', () => { + let result: TestrunStatus[] | null = null; - expect(req.request.method).toBe('GET'); + service.getHistory().subscribe(res => { + expect(res).toEqual(result); + }); - req.flush(reports); + result = []; + service.fetchHistory(); + const req = httpTestingController.expectOne({ + url: 'http://localhost:8000/reports', + }); + + req.flush([], { status: 500, statusText: 'error' }); + }); }); describe('#getResultClass', () => { @@ -435,4 +451,32 @@ describe('TestRunService', () => { ) ).toEqual(false); })); + + it('#saveDevice should have necessary request data', () => { + const apiUrl = 'http://localhost:8000/device'; + + service.saveDevice(device).subscribe(res => { + expect(res).toEqual(true); + }); + + const req = httpTestingController.expectOne(apiUrl); + expect(req.request.method).toBe('POST'); + expect(req.request.body).toEqual(JSON.stringify(device)); + req.flush(true); + }); + + it('#editDevice should have necessary request data', () => { + const apiUrl = 'http://localhost:8000/device/edit'; + + service.editDevice(device, '01:01:01:01:01:01').subscribe(res => { + expect(res).toEqual(true); + }); + + const req = httpTestingController.expectOne(apiUrl); + expect(req.request.method).toBe('POST'); + expect(req.request.body).toEqual( + JSON.stringify({ mac_addr: '01:01:01:01:01:01', device }) + ); + req.flush(true); + }); }); diff --git a/modules/ui/src/app/services/test-run.service.ts b/modules/ui/src/app/services/test-run.service.ts index 22b4d2328..37d46f23b 100644 --- a/modules/ui/src/app/services/test-run.service.ts +++ b/modules/ui/src/app/services/test-run.service.ts @@ -30,6 +30,10 @@ import { Version } from '../model/version'; const API_URL = 'http://localhost:8000'; +export type SystemInterfaces = { + [key: string]: string; +}; + @Injectable({ providedIn: 'root', }) @@ -55,11 +59,6 @@ export class TestRunService { name: 'nmap', enabled: true, }, - { - displayName: 'Security', - name: 'security', - enabled: true, - }, { displayName: 'TLS', name: 'tls', @@ -70,6 +69,8 @@ export class TestRunService { private devices = new BehaviorSubject(null); private isOpenAddDeviceSub$ = new BehaviorSubject(false); public isOpenAddDevice$ = this.isOpenAddDeviceSub$.asObservable(); + private isOpenStartTestrunSub$ = new BehaviorSubject(false); + public isOpenStartTestrun$ = this.isOpenStartTestrunSub$.asObservable(); private _systemConfig = new BehaviorSubject(null); public systemConfig$ = this._systemConfig.asObservable(); private systemStatusSubject = new ReplaySubject(1); @@ -87,6 +88,10 @@ export class TestRunService { this.isOpenAddDeviceSub$.next(isOpen); } + setIsOpenStartTestrun(isOpen: boolean): void { + this.isOpenStartTestrunSub$.next(isOpen); + } + setHasConnectionSetting(hasSetting: boolean): void { this.hasConnectionSettingSub$.next(hasSetting); } @@ -124,8 +129,8 @@ export class TestRunService { .pipe(retry(1)); } - getSystemInterfaces(): Observable { - return this.http.get(`${API_URL}/system/interfaces`); + getSystemInterfaces(): Observable { + return this.http.get(`${API_URL}/system/interfaces`); } /** @@ -161,6 +166,20 @@ export class TestRunService { .pipe(map(() => true)); } + editDevice(device: Device, mac_addr: string): Observable { + type EditDeviceRequest = { + mac_addr: string; // original mac address + device: Device; + }; + const request: EditDeviceRequest = { + mac_addr, + device, + }; + + return this.http + .post(`${API_URL}/device/edit`, JSON.stringify(request)) + .pipe(map(() => true)); + } deleteDevice(device: Device): Observable { return this.http .delete(`${API_URL}/device`, { @@ -185,12 +204,13 @@ export class TestRunService { updateDevice(deviceToUpdate: Device, update: Device): void { const device = this.devices.value?.find( - device => update.mac_addr === device.mac_addr + device => deviceToUpdate.mac_addr === device.mac_addr ); if (device) { device.model = update.model; device.manufacturer = update.manufacturer; device.test_modules = update.test_modules; + device.mac_addr = update.mac_addr; this.devices.next(this.devices.value); } @@ -207,12 +227,14 @@ export class TestRunService { } fetchHistory(): void { - this.http - .get(`${API_URL}/reports`) - .pipe(retry(1)) - .subscribe(data => { + this.http.get(`${API_URL}/reports`).subscribe( + data => { this.history.next(data); - }); + }, + () => { + this.history.next([]); + } + ); } getHistory(): BehaviorSubject { diff --git a/modules/ui/src/index.html b/modules/ui/src/index.html index 37521c779..aee186784 100644 --- a/modules/ui/src/index.html +++ b/modules/ui/src/index.html @@ -16,6 +16,20 @@ + + +