From 549641c26a1ec0b6e240deb5772c7268d66728ca Mon Sep 17 00:00:00 2001 From: J Boddey Date: Tue, 20 Aug 2024 16:56:06 +0100 Subject: [PATCH 1/9] Release v1.4 into main (#687) * Update requirements.txt (#429) Signed-off-by: J Boddey * Remove in progress status after discovery (#422) * Add steps to resolve to PDF report (#411) * Add progress for steps to resolve * Formatting * Remove print statements * Allow for no steps to resolve * Add multipage * Fix multipage * Fix config * update report unit tests --------- Signed-off-by: J Boddey Co-authored-by: jhughesbiot * Bump ejs in /modules/ui in the npm_and_yarn group across 1 directory (#428) Bumps the npm_and_yarn group with 1 update in the /modules/ui directory: [ejs](https://github.com/mde/ejs). Updates `ejs` from 3.1.9 to 3.1.10 - [Release notes](https://github.com/mde/ejs/releases) - [Commits](https://github.com/mde/ejs/compare/v3.1.9...v3.1.10) --- updated-dependencies: - dependency-name: ejs dependency-type: indirect dependency-group: npm_and_yarn ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * For delete report take mac_addr from upper level (#436) * Update dependencies (#435) * Add new mac addr field for report deleting (#432) * Bump version to v1.2.2 (#433) * fix: replace Feature Not Present to Feature Not Detected (#445) Co-authored-by: Volha Mardvilka * Fix PDF alignment (#441) * Do not remove form control on destroy as it causes error; call system/config on settings open (#442) * Fix some pylint issues (#437) * Update documentation (#448) * Update documentation * Update docs * V1.3 (#393) * Adds version analytics event (#306) * Techdebt: adds state for testrun page (#392) * Fix tests (#397) * Fix tests * Update node version * 331379891: (feat) disable connection settings when testrun is in progress (#371) * 331379891: (feat) disable connection settings when testrun is in progress * 331379891: (fix) include more testrun results as progress * 331379891: (fix) fix spelling * 333349715: (fix) GAR 1.3 The disabled system settings panel contains a focusable element (#388) Co-authored-by: Volha Mardvilka --------- Co-authored-by: Volha Mardvilka * Disable device item if device in progress (#370) * Adds ga to track testrun initiation (#415) Adds ga ti track testrun initiation * Fix function return value (#421) * Fix redirect + adds google analytics (#420) * Adds certificates drawer component with list of certificates (#414) Adds certificates drawer component with list of certificates. Upload and delete is out of scope * List modules (#425) * Adds delete certificate (#424) Adds delete certificate * Adds class for links to track report type on download; adds event when download report is clicked on progress page (#434) * Adds upload certificate (#431) * Adds upload certificate * move delete functionality to certificates store * Catch error on report/certificate deletion (#438) * Fix button size; fix text-overflow (#440) * Adds focus on next or close button when certificate is deleted (#439) * Adds focus on next or close button when certificate is deleted * Enable test modules for initiate test run dialog (#400) Enable test modules for initiate test run dialog * Add steps to resolve to PDF report (#411) * Add progress for steps to resolve * Formatting * Remove print statements * Allow for no steps to resolve * Add multipage * Fix multipage * Fix config * update report unit tests --------- Signed-off-by: J Boddey Co-authored-by: jhughesbiot * Add new mac addr field for report deleting (#432) * Bump version to v1.2.2 (#433) * 337012359: (feat) add snackBar with wait or stop testrun (#444) * 337012359: (feat) add snackBar with wait or stop testrun * 337012359: (fix) update to fix failed tests * 337012359: (fix) add fix for deletCertificate test --------- Co-authored-by: Volha Mardvilka * Feature/port stats (#430) * Add port speed and duplex tests Add unit tests for port stats testing Add place holder for ip port link test * Add ethtool to system dependencies Restructure conn stats methods and tests Resolve port stat information for link test * Fix runtime issue * Implement link test Fix unit tests Misc port stats updates * fix pylint issues * update readme * pylint fixes --------- Signed-off-by: J Boddey Co-authored-by: J Boddey * 339315842: (feat) add risk assessment tab (#450) * 339315842: (feat) add risk assessment tab * 339315842: (feat) add risk assessment tab --------- Co-authored-by: Volha Mardvilka * Add steps to resolve to PDF report (#411) * Add progress for steps to resolve * Formatting * Remove print statements * Allow for no steps to resolve * Add multipage * Fix multipage * Fix config * update report unit tests --------- Signed-off-by: J Boddey Co-authored-by: jhughesbiot * Add new mac addr field for report deleting (#432) * Remove rebase error --------- Signed-off-by: J Boddey Co-authored-by: Olga Mardvilko Co-authored-by: Volha Mardvilka Co-authored-by: J Boddey Co-authored-by: jhughesbiot Co-authored-by: jhughesbiot <50999916+jhughesbiot@users.noreply.github.com> * Rename "history" to "reports" (#456) * Rename "device-repository" to "devices" (#455) * Change certificates endpoints (#458) * 339311887: (feat) display saved risk profile (#460) Co-authored-by: Volha Mardvilka * Fix ui defects (#459) * 340835710: (fix) [Risk assessment] change page view when callout is visible (#461) Co-authored-by: Volha Mardvilka * Fix style to allow screen reader to read label (#462) * Updaate device test module configuration from api start endpoint (#463) * Add get profiles format endpoint (#465) * 339311250: (feat) delete risk profile (#466) Co-authored-by: Volha Mardvilka * 341254121: (fix) [a11y] add item name for aria-labels delete and copy buttons (#467) Co-authored-by: Volha Mardvilka * Make testing statuses available outside of Testing Tab (#468) * Adds timeout token * Status is available on all pages; Removed unused field isTestrunStarted - actual status is available * Validate CA certificate on FE (#464) * Adds validation on certificate upload * Show notification when error happens * 341901606: (fix) [GAR 1.6]: update for arrows usage on risk profile (#471) Co-authored-by: Volha Mardvilka * Adds space in certificate name regexp (#469) * 342096458: (fix) [GAR 1.3] add focus trap to certificates panel to prevent move focus to risk profile (#472) Co-authored-by: Volha Mardvilka * The focus is not moved to the snackbar with certificate validation rule (#470) * Adds class to fix focus flow * Changes the delay according to string length * Adds file name in error message (#473) * Add certificate endpoints (#451) * Adds version analytics event (#306) * Techdebt: adds state for testrun page (#392) * Fix tests (#397) * Fix tests * Update node version * 331379891: (feat) disable connection settings when testrun is in progress (#371) * 331379891: (feat) disable connection settings when testrun is in progress * 331379891: (fix) include more testrun results as progress * 331379891: (fix) fix spelling * 333349715: (fix) GAR 1.3 The disabled system settings panel contains a focusable element (#388) Co-authored-by: Volha Mardvilka --------- Co-authored-by: Volha Mardvilka * Disable device item if device in progress (#370) * Adds ga to track testrun initiation (#415) Adds ga ti track testrun initiation * Certs progress * Add list and upload cert endpoint * Add delete certificate endpoint * Update cert response codes * Upgrade dependency --------- Co-authored-by: Sofia Kurilova Co-authored-by: Olga Mardvilko Co-authored-by: Volha Mardvilka * Fix modify devices test (#449) * Fix modify devices * Remove excess logging * "Waiting for Device" :the snack bar appears on all pages (#474) * Make waiting for device snackbar global * Do not dismiss snack bar onDestroy * Rename testrun component (#480) * Rename settings component (#476) * Rename testrun component (#477) * Rename testrun component * Improve API test coverage (#291) * Improve API test coverage * Add timeouts * Improve coverage * Increase API timeout Signed-off-by: J Boddey * Pylint fixes * Disable broken tests * Fix skip * Disable broken tests * Fix test --------- Signed-off-by: J Boddey * Remove certificate if BE error happens (#484) * Add cert status (#478) * Add ethtool to make and docs (#483) * Add exception handling to certificate upload (#479) * Add cert status * Add exception handling to cert upload * Add get profiles format endpoint (#475) * Add get profiles format endpoint * Update risk assessment format * Re-add modules endpoint * Update session.py Signed-off-by: J Boddey --------- Signed-off-by: J Boddey * Reduce locations of Testrun version (#453) * Reduce locations of Testrun version * Update from comments * Resolve make control file --------- Signed-off-by: J Boddey * Allow stop testrun from any other page (#487) * Feature/tls client protocols (#485) * Add methods to allow known protocols that dont fit into the strict tls client detection methods Add QUIC protocol to approved protocols Start moving unit tests to a docker container for consistency * Fix client connections protocol method script Downgrade cryptography and pyOpenSSL libraries Move unit testing into docker for TLS module * Misc cleanup and pylint issues --------- Co-authored-by: J Boddey * The focus goes to the main page element after adding the ceriticate (#489) * Focus first interactive element in cert container when cert snack bar closed * The snack bar with test attempt status appears if the user is not on Testing page (#488) * Remove testrun status snack bar * Update consent form (#490) * Adds opt out checkbox * Adds announce when settings or certificate panel is opened (#493) * Set status code on failed cert upload (#491) * Add risk profiles (#486) * Add get profiles format endpoint * Update risk assessment format * Re-add modules endpoint * Work on profiles * Add load profiles format * Pylint * Add check status --------- Signed-off-by: J Boddey * Add test count to PDF report (#482) * Add test count to PDF report * Fix pylint issue * Exclude error --------- Signed-off-by: J Boddey * Change network mode on network modules to fix gateway routing (#495) * 342365574: (feat) display info about Risk Assessment during testing (#492) * 342365574: (feat) display info about Risk Assessment during testing * 342365574: (fix) change to show Risk Assessment callout only on InProgress --------- Co-authored-by: Volha Mardvilka * Updates status with data from start response (#494) * 341966862: (feat) display info about risk assessment in welcome modal (#496) Co-authored-by: Volha Mardvilka * Adds download zip window (#497) * 344874424: (fix) disable device tile in Canceling status (#500) Co-authored-by: Volha Mardvilka * Fix/UI/345164706 (#501) * 345164706: (fix) disable start testrun btn in canceling state * 345164706: (fix) remove commented code --------- Co-authored-by: Volha Mardvilka * GA option issues on the Welcome modal (#502) * Fix ga initial value * Fix label * 345202815: (fix) callout with the RA message is shown on the RA page (#503) Co-authored-by: Volha Mardvilka * 345203686: (fix) update callout block view on the welcome modal (#504) Co-authored-by: Volha Mardvilka * Allow UI to specify modules (#505) * Update risk assessment format (#499) * Fix issue with checking for error result (#498) * Fix issue with checking for error result * Resolve issue * Update html in test report Signed-off-by: J Boddey --------- Signed-off-by: J Boddey * Announce disabled state of settings panel (#506) * The Downlaod ZIP action can not be performed using the keaboard (#508) * Changes aria-label for Download Anyway button * Fix keyboard navigation for Download zip component * Add create and delete profile endpoints (#507) * Work towards creating profiles * Add delete profile endpoint * Exclude link local for arp (#418) * Add feature not detected test result (#396) * Add feature not present test result * Remove changes to tls module * Further pylint fixes * Add Feature Not Present to relevant tests * Reduce pylint limit * Change to feature not detected * Modify bacnet result * Update DNS test --------- Signed-off-by: J Boddey * Add extension for cert upload (#510) * 340859666: (feat): display risk profile form with name field (#514) Co-authored-by: Volha Mardvilka * Fixes some pylint issues (#511) * Fix some pylint issues * Fix more pylint issues * bug/test_baseline (#513) * Update mac address for baseline config dynamically * fix line endings * Update device config via Testrun api * Add API to no ui mode * Fix API endpoint call * Remove mac address update Change mac address in container * Add container start command back * Skip DNS tests * Cleanup --------- Co-authored-by: J Boddey * Attach profile to ZIP report (#518) * Work towards creating profiles * Add delete profile endpoint * Work on attaching profile to zip * Pylint * Fix zipping * Pylint fixes --------- Signed-off-by: J Boddey * Generate Risk profile from json (#515) Generate form from json * The Downlaod ZIP action can not be performed using the keyboard (#517) * Stop propagating event to eliminate the error * Fix tests * Adds tooltip * Fix export endpoint to use POST request * Fix zip download --------- Co-authored-by: Jacob Boddey * Add required if applicable (#519) * WIP: Add required if applicable * Fix bug with TLS client test * Update TLS results * Fix styles for helperbird (#524) * Form from json validation (#523) * Adds field validation * Fix vulnerabilities in dependencies (#526) * Enable draft button when profile name is present; enable save button when form is valid; remove discard button (#530) * Correct result on tls client test (#528) * Add informational and fnd to report (#527) * Mark fields required when trimmed value is empty (#529) * Update requests dependency (#525) * Remove debug artifact (#531) * Fix scroll area on reports page (#532) * Announce risk form open; focus first element in container (#536) * Fix validation; change element for text-long (#538) * 345258435: (feat) display expired certificate (#539) Co-authored-by: Volha Mardvilka * 348187954: (fix) update callouts position on the small window size (#540) Co-authored-by: Volha Mardvilka * 348356236: (fix) add tooltips for icons without any accompanying text (#541) Co-authored-by: Volha Mardvilka * 348353479: (fix) add tooltip for the download zip button using the keyboard (#542) Co-authored-by: Volha Mardvilka * 348361925: (fix) add helper text for mandatory profile name field (#543) Co-authored-by: Volha Mardvilka * Save new risk profile (#533) * Save new risk profile * Clear and close form after profile saved * Adds status valid for save profile * Fix DNS report when DNS packet is missing the qname property (#546) * Adds edit risk profile (#544) * Refactor delete form: rename it to simple dialog as it is not used for delete anymore * Adds edit risk profile * Fix autozise (#547) * Feature/risk profile (#522) * Update risk profile * Add unit tests for risk profile Fix service nmap unit test * Add unit tests for risk profile Fix service nmap unit test * Update api and session for new risk profile methods * pylint * pylint * Fix some pylint * Fix more pylint * Fix some bugs * Update risk profile logic (#535) * Update risk profile logic * Update test profiles Remove duplicate test file Cleanup temp test files * Remove categories from profile * Update expiration check to account for leap years * Update risk assessment questions * Update dependency * Add missing question * Update format and add error handling --------- Co-authored-by: jhughesbiot --------- Signed-off-by: J Boddey Co-authored-by: Jacob Boddey * Adds save draft (#549) Co-authored-by: Sofia Kurilova * 346351108: (feat) display risk assessment result (#553) * Close form after selected risk profile was deleted (#554) Co-authored-by: Sofia Kurilova * Fix error when updating profile (#555) * Render informational result correctly (#551) * Re-add method for exporting profle (#550) * Fix bug when failed to fetch latest version (#548) * 349769454: (fix) update size on the empty reports page to prevent overlapping (#557) * Fix bad multiple ip report when no ip requested (#556) * Fix Duplicate Certificate Names (#545) * Check for existing common name before uploading new cert Misc pylint updates * Re-add missing session content --------- Signed-off-by: J Boddey Co-authored-by: Jacob Boddey * Changes the icon; adds create date (#552) * 349783464: (fix) add styles for selected elements and fix form size on RA (#558) * 349793005: (fix) change focus to profile form to scroll up on opening profile (#559) * Return focus on create button when new profile created; return focus on profile is profile was edited (#560) * Small refactoring; update validators on selected profile update and on profile list update (#564) * Enables save and draft button for edit mode (#565) * Validate multi select form group on last checkbox tab press (#566) * Remove copy button (#567) * Bug/bacnet device (#568) * Change BACnet device detection validation Add unit tests * Fix runtime * cleanup * Update unit tests to match runtime types * Show only valid profile in modal; fix condition to not download profile if redirect button clicked (#569) * Fix bug when saving draft profile (#563) * Fix bug when saving draft * Remove risk when status changes to draft * Re-add exception handling * Fix formatting : * Update questions before calculating risk * Update unit testing * Update unit testing * Invoke risk profile update method in favor of manually updating properties in session * Update risk after update and fix type error * Update risk correctly --------- Signed-off-by: J Boddey Co-authored-by: jhughesbiot * Focus title or first element in container when navigation is triggered by enter (#570) * Adds aria label and tooltip to risk profile icons (#572) * Add profile PDF (#562) * Work on pdf * Work on profile PDF * Fix risk answer formatting * Downgrade weasyprint * Remove duplicate line * Update risk assessment after review * Fix profile format undefined * Update zip file to use /tmp directory (#571) Update device report folder Update file paths to use old and new patterns Co-authored-by: J Boddey * Remove cert from session after delete request (#575) * Fix error when only draft profiles are exist (#576) * Focus fix; fix profile status icon (#579) * Fix focus after page is opened * Fix profile status * Support longer string answers in profile PDF (#578) * Add extra spacing for long string answers * Format JSON * Cleanup old test devices from runtime (#583) * 351758698: (fix) update app version styles to meet design on expanded nav (#586) * Update risk profile description (#582) * Bump version for release (#584) * Remove skipped result (#580) * Prevent creating device with duplicate manufacturer and model (#581) * Prevent duplicate mf and model * Change error message to be more precise * Fix logic * Remove profile from runtime once included in ZIP (#577) * Remove profile after ZIP creation * Create /tmp dir for results Copy test results and profile to results dir Zip results dir and cleanup --------- Co-authored-by: jhughesbiot * Do not show settings callout if there are no interfaces and saved settings (#587) * Fix modbus results (#588) * Don't show error if config is empty; don't show settings callout when settings are not empty after saving (#589) * Catch error to proceed with device creation/editing (#592) * Remove output logging from OS level commands (#594) * Fix cancel after monitor bug and add testrun.log (#595) * Fix step 1 callout error (#593) * Fix GAR bug with cert upload (#590) * Change exception logic on cert upload * Fix error logic * Update documentation (#591) * Work on docs changes * Update docs * Update roadmap * Update docs * Update docs * Remove dev README.md * Remove skipped from docs * Add exception handling to timestamp parsing (#598) * Change exception logic on cert upload * Fix error logic * Add exception handling to timestamp parse * Stick button to the bottom of risk page (#574) * Adds copy of risk profile (#573) * Lint fix * Set testrun IDLE status if report of finished test run is removed (#585) * Adds Discard risk profile (#596) * Load test modules dynamically (#597) * Update download zip modal: add link to Risk Profiles, remove redirect button, rename download button, add No Profile option (#599) * 351338001: (feat) update rule for the third step message (#600) * 347009372: (feat) add selector for profile options for GA4 (#604) * Adds tooltip for copy and delete; show "same name" error when more than one copy is created (#606) * 346999760: (feat) [GA4] Track CA Certificates (#607) * 353476778: (fix) GAR 2.11 change for prevent risk profile tiles overlap (#609) * If profile is editing, return focus on profile (#612) * Change title for save profile dialog according to type of profile (#610) * Get reports when app is opened and when report page is opened; change status if testrun does not exist in reports; do not change status if testrun is just finished but not in reports yet (#613) * Show tooltip only on keyboard or hover (#614) * Update condition as report field is unique (#615) * Expired profile profile (#619) * Use device mac_addr if report mac_addr is missing (#622) * get system network interfaces util func * test get_sys_interfaces * pylint * get all network interfaces on session start * dicts diff * detect network adapter change * pylint * logging * Add ws server * Add MQTT protocol * Upgrade ws server * paho-mqtt dependency * getting docker container IP by container name * MQTT client class * Fix pylint * rename mqtt client logger * APScheduler * initializing the client inside the testrun object * pylint * check networks adapters in background * remove extra lines at the end of a file * rename mgtt logger * move network_adapters_checker to network_orchestrator * Adds mqtt client, adds pop up when new adapter is available (#603) * Fix some pylint issues (#620) * Disabled test results (#212) * Better handling of disbled tests Add dns and tls tests back in disabled state * pyling fixes * Fix subscriptable error * Re-add old tests as informational --------- Co-authored-by: J Boddey * remove get ip of docker container because it is not necessary * Fix individual test disabling when run from the UI (#629) * Fix individual test disabling when run from the UI * Remove disabled result for now --------- Co-authored-by: Jacob Boddey * Update package.yml (#624) Signed-off-by: J Boddey * Added risk profile api testing (#628) * added tests for profile endpoints * Modified test_start_testrun_started_successfully payload to match the expected json format, updated the profile endpoints tests * fixed the pylint errors from test_api.py * fixed few more pylint errors * Expired profile profile (#619) * Use device mac_addr if report mac_addr is missing (#622) * Fix some pylint issues (#620) * Disabled test results (#212) * Better handling of disbled tests Add dns and tls tests back in disabled state * pyling fixes * Fix subscriptable error * Re-add old tests as informational --------- Co-authored-by: J Boddey * added try-except block to delete_all_profiles() * Revert session file back * Add new line * Update api testing * updated the tests for profile endpoint, added a new fixture (add_profile) to create a profile * Updated profile endpoints: created a new fixture add_profile for creating profiles * Fix pylint * Fix pylint * Fix pylint issues --------- Co-authored-by: Sofia Kurilova Co-authored-by: J Boddey Co-authored-by: jhughesbiot * Adds close button for expired profile (#635) * Copy changes from hotfix 1.3.1 to dev (#631) * Copy changes from hotfix 1.3.1 to dev * Add pydyf version Signed-off-by: J Boddey --------- Signed-off-by: J Boddey * Inform FE about a new network adapter discovered( rename mqtt topic according to naming convention) (#634) * rename mqtt topic * Rename topic in ui --------- Co-authored-by: kurilova Co-authored-by: J Boddey * Fix network only mode issues (#617) Co-authored-by: J Boddey * Update all unit tests to work within the runtime environment (#611) * Update all unit tests to work within the runtime environment * Fix some formatting * fix line endings * update gitignore * Fix binary files in dockerfile * Change unit tests to run from testrun root directory * Run unit tests in actions * Fix pylint issues * Change command in actions * Update testing.yml Signed-off-by: J Boddey --------- Signed-off-by: J Boddey Co-authored-by: Jacob Boddey * Feature/dns report update (#637) * Update DNS module report Downgrade python packages for tls module * Fix header * pylint fixes * refactor func to handle case when network interface not exists * set test result "Error" * check device connected * thread for monitoring device connection * Minor changes * check the device connection only before each test * Adds tooltip (#638) Adds tooltip * send testrun status using mqtt * remove duplicatied line * refactor setting remaining tests to error * pylint * Fix focus after profile delete - track by name (#640) Fix focus after profile delete - track by name * Update the requests dependency (#643) * Update requests dependency * Update requests dependency * Update dependency in TLS test * Update docker dependency --------- Signed-off-by: J Boddey * Revert "Expired profile (#619)" (#645) Prevent opening of Expired risk profile * Improve documentation (#639) * Improve docs * Remove paragraph * Text changes * Fix text for the BE error * Change tooltip (#650) * Change tooltip * Allows draft profiles to become expired (#636) * Allow draft profiles to expire * Move status method into risk profile class * Use existing method * Check for expiry in validate method * Remove unused variable * Build UI during package instead of install (#621) * Build UI during package * Fix local build * Install npm * Remove duplicate build message * Fix ESLint * Fix script * Modify scripts * Improve scripts * Fix copy command * Try installing package * Depend on package job * Add sudo * Add sudo * Troubleshoot * Fix workflow * Checkout source for prepare command * Built ui within a container * Mount src files for build instead of static copy in build image * Attempt to fix actions * Remove manual build container cleanup methods * undo failed attempts to fix actions * Fix path * Remove -it flag --------- Signed-off-by: J Boddey Co-authored-by: kurilova Co-authored-by: jhughesbiot * Feature/risk in selected (#654) * Adds risk to selected value * Adds risk to selected value --------- Co-authored-by: J Boddey * Show risk for each question in the Risk profile (#647) * Show risk for each question in the Risk profile * set top position to 0 --------- Co-authored-by: J Boddey * Show internet connection (#653) * MQTT show internet connection * remove unused method * Change tooltip for internet icon (#656) * Change tooltip for internet icon * Remove unused import' --------- Co-authored-by: Jacob Boddey --------- Signed-off-by: J Boddey Co-authored-by: J Boddey Co-authored-by: Sofia Kurilova * bug/modbus_constructor (#657) * Pin all required packages Update modbus constructor to prevent error Add full trace logging for general errors in tests * Fix pylint issue --------- Co-authored-by: Jacob Boddey * Use mqtt service instead of calling GET /status every 5 seconds. (#644) * Use mqtt service instead of calling GET /status every 5 seconds. * Adds tooltip (#638) Adds tooltip * Fix focus after profile delete - track by name (#640) Fix focus after profile delete - track by name * Update the requests dependency (#643) * Update requests dependency * Update requests dependency * Update dependency in TLS test * Update docker dependency --------- Signed-off-by: J Boddey * remove unused output * encode mqtt message to json * Revert "Expired profile (#619)" (#645) Prevent opening of Expired risk profile * Improve documentation (#639) * Improve docs * Remove paragraph * Text changes * Fix text for the BE error * Change tooltip (#650) * Change tooltip * Allows draft profiles to become expired (#636) * Allow draft profiles to expire * Move status method into risk profile class * Use existing method * Check for expiry in validate method * Remove unused variable * Build UI during package instead of install (#621) * Build UI during package * Fix local build * Install npm * Remove duplicate build message * Fix ESLint * Fix script * Modify scripts * Improve scripts * Fix copy command * Try installing package * Depend on package job * Add sudo * Add sudo * Troubleshoot * Fix workflow * Checkout source for prepare command * Built ui within a container * Mount src files for build instead of static copy in build image * Attempt to fix actions * Remove manual build container cleanup methods * undo failed attempts to fix actions * Fix path * Remove -it flag --------- Signed-off-by: J Boddey Co-authored-by: kurilova Co-authored-by: jhughesbiot * Feature/risk in selected (#654) * Adds risk to selected value * Adds risk to selected value --------- Co-authored-by: J Boddey * Show risk for each question in the Risk profile (#647) * Show risk for each question in the Risk profile * set top position to 0 --------- Co-authored-by: J Boddey * Use mqtt service instead of calling GET /status every 5 seconds. * Use mqtt service instead of calling GET /status every 5 seconds. * Use mqtt service instead of calling GET /status every 5 seconds. * pylint --------- Signed-off-by: J Boddey Co-authored-by: J Boddey Co-authored-by: Aliaksandr Nikitsin Co-authored-by: jhughesbiot * Adds test statuses (#661) * Ignore folders when loading certs (#660) * Remove scorecard schedule and bump version (#659) * Allow ICMP response to DHCP messages in DHCP snooping test (#608) * Allow ICMP response to DHCP messages * Bug/unit test runtime (#655) * Change base test module startup to allow setup script to run independent of module startup process Update connection_module to allow for unit testing Update unit test run script to use new process * enable all unit tests update google cert * Remove binary fix lines from docker files pylint updates * pylint updates --------- Co-authored-by: jhughesbiot * The risk profile saved with old format is shown improperly while loading based on a new format (#664) * Fill only fields that are present in profile * GAR : The alt text for the expired risk profile should be communicated on Enter key (#662) * Change Expired profile title on Enter; announce Expired profile title on Enter * Update wording of tls cipher results (#671) * Show error message if provided; show default message if no (#680) * Test install on supported operating systems (#675) * Test install on multiple versions * Update step names * Remove recommendations on error (#674) * Tests for API (#649) * changed the tests order in test_api.py * added tests for '/system/config' POST endpoint * added the tests for 'system/shutdown' endpoint * added the test for GET '/reports' endpoint, updated 'test_update_system_config_invalid_config' to return error 400 * Check for missing fields Signed-off-by: J Boddey * added tests for delete profile (404, 400), added tests for create and update profile (400), added test 'run_test_and_get_report' skipped due to blocking during testing phase * added a new json file in '/testing/api/' used in 400 error tests * added error handling if 'name' and 'questions' not in profile json * fixed pylint * Added tests when update is available and 500 status code for '/system/version', test for system/modules * added responses library in requirements.txt * fixed the requested changes in api.py * Renamed the load_profile method to load_json and changed the logic to allow to load any json based on file name and relative path, corrected the new lines issues * updated restore_config fixture to run after the test * added test for create/update profile (500 error) * fixed pylint * fixed spacing, removed get_report_one_report * added tests: 500 error for delete '/profiles', 500 error for 'profles/format', 400, 404, 409 for '/system/start' * modified the tests for 500 response * added new profile with missing 'answer' * removed the tests with mock response * Update NTP report (#666) * Update NTP report * cleanup imports * pylint updates * modified update profile for bad request * changed validate_profile_json: handling empty spaces in name and question, handle if 'risk' field missing if status is 'Valid' * updated the requested changes * Add further profile validation * Fix pylint issues * Fix profile tests * Move validation to session * Fix pylint issue Signed-off-by: J Boddey --------- Signed-off-by: J Boddey Co-authored-by: J Boddey Co-authored-by: jhughesbiot * Fix a delay in the internet connectivity check (#669) * Add a timeout to the command * move single-intf check to scheduler * add timeout arg to run_command * move jobs to constructor * fping for internet connection checking * Update internet connection when device is In Progress, Monitoring, Waiting for Device status (#677) * Update internet connection when device is In Progress, Monitoring, Waiting for Device status * check if interface physically connected * Revert "fping for internet connection checking" --------- Co-authored-by: Aliaksandr Nikitsin Co-authored-by: Sofia Kurilova * Check if device folder already exists (#678) * Check if device folder already exists * Fix error message * Fix pylint issue * Remove debug mqtt logs (#692) --------- Signed-off-by: J Boddey Signed-off-by: dependabot[bot] Co-authored-by: jhughesbiot Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Sofia Kurilova Co-authored-by: Olga Mardvilko Co-authored-by: Volha Mardvilka Co-authored-by: jhughesbiot <50999916+jhughesbiot@users.noreply.github.com> Co-authored-by: Sofia Kurilova Co-authored-by: Aliaksandr Nikitsin Co-authored-by: Marius <86727846+MariusBaldovin@users.noreply.github.com> --- .github/workflows/package.yml | 68 +- .github/workflows/scorecard.yml | 6 +- .github/workflows/testing.yml | 24 +- .gitignore | 2 + README.md | 4 +- cmd/build | 19 +- cmd/build_ui | 37 + cmd/install | 18 +- cmd/package | 13 +- docs/README.md | 3 + docs/dev/README.md | 25 + docs/dev/code_quality.md | 16 + docs/network/README.md | 3 +- docs/network/add_new_service.md | 2 +- docs/test/README.md | 1 - docs/test/modules.md | 2 +- framework/python/src/api/api.py | 47 +- framework/python/src/common/mqtt.py | 62 + framework/python/src/common/risk_profile.py | 43 +- framework/python/src/common/session.py | 252 ++- framework/python/src/common/tasks.py | 78 + framework/python/src/common/util.py | 36 +- framework/python/src/core/testrun.py | 70 +- framework/python/src/net_orc/ip_control.py | 36 +- .../src/net_orc/network_orchestrator.py | 53 +- .../python/src/test_orc/test_orchestrator.py | 39 +- framework/requirements.txt | 12 +- make/DEBIAN/control | 2 +- modules/test/base/README.md | 7 + modules/test/base/bin/setup | 71 + modules/test/base/bin/start | 13 +- modules/test/base/bin/start_module | 146 +- modules/test/base/python/src/test_module.py | 13 +- .../test/conn/python/src/connection_module.py | 42 +- modules/test/conn/python/src/dhcp_util.py | 2 +- modules/test/dns/README.md | 3 +- modules/test/dns/conf/module_config.json | 6 + modules/test/dns/python/src/dns_module.py | 96 +- modules/test/ntp/python/src/ntp_module.py | 101 +- modules/test/protocol/bin/start_test_module | 104 +- modules/test/protocol/python/requirements.txt | 8 +- .../protocol/python/src/protocol_modbus.py | 2 +- .../services/python/src/services_module.py | 4 +- .../tls/bin/get_tls_client_connections.sh | 62 +- modules/test/tls/conf/module_config.json | 21 + modules/test/tls/python/requirements-test.txt | 1 + modules/test/tls/python/requirements.txt | 6 +- modules/test/tls/python/src/tls_util.py | 6 +- modules/test/tls/tls.Dockerfile | 14 +- modules/ui/angular.json | 8 +- .../build.sh => modules/ui/build.Dockerfile | 9 +- modules/ui/package-lock.json | 256 ++- modules/ui/package.json | 1 + modules/ui/src/app/app.component.html | 20 +- modules/ui/src/app/app.component.scss | 6 + modules/ui/src/app/app.component.spec.ts | 71 +- modules/ui/src/app/app.component.ts | 3 + modules/ui/src/app/app.module.ts | 10 + modules/ui/src/app/app.store.spec.ts | 84 +- modules/ui/src/app/app.store.ts | 75 +- .../download-report-zip.component.spec.ts | 8 +- .../download-report-zip.component.ts | 4 +- .../download-zip-modal.component.html | 93 +- .../download-zip-modal.component.scss | 23 +- .../download-zip-modal.component.spec.ts | 38 +- .../download-zip-modal.component.ts | 27 +- .../snack-bar/snack-bar.component.html | 5 +- .../app/components/wifi/wifi.component.html | 25 + .../app/components/wifi/wifi.component.scss | 40 + .../components/wifi/wifi.component.spec.ts | 100 ++ .../src/app/components/wifi/wifi.component.ts | 40 + .../interceptors/error.interceptor.spec.ts | 30 +- .../src/app/interceptors/error.interceptor.ts | 8 +- modules/ui/src/app/mocks/device.mock.ts | 4 +- modules/ui/src/app/mocks/profile.mock.ts | 54 + modules/ui/src/app/mocks/reports.mock.ts | 45 +- modules/ui/src/app/mocks/settings.mock.ts | 7 +- modules/ui/src/app/mocks/testrun.mock.ts | 2 +- modules/ui/src/app/mocks/topic.mock.ts | 5 + modules/ui/src/app/model/profile.ts | 6 +- modules/ui/src/app/model/setting.ts | 5 + modules/ui/src/app/model/testrun-status.ts | 21 +- modules/ui/src/app/model/topic.ts | 9 + .../certificates/certificates.store.spec.ts | 17 + .../pages/certificates/certificates.store.ts | 4 + .../device-form/device.validators.ts | 5 +- .../app/pages/devices/devices.component.html | 8 +- .../pages/devices/devices.component.spec.ts | 31 +- .../app/pages/devices/devices.component.ts | 27 +- .../app/pages/devices/devices.store.spec.ts | 3 + .../ui/src/app/pages/devices/devices.store.ts | 9 +- .../pages/reports/reports-routing.module.ts | 2 +- .../pages/reports/reports.component.spec.ts | 18 +- .../app/pages/reports/reports.component.ts | 173 ++ .../src/app/pages/reports/reports.module.ts | 2 +- .../app/pages/reports/reports.store.spec.ts | 100 +- .../ui/src/app/pages/reports/reports.store.ts | 91 +- .../src/app/pages/reports/reportscomponent.ts | 3 +- .../profile-form/profile-form.component.html | 15 + .../profile-form/profile-form.component.scss | 14 +- .../profile-form.component.spec.ts | 55 +- .../profile-form/profile-form.component.ts | 13 +- .../profile-form/profile.validators.ts | 8 +- .../profile-item/profile-item.component.html | 43 +- .../profile-item/profile-item.component.scss | 17 +- .../profile-item.component.spec.ts | 37 +- .../profile-item/profile-item.component.ts | 36 +- .../risk-assessment.component.html | 14 +- .../risk-assessment.component.scss | 2 +- .../risk-assessment.component.spec.ts | 78 +- .../risk-assessment.component.ts | 58 +- .../pages/settings/settings.component.html | 2 +- .../app/pages/settings/settings.store.spec.ts | 45 +- .../src/app/pages/settings/settings.store.ts | 78 +- .../testrun-initiate-form.component.spec.ts | 22 +- .../testrun-initiate-form.component.ts | 3 +- .../app/pages/testrun/testrun.component.html | 2 +- .../pages/testrun/testrun.component.spec.ts | 16 +- .../app/pages/testrun/testrun.component.ts | 16 +- .../app/pages/testrun/testrun.store.spec.ts | 3 + .../ui/src/app/pages/testrun/testrun.store.ts | 7 + .../services/test-run-mqtt.service.spec.ts | 102 ++ .../src/app/services/test-run-mqtt.service.ts | 38 + .../src/app/services/test-run.service.spec.ts | 56 +- .../ui/src/app/services/test-run.service.ts | 10 +- modules/ui/src/app/store/actions.ts | 25 +- modules/ui/src/app/store/effects.spec.ts | 135 +- modules/ui/src/app/store/effects.ts | 143 +- modules/ui/src/app/store/reducers.spec.ts | 71 +- modules/ui/src/app/store/reducers.ts | 24 + modules/ui/src/app/store/selectors.spec.ts | 28 + modules/ui/src/app/store/selectors.ts | 20 + modules/ui/src/app/store/state.ts | 16 +- modules/ui/ui.Dockerfile | 11 +- modules/ws/conf/mosquitto.conf | 22 + modules/ws/ws.Dockerfile | 4 + testing/api/profiles/new_profile.json | 54 + testing/api/profiles/new_profile_2.json | 56 + testing/api/profiles/updated_profile.json | 57 + testing/api/test_api.py | 1370 ++++++++++++---- testing/pylint/test_pylint | 28 +- testing/tests/test_tests.py | 2 +- testing/unit/conn/captures/monitor.pcap | Bin 0 -> 389089 bytes testing/unit/conn/captures/startup.pcap | Bin 0 -> 3404 bytes testing/unit/conn/conn_module_test.py | 32 +- testing/unit/dns/dns_module_test.py | 30 +- .../unit/dns/reports/dns_report_local.html | 2 +- testing/unit/framework/session_test.py | 57 + testing/unit/framework/util_test.py | 61 + testing/unit/ntp/ntp_module_test.py | 28 +- .../unit/ntp/reports/ntp_report_local.html | 1399 +---------------- .../ntp/reports/ntp_report_local_no_ntp.html | 1 + testing/unit/protocol/protocol_module_test.py | 1 - testing/unit/report/report_test.py | 49 + testing/unit/run.sh | 57 +- testing/unit/run_tests.sh | 69 - testing/unit/services/output/services.log | 6 - testing/unit/services/services_module_test.py | 31 +- testing/unit/tls/certs/_.google.com.crt | 153 +- testing/unit/tls/tls_module_test.py | 6 +- testing/unit/unit_test.Dockerfile | 47 - 161 files changed, 5329 insertions(+), 2892 deletions(-) create mode 100755 cmd/build_ui create mode 100644 docs/dev/README.md create mode 100644 docs/dev/code_quality.md create mode 100644 framework/python/src/common/mqtt.py create mode 100644 framework/python/src/common/tasks.py create mode 100644 modules/test/base/bin/setup create mode 100644 modules/test/tls/python/requirements-test.txt rename testing/unit/build.sh => modules/ui/build.Dockerfile (77%) create mode 100644 modules/ui/src/app/components/wifi/wifi.component.html create mode 100644 modules/ui/src/app/components/wifi/wifi.component.scss create mode 100644 modules/ui/src/app/components/wifi/wifi.component.spec.ts create mode 100644 modules/ui/src/app/components/wifi/wifi.component.ts create mode 100644 modules/ui/src/app/mocks/topic.mock.ts create mode 100644 modules/ui/src/app/model/topic.ts create mode 100644 modules/ui/src/app/pages/reports/reports.component.ts create mode 100644 modules/ui/src/app/services/test-run-mqtt.service.spec.ts create mode 100644 modules/ui/src/app/services/test-run-mqtt.service.ts create mode 100644 modules/ws/conf/mosquitto.conf create mode 100644 modules/ws/ws.Dockerfile create mode 100644 testing/api/profiles/new_profile.json create mode 100644 testing/api/profiles/new_profile_2.json create mode 100644 testing/api/profiles/updated_profile.json create mode 100644 testing/unit/conn/captures/monitor.pcap create mode 100644 testing/unit/conn/captures/startup.pcap create mode 100644 testing/unit/framework/session_test.py create mode 100644 testing/unit/framework/util_test.py delete mode 100644 testing/unit/run_tests.sh delete mode 100644 testing/unit/services/output/services.log delete mode 100644 testing/unit/unit_test.Dockerfile diff --git a/.github/workflows/package.yml b/.github/workflows/package.yml index 0fdb8c379..8c4b5bcbe 100644 --- a/.github/workflows/package.yml +++ b/.github/workflows/package.yml @@ -7,9 +7,13 @@ on: push: branches: - 'dev' + - 'release/*' + +permissions: + contents: read jobs: - testrun_package: + create_package: permissions: {} name: Package runs-on: ubuntu-22.04 @@ -24,4 +28,64 @@ jobs: uses: actions/upload-artifact@694cdabd8bdb0f10b2cea11669e1bf5453eed0a6 # v4.2.0 with: name: testrun_package - path: testrun*.deb \ No newline at end of file + path: testrun*.deb + + install_package_20: + permissions: {} + needs: create_package + name: Install on Ubuntu 20.04 + runs-on: ubuntu-20.04 + timeout-minutes: 15 + steps: + - name: Checkout source + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + - name: Download package + uses: actions/download-artifact@v4 + with: + name: testrun_package + - name: Install dependencies + shell: bash {0} + run: sudo cmd/prepare + - name: Install package + shell: bash {0} + run: sudo apt install ./testrun*.deb + + install_package_22: + permissions: {} + needs: create_package + name: Install on Ubuntu 22.04 + runs-on: ubuntu-22.04 + timeout-minutes: 15 + steps: + - name: Checkout source + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + - name: Download package + uses: actions/download-artifact@v4 + with: + name: testrun_package + - name: Install dependencies + shell: bash {0} + run: sudo cmd/prepare + - name: Install package + shell: bash {0} + run: sudo apt install ./testrun*.deb + + install_package_24: + permissions: {} + needs: create_package + name: Install on Ubuntu 24.04 + runs-on: ubuntu-24.04 + timeout-minutes: 15 + steps: + - name: Checkout source + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + - name: Download package + uses: actions/download-artifact@v4 + with: + name: testrun_package + - name: Install dependencies + shell: bash {0} + run: sudo cmd/prepare + - name: Install package + shell: bash {0} + run: sudo apt install ./testrun*.deb diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml index 12884c718..f0f89a631 100644 --- a/.github/workflows/scorecard.yml +++ b/.github/workflows/scorecard.yml @@ -7,10 +7,6 @@ on: # For Branch-Protection check. Only the default branch is supported. See # https://github.com/ossf/scorecard/blob/main/docs/checks.md#branch-protection branch_protection_rule: - # To guarantee Maintained check is occasionally updated. See - # https://github.com/ossf/scorecard/blob/main/docs/checks.md#maintained - schedule: - - cron: '20 6 * * 4' push: branches: [ "main" ] @@ -70,4 +66,4 @@ jobs: - name: "Upload to code-scanning" uses: github/codeql-action/upload-sarif@1b1aada464948af03b950897e5eb522f92603cc2 # v3.24.9 with: - sarif_file: results.sarif + sarif_file: results.sarif \ No newline at end of file diff --git a/.github/workflows/testing.yml b/.github/workflows/testing.yml index 0556a2189..d6deb1ab0 100644 --- a/.github/workflows/testing.yml +++ b/.github/workflows/testing.yml @@ -39,7 +39,7 @@ jobs: run: cmd/prepare - name: Install Testrun shell: bash {0} - run: TESTRUN_DIR=. cmd/install + run: cmd/install -l timeout-minutes: 30 - name: Run tests shell: bash {0} @@ -55,6 +55,28 @@ jobs: name: runtime_api_${{ github.run_id }} path: runtime.tgz + testrun_unit: + permissions: {} + name: Unit + runs-on: ubuntu-20.04 + timeout-minutes: 15 + steps: + - name: Checkout source + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + - name: Install dependencies + shell: bash {0} + run: cmd/prepare + - name: Install Testrun + shell: bash {0} + run: cmd/install -l + - name: Build Testrun + shell: bash {0} + run: cmd/build + timeout-minutes: 10 + - name: Run tests + shell: bash {0} + run: bash testing/unit/run.sh + pylint: permissions: {} name: Pylint diff --git a/.gitignore b/.gitignore index 82b6bbf64..92779dc04 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,7 @@ build/ # Ignore generated files from unit tests testing/unit_test/temp/ +testing/unit/conn/output/ testing/unit/dns/output/ testing/unit/nmap/output/ testing/unit/ntp/output/ @@ -15,6 +16,7 @@ testing/unit/tls/output/ testing/unit/tls/tmp/ testing/unit/report/output/ testing/unit/risk_profile/output/ +testing/unit/services/output/ *.deb make/DEBIAN/postinst diff --git a/README.md b/README.md index 4a04e8885..23fd843ca 100644 --- a/README.md +++ b/README.md @@ -47,7 +47,7 @@ When manual testing or configuration changes are required, Testrun will provide - DHCP client - The device must be able to obtain an IP address via DHCP ## Get started ▶️ -Once you have met the hardware and software requirements, you can get started with Testrun by following the [Get started guide](docs/get_started.md). +Once you have met the hardware and software requirements, you can get started with Testrun by following the [Get started guide](docs/get_started.md). Further docs are available in the [docs directory](docs) ## Roadmap :chart_with_upwards_trend: Testrun will constantly evolve to further support end-users by automating device network behaviour against industry standards. For further information on upcoming features, check out the [Roadmap](docs/roadmap.pdf). @@ -59,7 +59,7 @@ We are proud of our tool and strive to provide an enjoyable experience for all o If the application has come across a problem at any point during setup or use, please raise an issue under the [issues tab](https://github.com/google/testrun/issues). Issue templates exist for both bug reports and feature requests. If neither of these are appropriate for your issue, raise a blank issue instead. ## Contributing :keyboard: -The contributing requirements can be found in [CONTRIBUTING.md](CONTRIBUTING.md). In short, checkout the [Google CLA](https://cla.developers.google.com/) site to get started. +The contributing requirements can be found in [CONTRIBUTING.md](CONTRIBUTING.md). In short, checkout the [Google CLA](https://cla.developers.google.com/) site to get started. After that, check out our [developer documentation](docs/dev/README.md). ## FAQ :raising_hand: 1) I have an issue whilst installing/upgrading Testrun, what do I do? diff --git a/cmd/build b/cmd/build index d15171f31..d3294a681 100755 --- a/cmd/build +++ b/cmd/build @@ -36,15 +36,28 @@ fi # Builds all docker images echo Building docker images -# Build user interface -echo Building user interface -if docker build -t test-run/ui -f modules/ui/ui.Dockerfile . ; then +# Check if UI has already been built (if -l was used during install) +if [ ! -d "modules/ui/dist" ]; then + cmd/build_ui +fi + +# Build UI image +if docker build -t testrun/ui -f modules/ui/ui.Dockerfile . ; then echo Successully built the user interface else echo An error occured whilst building the user interface exit 1 fi +# Build websockets server +echo Building websockets server +if docker build -t testrun/ws -f modules/ws/ws.Dockerfile . ; then + echo Successully built the web sockets server +else + echo An error occured whilst building the websockets server + exit 1 +fi + # Build network modules echo Building network modules mkdir -p build/network diff --git a/cmd/build_ui b/cmd/build_ui new file mode 100755 index 000000000..afb0d8827 --- /dev/null +++ b/cmd/build_ui @@ -0,0 +1,37 @@ +#!/bin/bash -e + +# Copyright 2023 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Build the UI +echo Building the ui builder + +# Build UI builder image +if docker build -t testrun/build-ui -f modules/ui/build.Dockerfile . ; then + echo Successully built the ui builder +else + echo An error occured whilst building the ui builder + exit 1 +fi + +# Check that the container is not already running +docker kill tr-ui-build 2> /dev/null || true + +echo "Building the user interface" + +# Start build container and build the ui dist +docker run --rm -v $PWD/modules/ui:/modules/ui testrun/build-ui /bin/sh -c "npm install && npm run build" + +# Kill the container (Should not be running anymore) +docker kill tr-ui-build 2> /dev/null || true diff --git a/cmd/install b/cmd/install index 53d12b324..c350a969f 100755 --- a/cmd/install +++ b/cmd/install @@ -20,15 +20,29 @@ echo Installing application dependencies while getopts ":l" option; do case $option in l) # Install Testrun in local directory - TESTRUN_DIR=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")"/.. && pwd) + TESTRUN_DIR=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")"/.. && pwd) esac done # Check if TESTRUN_DIR has been set, otherwise install in /usr/local/testrun if [[ -z "${TESTRUN_DIR}" ]]; then TESTRUN_DIR=/usr/local/testrun + + # Check that user is sudo + if [[ "$EUID" -ne 0 ]]; then + echo "Installing Testrun in the default location requires sudo. Run using sudo cmd/install" + exit 1 + fi + else TESTRUN_DIR="${TESTRUN_DIR}" + + # Check that user is in docker group + if ! (id -nGz "$USER" | grep -qzxF "docker"); then + echo User is not in docker group. Follow https://docs.docker.com/engine/install/linux-postinstall/ to finish setting up docker. + exit 1 + fi + fi echo Installing Testrun at $TESTRUN_DIR @@ -51,7 +65,7 @@ cp -n local/system.json.example local/system.json deactivate # Build docker images -sudo cmd/build +cmd/build # Create local folders mkdir -p local/devices diff --git a/cmd/package b/cmd/package index fc418ab05..719258a83 100755 --- a/cmd/package +++ b/cmd/package @@ -16,6 +16,12 @@ # Creates a package for Testrun +# Check that user is not root +if [[ "$EUID" == 0 ]]; then + echo "Must not run as root. Use cmd/package as regular user" + exit 1 +fi + MAKE_SRC_DIR=make MAKE_CONTROL_DIR=make/DEBIAN/control @@ -25,10 +31,10 @@ version=$(grep -R "Version: " $MAKE_CONTROL_DIR | awk '{print $2}') # Replace invalid characters version="${version//./_}" -# Delete existing make files -rm -rf $MAKE_SRC_DIR/usr +echo Building package for testrun v${version} # Delete existing make files +echo Cleaning up previous build files rm -rf $MAKE_SRC_DIR/usr # Copy testrun script to /bin @@ -60,6 +66,9 @@ mkdir -p $MAKE_SRC_DIR/usr/local/testrun/local/risk_profiles mkdir -p local/root_certs cp -r local/root_certs $MAKE_SRC_DIR/usr/local/testrun/local/ +# Build the UI +cmd/build_ui + # Copy framework and modules into testrun folder cp -r {framework,modules} $MAKE_SRC_DIR/usr/local/testrun diff --git a/docs/README.md b/docs/README.md index 96eb32223..5f055dbb9 100644 --- a/docs/README.md +++ b/docs/README.md @@ -16,3 +16,6 @@ - [Running on a virtual machine](virtual_machine.md) - [Accessibility](ui/accessibility.mp4) - [Roadmap](roadmap.pdf) + +## Something missing? +If you feel there is some documentation that you would find useful, or have found an issue with existing documentation, please raise an issue on GitHub by navigating [here](https://github.com/google/testrun/issues/new/choose) \ No newline at end of file diff --git a/docs/dev/README.md b/docs/dev/README.md new file mode 100644 index 000000000..f11b1b092 --- /dev/null +++ b/docs/dev/README.md @@ -0,0 +1,25 @@ +Testrun logo + +## Developer docs + +## Table of Contents +1) General guidelines (this page) +2) [Code quality](code_quality.md) + +## General guidelines +As an open source project, we absolutely encourage contributions from the community to help Testrun remain an expanding but stable product. However, before contributing there are a number of things to take into consideration. + +1) [Sign the Google CLA](https://cla.developers.google.com/): Whether you are an individual or contributing on behalf of your organisation, you must be covered by a Google CLA. + +2) Determine the scope of your contribution + + - Your contribution is more likely to be accepted if fewer files are changed (keep it simple) + - Are you going to be fixing a bug, dependency issue or a new framework capability? Whatever it is, ensure your pull request fixes or changes just one thing. + +3) Get in touch to discuss whether your proposed changes are likely to be accepted + + - It is best to get the opinion from the core maintainers whether your proposed changes meet our objectives and align with Testrun principles. + +4) Fork Testrun and get developing + + - We aim to provide thorough and easy to ready developer documentation to help you contribute successfully. \ No newline at end of file diff --git a/docs/dev/code_quality.md b/docs/dev/code_quality.md new file mode 100644 index 000000000..47eabcf95 --- /dev/null +++ b/docs/dev/code_quality.md @@ -0,0 +1,16 @@ +Testrun logo + +## Code quality + +Whilst developing code for Testrun, there are some style guides that you should follow. + + - Python: https://google.github.io/styleguide/pyguide.html + - Angular: https://google.github.io/styleguide/angularjs-google-style.html + - Shell: https://google.github.io/styleguide/shellguide.html + - HTML/CSS: https://google.github.io/styleguide/htmlcssguide.html + - JSON: https://google.github.io/styleguide/jsoncstyleguide.xml + - Markdown: https://google.github.io/styleguide/docguide/style.html + +### Automated actions + +The current code base has been able to achieve 0 code lint issues. To maintain this, all lint checks are enforced on pull requests to dev and main. Please ensure that these lint checks are passing before marking your pull requests as 'Ready for review'. \ No newline at end of file diff --git a/docs/network/README.md b/docs/network/README.md index b5536c30c..0f97ecd7b 100644 --- a/docs/network/README.md +++ b/docs/network/README.md @@ -1,10 +1,9 @@ Testrun logo - ## Network Overview ## Table of Contents -1) Network Overview (this page) +1) Network overview (this page) 2) [How to identify network interfaces](identify_interfaces.md) 3) [Addresses](addresses.md) 4) [Add a new network service](add_new_service.md) diff --git a/docs/network/add_new_service.md b/docs/network/add_new_service.md index 7a07e43be..b3fa22514 100644 --- a/docs/network/add_new_service.md +++ b/docs/network/add_new_service.md @@ -65,7 +65,7 @@ COPY $MODULE_DIR/bin /testrun/bin # Copy over all python files COPY $MODULE_DIR/python /testrun/python -# Do not specify a CMD or Entrypoint as Test Run will automatically start your service as required +# Do not specify a CMD or Entrypoint as Testrun will automatically start your service as required ``` ### Example of start_network_service script diff --git a/docs/test/README.md b/docs/test/README.md index 19aa691d8..3163b4c84 100644 --- a/docs/test/README.md +++ b/docs/test/README.md @@ -2,7 +2,6 @@ ## Testing - The test requirements that are investigated by Testrun can be found in the [test modules documentation](/docs/test/modules.md). To understand the testing results, various definitions of test results and requirements are specified in the [statuses documentation](/docs/test/statuses.md). \ No newline at end of file diff --git a/docs/test/modules.md b/docs/test/modules.md index 7c5851ba4..2fe5983b1 100644 --- a/docs/test/modules.md +++ b/docs/test/modules.md @@ -10,7 +10,7 @@ Testrun provides some pre-built test modules for you to use when testing your ow | Baseline | A sample test module | [Baseline module](/modules/test/baseline/README.md) | | Connection | Verify IP and DHCP based behavior | [Connection module](/modules/test/conn/README.md) | | DNS | Verify DNS functionality | [DNS module](/modules/test/dns/README.md) | -| NMAP | Ensure unsecure services are disabled | [NMAP module](/modules/test/nmap/README.md) | +| Services | Ensure unsecure services are disabled | [Services module](/modules/test/services/README.md) | | NTP | Verify NTP functionality | [NTP module](/modules/test/ntp/README.md) | | Protocol | Inspect BMS protocol implementation | [Protocol Module](/modules/test/protocol/README.md) | | TLS | Determine TLS client and server behavior | [TLS module](/modules/test/tls/README.md) | diff --git a/framework/python/src/api/api.py b/framework/python/src/api/api.py index aed663ab8..e8e87465d 100644 --- a/framework/python/src/api/api.py +++ b/framework/python/src/api/api.py @@ -26,7 +26,7 @@ import uvicorn from urllib.parse import urlparse -from common import logger +from common import logger, tasks from common.device import Device LOGGER = logger.get_logger("api") @@ -114,7 +114,10 @@ def __init__(self, test_run): # Allow all origins to access the API origins = ["*"] - self._app = FastAPI() + # Scheduler for background periodic tasks + self._scheduler = tasks.PeriodicTasks(self._test_run) + + self._app = FastAPI(lifespan=self._scheduler.start) self._app.include_router(self._router) self._app.add_middleware( CORSMiddleware, @@ -165,7 +168,19 @@ async def post_sys_config(self, request: Request, response: Response): try: config = (await request.body()).decode("UTF-8") config_json = json.loads(config) + + # Validate req fields + if ("network" not in config_json or + "device_intf" not in config_json.get("network") or + "internet_intf" not in config_json.get("network") or + "log_level" not in config_json): + response.status_code = status.HTTP_400_BAD_REQUEST + return self._generate_msg( + False, + "Configuration is missing required fields") + self._session.set_config(config_json) + # Catch JSON Decode error etc except JSONDecodeError: response.status_code = status.HTTP_400_BAD_REQUEST @@ -231,7 +246,15 @@ async def start_test_run(self, request: Request, response: Response): False, "Configured interfaces are not " + "ready for use. Ensure required interfaces " + "are connected.") - device.test_modules = body_json["device"]["test_modules"] + # UI doesn't send individual test configs so we need to + # merge these manually until the UI is updated to handle + # the full config file + for module_name, module_config in device.test_modules.items(): + # Check if the module exists in UI test modules + if module_name in body_json["device"]["test_modules"]: + # Merge the enabled state + module_config["enabled"] = body_json[ + "device"]["test_modules"][module_name]["enabled"] LOGGER.info("Starting Testrun with device target " + f"{device.manufacturer} {device.model} with " + @@ -464,6 +487,19 @@ async def save_device(self, request: Request, response: Response): device_json.get(DEVICE_MODEL_KEY) ) + # Check if device folder exists + device_folder = os.path.join(self._test_run.get_root_dir(), + DEVICES_PATH, + device_json.get(DEVICE_MANUFACTURER_KEY) + + " " + + device_json.get(DEVICE_MODEL_KEY)) + + if os.path.exists(device_folder): + response.status_code = status.HTTP_409_CONFLICT + return self._generate_msg( + False, "A folder with that name already exists, " \ + "please rename the device or folder") + if device is None: # Create new device @@ -679,6 +715,11 @@ async def update_profile(self, request: Request, response: Response): response.status_code = status.HTTP_400_BAD_REQUEST return self._generate_msg(False, "Invalid request received") + # Validate json profile + if not self.get_session().validate_profile_json(req_json): + response.status_code = status.HTTP_400_BAD_REQUEST + return self._generate_msg(False, "Invalid request received") + profile_name = req_json.get("name") # Check if profile exists diff --git a/framework/python/src/common/mqtt.py b/framework/python/src/common/mqtt.py new file mode 100644 index 000000000..c58d24d3f --- /dev/null +++ b/framework/python/src/common/mqtt.py @@ -0,0 +1,62 @@ +# Copyright 2024 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""MQTT client""" +import json +import typing as t +import paho.mqtt.client as mqtt_client +from common import logger + +LOGGER = logger.get_logger("mqtt") +WEBSOCKETS_HOST = "localhost" +WEBSOCKETS_PORT = 1883 + +class MQTTException(Exception): + def __init__(self, message: str) -> None: + super().__init__(message) + + +class MQTT: + """ MQTT client class + """ + def __init__(self) -> None: + self._host = WEBSOCKETS_HOST + self._client = mqtt_client.Client(mqtt_client.CallbackAPIVersion.VERSION2) + LOGGER.setLevel(logger.logging.INFO) + self._client.enable_logger(LOGGER) + + def _connect(self): + """Establish connection to Mosquitto server + + Raises: + MQTTException: Raises exception on connection error + """ + if not self._client.is_connected(): + try: + self._client.connect(self._host, WEBSOCKETS_PORT, 60) + except (ValueError, ConnectionRefusedError) as e: + LOGGER.error("Can't connect to host") + raise MQTTException("Connection to the Mosquitto server failed") from e + + def send_message(self, topic: str, message: t.Union[str, dict]) -> None: + """Send message to specific topic + + Args: + topic (str): mqtt topic + message (t.Union[str, dict]): message + """ + self._connect() + if isinstance(message, dict): + message = json.dumps(message) + self._client.publish(topic, str(message)) diff --git a/framework/python/src/common/risk_profile.py b/framework/python/src/common/risk_profile.py index 6afb229ac..f50dffdde 100644 --- a/framework/python/src/common/risk_profile.py +++ b/framework/python/src/common/risk_profile.py @@ -96,11 +96,11 @@ def get_file_path(self): self.name + '.json') def _validate(self, profile_json, profile_format): - if self._valid(profile_json, profile_format): - if self._expired(): - self.status = 'Expired' + if self._expired(): + self.status = 'Expired' + elif self._valid(profile_json, profile_format): # User only wants to save a draft - elif 'status' in profile_json and profile_json['status'] == 'Draft': + if 'status' in profile_json and profile_json['status'] == 'Draft': self.status = 'Draft' else: self.status = 'Valid' @@ -409,6 +409,14 @@ def _generate_risk_questions(self): content += '' + # Question risk label + if 'risk' in question: + if question['risk'] == 'High': + content += '
HIGH RISK
' + elif question['risk'] == 'Limited': + content += '''
+ LIMITED RISK
''' + content += '''''' index += 1 @@ -635,6 +643,33 @@ def _generate_css(self): ul { margin-top: 0; } + + .risk-label{ + position: absolute; + top: 0px; + right: 0px; + width: 52px; + height: 16px; + font-family: 'Google Sans', sans-serif; + font-size: 8px; + font-weight: 500; + line-height: 16px; + letter-spacing: 0.64px; + text-align: center; + font-weight: bold; + border-radius: 3px; + } + + .risk-label-high{ + background-color: #FCE8E6; + color: #C5221F; + } + + .risk-label-limited{ + width: 65px; + background-color:#E4F7FB; + color: #007B83; + } ''' def to_pdf(self, device): diff --git a/framework/python/src/common/session.py b/framework/python/src/common/session.py index f555a9732..940fbe8f0 100644 --- a/framework/python/src/common/session.py +++ b/framework/python/src/common/session.py @@ -17,8 +17,10 @@ import pytz import json import os -from common import util, logger +from fastapi.encoders import jsonable_encoder +from common import util, logger, mqtt from common.risk_profile import RiskProfile +from net_orc.ip_control import IPControl # Certificate dependencies from cryptography import x509 @@ -36,7 +38,7 @@ MAX_DEVICE_REPORTS_KEY = 'max_device_reports' CERTS_PATH = 'local/root_certs' CONFIG_FILE_PATH = 'local/system.json' -SECONDS_IN_YEAR = 31536000 +STATUS_TOPIC = 'status' PROFILE_FORMAT_PATH = 'resources/risk_assessment.json' PROFILES_DIR = 'local/risk_profiles' @@ -44,8 +46,36 @@ LOGGER = logger.get_logger('session') +def session_tracker(method): + """Session changes tracker.""" + def wrapper(self, *args, **kwargs): + + result = method(self, *args, **kwargs) + + if self.get_status() != 'Idle': + self.get_mqtt_client().send_message( + STATUS_TOPIC, + jsonable_encoder(self.to_json()) + ) + + return result + return wrapper + +def apply_session_tracker(cls): + """Applies tracker decorator to class methods""" + for attr in dir(cls): + if (callable(getattr(cls, attr)) + and not attr.startswith('_') + and not attr.startswith('get') + and not attr == 'to_json' + ): + setattr(cls, attr, session_tracker(getattr(cls, attr))) + return cls + + +@apply_session_tracker class TestrunSession(): - """Represents the current session of Test Run.""" + """Represents the current session of Testrun.""" def __init__(self, root_dir): self._root_dir = root_dir @@ -93,6 +123,8 @@ def __init__(self, root_dir): self._config_file = os.path.join(root_dir, CONFIG_FILE_PATH) self._config = self._get_default_config() + # System network interfaces + self._ifaces = {} # Loading methods self._load_version() self._load_config() @@ -107,6 +139,9 @@ def __init__(self, root_dir): self._timezone = tz[0] LOGGER.debug(f'System timezone is {self._timezone}') + # MQTT client + self._mqtt_client = mqtt.MQTT() + def start(self): self.reset() self._status = 'Waiting for Device' @@ -332,6 +367,12 @@ def add_test_result(self, result): result.result = 'In Progress' self._results.append(result) + def set_test_result_error(self, result): + """Set test result error""" + result.result = 'Error' + result.recommendations = None + self._results.append(result) + def add_module_report(self, module_report): self._module_reports.append(module_report) @@ -399,17 +440,31 @@ def _load_profiles(self): try: for risk_profile_file in os.listdir( os.path.join(self._root_dir, PROFILES_DIR)): + LOGGER.debug(f'Discovered profile {risk_profile_file}') + # Open the risk profile file with open(os.path.join(self._root_dir, PROFILES_DIR, risk_profile_file), encoding='utf-8') as f: + + # Parse risk profile json json_data = json.load(f) + + # Validate profile JSON + if not self.validate_profile_json(json_data): + LOGGER.error('Profile failed validation') + continue + + # Instantiate a new risk profile risk_profile = RiskProfile() + + # Pass JSON to populate risk profile risk_profile.load( profile_json=json_data, profile_format=self._profile_format ) - risk_profile.status = self.check_profile_status(risk_profile) + + # Add risk profile to session self._profiles.append(risk_profile) except Exception as e: @@ -428,25 +483,6 @@ def get_profile(self, name): return profile return None - def validate_profile(self, profile_json): - - # Check name field is present - if 'name' not in profile_json: - return False - - # Check questions field is present - if 'questions' not in profile_json: - return False - - # Check all questions are present - for format_q in self.get_profiles_format(): - if self._get_profile_question(profile_json, - format_q.get('question')) is None: - LOGGER.error('Missing question: ' + format_q.get('question')) - return False - - return True - def _get_profile_question(self, profile_json, question): for q in profile_json.get('questions'): @@ -455,7 +491,14 @@ def _get_profile_question(self, profile_json, question): return None + def get_profile_format_question(self, question): + for q in self.get_profiles_format(): + if q.get('question') == question: + return q + def update_profile(self, profile_json): + """Update the risk profile with the provided JSON. + The content has already been validated in the API""" profile_name = profile_json['name'] @@ -463,39 +506,8 @@ def update_profile(self, profile_json): profile_json['version'] = self.get_version() profile_json['created'] = datetime.datetime.now().strftime('%Y-%m-%d') - if 'status' in profile_json and profile_json.get('status') == 'Valid': - # Attempting to submit a risk profile, we need to check it - - # Check all questions have been answered - all_questions_answered = True - - for question in self.get_profiles_format(): - - # Check question is present - profile_question = self._get_profile_question(profile_json, - question.get('question')) - - if profile_question is not None: - - # Check answer is present - if 'answer' not in profile_question: - LOGGER.error('Missing answer for question: ' + - question.get('question')) - all_questions_answered = False - - else: - LOGGER.error('Missing question: ' + question.get('question')) - all_questions_answered = False - - if not all_questions_answered: - LOGGER.error('Not all questions answered') - return None - - else: - profile_json['status'] = 'Draft' - + # Check if profile already exists risk_profile = self.get_profile(profile_name) - if risk_profile is None: # Create a new risk profile @@ -524,19 +536,105 @@ def update_profile(self, profile_json): return risk_profile - def check_profile_status(self, profile): + def validate_profile_json(self, profile_json): + """Validate properties in profile update requests""" + + # Get the status field + valid = False + if 'status' in profile_json and profile_json.get('status') == 'Valid': + valid = True + + # Check if 'name' exists in profile + if 'name' not in profile_json: + LOGGER.error('Missing "name" in profile') + return False + + # Check if 'name' field not empty + elif len(profile_json.get('name').strip()) == 0: + LOGGER.error('Name field left empty') + return False + + # Error handling if 'questions' not in request + if 'questions' not in profile_json and valid: + LOGGER.error('Missing "questions" field in profile') + return False + + # Validating the questions section + for question in profile_json.get('questions'): + + # Check if the question field is present + if 'question' not in question: + LOGGER.error('The "question" field is missing') + return False + + # Check if 'question' field not empty + elif len(question.get('question').strip()) == 0: + LOGGER.error('A question is missing from "question" field') + return False + + # Check if question is a recognized question + format_q = self.get_profile_format_question( + question.get('question')) + + if format_q is None: + LOGGER.error(f'Unrecognized question: {question.get("question")}') + return False + + # Error handling if 'answer' is missing + if 'answer' not in question and valid: + LOGGER.error('The answer field is missing') + return False + + # If answer is present, check the validation rules + else: + + # Extract the answer out of the profile + answer = question.get('answer') + + # Get the validation rules + field_type = format_q.get('type') - if profile.status == 'Valid': + # Check if type is string or single select, answer should be a string + if ((field_type in ['string', 'select']) + and not isinstance(answer, str)): + LOGGER.error(f'''Answer for question \ +{question.get('question')} is incorrect data type''') + return False - # Check expiry - created_date = profile.created.timestamp() + # Check if type is select, answer must be from list + if field_type == 'select' and valid: + possible_answers = format_q.get('options') + if answer not in possible_answers: + LOGGER.error(f'''Answer for question \ +{question.get('question')} is not valid''') + return False - today = datetime.datetime.now().timestamp() + # Validate select multiple field types + if field_type == 'select-multiple': - if created_date < (today - SECONDS_IN_YEAR): - profile.status = 'Expired' + if not isinstance(answer, list): + LOGGER.error(f'''Answer for question \ +{question.get('question')} is incorrect data type''') + return False - return profile.status + question_options_len = len(format_q.get('options')) + + # We know it is a list, now check the indexes + for index in answer: + + # Check if the index is an integer + if not isinstance(index, int): + LOGGER.error(f'''Answer for question \ +{question.get('question')} is incorrect data type''') + return False + + # Check if index is 0 or above and less than the num of options + if index < 0 or index >= question_options_len: + LOGGER.error(f'''Invalid index provided as answer for \ +question {question.get('question')}''') + return False + + return True def delete_profile(self, profile): @@ -565,6 +663,7 @@ def reset(self): self._results = [] self._started = None self._finished = None + self._ifaces = IPControl.get_sys_interfaces() def to_json(self): @@ -650,6 +749,11 @@ def load_certs(self): self._certs = [] for cert_file in os.listdir(CERTS_PATH): + + # Ignore directories + if os.path.isdir(os.path.join(CERTS_PATH, cert_file)): + continue + LOGGER.debug(f'Loading certificate {cert_file}') try: @@ -712,3 +816,25 @@ def delete_cert(self, filename): def get_certs(self): return self._certs + + def detect_network_adapters_change(self) -> dict: + adapters = {} + ifaces_new = IPControl.get_sys_interfaces() + + # Difference between stored and newly received network interfaces + diff = util.diff_dicts(self._ifaces, ifaces_new) + if diff: + if 'items_added' in diff: + adapters['adapters_added'] = diff['items_added'] + if 'items_removed' in diff: + adapters['adapters_removed'] = diff['items_removed'] + # Save new network interfaces to session + LOGGER.debug(f'Network adapters change detected: {adapters}') + self._ifaces = ifaces_new + return adapters + + def get_mqtt_client(self): + return self._mqtt_client + + def get_ifaces(self): + return self._ifaces diff --git a/framework/python/src/common/tasks.py b/framework/python/src/common/tasks.py new file mode 100644 index 000000000..5da0b40c9 --- /dev/null +++ b/framework/python/src/common/tasks.py @@ -0,0 +1,78 @@ +# Copyright 2024 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +"""Periodic background tasks""" + +from contextlib import asynccontextmanager +import datetime +import logging + +from apscheduler.schedulers.asyncio import AsyncIOScheduler +from fastapi import FastAPI + +from common import logger + +# Check adapters period seconds +# Check adapters period seconds +CHECK_NETWORK_ADAPTERS_PERIOD = 5 +CHECK_INTERNET_PERIOD = 2 +INTERNET_CONNECTION_TOPIC = 'events/internet' +NETWORK_ADAPTERS_TOPIC = 'events/adapter' + +LOGGER = logger.get_logger('tasks') + + +class PeriodicTasks: + """Background periodic tasks + """ + def __init__( + self, testrun_obj, + ) -> None: + self._testrun = testrun_obj + self._mqtt_client = self._testrun.get_mqtt_client() + local_tz = datetime.datetime.now().astimezone().tzinfo + self._scheduler = AsyncIOScheduler(timezone=local_tz) + # Prevent scheduler warnings + self._scheduler._logger.setLevel(logging.ERROR) + + self.adapters_checker_job = self._scheduler.add_job( + func=self._testrun.get_net_orc().network_adapters_checker, + kwargs={ + 'mqtt_client': self._mqtt_client, + 'topic': NETWORK_ADAPTERS_TOPIC + }, + trigger='interval', + seconds=CHECK_NETWORK_ADAPTERS_PERIOD, + ) + # add internet connection cheking job only in single-intf mode + if 'single_intf' not in self._testrun.get_session().get_runtime_params(): + self.internet_shecker = self._scheduler.add_job( + func=self._testrun.get_net_orc().internet_conn_checker, + kwargs={ + 'mqtt_client': self._mqtt_client, + 'topic': INTERNET_CONNECTION_TOPIC + }, + trigger='interval', + seconds=CHECK_INTERNET_PERIOD, + ) + + @asynccontextmanager + async def start(self, app: FastAPI): # pylint: disable=unused-argument + """Start background tasks + + Args: + app (FastAPI): app instance + """ + # Job that checks for changes in network adapters + self._scheduler.start() + yield diff --git a/framework/python/src/common/util.py b/framework/python/src/common/util.py index 096aaf4df..7c31631fb 100644 --- a/framework/python/src/common/util.py +++ b/framework/python/src/common/util.py @@ -17,13 +17,14 @@ import os import subprocess import shlex -from common import logger +import typing as t import netifaces +from common import logger LOGGER = logger.get_logger('util') -def run_command(cmd, output=True): +def run_command(cmd, output=True, timeout=None): """Runs a process at the os level By default, returns the standard output and error output If the caller sets optional output parameter to False, @@ -35,7 +36,7 @@ def run_command(cmd, output=True): with subprocess.Popen(shlex.split(cmd), stdout=subprocess.PIPE, stderr=subprocess.PIPE) as process: - stdout, stderr = process.communicate() + stdout, stderr = process.communicate(timeout) if process.returncode != 0 and output: err_msg = f'{stderr.strip()}. Code: {process.returncode}' @@ -113,3 +114,32 @@ def get_module_display_name(search): return module[1] return 'Unknown' + + +def diff_dicts(d1: t.Dict[t.Any, t.Any], d2: t.Dict[t.Any, t.Any]) -> t.Dict: + """Compares two dictionaries by keys + + Args: + d1 (t.Dict[t.Any, t.Any]): first dict to compare + d2 (t.Dict[t.Any, t.Any]): second dict to compare + + Returns: + t.Dict[t.Any, t.Any]: Returns an empty dictionary + if the compared dictionaries are equal, + otherwise returns a dictionary that contains + the removed items(if available) + and the added items(if available). + """ + diff = {} + if d1 != d2: + s1 = set(d1) + s2 = set(d2) + keys_removed = s1 - s2 + keys_added = s2 - s1 + items_removed = {k:d1[k] for k in keys_removed} + items_added = {k:d2[k] for k in keys_added} + if items_removed: + diff['items_removed'] = items_removed + if items_added: + diff['items_added'] = items_added + return diff diff --git a/framework/python/src/core/testrun.py b/framework/python/src/core/testrun.py index 5b43cfd65..dccde6a35 100644 --- a/framework/python/src/core/testrun.py +++ b/framework/python/src/core/testrun.py @@ -15,7 +15,7 @@ """The overall control of the Test Run application. This file provides the integration between all of the -Test Run components, such as net_orc, test_orc and test_ui. +Testrun components, such as net_orc, test_orc and test_ui. Run using the provided command scripts in the cmd folder. E.g sudo cmd/start @@ -27,7 +27,7 @@ import signal import sys import time -from common import logger, util +from common import logger, util, mqtt from common.device import Device from common.session import TestrunSession from common.testreport import TestReport @@ -81,7 +81,9 @@ def __init__(self, self._net_only = net_only self._single_intf = single_intf - self._no_ui = no_ui + # Network only option only works if UI is also + # disbled so need to set no_ui if net_only is selected + self._no_ui = no_ui or net_only # Catch any exit signals self._register_exits() @@ -109,6 +111,12 @@ def __init__(self, # Load test modules self._test_orc.start() + # Start websockets server + self.start_ws() + + # Init MQTT client + self._mqtt_client = mqtt.MQTT() + if self._no_ui: # Check Testrun is able to start @@ -216,7 +224,14 @@ def _load_test_reports(self, device): 'test', device.mac_addr.replace(':',''), 'report.json') - + + if not os.path.isfile(report_json_file_path): + # Revert to pre 1.3 file path + report_json_file_path = os.path.join( + reports_folder, + report_folder, + 'report.json') + if not os.path.isfile(report_json_file_path): # Revert to pre 1.3 file path report_json_file_path = os.path.join( @@ -369,6 +384,7 @@ def shutdown(self): LOGGER.info('Shutting down Testrun') self.stop() self._stop_ui() + self._stop_ws() def _exit_handler(self, signum, arg): # pylint: disable=unused-argument LOGGER.debug('Exit signal received: ' + str(signum)) @@ -385,6 +401,9 @@ def _get_config_abs(self, config_file=None): # Expand the config file to absolute pathing return os.path.abspath(config_file) + def get_root_dir(self): + return root_dir + def get_config_file(self): return self._get_config_abs() @@ -406,6 +425,9 @@ def _stop_network(self, kill=True): def _stop_tests(self): self._test_orc.stop() + def get_mqtt_client(self): + return self._mqtt_client + def get_device(self, mac_addr): """Returns a loaded device object from the device mac address.""" for device in self.get_session().get_device_repository(): @@ -463,7 +485,7 @@ def start_ui(self): try: client.containers.run( - image='test-run/ui', + image='testrun/ui', auto_remove=True, name='tr-ui', hostname='testrun.io', @@ -489,4 +511,40 @@ def _stop_ui(self): if container is not None: container.kill() except docker.errors.NotFound: - return + pass + + + def start_ws(self): + + self._stop_ws() + + LOGGER.info('Starting WS server') + + client = docker.from_env() + + try: + client.containers.run( + image='testrun/ws', + auto_remove=True, + name='tr-ws', + detach=True, + ports={ + '9001': 9001, + '1883': 1883 + } + ) + except ImageNotFound as ie: + LOGGER.error('An error occured whilst starting the websockets server. ' + + 'Please investigate and try again.') + LOGGER.error(ie) + sys.exit(1) + + def _stop_ws(self): + LOGGER.info('Stopping websockets server') + client = docker.from_env() + try: + container = client.containers.get('tr-ws') + if container is not None: + container.kill() + except docker.errors.NotFound: + pass diff --git a/framework/python/src/net_orc/ip_control.py b/framework/python/src/net_orc/ip_control.py index 506b23a95..04686f0cd 100644 --- a/framework/python/src/net_orc/ip_control.py +++ b/framework/python/src/net_orc/ip_control.py @@ -12,6 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. """IP Control Module""" +import psutil +import typing as t from common import logger from common import util import re @@ -43,10 +45,7 @@ def add_namespace(self, namespace): def check_interface_status(self, interface_name): output = util.run_command(cmd=f'ip link show {interface_name}', output=True) - if 'state DOWN ' in output[0]: - return False - else: - return True + return 'state UP ' in output[0] def delete_link(self, interface_name): """Delete an ip link""" @@ -99,7 +98,7 @@ def get_iface_port_stats(self, iface): def get_namespaces(self): result = util.run_command('ip netns list') - #Strip ID's from the namespace results + # Strip ID's from the namespace results namespaces = re.findall(r'(\S+)(?:\s+\(id: \d+\))?', result[0]) return namespaces @@ -237,3 +236,30 @@ def configure_container_interface(self, LOGGER.error(f'Failed to set interface up {namespace_intf}') return False return True + + def ping_via_gateway(self, host): + """Ping the host trough the gateway container""" + command = f'timeout 3 docker exec tr-ct-gateway ping -W 1 -c 1 {host}' + output = util.run_command(command) + if '0% packet loss' in output[0]: + return True + return False + + @staticmethod + def get_sys_interfaces() -> t.Dict[str, t.Dict[str, str]]: + """ Retrieves all Ethernet network interfaces from the host system + Returns: + t.Dict[str, str] + """ + addrs = psutil.net_if_addrs() + ifaces = {} + + for key in addrs: + nic = addrs[key] + # Ignore any interfaces that are not ethernet + if not (key.startswith('en') or key.startswith('eth')): + continue + + ifaces[key] = nic[0].address + + return ifaces diff --git a/framework/python/src/net_orc/network_orchestrator.py b/framework/python/src/net_orc/network_orchestrator.py index f20093a28..a94bca89b 100644 --- a/framework/python/src/net_orc/network_orchestrator.py +++ b/framework/python/src/net_orc/network_orchestrator.py @@ -22,8 +22,9 @@ import sys import docker import time +import traceback from docker.types import Mount -from common import logger, util +from common import logger, util, mqtt from net_orc.listener import Listener from net_orc.network_event import NetworkEvent from net_orc.network_validator import NetworkValidator @@ -223,7 +224,9 @@ def _device_discovered(self, mac_addr): #self._ovs.add_arp_inspection_filter(ip_address=device.ip_addr, # mac_address=device.mac_addr) - self._start_device_monitor(device) + # Don't monitor devices when in network only mode + if 'net_only' not in self._session.get_runtime_params(): + self._start_device_monitor(device) def _get_conn_stats(self): """ Extract information about the physical connection @@ -547,10 +550,6 @@ def _start_network_service(self, net_module): cap_add=['NET_ADMIN'], name=net_module.container_name, hostname=net_module.container_name, - # Undetermined version of docker seems to have broken - # DNS configuration (/etc/resolv.conf) Re-add when/if - # this network is utilized and DNS issue is resolved - #network=PRIVATE_DOCKER_NET, network_mode='none', privileged=True, detach=True, @@ -786,6 +785,48 @@ def restore_net(self): def get_session(self): return self._session + def network_adapters_checker(self, mqtt_client: mqtt.MQTT, topic: str): + """Checks for changes in network adapters + and sends a message to the frontend + """ + try: + adapters = self._session.detect_network_adapters_change() + if adapters: + mqtt_client.send_message(topic, adapters) + except Exception: + LOGGER.error(traceback.format_exc()) + + def is_device_connected(self): + """Check if device connected""" + return self._ip_ctrl.check_interface_status( + self._session.get_device_interface() + ) + + def internet_conn_checker(self, mqtt_client: mqtt.MQTT, topic: str): + """Checks internet connection and sends a status to frontend""" + + # Only check if Testrun is running not in single-intf mode + if (self.get_session().get_status() in [ + 'Waiting for Device', + 'Monitoring', + 'In Progress' + ]): + # Default message + message = {'connection': False} + iface = self._session.get_internet_interface() + + # Check that an internet intf has been selected + if iface and iface in self._ip_ctrl.get_sys_interfaces(): + + # Ping google.com from gateway container + internet_connection = self._ip_ctrl.ping_via_gateway( + 'google.com') + + if internet_connection: + message['connection'] = True + + # Broadcast via MQTT client + mqtt_client.send_message(topic, message) class NetworkModule: """Define all the properties of a Network Module""" diff --git a/framework/python/src/test_orc/test_orchestrator.py b/framework/python/src/test_orc/test_orchestrator.py index d38f888a1..a38371d07 100644 --- a/framework/python/src/test_orc/test_orchestrator.py +++ b/framework/python/src/test_orc/test_orchestrator.py @@ -60,6 +60,8 @@ def __init__(self, session, net_orc): os.path.dirname( os.path.dirname( os.path.dirname(os.path.dirname(os.path.realpath(__file__)))))) + self._test_modules_running = [] + self._current_module = 0 def start(self): LOGGER.debug("Starting test orchestrator") @@ -102,7 +104,13 @@ def run_test_modules(self): test_modules.append(module) self.get_session().add_total_tests(len(module.tests)) - for module in test_modules: + # Store enabled test modules in the TestsOrchectrator object + self._test_modules_running = test_modules + self._current_module = 0 + + for index, module in enumerate(test_modules): + + self._current_module = index self._run_test_module(module) LOGGER.info("All tests complete") @@ -362,7 +370,14 @@ def _run_test_module(self, module): LOGGER.info(f"Running test module {module.name}") # Get all tests to be executed and set to in progress - for test in module.tests: + for current_test,test in enumerate(module.tests): + + # Check that device is connected + if not self._net_orc.is_device_connected(): + LOGGER.error("Device was disconnected") + self._set_test_modules_error(current_test) + self._session.set_status("Cancelled") + return test_copy = copy.deepcopy(test) test_copy.result = "In Progress" @@ -486,19 +501,25 @@ def _run_test_module(self, module): try: with open(results_file, "r", encoding="utf-8-sig") as f: + + # Load results from JSON file module_results_json = json.load(f) module_results = module_results_json["results"] for test_result in module_results: - # Convert dict into TestCase object + # Convert dict from json into TestCase object test_case = TestCase( name=test_result["name"], description=test_result["description"], expected_behavior=test_result["expected_behavior"], required_result=test_result["required_result"], result=test_result["result"]) - test_case.result=test_result["result"] + # Any informational test should always report informational + if test_case.required_result == "Informational": + test_case.result = "Informational" + + # Add steps to resolve if test is non-compliant if (test_case.result == "Non-Compliant" and "recommendations" in test_result): test_case.recommendations = test_result["recommendations"] @@ -729,3 +750,13 @@ def get_test_case(self, name): def get_session(self): return self._session + + def _set_test_modules_error(self, current_test): + """Set all remaining tests to error""" + for i in range(self._current_module, len(self._test_modules_running)): + start_idx = current_test if i == self._current_module else 0 + for j in range(start_idx, len(self._test_modules_running[i].tests)): + self.get_session().set_test_result_error( + self._test_modules_running[i].tests[j] + ) + diff --git a/framework/requirements.txt b/framework/requirements.txt index c31978d99..0484905ee 100644 --- a/framework/requirements.txt +++ b/framework/requirements.txt @@ -1,8 +1,8 @@ # Requirements for the core module -requests<2.32.0 +requests==2.32.3 # Requirements for the net_orc module -docker==7.0.0 +docker==7.1.0 ipaddress==1.0.23 netifaces==0.11.0 scapy==2.5.0 @@ -21,6 +21,8 @@ pydantic==2.7.1 # Requirements for testing pytest==7.4.4 pytest-timeout==2.2.0 +responses==0.25.3 + # Requirements for the report markdown==3.5.2 @@ -31,3 +33,9 @@ pytz==2024.1 # Requirements for the risk profile python-dateutil==2.9.0 + +# Requirements for MQTT client +paho-mqtt==2.1.0 + +# Requirements for background tasks +APScheduler==3.10.4 diff --git a/make/DEBIAN/control b/make/DEBIAN/control index 488f69458..cecad9d17 100644 --- a/make/DEBIAN/control +++ b/make/DEBIAN/control @@ -1,5 +1,5 @@ Package: Testrun -Version: 1.3.1 +Version: 1.4-a Architecture: amd64 Maintainer: Google Homepage: https://github.com/google/testrun diff --git a/modules/test/base/README.md b/modules/test/base/README.md index e7f05d80e..24a725607 100644 --- a/modules/test/base/README.md +++ b/modules/test/base/README.md @@ -14,6 +14,13 @@ The ```config/module_config.json``` provides the name and description of the mod Within the ```python/src``` directory, basic logging and environment variables are provided to the test module. +Within the ```usr/local/etc``` directory there is a local copy of the MAC OUI database. This is just in case a new copy is unable to be downloaded during the install or update process. + +## GRPC server +Within the python directory, GRPC client code is provided to allow test modules to programmatically modify the various network services provided by Testrun. + +These currently include obtaining information about and controlling the DHCP servers in failover configuration. + ## Tests covered No tests are run by this module \ No newline at end of file diff --git a/modules/test/base/bin/setup b/modules/test/base/bin/setup new file mode 100644 index 000000000..23c96c513 --- /dev/null +++ b/modules/test/base/bin/setup @@ -0,0 +1,71 @@ +#!/bin/bash + +# Copyright 2023 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Define the local mount point to store local files to +export OUTPUT_DIR="/runtime/output" + +# Directory where all binaries will be loaded +export BIN_DIR="/testrun/bin" + +# Default interface should be veth0 for all containers +export IFACE=veth0 + +# Create a local user that matches the same as the host +# to be used for correct file ownership for various logs +# HOST_USER mapped in via docker container environemnt variables +useradd $HOST_USER + +# Set permissions on the output files +chown -R $HOST_USER $OUTPUT_DIR + +# Enable IPv6 for all containers +sysctl net.ipv6.conf.all.disable_ipv6=0 +sysctl -p + +# Read in the config file +CONF_FILE="/testrun/conf/module_config.json" +CONF=`cat $CONF_FILE` + +if [[ -z $CONF ]] +then + echo "No config file present at $CONF_FILE. Exiting startup." + exit 1 +fi + +# Extract the necessary config parameters +export MODULE_NAME=$(echo "$CONF" | jq -r '.config.meta.name') +export NETWORK_REQUIRED=$(echo "$CONF" | jq -r '.config.network') +export GRPC=$(echo "$CONF" | jq -r '.config.grpc') + +# Validate the module name is present +if [[ -z "$MODULE_NAME" || "$MODULE_NAME" == "null" ]] +then + echo "No module name present in $CONF_FILE. Exiting startup." + exit 1 +fi + +# Setup the PYTHONPATH so all imports work as expected +echo "Setting up PYTHONPATH..." +export PYTHONPATH=$($BIN_DIR/setup_python_path) +echo "PYTHONPATH: $PYTHONPATH" + +echo "Configuring binary files..." +$BIN_DIR/setup_binaries $BIN_DIR + +# Build all gRPC files from the proto for use in +# gRPC clients for communications to network modules +echo "Building gRPC files from available proto files..." +$BIN_DIR/setup_grpc_clients \ No newline at end of file diff --git a/modules/test/base/bin/start b/modules/test/base/bin/start index 37902b868..d1f29989f 100755 --- a/modules/test/base/bin/start +++ b/modules/test/base/bin/start @@ -14,4 +14,15 @@ # See the License for the specific language governing permissions and # limitations under the License. -/testrun/bin/start_module \ No newline at end of file +# Allow one argument which is the unit test file to run +# instead of running the test module +UNIT_TEST_FILE=$1 + +source /testrun/bin/setup + +# Conditionally run start_module based on RUN +if [[ -z "$UNIT_TEST_FILE" ]];then + /testrun/bin/start_module +else + python3 $UNIT_TEST_FILE +fi diff --git a/modules/test/base/bin/start_module b/modules/test/base/bin/start_module index 0ee68fa6a..fb79cc018 100644 --- a/modules/test/base/bin/start_module +++ b/modules/test/base/bin/start_module @@ -1,102 +1,46 @@ -#!/bin/bash - -# Copyright 2023 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Define the local mount point to store local files to -OUTPUT_DIR="/runtime/output" - -# Directory where all binaries will be loaded -BIN_DIR="/testrun/bin" - -# Default interface should be veth0 for all containers -IFACE=veth0 - -# Create a local user that matches the same as the host -# to be used for correct file ownership for various logs -# HOST_USER mapped in via docker container environemnt variables -useradd $HOST_USER - -# Set permissions on the output files -chown -R $HOST_USER $OUTPUT_DIR - -# Enable IPv6 for all containers -sysctl net.ipv6.conf.all.disable_ipv6=0 -sysctl -p - -# Read in the config file -CONF_FILE="/testrun/conf/module_config.json" -CONF=`cat $CONF_FILE` - -if [[ -z $CONF ]] -then - echo "No config file present at $CONF_FILE. Exiting startup." - exit 1 -fi - -# Extract the necessary config parameters -MODULE_NAME=$(echo "$CONF" | jq -r '.config.meta.name') -NETWORK_REQUIRED=$(echo "$CONF" | jq -r '.config.network') -GRPC=$(echo "$CONF" | jq -r '.config.grpc') - -# Validate the module name is present -if [[ -z "$MODULE_NAME" || "$MODULE_NAME" == "null" ]] -then - echo "No module name present in $CONF_FILE. Exiting startup." - exit 1 -fi - -# Setup the PYTHONPATH so all imports work as expected -echo "Setting up PYTHONPATH..." -export PYTHONPATH=$($BIN_DIR/setup_python_path) -echo "PYTHONPATH: $PYTHONPATH" - -# Build all gRPC files from the proto for use in -# gRPC clients for communications to network modules -echo "Building gRPC files from available proto files..." -$BIN_DIR/setup_grpc_clients - -echo "Configuring binary files..." -$BIN_DIR/setup_binaries $BIN_DIR - -echo "Starting module $MODULE_NAME..." - -# Only start network services if the test container needs -# a network connection to run its tests -if [ $NETWORK_REQUIRED == "true" ];then - # Wait for interface to become ready - $BIN_DIR/wait_for_interface $IFACE - - # Start network capture - $BIN_DIR/capture $MODULE_NAME $IFACE -fi - -# Start the grpc server -if [[ ! -z $GRPC && ! $GRPC == "null" ]] -then - GRPC_PORT=$(echo "$GRPC" | jq -r '.port') - if [[ ! -z $GRPC_PORT && ! $GRPC_PORT == "null" ]] - then - echo "gRPC port resolved from config: $GRPC_PORT" - $BIN_DIR/start_grpc "-p $GRPC_PORT" - else - $BIN_DIR/start_grpc - fi -fi - -# Small pause to let all core services stabalize -sleep 3 - -# Start the test module +#!/bin/bash + +# Copyright 2023 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +echo "Starting module $MODULE_NAME..." + +# Only start network services if the test container needs +# a network connection to run its tests +if [ $NETWORK_REQUIRED == "true" ];then + # Wait for interface to become ready + $BIN_DIR/wait_for_interface $IFACE + + # Start network capture + $BIN_DIR/capture $MODULE_NAME $IFACE +fi + +# Start the grpc server +if [[ ! -z $GRPC && ! $GRPC == "null" ]] +then + GRPC_PORT=$(echo "$GRPC" | jq -r '.port') + if [[ ! -z $GRPC_PORT && ! $GRPC_PORT == "null" ]] + then + echo "gRPC port resolved from config: $GRPC_PORT" + $BIN_DIR/start_grpc "-p $GRPC_PORT" + else + $BIN_DIR/start_grpc + fi +fi + +# Small pause to let all core services stabalize +sleep 3 + +# Start the test module $BIN_DIR/start_test_module $MODULE_NAME $IFACE \ No newline at end of file diff --git a/modules/test/base/python/src/test_module.py b/modules/test/base/python/src/test_module.py index 00f74df82..deed0d978 100644 --- a/modules/test/base/python/src/test_module.py +++ b/modules/test/base/python/src/test_module.py @@ -17,6 +17,7 @@ import os import util from datetime import datetime +import traceback LOGGER = None RESULTS_DIR = '/runtime/output/' @@ -48,9 +49,9 @@ def __init__(self, def _add_logger(self, log_name, module_name, log_dir=None): global LOGGER - LOGGER = logger.get_logger(name=log_name, + LOGGER = logger.get_logger(name=log_name, # pylint: disable=E1123 log_file=module_name, - log_dir=log_dir) # pylint: disable=E1123 + log_dir=log_dir) def generate_module_report(self): pass @@ -113,11 +114,17 @@ def run_tests(self): except Exception as e: # pylint: disable=W0718 LOGGER.error(f'An error occurred whilst running {test["name"]}') LOGGER.error(e) + traceback.print_exc() else: LOGGER.info(f'Test {test["name"]} not implemented. Skipping') + test['result'] = 'Error' + test['description'] = 'This test could not be found' else: LOGGER.debug(f'Test {test["name"]} is disabled') + # To be added in v1.3.2 + # result = 'Disabled', 'This test is disabled and did not run' + if result is not None: # Compliant or non-compliant as a boolean only if isinstance(result, bool): @@ -182,7 +189,7 @@ def _write_results(self, results): def _get_device_ipv4(self): command = f"""/testrun/bin/get_ipv4_addr {self._ipv4_subnet} {self._device_mac.upper()}""" - text = util.run_command(command)[0] + text = util.run_command(command)[0] # pylint: disable=E1120 if text: return text.split('\n')[0] return None diff --git a/modules/test/conn/python/src/connection_module.py b/modules/test/conn/python/src/connection_module.py index 5e8b78ec3..88dd40393 100644 --- a/modules/test/conn/python/src/connection_module.py +++ b/modules/test/conn/python/src/connection_module.py @@ -15,7 +15,7 @@ import util import time import traceback -from scapy.all import rdpcap, DHCP, ARP, Ether, IPv6, ICMPv6ND_NS +from scapy.all import rdpcap, DHCP, ARP, Ether, ICMP, IPv6, ICMPv6ND_NS from test_module import TestModule from dhcp1.client import Client as DHCPClient1 from dhcp2.client import Client as DHCPClient2 @@ -39,7 +39,14 @@ class ConnectionModule(TestModule): """Connection Test module""" - def __init__(self, module, log_dir=None, conf_file=None, results_dir=None): + def __init__(self, + module, + log_dir=None, + conf_file=None, + results_dir=None, + startup_capture_file=STARTUP_CAPTURE_FILE, + monitor_capture_file=MONITOR_CAPTURE_FILE): + super().__init__(module_name=module, log_name=LOG_NAME, log_dir=log_dir, @@ -47,6 +54,8 @@ def __init__(self, module, log_dir=None, conf_file=None, results_dir=None): results_dir=results_dir) global LOGGER LOGGER = self._get_logger() + self.startup_capture_file = startup_capture_file + self.monitor_capture_file = monitor_capture_file self._port_stats = PortStatsUtil(logger=LOGGER) self.dhcp1_client = DHCPClient1() self.dhcp2_client = DHCPClient2() @@ -106,7 +115,8 @@ def _connection_switch_arp_inspection(self): no_arp = True # Read all the pcap files - packets = rdpcap(STARTUP_CAPTURE_FILE) + rdpcap(MONITOR_CAPTURE_FILE) + packets = rdpcap(self.startup_capture_file) + rdpcap( + self.monitor_capture_file) for packet in packets: # We are not interested in packets unless they are ARP packets @@ -123,12 +133,8 @@ def _connection_switch_arp_inspection(self): # Check MAC address matches IP address if (arp_packet.hwsrc == self._device_mac - and (arp_packet.psrc not in ( - self._device_ipv4_addr, - '0.0.0.0' - )) and not arp_packet.psrc.startswith( - '169.254' - )): + and (arp_packet.psrc not in (self._device_ipv4_addr, '0.0.0.0')) + and not arp_packet.psrc.startswith('169.254')): LOGGER.info(f'Bad ARP packet detected for MAC: {self._device_mac}') LOGGER.info(f'''ARP packet from IP {arp_packet.psrc} does not match {self._device_ipv4_addr}''') @@ -145,7 +151,8 @@ def _connection_switch_dhcp_snooping(self): disallowed_dhcp_types = [2, 4, 5, 6, 9, 10, 12, 13, 15, 17] # Read all the pcap files - packets = rdpcap(STARTUP_CAPTURE_FILE) + rdpcap(MONITOR_CAPTURE_FILE) + packets = rdpcap(self.startup_capture_file) + rdpcap( + self.monitor_capture_file) for packet in packets: # We are not interested in packets unless they are DHCP packets @@ -158,6 +165,11 @@ def _connection_switch_dhcp_snooping(self): dhcp_type = self._get_dhcp_type(packet) if dhcp_type in disallowed_dhcp_types: + + # Check if packet is responding with port unreachable + if ICMP in packet and packet[ICMP].type == 3: + continue + return False, 'Device has sent disallowed DHCP message' return True, 'Device does not act as a DHCP server' @@ -220,7 +232,8 @@ def _connection_single_ip(self): return result, 'No MAC address found.' # Read all the pcap files containing DHCP packet information - packets = rdpcap(STARTUP_CAPTURE_FILE) + rdpcap(MONITOR_CAPTURE_FILE) + packets = rdpcap(self.startup_capture_file) + rdpcap( + self.monitor_capture_file) # Extract MAC addresses from DHCP packets mac_addresses = set() @@ -394,8 +407,9 @@ def _connection_ipv6_slaac(self): return result def _has_slaac_addres(self): - packet_capture = (rdpcap(STARTUP_CAPTURE_FILE) + - rdpcap(MONITOR_CAPTURE_FILE) + rdpcap(DHCP_CAPTURE_FILE)) + packet_capture = (rdpcap(self.startup_capture_file) + + rdpcap(self.monitor_capture_file) + + rdpcap(DHCP_CAPTURE_FILE)) sends_ipv6 = False for packet_number, packet in enumerate(packet_capture, start=1): if IPv6 in packet and packet.src == self._device_mac: @@ -432,7 +446,7 @@ def _ping(self, host, ipv6=False): cmd += ' -6 ' if ipv6 else '' cmd += str(host) #cmd = 'ping -c 1 ' + str(host) - success = util.run_command(cmd, output=False) + success = util.run_command(cmd, output=False) # pylint: disable=E1120 return success def restore_failover_dhcp_server(self, subnet): diff --git a/modules/test/conn/python/src/dhcp_util.py b/modules/test/conn/python/src/dhcp_util.py index be5f0cac2..3654d0401 100644 --- a/modules/test/conn/python/src/dhcp_util.py +++ b/modules/test/conn/python/src/dhcp_util.py @@ -207,7 +207,7 @@ def is_lease_active(self, lease): def ping(self, host): cmd = 'ping -c 1 ' + str(host) - success = util.run_command(cmd, output=False) + success = util.run_command(cmd, output=False) # pylint: disable=E1120 return success def add_reserved_lease(self, diff --git a/modules/test/dns/README.md b/modules/test/dns/README.md index 13f0df5fd..79bce57f7 100644 --- a/modules/test/dns/README.md +++ b/modules/test/dns/README.md @@ -15,4 +15,5 @@ Within the ```python/src``` directory, the below tests are executed. | ID | Description | Expected behavior | Required result |---|---|---|---| | dns.network.hostname_resolution | Verifies that the device resolves hostnames | The device sends DNS requests | Required | -| dns.network.from_dhcp | Verifies that the device allows for a DNS server to be provided by the DHCP server | The device sends DNS requests to the DNS server provided by the DHCP server | Roadmap | \ No newline at end of file +| dns.network.from_dhcp | Verifies that the device allows for a DNS server to be provided by the DHCP server | The device sends DNS requests to the DNS server provided by the DHCP server | Roadmap | +| dns.mdns | Does the device has MDNS (or any kind of IP multicast) | Device may send MDNS requests | Informational | \ No newline at end of file diff --git a/modules/test/dns/conf/module_config.json b/modules/test/dns/conf/module_config.json index 13c9b3236..f048d5deb 100644 --- a/modules/test/dns/conf/module_config.json +++ b/modules/test/dns/conf/module_config.json @@ -31,6 +31,12 @@ "recommendations": [ "Install a DNS client that supports fetching DNS servers from DHCP options" ] + }, + { + "name": "dns.mdns", + "test_description": "Does the device has MDNS (or any kind of IP multicast)", + "expected_behavior": "Device may send MDNS requests", + "required_result": "Informational" } ] } diff --git a/modules/test/dns/python/src/dns_module.py b/modules/test/dns/python/src/dns_module.py index 607a026b5..c04e289d3 100644 --- a/modules/test/dns/python/src/dns_module.py +++ b/modules/test/dns/python/src/dns_module.py @@ -13,12 +13,13 @@ # limitations under the License. """DNS test module""" import subprocess -from scapy.all import rdpcap, DNS, IP +from scapy.all import rdpcap, DNS, IP, Ether from test_module import TestModule import os +from collections import Counter LOG_NAME = 'test_dns' -MODULE_REPORT_FILE_NAME='dns_report.html' +MODULE_REPORT_FILE_NAME = 'dns_report.html' DNS_SERVER_CAPTURE_FILE = '/runtime/network/dns.pcap' STARTUP_CAPTURE_FILE = '/runtime/device/startup.pcap' MONITOR_CAPTURE_FILE = '/runtime/device/monitor.pcap' @@ -41,9 +42,9 @@ def __init__(self, log_dir=log_dir, conf_file=conf_file, results_dir=results_dir) - self.dns_server_capture_file=dns_server_capture_file - self.startup_capture_file=startup_capture_file - self.monitor_capture_file=monitor_capture_file + self.dns_server_capture_file = dns_server_capture_file + self.startup_capture_file = startup_capture_file + self.monitor_capture_file = monitor_capture_file self._dns_server = '10.10.10.4' global LOGGER LOGGER = self._get_logger() @@ -55,18 +56,17 @@ def generate_module_report(self): html_content = '

DNS Module

' # Set the summary variables - local_requests = sum(1 for row in dns_table_data - if row['Destination'] == - self._dns_server and row['Type'] == 'Query') - external_requests = sum(1 for row in dns_table_data - if row['Destination'] != - self._dns_server and row['Type'] == 'Query') + local_requests = sum( + 1 for row in dns_table_data + if row['Destination'] == self._dns_server and row['Type'] == 'Query') + external_requests = sum( + 1 for row in dns_table_data + if row['Destination'] != self._dns_server and row['Type'] == 'Query') - total_requests = sum(1 for row in dns_table_data - if row['Type'] == 'Query') + total_requests = sum(1 for row in dns_table_data if row['Type'] == 'Query') total_responses = sum(1 for row in dns_table_data - if row['Type'] == 'Response') + if row['Type'] == 'Response') # Add summary table html_content += (f''' @@ -99,18 +99,26 @@ def generate_module_report(self): Destination Type URL + Count ''' - for row in dns_table_data: - table_content += (f''' - - {row['Source']} - {row['Destination']} - {row['Type']} - {row['Data']} - ''') + # Count unique combinations + counter = Counter( + (row['Source'], row['Destination'], row['Type'], row['Data']) + for row in dns_table_data) + + # Generate the HTML table with the count column + for (src, dst, typ, dat), count in counter.items(): + table_content += f''' + + {src} + {dst} + {typ} + {dat} + {count} + ''' table_content += ''' @@ -149,26 +157,28 @@ def extract_dns_data(self): # Iterate through DNS packets for packet in packets: if DNS in packet and packet.haslayer(IP): - source_ip = packet[IP].src - destination_ip = packet[IP].dst - dns_layer = packet[DNS] - - # 'qr' field indicates query (0) or response (1) - dns_type = 'Query' if dns_layer.qr == 0 else 'Response' - - # Check for the presence of DNS query name - if hasattr(dns_layer, 'qd') and dns_layer.qd is not None: + + # Check if either source or destination MAC matches the device + if self._device_mac in (packet[Ether].src, packet[Ether].dst): + source_ip = packet[IP].src + destination_ip = packet[IP].dst + dns_layer = packet[DNS] + # 'qr' field indicates query (0) or response (1) + dns_type = 'Query' if dns_layer.qr == 0 else 'Response' + + # Check for the presence of DNS query name + if hasattr(dns_layer, 'qd') and dns_layer.qd is not None: qname = dns_layer.qd.qname.decode() if dns_layer.qd.qname else 'N/A' - else: + else: qname = 'N/A' - dns_data.append({ - 'Timestamp': float(packet.time), # Timestamp of the DNS packet - 'Source': source_ip, - 'Destination': destination_ip, - 'Type': dns_type, - 'Data': qname[:-1] - }) + dns_data.append({ + 'Timestamp': float(packet.time), # Timestamp of the DNS packet + 'Source': source_ip, + 'Destination': destination_ip, + 'Type': dns_type, + 'Data': qname[:-1] + }) # Filter unique entries based on 'Timestamp' # DNS Server will duplicate messages caught by @@ -273,10 +283,10 @@ def _exec_tcpdump(self, tcpdump_filter, capture_file): LOGGER.debug('tcpdump command: ' + command) with subprocess.Popen(command, - universal_newlines=True, - shell=True, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE) as process: + universal_newlines=True, + shell=True, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) as process: text = str(process.stdout.read()).rstrip() LOGGER.debug('tcpdump response: ' + text) diff --git a/modules/test/ntp/python/src/ntp_module.py b/modules/test/ntp/python/src/ntp_module.py index 453c992e6..be27abbad 100644 --- a/modules/test/ntp/python/src/ntp_module.py +++ b/modules/test/ntp/python/src/ntp_module.py @@ -14,8 +14,8 @@ """NTP test module""" from test_module import TestModule from scapy.all import rdpcap, IP, IPv6, NTP, UDP, Ether -from datetime import datetime import os +from collections import defaultdict LOG_NAME = 'test_ntp' MODULE_REPORT_FILE_NAME = 'ntp_report.html' @@ -69,6 +69,33 @@ def generate_module_report(self): total_responses = sum(1 for row in ntp_table_data if row['Type'] == 'Server') + # Initialize a dictionary to store timestamps for each unique combination + timestamps = defaultdict(list) + + # Collect timestamps for each unique combination + for row in ntp_table_data: + # Add the timestamp to the corresponding combination + key = (row['Source'], row['Destination'], row['Type'], row['Version']) + timestamps[key].append(row['Timestamp']) + + # Calculate the average time between requests for each unique combination + average_time_between_requests = {} + + for key, times in timestamps.items(): + # Sort the timestamps + times.sort() + + # Calculate the time differences between consecutive timestamps + time_diffs = [t2 - t1 for t1, t2 in zip(times[:-1], times[1:])] + + # Calculate the average of the time differences + if time_diffs: + avg_diff = sum(time_diffs) / len(time_diffs) + else: + avg_diff = 0 # one timestamp, the average difference is 0 + + average_time_between_requests[key] = avg_diff + # Add summary table html_content += (f''' @@ -92,7 +119,6 @@ def generate_module_report(self): ''') if total_requests + total_responses > 0: - table_content = '''
@@ -101,37 +127,39 @@ def generate_module_report(self): - + + ''' - for row in ntp_table_data: - - # Timestamp of the NTP packet - dt_object = datetime.utcfromtimestamp(row['Timestamp']) - - # Extract milliseconds from the fractional part of the timestamp - milliseconds = int((row['Timestamp'] % 1) * 1000) + # Generate the HTML table with the count column + for (src, dst, typ, + version), avg_diff in average_time_between_requests.items(): + cnt = len(timestamps[(src, dst, typ, version)]) - # Format the datetime object with milliseconds - formatted_time = dt_object.strftime( - '%b %d, %Y %H:%M:%S.') + f'{milliseconds:03d}' + # Sync Average only applies to client requests + if 'Client' in typ: + # Convert avg_diff to seconds and format it + avg_diff_seconds = avg_diff + avg_formatted_time = f'{avg_diff_seconds:.3f} seconds' + else: + avg_formatted_time = 'N/A' - table_content += (f''' + table_content += f''' - - - - - - ''') + + + + + + + ''' table_content += '''
Destination Type VersionTimestampCountSync Request Average
{row['Source']}{row['Destination']}{row['Type']}{row['Version']}{formatted_time}
{src}{dst}{typ}{version}{cnt}{avg_formatted_time}
''' - html_content += table_content else: @@ -159,8 +187,8 @@ def extract_ntp_data(self): # Read the pcap files packets = (rdpcap(self.startup_capture_file) + - rdpcap(self.monitor_capture_file) + - rdpcap(self.ntp_server_capture_file)) + rdpcap(self.monitor_capture_file) + + rdpcap(self.ntp_server_capture_file)) # Iterate through NTP packets for packet in packets: @@ -171,6 +199,10 @@ def extract_ntp_data(self): # Local NTP server syncs to external servers so we need to filter only # for traffic to/from the device if self._device_mac in (source_mac, destination_mac): + + source_ip = None + dest_ip = None + if IP in packet: source_ip = packet[IP].src dest_ip = packet[IP].dst @@ -218,6 +250,9 @@ def _ntp_network_ntp_support(self): for packet in packet_capture: if NTP in packet and packet.src == self._device_mac: + + dest_ip = None + if IP in packet: dest_ip = packet[IP].dst elif IPv6 in packet: @@ -229,16 +264,17 @@ def _ntp_network_ntp_support(self): device_sends_ntp3 = True LOGGER.info(f'Device sent NTPv3 request to {dest_ip}') - if not (device_sends_ntp3 or device_sends_ntp4): - result = False, 'Device has not sent any NTP requests' - elif device_sends_ntp3 and device_sends_ntp4: + result = False, 'Device has not sent any NTP requests' + + if device_sends_ntp3 and device_sends_ntp4: result = False, ('Device sent NTPv3 and NTPv4 packets. ' + - 'NTPv3 is not allowed.') + 'NTPv3 is not allowed') elif device_sends_ntp3: result = False, ('Device sent NTPv3 packets. ' - 'NTPv3 is not allowed.') + 'NTPv3 is not allowed') elif device_sends_ntp4: - result = True, 'Device sent NTPv4 packets.' + result = True, 'Device sent NTPv4 packets' + LOGGER.info(result[1]) return result @@ -255,6 +291,7 @@ def _ntp_network_ntp_dhcp(self): for packet in packet_capture: if NTP in packet and packet.src == self._device_mac: device_sends_ntp = True + dest_ip = None if IP in packet: dest_ip = packet[IP].dst elif IPv6 in packet: @@ -266,17 +303,17 @@ def _ntp_network_ntp_dhcp(self): LOGGER.info('Device sent NTP request to non-DHCP provided NTP server') ntp_to_remote = True + result = 'Feature Not Detected', 'Device has not sent any NTP requests' + if device_sends_ntp: if ntp_to_local and ntp_to_remote: result = False, ('Device sent NTP request to DHCP provided ' + 'server and non-DHCP provided server') elif ntp_to_remote: result = ('Feature Not Detected', - 'Device sent NTP request to non-DHCP provided server') + 'Device sent NTP request to non-DHCP provided server') elif ntp_to_local: result = True, 'Device sent NTP request to DHCP provided server' - else: - result = 'Feature Not Detected', 'Device has not sent any NTP requests' LOGGER.info(result[1]) return result diff --git a/modules/test/protocol/bin/start_test_module b/modules/test/protocol/bin/start_test_module index a0754836c..d85ae7d6b 100644 --- a/modules/test/protocol/bin/start_test_module +++ b/modules/test/protocol/bin/start_test_module @@ -1,53 +1,53 @@ -#!/bin/bash - -# Copyright 2023 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Setup and start the connection test module - -# Define where the python source files are located -PYTHON_SRC_DIR=/testrun/python/src - -# Fetch module name -MODULE_NAME=$1 - -# Default interface should be veth0 for all containers -DEFAULT_IFACE=veth0 - -# Allow a user to define an interface by passing it into this script -DEFINED_IFACE=$2 - -# Select which interace to use -if [[ -z $DEFINED_IFACE || "$DEFINED_IFACE" == "null" ]] -then - echo "No interface defined, defaulting to veth0" - INTF=$DEFAULT_IFACE -else - INTF=$DEFINED_IFACE -fi - -# Create and set permissions on the log files -LOG_FILE=/runtime/output/$MODULE_NAME.log -RESULT_FILE=/runtime/output/$MODULE_NAME-result.json -touch $LOG_FILE -touch $RESULT_FILE -chown $HOST_USER $LOG_FILE -chown $HOST_USER $RESULT_FILE - -# Run the python script that will execute the tests for this module -# -u flag allows python print statements -# to be logged by docker by running unbuffered -python3 -u $PYTHON_SRC_DIR/run.py "-m $MODULE_NAME" - +#!/bin/bash + +# Copyright 2023 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Setup and start the connection test module + +# Define where the python source files are located +PYTHON_SRC_DIR=/testrun/python/src + +# Fetch module name +MODULE_NAME=$1 + +# Default interface should be veth0 for all containers +DEFAULT_IFACE=veth0 + +# Allow a user to define an interface by passing it into this script +DEFINED_IFACE=$2 + +# Select which interace to use +if [[ -z $DEFINED_IFACE || "$DEFINED_IFACE" == "null" ]] +then + echo "No interface defined, defaulting to veth0" + INTF=$DEFAULT_IFACE +else + INTF=$DEFINED_IFACE +fi + +# Create and set permissions on the log files +LOG_FILE=/runtime/output/$MODULE_NAME.log +RESULT_FILE=/runtime/output/$MODULE_NAME-result.json +touch $LOG_FILE +touch $RESULT_FILE +chown $HOST_USER $LOG_FILE +chown $HOST_USER $RESULT_FILE + +# Run the python script that will execute the tests for this module +# -u flag allows python print statements +# to be logged by docker by running unbuffered +python3 -u $PYTHON_SRC_DIR/run.py "-m $MODULE_NAME" + echo Module has finished \ No newline at end of file diff --git a/modules/test/protocol/python/requirements.txt b/modules/test/protocol/python/requirements.txt index 57917735d..5b54a724d 100644 --- a/modules/test/protocol/python/requirements.txt +++ b/modules/test/protocol/python/requirements.txt @@ -1,7 +1,7 @@ # Required for BACnet protocol tests -netifaces -BAC0 -pytz +netifaces==0.11.0 +BAC0==23.7.3 +pytz==2024.1 # Required for Modbus protocol tests -pymodbus \ No newline at end of file +pymodbus==3.7.0 \ No newline at end of file diff --git a/modules/test/protocol/python/src/protocol_modbus.py b/modules/test/protocol/python/src/protocol_modbus.py index 925e9517a..a722f928e 100644 --- a/modules/test/protocol/python/src/protocol_modbus.py +++ b/modules/test/protocol/python/src/protocol_modbus.py @@ -103,7 +103,7 @@ def __init__(self, log, device_ip, config): self._discrete_input_enabled = False # Initialize the modbus client - self.client = ModbusClient(device_ip, self._port) + self.client = ModbusClient(host=device_ip, port=self._port) # Connections created from this method are simple socket connections # and aren't indicative of valid modbus diff --git a/modules/test/services/python/src/services_module.py b/modules/test/services/python/src/services_module.py index bfa232c87..b14c74234 100644 --- a/modules/test/services/python/src/services_module.py +++ b/modules/test/services/python/src/services_module.py @@ -200,7 +200,7 @@ def _process_port_results(self): def _scan_tcp_ports(self): max_port = 1000 LOGGER.info('Running nmap TCP port scan') - nmap_results = util.run_command( + nmap_results = util.run_command( # pylint: disable=E1120 f'''nmap --open -sT -sV -Pn -v -p 1-{max_port} --version-intensity 7 -T4 -oX - {self._ipv4_addr}''')[0] @@ -228,7 +228,7 @@ def _scan_udp_ports(self): port_list = ','.join(ports) LOGGER.info('Running nmap UDP port scan') LOGGER.debug('UDP ports: ' + str(port_list)) - nmap_results = util.run_command( + nmap_results = util.run_command( # pylint: disable=E1120 f'nmap -sU -sV -p {port_list} -oX - {self._ipv4_addr}')[0] LOGGER.info('UDP port scan complete') nmap_results_json = self._nmap_results_to_json(nmap_results) diff --git a/modules/test/tls/bin/get_tls_client_connections.sh b/modules/test/tls/bin/get_tls_client_connections.sh index e2e6da91b..7335cac80 100755 --- a/modules/test/tls/bin/get_tls_client_connections.sh +++ b/modules/test/tls/bin/get_tls_client_connections.sh @@ -1,32 +1,32 @@ -#!/bin/bash - -# Copyright 2023 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -CAPTURE_FILE="$1" -SRC_IP="$2" -PROTOCOL=$3 - -TSHARK_OUTPUT="-T json -e ip.src -e tcp.dstport -e ip.dst" -TSHARK_FILTER="ip.src == $SRC_IP and tls" - -# Add a protocol filter if defined -if [ -n "$PROTOCOL" ];then - TSHARK_FILTER="$TSHARK_FILTER and $PROTOCOL" -fi - -response=$(tshark -r "$CAPTURE_FILE" $TSHARK_OUTPUT $TSHARK_FILTER) - -echo "$response" +#!/bin/bash + +# Copyright 2023 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +CAPTURE_FILE="$1" +SRC_IP="$2" +PROTOCOL=$3 + +TSHARK_OUTPUT="-T json -e ip.src -e tcp.dstport -e ip.dst" +TSHARK_FILTER="ip.src == $SRC_IP and tls" + +# Add a protocol filter if defined +if [ -n "$PROTOCOL" ];then + TSHARK_FILTER="$TSHARK_FILTER and $PROTOCOL" +fi + +response=$(tshark -r "$CAPTURE_FILE" $TSHARK_OUTPUT $TSHARK_FILTER) + +echo "$response" \ No newline at end of file diff --git a/modules/test/tls/conf/module_config.json b/modules/test/tls/conf/module_config.json index cd77f8299..c74bfd667 100644 --- a/modules/test/tls/conf/module_config.json +++ b/modules/test/tls/conf/module_config.json @@ -32,6 +32,27 @@ "Disable connections to unsecure services", "Ensure any URLs connected to are secure (https)" ] + }, + { + "name": "security.tls.v1_3_server", + "test_description": "Check the device web server TLS 1.3 & certificate is valid", + "expected_behavior": "TLS 1.3 certificate is issued to the web browser client when accessed", + "required_result": "Informational", + "recommendations": [ + "Enable TLS 1.3 support in the web server configuration", + "Disable TLS 1.0 and 1.1", + "Sign the certificate used by the web server" + ] + }, + { + "name": "security.tls.v1_3_client", + "test_description": "Device uses TLS with connection to an external service on port 443 (or any other port which could be running the webserver-HTTPS)", + "expected_behavior": "The packet indicates a TLS connection with at least TLS 1.3", + "required_result": "Informational", + "recommendations": [ + "Disable connections to unsecure services", + "Ensure any URLs connected to are secure (https)" + ] } ] } diff --git a/modules/test/tls/python/requirements-test.txt b/modules/test/tls/python/requirements-test.txt new file mode 100644 index 000000000..93b351f44 --- /dev/null +++ b/modules/test/tls/python/requirements-test.txt @@ -0,0 +1 @@ +scapy \ No newline at end of file diff --git a/modules/test/tls/python/requirements.txt b/modules/test/tls/python/requirements.txt index 7624a2c68..846a224f3 100644 --- a/modules/test/tls/python/requirements.txt +++ b/modules/test/tls/python/requirements.txt @@ -1,5 +1,5 @@ -cryptography==42.0.4 # Do not upgrade until TLS module can be fixed to account for removed x509 property in version 39 -pyOpenSSL==24.1.0 +cryptography==38.0.0 # Do not upgrade until TLS module can be fixed to account for removed x509 property in version 39 +pyOpenSSL==23.0.0 lxml==5.1.0 # Requirement of pyshark but if upgraded automatically above 5.1 will cause a python crash pyshark==0.6 -requests==2.32.0 +requests==2.32.3 diff --git a/modules/test/tls/python/src/tls_util.py b/modules/test/tls/python/src/tls_util.py index d8c1d7a16..0364479c6 100644 --- a/modules/test/tls/python/src/tls_util.py +++ b/modules/test/tls/python/src/tls_util.py @@ -372,6 +372,8 @@ def validate_tls_server(self, host, tls_version): public_key = self.get_public_key(public_cert) if public_key: key_valid = self.verify_public_key(public_key) + else: + key_valid = [0] sig_valid = self.validate_signature(host) @@ -527,7 +529,7 @@ def process_hello_packets(self, LOGGER.info('Checking client ciphers: ' + str(packet)) if packet['cipher_support']['ecdh'] and packet['cipher_support'][ 'ecdsa']: - LOGGER.info('Valid ciphers detected') + LOGGER.info('Required ciphers detected') client_hello_results['valid'].append(packet) # If a previous hello packet to the same destination failed, # we can now remove it as it has passed on a different attempt @@ -537,7 +539,7 @@ def process_hello_packets(self, if packet['dst_ip'] in str(invalid_packet): client_hello_results['invalid'].remove(invalid_packet) else: - LOGGER.info('Invalid ciphers detected') + LOGGER.info('Required ciphers not detected') if packet['dst_ip'] not in allowed_protocol_client_ips: if packet['dst_ip'] not in str(client_hello_results['invalid']): client_hello_results['invalid'].append(packet) diff --git a/modules/test/tls/tls.Dockerfile b/modules/test/tls/tls.Dockerfile index cedf9531b..987ede591 100644 --- a/modules/test/tls/tls.Dockerfile +++ b/modules/test/tls/tls.Dockerfile @@ -31,14 +31,20 @@ COPY $MODULE_DIR/conf /testrun/conf # Copy over all binary files COPY $MODULE_DIR/bin /testrun/bin +# Remove incorrect line endings +RUN dos2unix /testrun/bin/* + +# Make sure all the bin files are executable +RUN chmod u+x /testrun/bin/* + # Copy over all python files COPY $MODULE_DIR/python /testrun/python -#Install all python requirements for the module +# Install all python requirements for the module RUN pip3 install -r /testrun/python/requirements.txt +# Install all python requirements for the modules unit test +RUN pip3 install -r /testrun/python/requirements-test.txt + # Create a directory inside the container to store the root certificates RUN mkdir -p /testrun/root_certs - - - diff --git a/modules/ui/angular.json b/modules/ui/angular.json index d72fee51f..0bf42377f 100644 --- a/modules/ui/angular.json +++ b/modules/ui/angular.json @@ -25,14 +25,15 @@ "inlineStyleLanguage": "scss", "assets": ["src/favicon.ico", "src/assets"], "styles": ["src/styles.scss"], - "scripts": [] + "scripts": [], + "allowedCommonJsDependencies": ["mqtt-browser"] }, "configurations": { "production": { "budgets": [ { "type": "initial", - "maximumWarning": "1000kb", + "maximumWarning": "1500kb", "maximumError": "3000kb" }, { @@ -93,6 +94,7 @@ } }, "cli": { - "schematicCollections": ["@angular-eslint/schematics"] + "schematicCollections": ["@angular-eslint/schematics"], + "analytics": false } } diff --git a/testing/unit/build.sh b/modules/ui/build.Dockerfile similarity index 77% rename from testing/unit/build.sh rename to modules/ui/build.Dockerfile index db84e0299..180ad9747 100644 --- a/testing/unit/build.sh +++ b/modules/ui/build.Dockerfile @@ -1,5 +1,3 @@ -#!/bin/bash -e - # Copyright 2023 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -14,4 +12,9 @@ # See the License for the specific language governing permissions and # limitations under the License. -sudo docker build -f testing/unit/unit_test.Dockerfile -t testrun/unit-test . \ No newline at end of file +# Image name: testrun/build-ui +FROM node@sha256:ffebb4405810c92d267a764b21975fb2d96772e41877248a37bf3abaa0d3b590 as build + +# Set the working directory +WORKDIR /modules/ui + diff --git a/modules/ui/package-lock.json b/modules/ui/package-lock.json index e6903631a..637dc47e4 100644 --- a/modules/ui/package-lock.json +++ b/modules/ui/package-lock.json @@ -22,6 +22,7 @@ "@ngrx/effects": "^17.1.1", "@ngrx/store": "^17.0.1", "ngx-mask": "^16.4.2", + "ngx-mqtt": "^17.0.0", "rxjs": "~7.8.0", "tslib": "^2.6.2", "zone.js": "^0.14.4" @@ -6531,14 +6532,12 @@ "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" }, "node_modules/base64-js": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", - "dev": true, "funding": [ { "type": "github", @@ -6594,7 +6593,6 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", - "dev": true, "dependencies": { "buffer": "^5.5.0", "inherits": "^2.0.4", @@ -6713,7 +6711,6 @@ "version": "5.7.1", "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", - "dev": true, "funding": [ { "type": "github", @@ -6736,8 +6733,7 @@ "node_modules/buffer-from": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", - "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", - "dev": true + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==" }, "node_modules/bytes": { "version": "3.1.2", @@ -7089,6 +7085,15 @@ "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", "dev": true }, + "node_modules/commist": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/commist/-/commist-1.1.0.tgz", + "integrity": "sha512-rraC8NXWOEjhADbZe9QBNzLAN5Q3fsTPQtBV+fEVj6xKIgDgNiEVE6ZNfHpZOqfQ21YUzfVNUXLOEZquYvQPPg==", + "dependencies": { + "leven": "^2.1.0", + "minimist": "^1.1.0" + } + }, "node_modules/common-path-prefix": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/common-path-prefix/-/common-path-prefix-3.0.0.tgz", @@ -7167,8 +7172,21 @@ "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" + }, + "node_modules/concat-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-2.0.0.tgz", + "integrity": "sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A==", + "engines": [ + "node >= 6.0" + ], + "dependencies": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.0.2", + "typedarray": "^0.0.6" + } }, "node_modules/connect": { "version": "3.7.0", @@ -7599,7 +7617,6 @@ "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dev": true, "dependencies": { "ms": "2.1.2" }, @@ -7862,6 +7879,17 @@ "integrity": "sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==", "dev": true }, + "node_modules/duplexify": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-4.1.3.tgz", + "integrity": "sha512-M3BmBhwJRZsSx38lZyhE53Csddgzl5R7xGJNk7CVddZD6CcmwMCH8J+7AprIrQKH7TonKxaCjcv27Qmf+sQ+oA==", + "dependencies": { + "end-of-stream": "^1.4.1", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1", + "stream-shift": "^1.0.2" + } + }, "node_modules/eastasianwidth": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", @@ -7946,7 +7974,6 @@ "version": "1.4.4", "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", - "dev": true, "dependencies": { "once": "^1.4.0" } @@ -9156,8 +9183,7 @@ "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "dev": true + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" }, "node_modules/fsevents": { "version": "2.3.3", @@ -9244,7 +9270,6 @@ "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "dev": true, "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -9282,7 +9307,6 @@ "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -9292,7 +9316,6 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, "dependencies": { "brace-expansion": "^1.1.7" }, @@ -9437,6 +9460,15 @@ "node": ">= 0.4" } }, + "node_modules/help-me": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/help-me/-/help-me-3.0.0.tgz", + "integrity": "sha512-hx73jClhyk910sidBB7ERlnhMlFsJJIBqSVMFDwPN8o2v9nmp5KgLq1Xz1Bf1fCMMZ6mPrX159iG0VLy/fPMtQ==", + "dependencies": { + "glob": "^7.1.6", + "readable-stream": "^3.6.0" + } + }, "node_modules/hosted-git-info": { "version": "7.0.2", "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-7.0.2.tgz", @@ -9685,7 +9717,6 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", - "dev": true, "funding": [ { "type": "github", @@ -9788,7 +9819,6 @@ "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "dev": true, "dependencies": { "once": "^1.3.0", "wrappy": "1" @@ -9797,8 +9827,7 @@ "node_modules/inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, "node_modules/ini": { "version": "4.1.1", @@ -10462,6 +10491,15 @@ "jiti": "bin/jiti.js" } }, + "node_modules/js-sdsl": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.3.0.tgz", + "integrity": "sha512-mifzlm2+5nZ+lEcLJMoBK0/IH/bDg8XnJfd/Wq6IP+xoCjLZsTOnV2QpxlVbX9bMnkl5PdEjNtBJ9Cj1NjifhQ==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/js-sdsl" + } + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -10947,6 +10985,14 @@ "node": ">=0.10.0" } }, + "node_modules/leven": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/leven/-/leven-2.1.0.tgz", + "integrity": "sha512-nvVPLpIHUxCUoRLrFqTgSxXJ614d8AgQoWl7zPe/2VadE8+1dpU3LBhowRuBAcuwruWtOdD8oYC9jDNJjXDPyA==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/levn": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", @@ -11445,7 +11491,6 @@ "version": "1.2.8", "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", - "dev": true, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -11649,6 +11694,92 @@ "mkdirp": "bin/cmd.js" } }, + "node_modules/mqtt": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/mqtt/-/mqtt-4.3.7.tgz", + "integrity": "sha512-ew3qwG/TJRorTz47eW46vZ5oBw5MEYbQZVaEji44j5lAUSQSqIEoul7Kua/BatBW0H0kKQcC9kwUHa1qzaWHSw==", + "dependencies": { + "commist": "^1.0.0", + "concat-stream": "^2.0.0", + "debug": "^4.1.1", + "duplexify": "^4.1.1", + "help-me": "^3.0.0", + "inherits": "^2.0.3", + "lru-cache": "^6.0.0", + "minimist": "^1.2.5", + "mqtt-packet": "^6.8.0", + "number-allocator": "^1.0.9", + "pump": "^3.0.0", + "readable-stream": "^3.6.0", + "reinterval": "^1.1.0", + "rfdc": "^1.3.0", + "split2": "^3.1.0", + "ws": "^7.5.5", + "xtend": "^4.0.2" + }, + "bin": { + "mqtt": "bin/mqtt.js", + "mqtt_pub": "bin/pub.js", + "mqtt_sub": "bin/sub.js" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/mqtt-browser": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/mqtt-browser/-/mqtt-browser-4.3.7.tgz", + "integrity": "sha512-4pxHxa3avIILr2CXhTKlArVpATqfyTu4zr5u2PoUwzgw0GDr5dpzZ0pmPgZyOoQBVgrVDEboCzb/b1Q0yWOm7g==", + "dependencies": { + "mqtt": "4.3.7" + } + }, + "node_modules/mqtt-packet": { + "version": "6.10.0", + "resolved": "https://registry.npmjs.org/mqtt-packet/-/mqtt-packet-6.10.0.tgz", + "integrity": "sha512-ja8+mFKIHdB1Tpl6vac+sktqy3gA8t9Mduom1BA75cI+R9AHnZOiaBQwpGiWnaVJLDGRdNhQmFaAqd7tkKSMGA==", + "dependencies": { + "bl": "^4.0.2", + "debug": "^4.1.1", + "process-nextick-args": "^2.0.1" + } + }, + "node_modules/mqtt/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/mqtt/node_modules/ws": { + "version": "7.5.10", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz", + "integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==", + "engines": { + "node": ">=8.3.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/mqtt/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + }, "node_modules/mrmime": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.0.tgz", @@ -11661,8 +11792,7 @@ "node_modules/ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, "node_modules/multicast-dns": { "version": "7.2.5", @@ -11768,6 +11898,20 @@ "@angular/forms": ">=14.0.0" } }, + "node_modules/ngx-mqtt": { + "version": "17.0.0", + "resolved": "https://registry.npmjs.org/ngx-mqtt/-/ngx-mqtt-17.0.0.tgz", + "integrity": "sha512-54wVMyDOZkpTZEs0rTMWPP1Yz+6q3rRnHzIBnpqnBkDcyMfNrti45C7ijwnEIaPDzQHMOqVrDgh/6C4ocPPLJQ==", + "dependencies": { + "mqtt-browser": "4.3.7", + "mqtt-packet": "^6.10.0", + "tslib": "^2.3.0" + }, + "peerDependencies": { + "@angular/common": ">=14", + "@angular/core": ">=14" + } + }, "node_modules/nice-napi": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/nice-napi/-/nice-napi-1.0.2.tgz", @@ -12068,6 +12212,15 @@ "url": "https://github.com/fb55/nth-check?sponsor=1" } }, + "node_modules/number-allocator": { + "version": "1.0.14", + "resolved": "https://registry.npmjs.org/number-allocator/-/number-allocator-1.0.14.tgz", + "integrity": "sha512-OrL44UTVAvkKdOdRQZIJpLkAdjXGTRda052sN4sO77bKEzYYqWKMBjQvrJFzqygI99gL6Z4u2xctPW1tB8ErvA==", + "dependencies": { + "debug": "^4.3.1", + "js-sdsl": "4.3.0" + } + }, "node_modules/nx": { "version": "17.2.8", "resolved": "https://registry.npmjs.org/nx/-/nx-17.2.8.tgz", @@ -12354,7 +12507,6 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dev": true, "dependencies": { "wrappy": "1" } @@ -12726,7 +12878,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -13172,8 +13323,7 @@ "node_modules/process-nextick-args": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", - "dev": true + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" }, "node_modules/promise-inflight": { "version": "1.0.1", @@ -13229,6 +13379,15 @@ "dev": true, "optional": true }, + "node_modules/pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", @@ -13375,7 +13534,6 @@ "version": "3.6.2", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", - "dev": true, "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", @@ -13492,6 +13650,11 @@ "jsesc": "bin/jsesc" } }, + "node_modules/reinterval": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reinterval/-/reinterval-1.1.0.tgz", + "integrity": "sha512-QIRet3SYrGp0HUHO88jVskiG6seqUGC5iAG7AwI/BV4ypGcuqk9Du6YQBUOUqm9c8pw1eyLoIaONifRua1lsEQ==" + }, "node_modules/require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", @@ -13622,8 +13785,7 @@ "node_modules/rfdc": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.3.1.tgz", - "integrity": "sha512-r5a3l5HzYlIC68TpmYKlxWjmOP6wiPJ1vWv2HeLhNsRZMrCkxeqxiHlQ21oXmQ4F3SiryXBHhAD7JZqvOJjFmg==", - "dev": true + "integrity": "sha512-r5a3l5HzYlIC68TpmYKlxWjmOP6wiPJ1vWv2HeLhNsRZMrCkxeqxiHlQ21oXmQ4F3SiryXBHhAD7JZqvOJjFmg==" }, "node_modules/rimraf": { "version": "3.0.2", @@ -13719,7 +13881,6 @@ "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "dev": true, "funding": [ { "type": "github", @@ -14376,6 +14537,14 @@ "wbuf": "^1.7.3" } }, + "node_modules/split2": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/split2/-/split2-3.2.2.tgz", + "integrity": "sha512-9NThjpgZnifTkJpzTZ7Eue85S49QwpNhZTq6GRJwObb6jnLFNGB7Qm73V5HewTROPyxD0C29xqmaI68bQtV+hg==", + "dependencies": { + "readable-stream": "^3.0.0" + } + }, "node_modules/sprintf-js": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", @@ -14403,6 +14572,11 @@ "node": ">= 0.6" } }, + "node_modules/stream-shift": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.3.tgz", + "integrity": "sha512-76ORR0DO1o1hlKwTbi/DM3EXWGf3ZJYO8cXX5RJwnul2DEg2oyoZyjLNoQM8WsvZiFKCRfC1O0J7iCvie3RZmQ==" + }, "node_modules/streamroller": { "version": "3.1.5", "resolved": "https://registry.npmjs.org/streamroller/-/streamroller-3.1.5.tgz", @@ -14453,7 +14627,6 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", - "dev": true, "dependencies": { "safe-buffer": "~5.2.0" } @@ -14995,6 +15168,11 @@ "integrity": "sha512-KNNZtayBCtmnNmbo5mG47p1XsCyrx6iVqomjcZnec/1Y5GGARaxPs6r49RnSPeUP3YjNYiU9sQHAtY4BBvnZwg==", "dev": true }, + "node_modules/typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==" + }, "node_modules/typescript": { "version": "5.2.2", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz", @@ -15170,8 +15348,7 @@ "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", - "dev": true + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" }, "node_modules/utils-merge": { "version": "1.0.1", @@ -16192,8 +16369,7 @@ "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "dev": true + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" }, "node_modules/ws": { "version": "8.17.1", @@ -16216,6 +16392,14 @@ } } }, + "node_modules/xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "engines": { + "node": ">=0.4" + } + }, "node_modules/y18n": { "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", diff --git a/modules/ui/package.json b/modules/ui/package.json index 7f83fc5f7..aceb9c389 100644 --- a/modules/ui/package.json +++ b/modules/ui/package.json @@ -31,6 +31,7 @@ "@ngrx/effects": "^17.1.1", "@ngrx/store": "^17.0.1", "ngx-mask": "^16.4.2", + "ngx-mqtt": "^17.0.0", "rxjs": "~7.8.0", "tslib": "^2.6.2", "zone.js": "^0.14.4" diff --git a/modules/ui/src/app/app.component.html b/modules/ui/src/app/app.component.html index 38c210251..b1341a58d 100644 --- a/modules/ui/src/app/app.component.html +++ b/modules/ui/src/app/app.component.html @@ -116,6 +116,14 @@

Testrun

"> tune + + + @@ -127,7 +135,8 @@

Testrun

error.devicePortMissed && error.internetPortMissed; else onePortMissed "> - No ports are detected. Please define a valid ones using + No ports detected. Please connect and configure network and device + connections in the Selected port is missing! Please define a valid one using @@ -187,7 +196,8 @@

Testrun

vm.hasConnectionSettings === true && vm.hasDevices && (!vm.systemStatus || vm.systemStatus === StatusOfTestrun.Idle) && - vm.isStatusLoaded === true + vm.isStatusLoaded === true && + !vm.reports.length "> Step 3: Once device is created, you are able to Testrun vm.systemStatus === StatusOfTestrun.InProgress && isRiskAssessmentRoute === false "> - Congratulations, the device is under test now! Do not forget to fill + The device is now being tested. Why not take the time to complete the + device Testrun role="link" class="message-link" >Risk Assessment questionnaire. It is required to complete verification process. + >? @@ -265,6 +276,7 @@

Testrun

mat-button routerLink="{{ route }}" routerLinkActive="app-sidebar-button-active" + [matTooltip]="label" (keydown.enter)="onNavigationClick()"> {{ icon }} diff --git a/modules/ui/src/app/app.component.scss b/modules/ui/src/app/app.component.scss index 20e81c53e..9639f5cc0 100644 --- a/modules/ui/src/app/app.component.scss +++ b/modules/ui/src/app/app.component.scss @@ -208,3 +208,9 @@ app-version { display: flex; justify-content: center; } + +.separator { + width: 1px; + height: 28px; + background-color: $light-grey; +} diff --git a/modules/ui/src/app/app.component.spec.ts b/modules/ui/src/app/app.component.spec.ts index 81e93b4b6..df531c8b7 100644 --- a/modules/ui/src/app/app.component.spec.ts +++ b/modules/ui/src/app/app.component.spec.ts @@ -56,9 +56,11 @@ import { selectHasDevices, selectHasRiskProfiles, selectInterfaces, + selectInternetConnection, selectIsOpenStartTestrun, selectIsOpenWaitSnackBar, selectMenuOpened, + selectReports, selectStatus, selectSystemStatus, } from './store/selectors'; @@ -67,6 +69,11 @@ import { CertificatesComponent } from './pages/certificates/certificates.compone import { of } from 'rxjs'; import { WINDOW } from './providers/window.provider'; import { LiveAnnouncer } from '@angular/cdk/a11y'; +import { HISTORY } from './mocks/reports.mock'; +import { TestRunMqttService } from './services/test-run-mqtt.service'; +import { MOCK_ADAPTERS } from './mocks/settings.mock'; +import { WifiComponent } from './components/wifi/wifi.component'; +import { MatTooltipModule } from '@angular/material/tooltip'; const windowMock = { location: { @@ -84,6 +91,7 @@ describe('AppComponent', () => { let focusNavigation = true; let mockFocusManagerService: SpyObj; let mockLiveAnnouncer: SpyObj; + let mockMqttService: SpyObj; const enterKeyEvent = new KeyboardEvent('keydown', { key: 'Enter', @@ -109,6 +117,7 @@ describe('AppComponent', () => { 'testrunInProgress', 'fetchProfiles', 'fetchCertificates', + 'getHistory', ]); mockService.fetchCertificates.and.returnValue(of([])); @@ -116,6 +125,7 @@ describe('AppComponent', () => { 'focusFirstElementInContainer', ]); mockLiveAnnouncer = jasmine.createSpyObj('mockLiveAnnouncer', ['announce']); + mockMqttService = jasmine.createSpyObj(['getNetworkAdapters']); TestBed.configureTestingModule({ imports: [ @@ -131,10 +141,13 @@ describe('AppComponent', () => { CalloutComponent, MatIconTestingModule, CertificatesComponent, + WifiComponent, + MatTooltipModule, ], providers: [ { provide: TestRunService, useValue: mockService }, { provide: LiveAnnouncer, useValue: mockLiveAnnouncer }, + { provide: TestRunMqttService, useValue: mockMqttService }, { provide: State, useValue: { @@ -151,6 +164,7 @@ describe('AppComponent', () => { selectors: [ { selector: selectInterfaces, value: {} }, { selector: selectHasConnectionSettings, value: true }, + { selector: selectInternetConnection, value: true }, { selector: selectError, value: null }, { selector: selectMenuOpened, value: false }, { selector: selectHasDevices, value: false }, @@ -159,6 +173,7 @@ describe('AppComponent', () => { { selector: selectSystemStatus, value: null }, { selector: selectIsOpenStartTestrun, value: false }, { selector: selectIsOpenWaitSnackBar, value: false }, + { selector: selectReports, value: [] }, ], }), { provide: FocusManagerService, useValue: mockFocusManagerService }, @@ -173,6 +188,7 @@ describe('AppComponent', () => { ], }); + mockMqttService.getNetworkAdapters.and.returnValue(of(MOCK_ADAPTERS)); store = TestBed.inject(MockStore); fixture = TestBed.createComponent(AppComponent); component = fixture.componentInstance; @@ -429,6 +445,13 @@ describe('AppComponent', () => { expect(version).toBeTruthy(); }); + it('should internet icon', () => { + fixture.detectChanges(); + const internet = compiled.querySelector('app-wifi'); + + expect(internet).toBeTruthy(); + }); + describe('Callout component visibility', () => { describe('with no connection settings', () => { beforeEach(() => { @@ -486,6 +509,48 @@ describe('AppComponent', () => { expect(callout).toBeTruthy(); expect(calloutContent).toContain('Step 3'); }); + + it('should NOT have callout component with "Step 3" if has reports', () => { + store.overrideSelector(selectReports, [...HISTORY]); + store.refreshState(); + fixture.detectChanges(); + + const callout = compiled.querySelector('app-callout'); + + expect(callout).toBeFalsy(); + }); + }); + + describe('with systemStatus data IN Progress and without riskProfiles', () => { + beforeEach(() => { + store.overrideSelector(selectHasConnectionSettings, true); + store.overrideSelector(selectHasDevices, true); + store.overrideSelector(selectHasRiskProfiles, false); + store.overrideSelector( + selectStatus, + MOCK_PROGRESS_DATA_IN_PROGRESS.status + ); + fixture.detectChanges(); + }); + + it('should have callout component with "The device is now being tested" text', () => { + const callout = compiled.querySelector('app-callout'); + const calloutContent = callout?.innerHTML.trim(); + + expect(callout).toBeTruthy(); + expect(calloutContent).toContain('The device is now being tested'); + }); + + it('should have callout component with "Risk Assessment" link', () => { + const callout = compiled.querySelector('app-callout'); + const calloutLinkEl = compiled.querySelector( + '.message-link' + ) as HTMLAnchorElement; + const calloutLinkContent = calloutLinkEl.innerHTML.trim(); + + expect(callout).toBeTruthy(); + expect(calloutLinkContent).toContain('Risk Assessment'); + }); }); describe('with systemStatus data IN Progress and without riskProfiles', () => { @@ -500,12 +565,12 @@ describe('AppComponent', () => { fixture.detectChanges(); }); - it('should have callout component with "Congratulations" text', () => { + it('should have callout component with "The device is now being tested" text', () => { const callout = compiled.querySelector('app-callout'); const calloutContent = callout?.innerHTML.trim(); expect(callout).toBeTruthy(); - expect(calloutContent).toContain('Congratulations'); + expect(calloutContent).toContain('The device is now being tested'); }); it('should have callout component with "Risk Assessment" link', () => { @@ -686,7 +751,7 @@ describe('AppComponent', () => { const calloutContent = callout?.innerHTML.trim(); expect(callout).toBeTruthy(); - expect(calloutContent).toContain('No ports are detected.'); + expect(calloutContent).toContain('No ports detected.'); }); }); diff --git a/modules/ui/src/app/app.component.ts b/modules/ui/src/app/app.component.ts index 341f6bab5..2214b8927 100644 --- a/modules/ui/src/app/app.component.ts +++ b/modules/ui/src/app/app.component.ts @@ -80,6 +80,9 @@ export class AppComponent { this.appStore.getDevices(); this.appStore.getRiskProfiles(); this.appStore.getSystemStatus(); + this.appStore.getReports(); + this.appStore.getTestModules(); + this.appStore.getNetworkAdapters(); this.matIconRegistry.addSvgIcon( 'devices', this.domSanitizer.bypassSecurityTrustResourceUrl(DEVICES_LOGO_URL) diff --git a/modules/ui/src/app/app.module.ts b/modules/ui/src/app/app.module.ts index 78621a464..795d4e0d8 100644 --- a/modules/ui/src/app/app.module.ts +++ b/modules/ui/src/app/app.module.ts @@ -49,6 +49,14 @@ import { ShutdownAppComponent } from './components/shutdown-app/shutdown-app.com import { WindowProvider } from './providers/window.provider'; import { CertificatesComponent } from './pages/certificates/certificates.component'; import { LOADER_TIMEOUT_CONFIG_TOKEN } from './services/loaderConfig'; +import { WifiComponent } from './components/wifi/wifi.component'; + +import { MqttModule, IMqttServiceOptions } from 'ngx-mqtt'; + +export const MQTT_SERVICE_OPTIONS: IMqttServiceOptions = { + hostname: 'localhost', + port: 9001, +}; @NgModule({ declarations: [AppComponent, SettingsComponent], @@ -79,6 +87,8 @@ import { LOADER_TIMEOUT_CONFIG_TOKEN } from './services/loaderConfig'; SettingsDropdownComponent, ShutdownAppComponent, CertificatesComponent, + MqttModule.forRoot(MQTT_SERVICE_OPTIONS), + WifiComponent, ], providers: [ WindowProvider, diff --git a/modules/ui/src/app/app.store.spec.ts b/modules/ui/src/app/app.store.spec.ts index 2bdf63195..e26db7eb3 100644 --- a/modules/ui/src/app/app.store.spec.ts +++ b/modules/ui/src/app/app.store.spec.ts @@ -24,22 +24,30 @@ import { selectHasDevices, selectHasRiskProfiles, selectInterfaces, + selectInternetConnection, selectIsOpenWaitSnackBar, selectMenuOpened, + selectReports, selectStatus, + selectTestModules, } from './store/selectors'; import { TestRunService } from './services/test-run.service'; import SpyObj = jasmine.SpyObj; -import { device } from './mocks/device.mock'; +import { device, MOCK_MODULES, MOCK_TEST_MODULES } from './mocks/device.mock'; import { + fetchReports, fetchRiskProfiles, fetchSystemStatus, setDevices, + updateAdapters, + setTestModules, } from './store/actions'; import { MOCK_PROGRESS_DATA_IN_PROGRESS } from './mocks/testrun.mock'; import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; import { NotificationService } from './services/notification.service'; import { FocusManagerService } from './services/focus-manager.service'; +import { TestRunMqttService } from './services/test-run-mqtt.service'; +import { MOCK_ADAPTERS } from './mocks/settings.mock'; const mock = (() => { let store: { [key: string]: string } = {}; @@ -65,15 +73,20 @@ describe('AppStore', () => { let mockService: SpyObj; let mockNotificationService: SpyObj; let mockFocusManagerService: SpyObj; + let mockMqttService: SpyObj; beforeEach(() => { - mockService = jasmine.createSpyObj('mockService', ['fetchDevices']); + mockService = jasmine.createSpyObj('mockService', [ + 'fetchDevices', + 'getTestModules', + ]); mockNotificationService = jasmine.createSpyObj('mockNotificationService', [ 'notify', ]); mockFocusManagerService = jasmine.createSpyObj([ 'focusFirstElementInContainer', ]); + mockMqttService = jasmine.createSpyObj(['getNetworkAdapters']); TestBed.configureTestingModule({ providers: [ @@ -82,11 +95,14 @@ describe('AppStore', () => { selectors: [ { selector: selectStatus, value: null }, { selector: selectIsOpenWaitSnackBar, value: false }, + { selector: selectTestModules, value: MOCK_TEST_MODULES }, + { selector: selectInternetConnection, value: false }, ], }), { provide: TestRunService, useValue: mockService }, { provide: NotificationService, useValue: mockNotificationService }, { provide: FocusManagerService, useValue: mockFocusManagerService }, + { provide: TestRunMqttService, useValue: mockMqttService }, ], imports: [BrowserAnimationsModule], }); @@ -96,6 +112,7 @@ describe('AppStore', () => { store.overrideSelector(selectHasDevices, true); store.overrideSelector(selectHasRiskProfiles, false); + store.overrideSelector(selectReports, []); store.overrideSelector(selectHasConnectionSettings, true); store.overrideSelector(selectMenuOpened, true); store.overrideSelector(selectInterfaces, {}); @@ -140,12 +157,14 @@ describe('AppStore', () => { consentShown: false, hasDevices: true, hasRiskProfiles: false, + reports: [], isStatusLoaded: false, systemStatus: null, hasConnectionSettings: true, isMenuOpen: true, interfaces: {}, settingMissedError: null, + hasInternetConnection: false, }); done(); }); @@ -226,5 +245,66 @@ describe('AppStore', () => { ).toHaveBeenCalled(); })); }); + + describe('getReports', () => { + it('should dispatch fetchReports', () => { + appStore.getReports(); + + expect(store.dispatch).toHaveBeenCalledWith(fetchReports()); + }); + }); + + describe('getTestModules', () => { + const modules = [...MOCK_MODULES]; + + beforeEach(() => { + mockService.getTestModules.and.returnValue(of(modules)); + }); + + it('should dispatch action setDevices', () => { + appStore.getTestModules(); + + expect(store.dispatch).toHaveBeenCalledWith( + setTestModules({ + testModules: [ + { + displayName: 'Connection', + name: 'connection', + enabled: true, + }, + { + displayName: 'Udmi', + name: 'udmi', + enabled: true, + }, + ], + }) + ); + }); + }); + + describe('getNetworkAdapters', () => { + const adapters = MOCK_ADAPTERS; + + beforeEach(() => { + mockMqttService.getNetworkAdapters.and.returnValue(of(adapters)); + }); + + it('should dispatch action setDevices', () => { + appStore.getNetworkAdapters(); + + expect(store.dispatch).toHaveBeenCalledWith( + updateAdapters({ adapters }) + ); + }); + + it('should notify about new adapters', () => { + appStore.getNetworkAdapters(); + + expect(mockNotificationService.notify).toHaveBeenCalledWith( + 'New network adapter(s) mockNewInternetKey has been detected. You can switch to using it in the System settings menu' + ); + }); + }); }); }); diff --git a/modules/ui/src/app/app.store.ts b/modules/ui/src/app/app.store.ts index 9bd8dcff4..6e338968f 100644 --- a/modules/ui/src/app/app.store.ts +++ b/modules/ui/src/app/app.store.ts @@ -23,23 +23,34 @@ import { selectHasDevices, selectHasRiskProfiles, selectInterfaces, + selectInternetConnection, selectMenuOpened, + selectReports, selectStatus, } from './store/selectors'; import { Store } from '@ngrx/store'; import { AppState } from './store/state'; import { TestRunService } from './services/test-run.service'; import { delay, exhaustMap, Observable, skip } from 'rxjs'; -import { Device } from './model/device'; +import { Device, TestModule } from './model/device'; import { setDevices, setIsOpenStartTestrun, fetchSystemStatus, fetchRiskProfiles, + fetchReports, + setTestModules, + updateAdapters, } from './store/actions'; import { TestrunStatus } from './model/testrun-status'; -import { SettingMissedError, SystemInterfaces } from './model/setting'; +import { + Adapters, + SettingMissedError, + SystemInterfaces, +} from './model/setting'; import { FocusManagerService } from './services/focus-manager.service'; +import { TestRunMqttService } from './services/test-run-mqtt.service'; +import { NotificationService } from './services/notification.service'; export const CONSENT_SHOWN_KEY = 'CONSENT_SHOWN'; export interface AppComponentState { @@ -51,8 +62,10 @@ export interface AppComponentState { export class AppStore extends ComponentStore { private consentShown$ = this.select(state => state.consentShown); private isStatusLoaded$ = this.select(state => state.isStatusLoaded); + private hasInternetConnection$ = this.store.select(selectInternetConnection); private hasDevices$ = this.store.select(selectHasDevices); private hasRiskProfiles$ = this.store.select(selectHasRiskProfiles); + private reports$ = this.store.select(selectReports); private hasConnectionSetting$ = this.store.select( selectHasConnectionSettings ); @@ -67,12 +80,14 @@ export class AppStore extends ComponentStore { consentShown: this.consentShown$, hasDevices: this.hasDevices$, hasRiskProfiles: this.hasRiskProfiles$, + reports: this.reports$, isStatusLoaded: this.isStatusLoaded$, systemStatus: this.systemStatus$, hasConnectionSettings: this.hasConnectionSetting$, isMenuOpen: this.isMenuOpen$, interfaces: this.interfaces$, settingMissedError: this.settingMissedError$, + hasInternetConnection: this.hasInternetConnection$, }); updateConsent = this.updater((state, consentShown: boolean) => ({ @@ -131,6 +146,27 @@ export class AppStore extends ComponentStore { ); }); + getNetworkAdapters = this.effect(trigger$ => { + return trigger$.pipe( + exhaustMap(() => { + return this.testRunMqttService.getNetworkAdapters().pipe( + tap((adapters: Adapters) => { + if (adapters.adapters_added) { + this.notifyAboutTheAdapters(adapters.adapters_added); + } + this.store.dispatch(updateAdapters({ adapters })); + }) + ); + }) + ); + }); + + private notifyAboutTheAdapters(adapters: SystemInterfaces) { + this.notificationService.notify( + `New network adapter(s) ${Object.keys(adapters).join(', ')} has been detected. You can switch to using it in the System settings menu` + ); + } + setIsOpenStartTestrun = this.effect(trigger$ => { return trigger$.pipe( tap(() => { @@ -150,10 +186,43 @@ export class AppStore extends ComponentStore { ); }); + getReports = this.effect(trigger$ => { + return trigger$.pipe( + tap(() => { + this.store.dispatch(fetchReports()); + }) + ); + }); + + getTestModules = this.effect(trigger$ => { + return trigger$.pipe( + exhaustMap(() => { + return this.testRunService.getTestModules().pipe( + tap((testModules: string[]) => { + this.store.dispatch( + setTestModules({ + testModules: testModules.map( + module => + ({ + displayName: module, + name: module.toLowerCase(), + enabled: true, + }) as TestModule + ), + }) + ); + }) + ); + }) + ); + }); + constructor( private store: Store, private testRunService: TestRunService, - private focusManagerService: FocusManagerService + private testRunMqttService: TestRunMqttService, + private focusManagerService: FocusManagerService, + private notificationService: NotificationService ) { super({ consentShown: sessionStorage.getItem(CONSENT_SHOWN_KEY) !== null, diff --git a/modules/ui/src/app/components/download-report-zip/download-report-zip.component.spec.ts b/modules/ui/src/app/components/download-report-zip/download-report-zip.component.spec.ts index d87200987..2b2fe7994 100644 --- a/modules/ui/src/app/components/download-report-zip/download-report-zip.component.spec.ts +++ b/modules/ui/src/app/components/download-report-zip/download-report-zip.component.spec.ts @@ -171,9 +171,9 @@ describe('DownloadReportZipComponent', () => { expect(spyOnShow).toHaveBeenCalled(); }); - it('should be shown on focusin', () => { + it('should be shown on keyup', () => { const spyOnShow = spyOn(component.tooltip, 'show'); - fixture.nativeElement.dispatchEvent(new Event('focusin')); + fixture.nativeElement.dispatchEvent(new Event('keyup')); expect(spyOnShow).toHaveBeenCalled(); }); @@ -185,9 +185,9 @@ describe('DownloadReportZipComponent', () => { expect(spyOnHide).toHaveBeenCalled(); }); - it('should be hidden on focusout', () => { + it('should be hidden on keydown', () => { const spyOnHide = spyOn(component.tooltip, 'hide'); - fixture.nativeElement.dispatchEvent(new Event('focusout')); + fixture.nativeElement.dispatchEvent(new Event('keydown')); expect(spyOnHide).toHaveBeenCalled(); }); diff --git a/modules/ui/src/app/components/download-report-zip/download-report-zip.component.ts b/modules/ui/src/app/components/download-report-zip/download-report-zip.component.ts index e7b106f05..d5b4b41ce 100644 --- a/modules/ui/src/app/components/download-report-zip/download-report-zip.component.ts +++ b/modules/ui/src/app/components/download-report-zip/download-report-zip.component.ts @@ -87,13 +87,13 @@ export class DownloadReportZipComponent readonly tabIndex = 0; @HostListener('mouseenter') - @HostListener('focusin', ['$event']) + @HostListener('keyup', ['$event']) onEvent(): void { this.tooltip.show(); } @HostListener('mouseleave') - @HostListener('focusout', ['$event']) + @HostListener('keydown', ['$event']) outEvent(): void { this.tooltip.hide(); } diff --git a/modules/ui/src/app/components/download-zip-modal/download-zip-modal.component.html b/modules/ui/src/app/components/download-zip-modal/download-zip-modal.component.html index b3dfb77f4..2e9446cfa 100644 --- a/modules/ui/src/app/components/download-zip-modal/download-zip-modal.component.html +++ b/modules/ui/src/app/components/download-zip-modal/download-zip-modal.component.html @@ -14,21 +14,59 @@ limitations under the License. --> Download ZIP file -

- Risk profile is required for device verification. Please, consider creating a - Risk assessment profile for your ZIP report. +

+ Risk Profile is required for device verification. Please consider going to + Risk Assessment + and creating a profile to attach to your report.

-
+

+ Risk Profile is required for device verification. Please select a profile from + the list, or go to + Risk Assessment + and create a new one to attach to your report. +

+ +
+ aria-label="Please choose a Risk Profile from the list"> - {{ selectedProfile }} + {{ selectedProfile.name }} + + {{ selectedProfile.risk }} risk + - +
- Please choose risk assessment profile + Please choose a Risk Profile from the list -
- - - - - - diff --git a/modules/ui/src/app/components/download-zip-modal/download-zip-modal.component.scss b/modules/ui/src/app/components/download-zip-modal/download-zip-modal.component.scss index 3524cb936..0a92617c1 100644 --- a/modules/ui/src/app/components/download-zip-modal/download-zip-modal.component.scss +++ b/modules/ui/src/app/components/download-zip-modal/download-zip-modal.component.scss @@ -53,10 +53,6 @@ padding: 16px 0 0; } -.risk-profile-select-form-actions button:first-child { - margin-right: auto; -} - .profile-select { width: 100%; } @@ -69,3 +65,22 @@ font-size: 12px; color: $grey-700; } + +.redirect-link { + cursor: pointer; + color: $primary; + display: inline-block; + width: fit-content; +} + +::ng-deep mat-select-trigger { + display: inline-flex; + width: 100%; + justify-content: space-between; +} + +::ng-deep mat-select-trigger .profile-item-risk { + vertical-align: middle; + align-self: center; + margin-right: 16px; +} diff --git a/modules/ui/src/app/components/download-zip-modal/download-zip-modal.component.spec.ts b/modules/ui/src/app/components/download-zip-modal/download-zip-modal.component.spec.ts index cdd4c665f..728590ef8 100644 --- a/modules/ui/src/app/components/download-zip-modal/download-zip-modal.component.spec.ts +++ b/modules/ui/src/app/components/download-zip-modal/download-zip-modal.component.spec.ts @@ -59,23 +59,19 @@ describe('DownloadZipModalComponent', () => { expect(select).toBeTruthy(); }); - it('should preselect first profile', async () => { - const select = fixture.nativeElement.querySelector( - 'mat-select' - ) as HTMLElement; - - expect(select.getAttribute('ng-reflect-value')).toEqual( - 'Primary profile' + it('should preselect "no profile" option', async () => { + expect(component.selectedProfile.name).toEqual( + 'No Risk Profile selected' ); }); it('should close with null on redirect button click', async () => { const closeSpy = spyOn(component.dialogRef, 'close'); - const redirectButton = fixture.nativeElement.querySelector( - '.redirect-button' - ) as HTMLButtonElement; + const redirectLink = fixture.nativeElement.querySelector( + '.redirect-link' + ) as HTMLAnchorElement; - redirectButton.click(); + redirectLink.click(); expect(closeSpy).toHaveBeenCalledWith(null); @@ -103,13 +99,17 @@ describe('DownloadZipModalComponent', () => { downloadButton.click(); - expect(closeSpy).toHaveBeenCalledWith('Primary profile'); + expect(closeSpy).toHaveBeenCalledWith(''); closeSpy.calls.reset(); }); it('should have filtered and sorted profiles', async () => { - expect(component.profiles).toEqual([PROFILE_MOCK, PROFILE_MOCK_2]); + expect(component.profiles).toEqual([ + component.NO_PROFILE, + PROFILE_MOCK, + PROFILE_MOCK_2, + ]); }); it('#getRiskClass should call the service method getRiskClass"', () => { @@ -141,19 +141,19 @@ describe('DownloadZipModalComponent', () => { fixture.detectChanges(); }); - it('should have no dropdown with profiles', async () => { + it('should have disabled dropdown', async () => { const select = fixture.nativeElement.querySelector('mat-select'); - expect(select).toEqual(null); + expect(select.classList.contains('mat-mdc-select-disabled')).toBeTruthy(); }); it('should close with null on redirect button click', async () => { const closeSpy = spyOn(component.dialogRef, 'close'); - const redirectButton = fixture.nativeElement.querySelector( - '.redirect-button' - ) as HTMLButtonElement; + const redirectLink = fixture.nativeElement.querySelector( + '.redirect-link' + ) as HTMLAnchorElement; - redirectButton.click(); + redirectLink.click(); expect(closeSpy).toHaveBeenCalledWith(null); diff --git a/modules/ui/src/app/components/download-zip-modal/download-zip-modal.component.ts b/modules/ui/src/app/components/download-zip-modal/download-zip-modal.component.ts index 395bcb480..b042bdabf 100644 --- a/modules/ui/src/app/components/download-zip-modal/download-zip-modal.component.ts +++ b/modules/ui/src/app/components/download-zip-modal/download-zip-modal.component.ts @@ -17,6 +17,9 @@ import { MatFormField } from '@angular/material/form-field'; import { MatSelectModule } from '@angular/material/select'; import { MatOptionModule } from '@angular/material/core'; import { TestRunService } from '../../services/test-run.service'; +import { Routes } from '../../model/routes'; +import { RouterLink } from '@angular/router'; +import { MatTooltip, MatTooltipModule } from '@angular/material/tooltip'; interface DialogData { profiles: Profile[]; @@ -35,14 +38,22 @@ interface DialogData { MatFormField, MatSelectModule, MatOptionModule, + RouterLink, + MatTooltip, + MatTooltipModule, ], templateUrl: './download-zip-modal.component.html', styleUrl: './download-zip-modal.component.scss', changeDetection: ChangeDetectionStrategy.OnPush, }) export class DownloadZipModalComponent extends EscapableDialogComponent { + readonly NO_PROFILE = { + name: 'No Risk Profile selected', + questions: [], + } as Profile; + public readonly Routes = Routes; profiles: Profile[] = []; - selectedProfile: string = ''; + selectedProfile: Profile; constructor( private readonly testRunService: TestRunService, public override dialogRef: MatDialogRef, @@ -56,12 +67,20 @@ export class DownloadZipModalComponent extends EscapableDialogComponent { this.profiles.sort((a, b) => a.name.toLowerCase().localeCompare(b.name.toLowerCase()) ); - this.selectedProfile = this.profiles[0].name; } + this.profiles.unshift(this.NO_PROFILE); + this.selectedProfile = this.profiles[0]; } - cancel(profile?: string | null) { - this.dialogRef.close(profile); + cancel(profile?: Profile | null) { + if (profile === null) { + this.dialogRef.close(null); + } + let value = profile?.name; + if (profile && profile?.name === this.NO_PROFILE.name) { + value = ''; + } + this.dialogRef.close(value); } public getRiskClass(riskResult: string): RiskResultClassName { diff --git a/modules/ui/src/app/components/snack-bar/snack-bar.component.html b/modules/ui/src/app/components/snack-bar/snack-bar.component.html index 716198299..539623d4b 100644 --- a/modules/ui/src/app/components/snack-bar/snack-bar.component.html +++ b/modules/ui/src/app/components/snack-bar/snack-bar.component.html @@ -15,9 +15,10 @@ -->
-

The Waiting for Device stage is taking more than one minute.

+

It is taking longer than expected to find your device on the network.

- Please check device connection or stop and update system configuration. + Please check the connection to the device or stop and update your system + configuration.

diff --git a/modules/ui/src/app/components/wifi/wifi.component.html b/modules/ui/src/app/components/wifi/wifi.component.html new file mode 100644 index 000000000..c93d05f7e --- /dev/null +++ b/modules/ui/src/app/components/wifi/wifi.component.html @@ -0,0 +1,25 @@ + + diff --git a/modules/ui/src/app/components/wifi/wifi.component.scss b/modules/ui/src/app/components/wifi/wifi.component.scss new file mode 100644 index 000000000..bc0ac542e --- /dev/null +++ b/modules/ui/src/app/components/wifi/wifi.component.scss @@ -0,0 +1,40 @@ +/** + * Copyright 2023 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +@import '../../../theming/colors'; + +$icon-size: 24px; + +.app-toolbar-button { + border-radius: 20px; + border: 1px solid transparent; + min-width: 48px; + padding: 0; + box-sizing: border-box; + height: 34px; + margin: 11px 0; + line-height: 50% !important; + &.disabled { + opacity: 0.6; + } +} + +.wifi-icon { + margin-right: 0; + width: $icon-size; + font-size: $icon-size; + color: $dark-grey; + height: $icon-size; +} diff --git a/modules/ui/src/app/components/wifi/wifi.component.spec.ts b/modules/ui/src/app/components/wifi/wifi.component.spec.ts new file mode 100644 index 000000000..55e85a6a7 --- /dev/null +++ b/modules/ui/src/app/components/wifi/wifi.component.spec.ts @@ -0,0 +1,100 @@ +/** + * Copyright 2023 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { WifiComponent } from './wifi.component'; + +describe('WifiComponent', () => { + let component: WifiComponent; + let fixture: ComponentFixture; + let compiled: HTMLElement; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [WifiComponent], + }).compileComponents(); + + fixture = TestBed.createComponent(WifiComponent); + component = fixture.componentInstance; + compiled = fixture.nativeElement as HTMLElement; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); + + describe('Class tests', () => { + describe('with internet connection', () => { + it('should return label', () => { + expect(component.getLabel(true)).toEqual( + 'Testrun detects a working internet connection for the device under test.' + ); + }); + }); + + describe('with no internet connection', () => { + it('should return label', () => { + expect(component.getLabel(false)).toEqual( + 'No internet connection detected for the device under test.' + ); + }); + }); + + describe('with N/A internet connection', () => { + it('should return label', () => { + expect(component.getLabel(false, true)).toEqual( + 'Internet connection is not being monitored.' + ); + }); + }); + }); + + describe('DOM tests', () => { + describe('with internet connection', () => { + it('should have wifi icon', () => { + component.on = true; + fixture.detectChanges(); + + const icon = compiled.querySelector('mat-icon')?.textContent?.trim(); + + expect(icon).toEqual('wifi'); + }); + }); + + describe('should have no wifi icon', () => { + it('should have no wifi icon', () => { + component.on = false; + fixture.detectChanges(); + + const icon = compiled.querySelector('mat-icon')?.textContent?.trim(); + + expect(icon).toEqual('wifi_off'); + }); + }); + + it('button should be disabled', () => { + component.disable = true; + fixture.detectChanges(); + + const shutdownButton = compiled.querySelector( + '.wifi-button' + ) as HTMLButtonElement; + + expect(shutdownButton?.classList.contains('disabled')).toBeTrue(); + }); + }); +}); diff --git a/modules/ui/src/app/components/wifi/wifi.component.ts b/modules/ui/src/app/components/wifi/wifi.component.ts new file mode 100644 index 000000000..e7e28e8f9 --- /dev/null +++ b/modules/ui/src/app/components/wifi/wifi.component.ts @@ -0,0 +1,40 @@ +/** + * Copyright 2023 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { Component, Input } from '@angular/core'; +import { MatIcon } from '@angular/material/icon'; +import { MatTooltip } from '@angular/material/tooltip'; +import { MatButton, MatIconButton } from '@angular/material/button'; + +@Component({ + selector: 'app-wifi', + standalone: true, + imports: [MatIcon, MatTooltip, MatButton, MatIconButton], + templateUrl: './wifi.component.html', + styleUrl: './wifi.component.scss', +}) +export class WifiComponent { + @Input() on: boolean | null = null; + @Input() disable: boolean = false; + + getLabel(on: boolean | null, disable: boolean = false) { + if (disable) { + return 'Internet connection is not being monitored.'; + } + return on + ? 'Testrun detects a working internet connection for the device under test.' + : 'No internet connection detected for the device under test.'; + } +} diff --git a/modules/ui/src/app/interceptors/error.interceptor.spec.ts b/modules/ui/src/app/interceptors/error.interceptor.spec.ts index 9fff32863..7271223a2 100644 --- a/modules/ui/src/app/interceptors/error.interceptor.spec.ts +++ b/modules/ui/src/app/interceptors/error.interceptor.spec.ts @@ -42,11 +42,15 @@ describe('ErrorInterceptor', () => { interceptor = TestBed.inject(ErrorInterceptor); }); + afterEach(() => { + notificationServiceMock.notify.calls.reset(); + }); + it('should be created', () => { expect(interceptor).toBeTruthy(); }); - it('should notify about backend errors', done => { + it('should notify about backend errors with message if exist', done => { const next: HttpHandler = { handle: () => { return throwError( @@ -66,6 +70,26 @@ describe('ErrorInterceptor', () => { ); }); + it('should notify about backend errors with default message', done => { + const next: HttpHandler = { + handle: () => { + return throwError(new HttpErrorResponse({ status: 500 })); + }, + }; + + const requestMock = new HttpRequest('GET', '/test'); + + interceptor.intercept(requestMock, next).subscribe( + () => ({}), + () => { + expect(notificationServiceMock.notify).toHaveBeenCalledWith( + 'Something went wrong. Check the Terminal for details.' + ); + done(); + } + ); + }); + it('should notify about other errors', done => { const next: HttpHandler = { handle: () => { @@ -79,7 +103,7 @@ describe('ErrorInterceptor', () => { () => ({}), () => { expect(notificationServiceMock.notify).toHaveBeenCalledWith( - 'Back End is not responding. Please, try again later.' + 'Testrun is not responding. Please try again in a moment.' ); done(); } @@ -99,7 +123,7 @@ describe('ErrorInterceptor', () => { () => ({}), () => { expect(notificationServiceMock.notify).toHaveBeenCalledWith( - 'Back End is not responding. Please, try again later.' + 'Testrun is not responding. Please try again in a moment.' ); done(); } diff --git a/modules/ui/src/app/interceptors/error.interceptor.ts b/modules/ui/src/app/interceptors/error.interceptor.ts index 924cbde02..9e653895a 100644 --- a/modules/ui/src/app/interceptors/error.interceptor.ts +++ b/modules/ui/src/app/interceptors/error.interceptor.ts @@ -57,17 +57,19 @@ export class ErrorInterceptor implements HttpInterceptor { catchError((error: HttpErrorResponse | TimeoutError) => { if (error instanceof TimeoutError) { this.notificationService.notify( - 'Back End is not responding. Please, try again later.' + 'Testrun is not responding. Please try again in a moment.' ); } else { if (error.status === 0) { this.notificationService.notify( - 'Back End is not responding. Please, try again later.' + 'Testrun is not responding. Please try again in a moment.' ); } else { this.notificationService.notify( - error.error?.error || error.message + error.error?.error || + 'Something went wrong. Check the Terminal for details.' ); + console.error(error.error?.error || error.message); } } return throwError(error); diff --git a/modules/ui/src/app/mocks/device.mock.ts b/modules/ui/src/app/mocks/device.mock.ts index 6066593e6..8bbfb56ea 100644 --- a/modules/ui/src/app/mocks/device.mock.ts +++ b/modules/ui/src/app/mocks/device.mock.ts @@ -43,8 +43,10 @@ export const MOCK_TEST_MODULES = [ enabled: true, }, { - displayName: 'Smart Ready', + displayName: 'Udmi', name: 'udmi', enabled: false, }, ]; + +export const MOCK_MODULES = ['Connection', 'Udmi']; diff --git a/modules/ui/src/app/mocks/profile.mock.ts b/modules/ui/src/app/mocks/profile.mock.ts index 3715685cd..d53703809 100644 --- a/modules/ui/src/app/mocks/profile.mock.ts +++ b/modules/ui/src/app/mocks/profile.mock.ts @@ -155,3 +155,57 @@ export const RENAME_PROFILE_MOCK = { name: 'Primary profile', rename: 'New profile', }; + +export const COPY_PROFILE_MOCK: Profile = { + name: 'Copy of Primary profile', + status: ProfileStatus.VALID, + questions: [ + { + question: 'What is the email of the device owner(s)?', + answer: 'boddey@google.com, cmeredith@google.com', + }, + { + question: 'What type of device do you need reviewed?', + answer: 'IoT Sensor', + }, + { + question: 'Are any of the following statements true about your device?', + answer: 'First', + }, + { + question: 'What features does the device have?', + answer: [0, 1, 2], + }, + { + question: 'Comments', + answer: 'Yes', + }, + ], +}; + +export const OUTDATED_DRAFT_PROFILE_MOCK: Profile = { + name: 'Outdated profile', + status: ProfileStatus.DRAFT, + questions: [ + { + question: 'Old question', + answer: 'qwerty', + }, + { + question: 'What is the email of the device owner(s)?', + answer: 'boddey@google.com, cmeredith@google.com', + }, + { + question: 'What type of device do you need reviewed?', + answer: 'IoT Sensor', + }, + { + question: 'Another old question', + answer: 'qwerty', + }, + ], +}; + +export const EXPIRED_PROFILE_MOCK: Profile = Object.assign({}, PROFILE_MOCK, { + status: ProfileStatus.EXPIRED, +}); diff --git a/modules/ui/src/app/mocks/reports.mock.ts b/modules/ui/src/app/mocks/reports.mock.ts index 0cfb39420..e1422a36c 100644 --- a/modules/ui/src/app/mocks/reports.mock.ts +++ b/modules/ui/src/app/mocks/reports.mock.ts @@ -28,6 +28,19 @@ export const HISTORY = [ started: '2023-07-23T10:11:00.123Z', finished: '2023-07-23T10:17:10.123Z', }, + { + mac_addr: null, + status: 'compliant', + device: { + manufacturer: 'Delta', + model: '03-DIN-SRC', + mac_addr: '01:02:03:04:05:08', + firmware: '1.2.2', + }, + report: 'https://api.testrun.io/report.pdf', + started: '2023-06-23T10:11:00.123Z', + finished: '2023-06-23T10:17:10.123Z', + }, ] as TestrunStatus[]; export const HISTORY_AFTER_REMOVE = [ @@ -43,9 +56,19 @@ export const HISTORY_AFTER_REMOVE = [ report: 'https://api.testrun.io/report.pdf', started: '2023-06-23T10:11:00.123Z', finished: '2023-06-23T10:17:10.123Z', - deviceFirmware: '1.2.2', - deviceInfo: 'Delta 03-DIN-SRC', - duration: '06m 10s', + }, + { + mac_addr: null, + status: 'compliant', + device: { + manufacturer: 'Delta', + model: '03-DIN-SRC', + mac_addr: '01:02:03:04:05:08', + firmware: '1.2.2', + }, + report: 'https://api.testrun.io/report.pdf', + started: '2023-06-23T10:11:00.123Z', + finished: '2023-06-23T10:17:10.123Z', }, ]; @@ -82,6 +105,22 @@ export const FORMATTED_HISTORY = [ deviceInfo: 'Delta 03-DIN-SRC', duration: '06m 10s', }, + { + mac_addr: null, + status: 'compliant', + device: { + manufacturer: 'Delta', + model: '03-DIN-SRC', + mac_addr: '01:02:03:04:05:08', + firmware: '1.2.2', + }, + report: 'https://api.testrun.io/report.pdf', + started: '2023-06-23T10:11:00.123Z', + finished: '2023-06-23T10:17:10.123Z', + deviceFirmware: '1.2.2', + deviceInfo: 'Delta 03-DIN-SRC', + duration: '06m 10s', + }, ]; export const FILTERS = { diff --git a/modules/ui/src/app/mocks/settings.mock.ts b/modules/ui/src/app/mocks/settings.mock.ts index baab9a2c0..49a11a895 100644 --- a/modules/ui/src/app/mocks/settings.mock.ts +++ b/modules/ui/src/app/mocks/settings.mock.ts @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { SystemConfig, SystemInterfaces } from '../model/setting'; +import { Adapters, SystemConfig, SystemInterfaces } from '../model/setting'; export const MOCK_SYSTEM_CONFIG_WITH_NO_DATA: SystemConfig = { network: { @@ -60,3 +60,8 @@ export const MOCK_PERIOD_VALUE: SystemInterfaces = { key: '600', value: 'Very slow device', }; + +export const MOCK_ADAPTERS: Adapters = { + adapters_added: { mockNewInternetKey: 'mockNewInternetValue' }, + adapters_removed: { mockInternetKey: 'mockInternetValue' }, +}; diff --git a/modules/ui/src/app/mocks/testrun.mock.ts b/modules/ui/src/app/mocks/testrun.mock.ts index 0572e79c0..bb588634c 100644 --- a/modules/ui/src/app/mocks/testrun.mock.ts +++ b/modules/ui/src/app/mocks/testrun.mock.ts @@ -65,7 +65,7 @@ const PROGRESS_DATA_RESPONSE = ( status: string, finished: string | null, tests: TestsData | IResult[], - report?: string + report: string = '' ) => { return { status, diff --git a/modules/ui/src/app/mocks/topic.mock.ts b/modules/ui/src/app/mocks/topic.mock.ts new file mode 100644 index 000000000..4309ae84f --- /dev/null +++ b/modules/ui/src/app/mocks/topic.mock.ts @@ -0,0 +1,5 @@ +import { InternetConnection } from '../model/topic'; + +export const MOCK_INTERNET: InternetConnection = { + connection: false, +}; diff --git a/modules/ui/src/app/model/profile.ts b/modules/ui/src/app/model/profile.ts index efdb779e6..059b3cafe 100644 --- a/modules/ui/src/app/model/profile.ts +++ b/modules/ui/src/app/model/profile.ts @@ -22,11 +22,6 @@ export interface Profile { created?: string; } -export interface Question { - question?: string; - answer?: string | number[]; -} - export enum FormControlType { SELECT = 'select', TEXTAREA = 'text-long', @@ -62,6 +57,7 @@ export enum ProfileRisk { export enum ProfileStatus { VALID = 'Valid', DRAFT = 'Draft', + EXPIRED = 'Expired', } export interface RiskResultClassName { diff --git a/modules/ui/src/app/model/setting.ts b/modules/ui/src/app/model/setting.ts index 5e71052f3..708dcfc94 100644 --- a/modules/ui/src/app/model/setting.ts +++ b/modules/ui/src/app/model/setting.ts @@ -48,3 +48,8 @@ export enum FormKey { export type SystemInterfaces = { [key: string]: string; }; + +export type Adapters = { + adapters_added?: SystemInterfaces; + adapters_removed?: SystemInterfaces; +}; diff --git a/modules/ui/src/app/model/testrun-status.ts b/modules/ui/src/app/model/testrun-status.ts index 2ac908185..3bc63804c 100644 --- a/modules/ui/src/app/model/testrun-status.ts +++ b/modules/ui/src/app/model/testrun-status.ts @@ -16,13 +16,13 @@ import { Device } from './device'; export interface TestrunStatus { - mac_addr: string; + mac_addr: string | null; status: string; device: IDevice; started: string | null; finished: string | null; tests?: TestsResponse; - report?: string; + report: string; } export interface HistoryTestrun extends TestrunStatus { @@ -75,7 +75,9 @@ export enum StatusOfTestResult { NotStarted = 'Not Started', InProgress = 'In Progress', Error = 'Error', // test failed to run - Info = 'Informational', // nice to know information, not necessarily compliant/non-compliant + Info = 'Informational', // nice to know information, not necessarily compliant/non-compliant, + Skipped = 'Skipped', + Disabled = 'Disabled', } export interface StatusResultClassName { @@ -85,6 +87,19 @@ export interface StatusResultClassName { grey: boolean; } +export const IDLE_STATUS = { + status: StatusOfTestrun.Idle, + device: {} as IDevice, + started: null, + finished: null, + report: '', + mac_addr: '', + tests: { + total: 0, + results: [], + }, +} as TestrunStatus; + export type TestrunStatusKey = keyof typeof StatusOfTestrun; export type TestrunStatusValue = (typeof StatusOfTestrun)[TestrunStatusKey]; export type TestResultKey = keyof typeof StatusOfTestResult; diff --git a/modules/ui/src/app/model/topic.ts b/modules/ui/src/app/model/topic.ts new file mode 100644 index 000000000..d330dbb82 --- /dev/null +++ b/modules/ui/src/app/model/topic.ts @@ -0,0 +1,9 @@ +export enum Topic { + NetworkAdapters = 'events/adapter', + InternetConnection = 'events/internet', + Status = 'status', +} + +export interface InternetConnection { + connection: boolean | null; +} diff --git a/modules/ui/src/app/pages/certificates/certificates.store.spec.ts b/modules/ui/src/app/pages/certificates/certificates.store.spec.ts index 06e3accf6..5e66104e6 100644 --- a/modules/ui/src/app/pages/certificates/certificates.store.spec.ts +++ b/modules/ui/src/app/pages/certificates/certificates.store.spec.ts @@ -42,6 +42,8 @@ describe('CertificatesStore', () => { 'uploadCertificate', 'deleteCertificate', ]); + // @ts-expect-error data layer should be defined + window.dataLayer = window.dataLayer || []; TestBed.configureTestingModule({ imports: [NoopAnimationsModule], @@ -152,6 +154,21 @@ describe('CertificatesStore', () => { container ); }); + + it('should send GA event "successful_saving_certificate"', () => { + const container = document.createElement('DIV'); + container.classList.add('certificates-drawer-content'); + document.querySelector('body')?.appendChild(container); + certificateStore.uploadCertificate(FILE); + + expect( + // @ts-expect-error data layer should be defined + window.dataLayer.some( + (item: { event: string }) => + item.event === 'successful_saving_certificate' + ) + ).toBeTruthy(); + }); }); describe('with invalid certificate file', () => { diff --git a/modules/ui/src/app/pages/certificates/certificates.store.ts b/modules/ui/src/app/pages/certificates/certificates.store.ts index 21f96eed0..610daeffb 100644 --- a/modules/ui/src/app/pages/certificates/certificates.store.ts +++ b/modules/ui/src/app/pages/certificates/certificates.store.ts @@ -97,6 +97,10 @@ export class CertificatesStore extends ComponentStore { !certificates.some(cert => cert.name === certificate.name) )[0]; this.updateCertificates(newCertificates); + // @ts-expect-error data layer is not null + window.dataLayer.push({ + event: 'successful_saving_certificate', + }); this.notify( `Certificate successfully added.\n${uploadedCertificate.name} by ${uploadedCertificate.organisation} valid until ${this.datePipe.transform(uploadedCertificate.expires, 'dd MMM yyyy')}` ); diff --git a/modules/ui/src/app/pages/devices/components/device-form/device.validators.ts b/modules/ui/src/app/pages/devices/components/device-form/device.validators.ts index 2b7b23ae1..60ceb5d48 100644 --- a/modules/ui/src/app/pages/devices/components/device-form/device.validators.ts +++ b/modules/ui/src/app/pages/devices/components/device-form/device.validators.ts @@ -22,8 +22,11 @@ import { Device } from '../../../../model/device'; * Validator uses for Device Name and Device Manufacturer inputs */ export class DeviceValidators { + static readonly STRING_FORMAT_MAX_LENGTH = 28; readonly STRING_FORMAT_REGEXP = new RegExp( - "^([a-z0-9\\p{L}\\p{M}.',-_ ]{1,28})$", + "^([a-z0-9\\p{L}\\p{M}.',-_ ]{1," + + DeviceValidators.STRING_FORMAT_MAX_LENGTH + + '})$', 'u' ); diff --git a/modules/ui/src/app/pages/devices/devices.component.html b/modules/ui/src/app/pages/devices/devices.component.html index c9f5d3aee..aef3730c5 100644 --- a/modules/ui/src/app/pages/devices/devices.component.html +++ b/modules/ui/src/app/pages/devices/devices.component.html @@ -22,8 +22,10 @@

Devices

Devices + +
{ name.dispatchEvent(new Event('input')); component.nameControl.markAsTouched(); - fixture.detectChanges(); fixture.detectChanges(); const nameError = compiled.querySelector('mat-error')?.innerHTML; @@ -388,9 +389,52 @@ describe('ProfileFormComponent', () => { }); }); }); + + describe('Discard button', () => { + beforeEach(() => { + fillForm(component); + fixture.detectChanges(); + }); + + it('should be enabled when form is filled', () => { + const discardButton = compiled.querySelector( + '.discard-button' + ) as HTMLButtonElement; + + expect(discardButton.disabled).toBeFalse(); + }); + + it('should emit discard', () => { + const emitSpy = spyOn(component.discard, 'emit'); + const discardButton = compiled.querySelector( + '.discard-button' + ) as HTMLButtonElement; + discardButton.click(); + + expect(emitSpy).toHaveBeenCalled(); + }); + }); }); describe('Class tests', () => { + describe('with outdated draft profile', () => { + beforeEach(() => { + component.selectedProfile = OUTDATED_DRAFT_PROFILE_MOCK; + fixture.detectChanges(); + }); + + it('should have an error when uses the name of copy profile', () => { + expect(component.profileForm.value).toEqual({ + 0: '', + 1: 'IoT Sensor', + 2: '', + 3: { 0: false, 1: false, 2: false }, + 4: '', + name: 'Outdated profile', + }); + }); + }); + describe('with profile', () => { beforeEach(() => { component.selectedProfile = PROFILE_MOCK; @@ -432,6 +476,15 @@ describe('ProfileFormComponent', () => { component.nameControl.hasError('has_same_profile_name') ).toBeTrue(); }); + + it('should have an error when uses the name of copy profile', () => { + component.selectedProfile = COPY_PROFILE_MOCK; + component.profiles = [PROFILE_MOCK, PROFILE_MOCK_2, COPY_PROFILE_MOCK]; + + expect( + component.nameControl.hasError('has_same_profile_name') + ).toBeTrue(); + }); }); describe('with no profile', () => { diff --git a/modules/ui/src/app/pages/risk-assessment/profile-form/profile-form.component.ts b/modules/ui/src/app/pages/risk-assessment/profile-form/profile-form.component.ts index a15867ae7..567eb6c34 100644 --- a/modules/ui/src/app/pages/risk-assessment/profile-form/profile-form.component.ts +++ b/modules/ui/src/app/pages/risk-assessment/profile-form/profile-form.component.ts @@ -105,6 +105,7 @@ export class ProfileFormComponent implements OnInit { } @Output() saveProfile = new EventEmitter(); + @Output() discard = new EventEmitter(); constructor( private deviceValidators: DeviceValidators, private profileValidators: ProfileValidators, @@ -206,18 +207,22 @@ export class ProfileFormComponent implements OnInit { fillProfileForm(profileFormat: ProfileFormat[], profile: Profile): void { this.nameControl.setValue(profile.name); profileFormat.forEach((question, index) => { + const answer = profile.questions.find( + answers => answers.question === question.question + ); if (question.type === FormControlType.SELECT_MULTIPLE) { question.options?.forEach((item, idx) => { - if ((profile.questions[index].answer as number[])?.includes(idx)) { + if ((answer?.answer as number[])?.includes(idx)) { this.getFormGroup(index).controls[idx].setValue(true); } else { this.getFormGroup(index).controls[idx].setValue(false); } }); } else { - this.getControl(index).setValue(profile.questions[index].answer); + this.getControl(index).setValue(answer?.answer || ''); } }); + this.nameControl.markAsTouched(); this.triggerResize(); } @@ -241,6 +246,10 @@ export class ProfileFormComponent implements OnInit { } } + onDiscardClick() { + this.discard.emit(); + } + private buildResponseFromForm( initialQuestions: ProfileFormat[], profileForm: FormGroup, diff --git a/modules/ui/src/app/pages/risk-assessment/profile-form/profile.validators.ts b/modules/ui/src/app/pages/risk-assessment/profile-form/profile.validators.ts index dcad4b397..34bac3ebf 100644 --- a/modules/ui/src/app/pages/risk-assessment/profile-form/profile.validators.ts +++ b/modules/ui/src/app/pages/risk-assessment/profile-form/profile.validators.ts @@ -37,7 +37,13 @@ export class ProfileValidators { ): ValidatorFn { return (control: AbstractControl): ValidationErrors | null => { const value = control.value?.trim(); - if (value && profiles.length && (!profile || profile?.name !== value)) { + if ( + value && + profiles.length && + (!profile || + !profile.created || + (profile.created && profile?.name !== value)) + ) { const isSameProfileName = this.hasSameProfileName(value, profiles); return isSameProfileName ? { has_same_profile_name: true } : null; } diff --git a/modules/ui/src/app/pages/risk-assessment/profile-item/profile-item.component.html b/modules/ui/src/app/pages/risk-assessment/profile-item/profile-item.component.html index 35850f0ed..31049cd93 100644 --- a/modules/ui/src/app/pages/risk-assessment/profile-item/profile-item.component.html +++ b/modules/ui/src/app/pages/risk-assessment/profile-item/profile-item.component.html @@ -13,17 +13,30 @@ See the License for the specific language governing permissions and limitations under the License. --> -
+
+ (keydown.enter)="enterProfileItem(profile)"> + [attr.aria-label]=" + profile.status === ProfileStatus.EXPIRED + ? EXPIRED_TOOLTIP + : profile.status + "> + +

- {{ profile.created | date: 'dd MMM yyyy' }} + + Outdated ({{ profile.created | date: 'dd MMM yyyy' }}) + + + {{ profile.created | date: 'dd MMM yyyy' }} +

+ diff --git a/modules/ui/src/app/pages/risk-assessment/profile-item/profile-item.component.scss b/modules/ui/src/app/pages/risk-assessment/profile-item/profile-item.component.scss index a9a22b9e4..739a7bd14 100644 --- a/modules/ui/src/app/pages/risk-assessment/profile-item/profile-item.component.scss +++ b/modules/ui/src/app/pages/risk-assessment/profile-item/profile-item.component.scss @@ -28,15 +28,28 @@ $profile-item-container-gap: 16px; .profile-item-container { display: grid; - grid-template-columns: minmax(160px, 1fr) $profile-icon-container-size; + grid-template-columns: minmax(160px, 1fr) repeat( + 2, + $profile-icon-container-size + ); gap: $profile-item-container-gap; box-sizing: border-box; padding: 12px 16px; border-bottom: 1px solid $lighter-grey; align-items: center; - height: 92px; + min-height: 92px; + &-expired { + grid-template-columns: minmax(160px, 1fr) $profile-icon-container-size; + } } +.profile-item-container-expired .profile-item-info { + .profile-item-icon, + .profile-item-name, + .profile-item-created { + color: $red-800; + } +} .profile-item-icon-container { grid-area: icon; display: inline-block; diff --git a/modules/ui/src/app/pages/risk-assessment/profile-item/profile-item.component.spec.ts b/modules/ui/src/app/pages/risk-assessment/profile-item/profile-item.component.spec.ts index ae48e64ec..56f9ad6a4 100644 --- a/modules/ui/src/app/pages/risk-assessment/profile-item/profile-item.component.spec.ts +++ b/modules/ui/src/app/pages/risk-assessment/profile-item/profile-item.component.spec.ts @@ -16,8 +16,12 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; import { ProfileItemComponent } from './profile-item.component'; -import { PROFILE_MOCK } from '../../../mocks/profile.mock'; +import { + EXPIRED_PROFILE_MOCK, + PROFILE_MOCK, +} from '../../../mocks/profile.mock'; import { TestRunService } from '../../../services/test-run.service'; +import { LiveAnnouncer } from '@angular/cdk/a11y'; describe('ProfileItemComponent', () => { let component: ProfileItemComponent; @@ -25,11 +29,16 @@ describe('ProfileItemComponent', () => { let compiled: HTMLElement; const testRunServiceMock = jasmine.createSpyObj(['getRiskClass']); - + const mockLiveAnnouncer = jasmine.createSpyObj('mockLiveAnnouncer', [ + 'announce', + ]); beforeEach(async () => { await TestBed.configureTestingModule({ imports: [ProfileItemComponent], - providers: [{ provide: TestRunService, useValue: testRunServiceMock }], + providers: [ + { provide: TestRunService, useValue: testRunServiceMock }, + { provide: LiveAnnouncer, useValue: mockLiveAnnouncer }, + ], }).compileComponents(); fixture = TestBed.createComponent(ProfileItemComponent); @@ -59,8 +68,12 @@ describe('ProfileItemComponent', () => { const deleteButton = fixture.nativeElement.querySelector( '.profile-item-button.delete' ); + const copyButton = fixture.nativeElement.querySelector( + '.profile-item-button.copy' + ); expect(deleteButton?.ariaLabel?.trim()).toContain(PROFILE_MOCK.name); + expect(copyButton?.ariaLabel?.trim()).toContain(PROFILE_MOCK.name); }); it('should emit delete event on delete button clicked', () => { @@ -84,4 +97,22 @@ describe('ProfileItemComponent', () => { expect(profileClickedSpy).toHaveBeenCalledWith(PROFILE_MOCK); }); + + describe('with Expired profile', () => { + beforeEach(() => { + component.enterProfileItem(EXPIRED_PROFILE_MOCK); + }); + + it('should change tooltip on enterProfileItem', () => { + expect(component.tooltip.message).toEqual( + 'This risk profile is outdated. Please create a new risk profile.' + ); + }); + + it('should announce', () => { + expect(mockLiveAnnouncer.announce).toHaveBeenCalledWith( + 'This risk profile is outdated. Please create a new risk profile.' + ); + }); + }); }); diff --git a/modules/ui/src/app/pages/risk-assessment/profile-item/profile-item.component.ts b/modules/ui/src/app/pages/risk-assessment/profile-item/profile-item.component.ts index 79bd08833..514cbd46e 100644 --- a/modules/ui/src/app/pages/risk-assessment/profile-item/profile-item.component.ts +++ b/modules/ui/src/app/pages/risk-assessment/profile-item/profile-item.component.ts @@ -17,8 +17,10 @@ import { ChangeDetectionStrategy, Component, EventEmitter, + HostListener, Input, Output, + ViewChild, } from '@angular/core'; import { Profile, @@ -29,25 +31,55 @@ import { MatIcon } from '@angular/material/icon'; import { MatButtonModule } from '@angular/material/button'; import { CommonModule } from '@angular/common'; import { TestRunService } from '../../../services/test-run.service'; -import { MatTooltipModule } from '@angular/material/tooltip'; +import { MatTooltip, MatTooltipModule } from '@angular/material/tooltip'; +import { LiveAnnouncer } from '@angular/cdk/a11y'; @Component({ selector: 'app-profile-item', standalone: true, imports: [MatIcon, MatButtonModule, CommonModule, MatTooltipModule], + providers: [MatTooltip], templateUrl: './profile-item.component.html', styleUrl: './profile-item.component.scss', changeDetection: ChangeDetectionStrategy.OnPush, }) export class ProfileItemComponent { public readonly ProfileStatus = ProfileStatus; + public readonly EXPIRED_TOOLTIP = + 'Expired. Please, create a new Risk profile.'; @Input() profile!: Profile; @Output() deleteButtonClicked = new EventEmitter(); @Output() profileClicked = new EventEmitter(); + @Output() copyProfileClicked = new EventEmitter(); - constructor(private readonly testRunService: TestRunService) {} + @ViewChild('tooltip') tooltip!: MatTooltip; + + @HostListener('focusout', ['$event']) + outEvent(): void { + if (this.profile.status === ProfileStatus.EXPIRED) { + this.tooltip.message = this.EXPIRED_TOOLTIP; + } + } + + constructor( + private readonly testRunService: TestRunService, + private liveAnnouncer: LiveAnnouncer + ) {} public getRiskClass(riskResult: string): RiskResultClassName { return this.testRunService.getRiskClass(riskResult); } + + public async enterProfileItem(profile: Profile) { + if (profile.status === ProfileStatus.EXPIRED) { + this.tooltip.message = + 'This risk profile is outdated. Please create a new risk profile.'; + this.tooltip.show(); + await this.liveAnnouncer.announce( + 'This risk profile is outdated. Please create a new risk profile.' + ); + } else { + this.profileClicked.emit(profile); + } + } } diff --git a/modules/ui/src/app/pages/risk-assessment/risk-assessment.component.html b/modules/ui/src/app/pages/risk-assessment/risk-assessment.component.html index 2f11ea76b..c5e38e360 100644 --- a/modules/ui/src/app/pages/risk-assessment/risk-assessment.component.html +++ b/modules/ui/src/app/pages/risk-assessment/risk-assessment.component.html @@ -25,9 +25,8 @@

Risk assessment

[selectedProfile]="vm.selectedProfile" [profiles]="vm.profiles" [profileFormat]="vm.profileFormat" - (saveProfile)=" - saveProfileClicked($event, vm.selectedProfile) - "> + (saveProfile)="saveProfileClicked($event, vm.selectedProfile)" + (discard)="discard(vm.selectedProfile)">
@@ -43,16 +42,13 @@

Saved profiles

+ (profileClicked)="profileClicked($event)" + (copyProfileClicked)="copyProfileAndOpenForm($event)">
diff --git a/modules/ui/src/app/pages/risk-assessment/risk-assessment.component.scss b/modules/ui/src/app/pages/risk-assessment/risk-assessment.component.scss index c4ef49782..c7241c6c4 100644 --- a/modules/ui/src/app/pages/risk-assessment/risk-assessment.component.scss +++ b/modules/ui/src/app/pages/risk-assessment/risk-assessment.component.scss @@ -61,7 +61,7 @@ .main-content { padding: 16px 32px; - overflow: scroll; + overflow: hidden; width: calc(100% - $profiles-drawer-width); } diff --git a/modules/ui/src/app/pages/risk-assessment/risk-assessment.component.spec.ts b/modules/ui/src/app/pages/risk-assessment/risk-assessment.component.spec.ts index e2aa6332e..8e792ff83 100644 --- a/modules/ui/src/app/pages/risk-assessment/risk-assessment.component.spec.ts +++ b/modules/ui/src/app/pages/risk-assessment/risk-assessment.component.spec.ts @@ -26,7 +26,12 @@ import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; import { TestRunService } from '../../services/test-run.service'; import SpyObj = jasmine.SpyObj; import { MatSidenavModule } from '@angular/material/sidenav'; -import { NEW_PROFILE_MOCK, PROFILE_MOCK } from '../../mocks/profile.mock'; +import { + COPY_PROFILE_MOCK, + NEW_PROFILE_MOCK, + NEW_PROFILE_MOCK_DRAFT, + PROFILE_MOCK, +} from '../../mocks/profile.mock'; import { of } from 'rxjs'; import { Component, Input } from '@angular/core'; import { Profile, ProfileFormat } from '../../model/profile'; @@ -217,6 +222,13 @@ describe('RiskAssessmentComponent', () => { }); }); + describe('#getCopyOfProfile', () => { + it('should open the form with copy of profile', () => { + const copy = component.getCopyOfProfile(PROFILE_MOCK); + expect(copy).toEqual(COPY_PROFILE_MOCK); + }); + }); + describe('#saveProfile', () => { describe('with no profile selected', () => { beforeEach(() => { @@ -236,7 +248,7 @@ describe('RiskAssessmentComponent', () => { }); describe('with profile selected', () => { - it('should open save profile modal', fakeAsync(() => { + it('should open save profile modal for valid profile', fakeAsync(() => { const openSpy = spyOn(component.dialog, 'open').and.returnValue({ afterClosed: () => of(true), } as MatDialogRef); @@ -244,9 +256,31 @@ describe('RiskAssessmentComponent', () => { component.saveProfileClicked(NEW_PROFILE_MOCK, PROFILE_MOCK); expect(openSpy).toHaveBeenCalledWith(SimpleDialogComponent, { - ariaLabel: 'Save changes', + ariaLabel: 'Save profile', data: { - title: 'Save changes', + title: 'Save profile', + content: `You are about to save changes in Primary profile. Are you sure?`, + }, + autoFocus: true, + hasBackdrop: true, + disableClose: true, + panelClass: 'simple-dialog', + }); + + openSpy.calls.reset(); + })); + + it('should open save draft profile modal', fakeAsync(() => { + const openSpy = spyOn(component.dialog, 'open').and.returnValue({ + afterClosed: () => of(true), + } as MatDialogRef); + + component.saveProfileClicked(NEW_PROFILE_MOCK_DRAFT, PROFILE_MOCK); + + expect(openSpy).toHaveBeenCalledWith(SimpleDialogComponent, { + ariaLabel: 'Save draft profile', + data: { + title: 'Save draft profile', content: `You are about to save changes in Primary profile. Are you sure?`, }, autoFocus: true, @@ -284,6 +318,42 @@ describe('RiskAssessmentComponent', () => { })); }); }); + + describe('#discard', () => { + describe('with no selected profile', () => { + beforeEach(() => { + component.discard(null); + }); + + it('should call setFocusOnCreateButton', () => { + expect( + mockRiskAssessmentStore.setFocusOnCreateButton + ).toHaveBeenCalled(); + }); + + it('should close the form', () => { + expect(component.isOpenProfileForm).toBeFalse(); + }); + }); + + describe('with selected profile', () => { + beforeEach(() => { + component.discard(PROFILE_MOCK); + }); + + it('should call setFocusOnCreateButton', () => { + expect( + mockRiskAssessmentStore.setFocusOnSelectedProfile + ).toHaveBeenCalled(); + }); + + it('should update selected profile', () => { + expect( + mockRiskAssessmentStore.updateSelectedProfile + ).toHaveBeenCalledWith(null); + }); + }); + }); }); }); diff --git a/modules/ui/src/app/pages/risk-assessment/risk-assessment.component.ts b/modules/ui/src/app/pages/risk-assessment/risk-assessment.component.ts index 503d87a52..dd3d33d9d 100644 --- a/modules/ui/src/app/pages/risk-assessment/risk-assessment.component.ts +++ b/modules/ui/src/app/pages/risk-assessment/risk-assessment.component.ts @@ -24,8 +24,9 @@ import { SimpleDialogComponent } from '../../components/simple-dialog/simple-dia import { Subject, takeUntil } from 'rxjs'; import { MatDialog } from '@angular/material/dialog'; import { LiveAnnouncer } from '@angular/cdk/a11y'; -import { Profile } from '../../model/profile'; +import { Profile, ProfileStatus } from '../../model/profile'; import { Observable } from 'rxjs/internal/Observable'; +import { DeviceValidators } from '../devices/components/device-form/device.validators'; @Component({ selector: 'app-risk-assessment', @@ -53,6 +54,12 @@ export class RiskAssessmentComponent implements OnInit, OnDestroy { this.destroy$.unsubscribe(); } + async profileClicked(profile: Profile | null = null) { + if (profile === null || profile.status !== ProfileStatus.EXPIRED) { + await this.openForm(profile); + } + } + async openForm(profile: Profile | null = null) { this.isOpenProfileForm = true; this.store.updateSelectedProfile(profile); @@ -60,6 +67,27 @@ export class RiskAssessmentComponent implements OnInit, OnDestroy { this.store.setFocusOnProfileForm(); } + async copyProfileAndOpenForm(profile: Profile) { + await this.openForm(this.getCopyOfProfile(profile)); + } + + getCopyOfProfile(profile: Profile): Profile { + const copyOfProfile = { ...profile }; + copyOfProfile.name = this.getCopiedProfileName(profile.name); + delete copyOfProfile.created; // new profile is not create yet + return copyOfProfile; + } + + private getCopiedProfileName(name: string): string { + name = `Copy of ${name}`; + if (name.length > DeviceValidators.STRING_FORMAT_MAX_LENGTH) { + name = + name.substring(0, DeviceValidators.STRING_FORMAT_MAX_LENGTH - 3) + + '...'; + } + return name; + } + deleteProfile( profileName: string, index: number, @@ -94,7 +122,10 @@ export class RiskAssessmentComponent implements OnInit, OnDestroy { this.saveProfile(profile); this.store.setFocusOnCreateButton(); } else { - this.openSaveDialog(selectedProfile.name) + this.openSaveDialog( + selectedProfile.name, + profile.status === ProfileStatus.DRAFT + ) .pipe(takeUntil(this.destroy$)) .subscribe(saveProfile => { if (saveProfile) { @@ -105,8 +136,18 @@ export class RiskAssessmentComponent implements OnInit, OnDestroy { } } - trackByIndex = (index: number): number => { - return index; + discard(selectedProfile: Profile | null) { + this.isOpenProfileForm = false; + if (selectedProfile) { + this.store.setFocusOnSelectedProfile(); + this.store.updateSelectedProfile(null); + } else { + this.store.setFocusOnCreateButton(); + } + } + + trackByName = (index: number, item: Profile): string => { + return item.name; }; private closeFormAfterDelete(name: string, selectedProfile: Profile | null) { @@ -132,11 +173,14 @@ export class RiskAssessmentComponent implements OnInit, OnDestroy { this.store.setFocus({ nextItem, firstItem }); } - private openSaveDialog(profileName: string): Observable { + private openSaveDialog( + profileName: string, + draft: boolean = false + ): Observable { const dialogRef = this.dialog.open(SimpleDialogComponent, { - ariaLabel: 'Save changes', + ariaLabel: `Save ${draft ? 'draft profile' : 'profile'}`, data: { - title: 'Save changes', + title: `Save ${draft ? 'draft profile' : 'profile'}`, content: `You are about to save changes in ${profileName}. Are you sure?`, }, autoFocus: true, diff --git a/modules/ui/src/app/pages/settings/settings.component.html b/modules/ui/src/app/pages/settings/settings.component.html index 36849b42e..089ebd5eb 100644 --- a/modules/ui/src/app/pages/settings/settings.component.html +++ b/modules/ui/src/app/pages/settings/settings.component.html @@ -116,7 +116,7 @@

System settings

- Warning! No ports is detected. + Warning! No ports detected.
diff --git a/modules/ui/src/app/pages/settings/settings.store.spec.ts b/modules/ui/src/app/pages/settings/settings.store.spec.ts index 669faef98..b51e1f2a6 100644 --- a/modules/ui/src/app/pages/settings/settings.store.spec.ts +++ b/modules/ui/src/app/pages/settings/settings.store.spec.ts @@ -25,13 +25,17 @@ import { TestBed } from '@angular/core/testing'; import { MockStore, provideMockStore } from '@ngrx/store/testing'; import { AppState } from '../../store/state'; import { skip, take } from 'rxjs'; -import { selectHasConnectionSettings } from '../../store/selectors'; +import { + selectAdapters, + selectHasConnectionSettings, +} from '../../store/selectors'; import { of } from 'rxjs/internal/observable/of'; import { fetchSystemConfigSuccess } from '../../store/actions'; import { fetchInterfacesSuccess } from '../../store/actions'; import { FormBuilder, FormControl } from '@angular/forms'; import { FormKey, SystemConfig } from '../../model/setting'; import { + MOCK_ADAPTERS, MOCK_DEVICE_VALUE, MOCK_INTERFACE_VALUE, MOCK_INTERFACES, @@ -60,7 +64,10 @@ describe('SettingsStore', () => { SettingsStore, { provide: TestRunService, useValue: mockService }, provideMockStore({ - selectors: [{ selector: selectHasConnectionSettings, value: true }], + selectors: [ + { selector: selectHasConnectionSettings, value: true }, + { selector: selectAdapters, value: {} }, + ], }), FormBuilder, ], @@ -308,5 +315,39 @@ describe('SettingsStore', () => { }); }); }); + + describe('adaptersUpdate', () => { + const updateInterfaces = { + mockDeviceKey: 'mockDeviceValue', + mockNewInternetKey: 'mockNewInternetValue', + }; + const updateInternetOptions = { + '': 'Not specified', + mockDeviceKey: 'mockDeviceValue', + mockNewInternetKey: 'mockNewInternetValue', + }; + + beforeEach(() => { + settingsStore.setInterfaces(MOCK_INTERFACES); + }); + + it('should update store', done => { + settingsStore.viewModel$ + .pipe(skip(3), take(1)) + .subscribe(storeValue => { + expect(storeValue.interfaces).toEqual(updateInterfaces); + expect(storeValue.deviceOptions).toEqual(updateInterfaces); + expect(storeValue.internetOptions).toEqual(updateInternetOptions); + + expect(store.dispatch).toHaveBeenCalledWith( + fetchInterfacesSuccess({ interfaces: updateInterfaces }) + ); + done(); + }); + + store.overrideSelector(selectAdapters, MOCK_ADAPTERS); + store.refreshState(); + }); + }); }); }); diff --git a/modules/ui/src/app/pages/settings/settings.store.ts b/modules/ui/src/app/pages/settings/settings.store.ts index f489228a9..fc4a00dc9 100644 --- a/modules/ui/src/app/pages/settings/settings.store.ts +++ b/modules/ui/src/app/pages/settings/settings.store.ts @@ -23,12 +23,15 @@ import { SystemConfig, SystemInterfaces, } from '../../model/setting'; -import { exhaustMap, switchMap, Observable } from 'rxjs'; +import { exhaustMap, switchMap, Observable, skip } from 'rxjs'; import { tap, withLatestFrom } from 'rxjs/operators'; import * as AppActions from '../../store/actions'; import { Store } from '@ngrx/store'; import { AppState } from '../../store/state'; -import { selectHasConnectionSettings } from '../../store/selectors'; +import { + selectAdapters, + selectHasConnectionSettings, +} from '../../store/selectors'; import { FormControl, FormGroup } from '@angular/forms'; export interface SettingsComponentState { @@ -75,6 +78,8 @@ export class SettingsStore extends ComponentStore { private hasConnectionSettings$ = this.store.select( selectHasConnectionSettings ); + + private adapters$ = this.store.select(selectAdapters); private isSubmitting$ = this.select(state => state.isSubmitting); private isLessThanOneInterfaces$ = this.select( state => state.isLessThanOneInterface @@ -108,26 +113,25 @@ export class SettingsStore extends ComponentStore { isSubmitting, })); - setInterfaces = this.updater((state, interfaces: SystemInterfaces) => ({ - ...state, - interfaces, - deviceOptions: interfaces, - internetOptions: { - ...DEFAULT_INTERNET_OPTION, - ...interfaces, - }, - isLessThanOneInterface: Object.keys(interfaces).length < 1, - })); + setInterfaces = this.updater((state, interfaces: SystemInterfaces) => { + return { + ...state, + interfaces, + deviceOptions: interfaces, + internetOptions: { + ...DEFAULT_INTERNET_OPTION, + ...interfaces, + }, + isLessThanOneInterface: Object.keys(interfaces).length < 1, + }; + }); getInterfaces = this.effect(trigger$ => { return trigger$.pipe( exhaustMap(() => { return this.testRunService.getSystemInterfaces().pipe( tap((interfaces: SystemInterfaces) => { - this.store.dispatch( - AppActions.fetchInterfacesSuccess({ interfaces }) - ); - this.setInterfaces(interfaces); + this.updateInterfaces(interfaces); }) ); }) @@ -202,6 +206,48 @@ export class SettingsStore extends ComponentStore { ); }); + adaptersUpdate = this.effect(() => { + return this.adapters$.pipe( + skip(1), + withLatestFrom(this.interfaces$), + tap(([adapters, interfaces]) => { + const updatedInterfaces = { ...interfaces }; + if (adapters.adapters_added) { + this.addInterfaces(adapters.adapters_added, updatedInterfaces); + } + if (adapters.adapters_removed) { + this.removeInterfaces(adapters.adapters_removed, updatedInterfaces); + } + this.updateInterfaces(updatedInterfaces); + }) + ); + }); + + private updateInterfaces(interfaces: SystemInterfaces) { + this.store.dispatch( + AppActions.fetchInterfacesSuccess({ interfaces: interfaces }) + ); + this.setInterfaces(interfaces); + } + + private addInterfaces( + newInterfaces: SystemInterfaces, + interfaces: SystemInterfaces + ): void { + for (const [key, value] of Object.entries(newInterfaces)) { + interfaces[key] = value; + } + } + + private removeInterfaces( + interfacesToDelete: SystemInterfaces, + interfaces: SystemInterfaces + ): void { + for (const key of Object.keys(interfacesToDelete)) { + delete interfaces[key]; + } + } + private setDefaultDeviceInterfaceValue( value: string | undefined, options: { [key: string]: string }, diff --git a/modules/ui/src/app/pages/testrun/components/testrun-initiate-form/testrun-initiate-form.component.spec.ts b/modules/ui/src/app/pages/testrun/components/testrun-initiate-form/testrun-initiate-form.component.spec.ts index dd614c9d1..2a99f17b0 100644 --- a/modules/ui/src/app/pages/testrun/components/testrun-initiate-form/testrun-initiate-form.component.spec.ts +++ b/modules/ui/src/app/pages/testrun/components/testrun-initiate-form/testrun-initiate-form.component.spec.ts @@ -29,7 +29,7 @@ import { ReactiveFormsModule } from '@angular/forms'; import { MatInputModule } from '@angular/material/input'; import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; import { DeviceTestsComponent } from '../../../../components/device-tests/device-tests.component'; -import { device } from '../../../../mocks/device.mock'; +import { device, MOCK_TEST_MODULES } from '../../../../mocks/device.mock'; import { of } from 'rxjs'; import { MOCK_PROGRESS_DATA_WAITING_FOR_DEVICE } from '../../../../mocks/testrun.mock'; import { SpinnerComponent } from '../../../../components/spinner/spinner.component'; @@ -44,25 +44,12 @@ describe('ProgressInitiateFormComponent', () => { const testRunServiceMock = jasmine.createSpyObj([ 'getDevices', 'fetchDevices', - 'getTestModules', 'startTestrun', 'systemStatus$', 'getSystemStatus', 'fetchVersion', 'setIsOpenStartTestrun', ]); - testRunServiceMock.getTestModules.and.returnValue([ - { - displayName: 'Connection', - name: 'connection', - enabled: true, - }, - { - displayName: 'DNS', - name: 'dns', - enabled: false, - }, - ]); testRunServiceMock.getDevices.and.returnValue( new BehaviorSubject([device, device]) ); @@ -81,7 +68,10 @@ describe('ProgressInitiateFormComponent', () => { close: () => ({}), }, }, - { provide: MAT_DIALOG_DATA, useValue: {} }, + { + provide: MAT_DIALOG_DATA, + useValue: { testModules: MOCK_TEST_MODULES }, + }, provideMockStore({ selectors: [{ selector: selectDevices, value: [device, device] }], }), @@ -214,7 +204,7 @@ describe('ProgressInitiateFormComponent', () => { connection: { enabled: true, }, - dns: { + udmi: { enabled: true, }, }, diff --git a/modules/ui/src/app/pages/testrun/components/testrun-initiate-form/testrun-initiate-form.component.ts b/modules/ui/src/app/pages/testrun/components/testrun-initiate-form/testrun-initiate-form.component.ts index c1a2afb92..a526e0973 100644 --- a/modules/ui/src/app/pages/testrun/components/testrun-initiate-form/testrun-initiate-form.component.ts +++ b/modules/ui/src/app/pages/testrun/components/testrun-initiate-form/testrun-initiate-form.component.ts @@ -42,6 +42,7 @@ import { TestrunStatus } from '../../../../model/testrun-status'; interface DialogData { device?: Device; + testModules: TestModule[]; } @Component({ @@ -91,7 +92,7 @@ export class TestrunInitiateFormComponent ngOnInit() { this.createInitiateForm(); - this.testModules = this.testRunService.getTestModules(); + this.testModules = this.data?.testModules; if (this.data?.device) { this.deviceSelected(this.data.device); diff --git a/modules/ui/src/app/pages/testrun/testrun.component.html b/modules/ui/src/app/pages/testrun/testrun.component.html index d485d1926..74b414abf 100644 --- a/modules/ui/src/app/pages/testrun/testrun.component.html +++ b/modules/ui/src/app/pages/testrun/testrun.component.html @@ -99,7 +99,7 @@

isTestrunInProgress(systemStatus?.status) || systemStatus?.status === StatusOfTestrun.Cancelling " - (click)="openTestRunModal()" + (click)="openTestRunModal(vm.testModules)" mat-flat-button> Start New Testrun diff --git a/modules/ui/src/app/pages/testrun/testrun.component.spec.ts b/modules/ui/src/app/pages/testrun/testrun.component.spec.ts index 98d1b986f..5867bc313 100644 --- a/modules/ui/src/app/pages/testrun/testrun.component.spec.ts +++ b/modules/ui/src/app/pages/testrun/testrun.component.spec.ts @@ -53,6 +53,7 @@ import { selectIsOpenWaitSnackBar, selectRiskProfiles, selectSystemStatus, + selectTestModules, } from '../../store/selectors'; import { TestrunStore } from './testrun.store'; import { @@ -123,6 +124,7 @@ describe('TestrunComponent', () => { { selector: selectIsOpenWaitSnackBar, value: false }, { selector: selectHasRiskProfiles, value: false }, { selector: selectRiskProfiles, value: [] }, + { selector: selectTestModules, value: [] }, { selector: selectSystemStatus, value: MOCK_PROGRESS_DATA_IN_PROGRESS, @@ -234,9 +236,17 @@ describe('TestrunComponent', () => { }, provideMockStore({ selectors: [ - { selector: selectHasDevices, value: false }, { selector: selectDevices, value: [] }, + { selector: selectHasDevices, value: false }, + { selector: selectIsOpenStartTestrun, value: false }, { selector: selectIsOpenWaitSnackBar, value: false }, + { selector: selectHasRiskProfiles, value: false }, + { selector: selectRiskProfiles, value: [] }, + { selector: selectTestModules, value: [] }, + { + selector: selectSystemStatus, + value: MOCK_PROGRESS_DATA_IN_PROGRESS, + }, ], }), ], @@ -324,6 +334,9 @@ describe('TestrunComponent', () => { hasBackdrop: true, disableClose: true, panelClass: 'initiate-test-run-dialog', + data: { + testModules: [], + }, }); expect(store.dispatch).toHaveBeenCalledWith( fetchSystemStatusSuccess({ @@ -405,6 +418,7 @@ describe('TestrunComponent', () => { MOCK_PROGRESS_DATA_COMPLIANT ); store.overrideSelector(selectHasDevices, true); + store.refreshState(); fixture.detectChanges(); }); diff --git a/modules/ui/src/app/pages/testrun/testrun.component.ts b/modules/ui/src/app/pages/testrun/testrun.component.ts index b861ef953..ce5c104d5 100644 --- a/modules/ui/src/app/pages/testrun/testrun.component.ts +++ b/modules/ui/src/app/pages/testrun/testrun.component.ts @@ -34,6 +34,8 @@ import { FocusManagerService } from '../../services/focus-manager.service'; import { TestrunStore } from './testrun.store'; import { TestRunService } from '../../services/test-run.service'; import { NotificationService } from '../../services/notification.service'; +import { TestModule } from '../../model/device'; +import { combineLatest } from 'rxjs/internal/observable/combineLatest'; @Component({ selector: 'app-progress', @@ -60,11 +62,14 @@ export class TestrunComponent implements OnInit, OnDestroy { ) {} ngOnInit(): void { - this.testrunStore.isOpenStartTestrun$ + combineLatest([ + this.testrunStore.isOpenStartTestrun$, + this.testrunStore.testModules$, + ]) .pipe(takeUntil(this.destroy$)) - .subscribe(isOpenStartTestrun => { + .subscribe(([isOpenStartTestrun, testModules]) => { if (isOpenStartTestrun) { - this.openTestRunModal(); + this.openTestRunModal(testModules); } }); } @@ -126,13 +131,16 @@ export class TestrunComponent implements OnInit, OnDestroy { this.destroy$.unsubscribe(); } - openTestRunModal(): void { + openTestRunModal(testModules: TestModule[]): void { const dialogRef = this.dialog.open(TestrunInitiateFormComponent, { ariaLabel: 'Initiate testrun', autoFocus: true, hasBackdrop: true, disableClose: true, panelClass: 'initiate-test-run-dialog', + data: { + testModules, + }, }); dialogRef diff --git a/modules/ui/src/app/pages/testrun/testrun.store.spec.ts b/modules/ui/src/app/pages/testrun/testrun.store.spec.ts index 03f7817af..e8be0f93d 100644 --- a/modules/ui/src/app/pages/testrun/testrun.store.spec.ts +++ b/modules/ui/src/app/pages/testrun/testrun.store.spec.ts @@ -23,6 +23,7 @@ import { selectIsOpenStartTestrun, selectRiskProfiles, selectSystemStatus, + selectTestModules, } from '../../store/selectors'; import { fetchSystemStatus, @@ -66,6 +67,7 @@ describe('TestrunStore', () => { { selector: selectHasConnectionSettings, value: true }, { selector: selectIsOpenStartTestrun, value: false }, { selector: selectRiskProfiles, value: [] }, + { selector: selectTestModules, value: [] }, ], }), ], @@ -89,6 +91,7 @@ describe('TestrunStore', () => { dataSource: [], stepsToResolveCount: 0, profiles: [], + testModules: [], }); done(); }); diff --git a/modules/ui/src/app/pages/testrun/testrun.store.ts b/modules/ui/src/app/pages/testrun/testrun.store.ts index eacad9959..4f4edabd2 100644 --- a/modules/ui/src/app/pages/testrun/testrun.store.ts +++ b/modules/ui/src/app/pages/testrun/testrun.store.ts @@ -24,6 +24,7 @@ import { selectIsOpenStartTestrun, selectRiskProfiles, selectSystemStatus, + selectTestModules, } from '../../store/selectors'; import { fetchSystemStatus, @@ -41,12 +42,14 @@ import { } from '../../model/testrun-status'; import { FocusManagerService } from '../../services/focus-manager.service'; import { LoaderService } from '../../services/loader.service'; +import { TestModule } from '../../model/device'; const EMPTY_RESULT = new Array(100).fill(null).map(() => ({}) as IResult); export interface TestrunComponentState { dataSource: IResult[] | undefined; stepsToResolveCount: number; + testModules: TestModule[]; } @Injectable() @@ -59,12 +62,15 @@ export class TestrunStore extends ComponentStore { private profiles$ = this.store.select(selectRiskProfiles); private systemStatus$ = this.store.select(selectSystemStatus); isOpenStartTestrun$ = this.store.select(selectIsOpenStartTestrun); + testModules$ = this.store.select(selectTestModules); + viewModel$ = this.select({ hasDevices: this.hasDevices$, systemStatus: this.systemStatus$, dataSource: this.dataSource$, stepsToResolveCount: this.stepsToResolveCount$, profiles: this.profiles$, + testModules: this.testModules$, }); setDataSource = this.updater((state, dataSource: IResult[] | undefined) => { @@ -215,6 +221,7 @@ export class TestrunStore extends ComponentStore { super({ dataSource: undefined, stepsToResolveCount: 0, + testModules: [], }); } } diff --git a/modules/ui/src/app/services/test-run-mqtt.service.spec.ts b/modules/ui/src/app/services/test-run-mqtt.service.spec.ts new file mode 100644 index 000000000..19bda437a --- /dev/null +++ b/modules/ui/src/app/services/test-run-mqtt.service.spec.ts @@ -0,0 +1,102 @@ +import { TestBed } from '@angular/core/testing'; + +import { TestRunMqttService } from './test-run-mqtt.service'; +import { IMqttMessage, MqttModule, MqttService } from 'ngx-mqtt'; +import { MQTT_SERVICE_OPTIONS } from '../app.module'; +import SpyObj = jasmine.SpyObj; +import { of } from 'rxjs'; +import { MOCK_ADAPTERS } from '../mocks/settings.mock'; +import { Topic } from '../model/topic'; +import { MOCK_INTERNET } from '../mocks/topic.mock'; +import { MOCK_PROGRESS_DATA_IN_PROGRESS } from '../mocks/testrun.mock'; + +describe('TestRunMqttService', () => { + let service: TestRunMqttService; + let mockService: SpyObj; + + beforeEach(() => { + mockService = jasmine.createSpyObj(['observe']); + + TestBed.configureTestingModule({ + imports: [MqttModule.forRoot(MQTT_SERVICE_OPTIONS)], + providers: [{ provide: MqttService, useValue: mockService }], + }); + service = TestBed.inject(TestRunMqttService); + }); + + it('should be created', () => { + expect(service).toBeTruthy(); + }); + + describe('getNetworkAdapters', () => { + beforeEach(() => { + mockService.observe.and.returnValue(of(getResponse(MOCK_ADAPTERS))); + }); + + it('should subscribe the topic', done => { + service.getNetworkAdapters().subscribe(() => { + expect(mockService.observe).toHaveBeenCalledWith(Topic.NetworkAdapters); + done(); + }); + }); + + it('should return object of type', done => { + service.getNetworkAdapters().subscribe(res => { + expect(res).toEqual(MOCK_ADAPTERS); + done(); + }); + }); + }); + + describe('getInternetConnection', () => { + beforeEach(() => { + mockService.observe.and.returnValue(of(getResponse(MOCK_INTERNET))); + }); + + it('should subscribe the topic', done => { + service.getInternetConnection().subscribe(() => { + expect(mockService.observe).toHaveBeenCalledWith( + Topic.InternetConnection + ); + done(); + }); + }); + + it('should return object of type', done => { + service.getInternetConnection().subscribe(res => { + expect(res).toEqual(MOCK_INTERNET); + done(); + }); + }); + }); + + describe('getStatus', () => { + beforeEach(() => { + mockService.observe.and.returnValue( + of(getResponse(MOCK_PROGRESS_DATA_IN_PROGRESS)) + ); + }); + + it('should subscribe the topic', done => { + service.getStatus().subscribe(() => { + expect(mockService.observe).toHaveBeenCalledWith(Topic.Status); + done(); + }); + }); + + it('should return object of type', done => { + service.getStatus().subscribe(res => { + expect(res).toEqual(MOCK_PROGRESS_DATA_IN_PROGRESS); + done(); + }); + }); + }); + + function getResponse(response: Type): IMqttMessage { + const enc = new TextEncoder(); + const message = enc.encode(JSON.stringify(response)); + return { + payload: message, + } as IMqttMessage; + } +}); diff --git a/modules/ui/src/app/services/test-run-mqtt.service.ts b/modules/ui/src/app/services/test-run-mqtt.service.ts new file mode 100644 index 000000000..d5e805da6 --- /dev/null +++ b/modules/ui/src/app/services/test-run-mqtt.service.ts @@ -0,0 +1,38 @@ +import { Injectable } from '@angular/core'; +import { IMqttMessage, MqttService } from 'ngx-mqtt'; +import { catchError, Observable, of } from 'rxjs'; +import { map } from 'rxjs/operators'; +import { Adapters } from '../model/setting'; +import { TestrunStatus } from '../model/testrun-status'; +import { InternetConnection, Topic } from '../model/topic'; + +@Injectable({ + providedIn: 'root', +}) +export class TestRunMqttService { + constructor(private mqttService: MqttService) {} + + getNetworkAdapters(): Observable { + return this.topic(Topic.NetworkAdapters); + } + + getInternetConnection(): Observable { + return this.topic(Topic.InternetConnection); + } + + getStatus(): Observable { + return this.topic(Topic.Status); + } + + private topic(topicName: string): Observable { + return this.mqttService.observe(topicName).pipe( + map( + (res: IMqttMessage) => + JSON.parse(new TextDecoder().decode(res.payload)) as Type + ), + catchError(() => { + return of({} as Type); + }) + ); + } +} 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 069c94c0a..c3c40185e 100644 --- a/modules/ui/src/app/services/test-run.service.spec.ts +++ b/modules/ui/src/app/services/test-run.service.spec.ts @@ -18,7 +18,7 @@ import { HttpTestingController, } from '@angular/common/http/testing'; import { fakeAsync, getTestBed, TestBed, tick } from '@angular/core/testing'; -import { Device, TestModule } from '../model/device'; +import { Device } from '../model/device'; import { TestRunService, UNAVAILABLE_VERSION } from './test-run.service'; import { SystemConfig, SystemInterfaces } from '../model/setting'; @@ -28,7 +28,7 @@ import { StatusOfTestrun, TestrunStatus, } from '../model/testrun-status'; -import { device } from '../mocks/device.mock'; +import { device, MOCK_MODULES } from '../mocks/device.mock'; import { NEW_VERSION, VERSION } from '../mocks/version.mock'; import { MockStore, provideMockStore } from '@ngrx/store/testing'; import { AppState } from '../store/state'; @@ -74,39 +74,23 @@ describe('TestRunService', () => { expect(service).toBeTruthy(); }); - it('should have test modules', () => { - expect(service.getTestModules()).toEqual([ - { - displayName: 'Connection', - name: 'connection', - enabled: true, - }, - { - displayName: 'NTP', - name: 'ntp', - enabled: true, - }, - { - displayName: 'DNS', - name: 'dns', - enabled: true, - }, - { - displayName: 'Services', - name: 'services', - enabled: true, - }, - { - displayName: 'TLS', - name: 'tls', - enabled: true, - }, - { - displayName: 'Protocol', - name: 'protocol', - enabled: true, - }, - ] as TestModule[]); + it('getTestModules should return modules', () => { + let result: string[] = []; + const testModules = MOCK_MODULES; + + service.getTestModules().subscribe(res => { + expect(res).toEqual(result); + }); + + result = testModules; + service.getTestModules(); + const req = httpTestingController.expectOne( + 'http://localhost:8000/system/modules' + ); + + expect(req.request.method).toBe('GET'); + + req.flush(testModules); }); it('fetchDevices should return devices', () => { @@ -284,6 +268,8 @@ describe('TestRunService', () => { const statusesForGreyRes = [ StatusOfTestResult.NotDetected, StatusOfTestResult.NotStarted, + StatusOfTestResult.Skipped, + StatusOfTestResult.Disabled, ]; statusesForGreenRes.forEach(testCase => { diff --git a/modules/ui/src/app/services/test-run.service.ts b/modules/ui/src/app/services/test-run.service.ts index 5620f9404..8d913ba61 100644 --- a/modules/ui/src/app/services/test-run.service.ts +++ b/modules/ui/src/app/services/test-run.service.ts @@ -123,8 +123,10 @@ export class TestRunService { .pipe(map(() => true)); } - getTestModules(): TestModule[] { - return this.testModules; + getTestModules(): Observable { + return this.http + .get(`${API_URL}/system/modules`) + .pipe(catchError(() => of([]))); } saveDevice(device: Device): Observable { @@ -183,7 +185,9 @@ export class TestRunService { result === StatusOfTestResult.InProgress, grey: result === StatusOfTestResult.NotDetected || - result === StatusOfTestResult.NotStarted, + result === StatusOfTestResult.NotStarted || + result === StatusOfTestResult.Skipped || + result === StatusOfTestResult.Disabled, }; } diff --git a/modules/ui/src/app/store/actions.ts b/modules/ui/src/app/store/actions.ts index 3ca38d16f..806618932 100644 --- a/modules/ui/src/app/store/actions.ts +++ b/modules/ui/src/app/store/actions.ts @@ -16,12 +16,13 @@ import { createAction, props } from '@ngrx/store'; import { + Adapters, InterfacesValidation, SettingMissedError, SystemConfig, } from '../model/setting'; import { SystemInterfaces } from '../model/setting'; -import { Device } from '../model/device'; +import { Device, TestModule } from '../model/device'; import { TestrunStatus } from '../model/testrun-status'; import { Profile } from '../model/profile'; @@ -124,3 +125,25 @@ export const setStatus = createAction( export const stopInterval = createAction('[Shared] Stop Interval'); export const fetchRiskProfiles = createAction('[Shared] Fetch risk profiles'); + +export const updateAdapters = createAction( + '[Shared] Update Adapters', + props<{ adapters: Adapters }>() +); + +export const fetchReports = createAction('[Shared] Fetch reports'); + +export const setReports = createAction( + '[Shared] Set Reports', + props<{ reports: TestrunStatus[] }>() +); + +export const setTestModules = createAction( + '[Shared] Set Test Modules', + props<{ testModules: TestModule[] }>() +); + +export const updateInternetConnection = createAction( + '[Shared] Fetch internet connection', + props<{ internetConnection: boolean | null }>() +); diff --git a/modules/ui/src/app/store/effects.spec.ts b/modules/ui/src/app/store/effects.spec.ts index 024782c63..7d33cc209 100644 --- a/modules/ui/src/app/store/effects.spec.ts +++ b/modules/ui/src/app/store/effects.spec.ts @@ -36,12 +36,24 @@ import { import { device } from '../mocks/device.mock'; import { MOCK_PROGRESS_DATA_CANCELLING, + MOCK_PROGRESS_DATA_COMPLIANT, MOCK_PROGRESS_DATA_IN_PROGRESS, MOCK_PROGRESS_DATA_WAITING_FOR_DEVICE, } from '../mocks/testrun.mock'; -import { fetchSystemStatus, setStatus, setTestrunStatus } from './actions'; +import { + fetchSystemStatus, + fetchSystemStatusSuccess, + setReports, + setStatus, + setTestrunStatus, +} from './actions'; import { NotificationService } from '../services/notification.service'; import { PROFILE_MOCK } from '../mocks/profile.mock'; +import { throwError } from 'rxjs/internal/observable/throwError'; +import { HttpErrorResponse } from '@angular/common/http'; +import { IDLE_STATUS } from '../model/testrun-status'; +import { HISTORY } from '../mocks/reports.mock'; +import { TestRunMqttService } from '../services/test-run-mqtt.service'; describe('Effects', () => { let actions$ = new Observable(); @@ -54,6 +66,11 @@ describe('Effects', () => { 'dismissWithTimout', 'openSnackBar', ]); + const mockMqttService: jasmine.SpyObj = + jasmine.createSpyObj('mockMqttService', [ + 'getStatus', + 'getInternetConnection', + ]); beforeEach(() => { testRunServiceMock = jasmine.createSpyObj('testRunServiceMock', [ @@ -64,6 +81,7 @@ describe('Effects', () => { 'testrunInProgress', 'stopTestrun', 'fetchProfiles', + 'getHistory', ]); testRunServiceMock.getSystemInterfaces.and.returnValue(of({})); testRunServiceMock.getSystemConfig.and.returnValue(of({ network: {} })); @@ -72,12 +90,21 @@ describe('Effects', () => { of(MOCK_PROGRESS_DATA_IN_PROGRESS) ); testRunServiceMock.fetchProfiles.and.returnValue(of([])); + testRunServiceMock.getHistory.and.returnValue(of([])); + mockMqttService.getInternetConnection.and.returnValue( + of({ connection: false }) + ); + + mockMqttService.getStatus.and.returnValue( + of(MOCK_PROGRESS_DATA_IN_PROGRESS) + ); TestBed.configureTestingModule({ providers: [ AppEffects, { provide: TestRunService, useValue: testRunServiceMock }, { provide: NotificationService, useValue: notificationServiceMock }, + { provide: TestRunMqttService, useValue: mockMqttService }, provideMockActions(() => actions$), provideMockStore({}), ], @@ -387,14 +414,15 @@ describe('Effects', () => { ); }); - it('should call fetchSystemStatus for status "in progress"', fakeAsync(() => { + it('should call fetchSystemStatus for status "in progress"', () => { effects.onFetchSystemStatusSuccess$.subscribe(() => { - tick(5000); - - expect(dispatchSpy).toHaveBeenCalledWith(fetchSystemStatus()); - discardPeriodicTasks(); + expect(dispatchSpy).toHaveBeenCalledWith( + fetchSystemStatusSuccess({ + systemStatus: MOCK_PROGRESS_DATA_IN_PROGRESS, + }) + ); }); - })); + }); it('should dispatch status and systemStatus', done => { effects.onFetchSystemStatusSuccess$.subscribe(() => { @@ -423,6 +451,12 @@ describe('Effects', () => { done(); }); }); + + it('should call fetchInternetConnection for status "in progress"', () => { + effects.onFetchSystemStatusSuccess$.subscribe(() => { + expect(mockMqttService.getInternetConnection).toHaveBeenCalled(); + }); + }); }); describe('with status "waiting for device"', () => { @@ -439,14 +473,15 @@ describe('Effects', () => { ); }); - it('should call fetchSystemStatus for status "waiting for device"', fakeAsync(() => { + it('should call fetchSystemStatus for status "waiting for device"', () => { effects.onFetchSystemStatusSuccess$.subscribe(() => { - tick(5000); - - expect(dispatchSpy).toHaveBeenCalledWith(fetchSystemStatus()); - discardPeriodicTasks(); + expect(dispatchSpy).toHaveBeenCalledWith( + fetchSystemStatusSuccess({ + systemStatus: MOCK_PROGRESS_DATA_IN_PROGRESS, + }) + ); }); - })); + }); it('should open snackbar when waiting for device is too long', fakeAsync(() => { effects.onFetchSystemStatusSuccess$.subscribe(() => { @@ -487,4 +522,78 @@ describe('Effects', () => { done(); }); }); + + describe('onFetchReports$', () => { + it(' should call setReports on success', done => { + testRunServiceMock.getHistory.and.returnValue(of([])); + actions$ = of(actions.fetchReports()); + + effects.onFetchReports$.subscribe(action => { + expect(action).toEqual( + actions.setReports({ + reports: [], + }) + ); + done(); + }); + }); + + it('should call setReports with empty array if null is returned', done => { + testRunServiceMock.getHistory.and.returnValue(of(null)); + actions$ = of(actions.fetchReports()); + + effects.onFetchReports$.subscribe(action => { + expect(action).toEqual( + actions.setReports({ + reports: [], + }) + ); + done(); + }); + }); + + it('should call setReports with empty array if error happens', done => { + testRunServiceMock.getHistory.and.returnValue( + throwError( + new HttpErrorResponse({ error: { error: 'error' }, status: 500 }) + ) + ); + actions$ = of(actions.fetchReports()); + + effects.onFetchReports$.subscribe({ + complete: () => { + expect(dispatchSpy).toHaveBeenCalledWith( + setReports({ + reports: [], + }) + ); + done(); + }, + }); + }); + }); + + describe('checkStatusInReports$', () => { + it('should call setTestrunStatus if current test run is completed and not present in reports', done => { + store.overrideSelector( + selectSystemStatus, + Object.assign({}, MOCK_PROGRESS_DATA_COMPLIANT, { + mac_addr: '01:02:03:04:05:07', + report: 'http://localhost:8000/report/1234 1234/2024-07-17T15:33:40', + }) + ); + actions$ = of( + actions.setReports({ + reports: HISTORY, + }) + ); + + effects.checkStatusInReports$.subscribe(action => { + expect(action).toEqual( + actions.setTestrunStatus({ systemStatus: IDLE_STATUS }) + ); + done(); + }); + }); + }); }); diff --git a/modules/ui/src/app/store/effects.ts b/modules/ui/src/app/store/effects.ts index b6cdfcc71..5fdb4d461 100644 --- a/modules/ui/src/app/store/effects.ts +++ b/modules/ui/src/app/store/effects.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { Injectable, NgZone } from '@angular/core'; +import { Injectable } from '@angular/core'; import { Actions, createEffect, ofType } from '@ngrx/effects'; import { Store } from '@ngrx/store'; import { map, switchMap, tap, withLatestFrom } from 'rxjs/operators'; @@ -22,29 +22,49 @@ import { map, switchMap, tap, withLatestFrom } from 'rxjs/operators'; import * as AppActions from './actions'; import { AppState } from './state'; import { TestRunService } from '../services/test-run.service'; -import { filter, combineLatest, interval, Subject, timer, take } from 'rxjs'; +import { + filter, + combineLatest, + Subject, + timer, + take, + catchError, + EMPTY, + Subscription, +} from 'rxjs'; import { selectIsOpenWaitSnackBar, selectMenuOpened, selectSystemStatus, } from './selectors'; -import { IResult, StatusOfTestrun, TestsData } from '../model/testrun-status'; +import { + IDLE_STATUS, + IResult, + StatusOfTestrun, + TestrunStatus, + TestsData, +} from '../model/testrun-status'; import { fetchSystemStatus, + fetchSystemStatusSuccess, + setReports, setStatus, setTestrunStatus, stopInterval, + updateInternetConnection, } from './actions'; import { takeUntil } from 'rxjs/internal/operators/takeUntil'; import { NotificationService } from '../services/notification.service'; import { Profile } from '../model/profile'; +import { TestRunMqttService } from '../services/test-run-mqtt.service'; +import { InternetConnection } from '../model/topic'; const WAIT_TO_OPEN_SNACKBAR_MS = 60 * 1000; @Injectable() export class AppEffects { - private startInterval = false; - private destroyInterval$: Subject = new Subject(); + private statusSubscription: Subscription | undefined; + private internetSubscription: Subscription | undefined; private destroyWaitDeviceInterval$: Subject = new Subject(); checkInterfacesInConfig$ = createEffect(() => @@ -190,8 +210,8 @@ export class AppEffects { return this.actions$.pipe( ofType(AppActions.stopInterval), tap(() => { - this.startInterval = false; - this.destroyInterval$.next(true); + this.statusSubscription?.unsubscribe(); + this.internetSubscription?.unsubscribe(); }) ); }, @@ -203,11 +223,9 @@ export class AppEffects { return this.actions$.pipe( ofType(AppActions.fetchSystemStatusSuccess), tap(({ systemStatus }) => { - if ( - this.testrunService.testrunInProgress(systemStatus.status) && - !this.startInterval - ) { + if (this.testrunService.testrunInProgress(systemStatus.status)) { this.pullingSystemStatusData(); + this.fetchInternetConnection(); } else if ( !this.testrunService.testrunInProgress(systemStatus.status) ) { @@ -235,12 +253,10 @@ export class AppEffects { tap(([{ systemStatus }, , status]) => { // for app - requires only status if (systemStatus.status !== status?.status) { - this.ngZone.run(() => { - this.store.dispatch(setStatus({ status: systemStatus.status })); - this.store.dispatch( - setTestrunStatus({ systemStatus: systemStatus }) - ); - }); + this.store.dispatch(setStatus({ status: systemStatus.status })); + this.store.dispatch( + setTestrunStatus({ systemStatus: systemStatus }) + ); } else if ( systemStatus.finished !== status?.finished || (systemStatus.tests as TestsData)?.results?.length !== @@ -248,11 +264,9 @@ export class AppEffects { (systemStatus.tests as IResult[])?.length !== (status?.tests as IResult[])?.length ) { - this.ngZone.run(() => { - this.store.dispatch( - setTestrunStatus({ systemStatus: systemStatus }) - ); - }); + this.store.dispatch( + setTestrunStatus({ systemStatus: systemStatus }) + ); } }) ); @@ -273,6 +287,53 @@ export class AppEffects { ); }); + onFetchReports$ = createEffect(() => { + return this.actions$.pipe( + ofType(AppActions.fetchReports), + switchMap(() => + this.testrunService.getHistory().pipe( + map((reports: TestrunStatus[] | null) => { + if (reports !== null) { + return AppActions.setReports({ reports }); + } + return AppActions.setReports({ reports: [] }); + }), + catchError(() => { + this.store.dispatch(setReports({ reports: [] })); + return EMPTY; + }) + ) + ) + ); + }); + + checkStatusInReports$ = createEffect(() => { + return this.actions$.pipe( + ofType(AppActions.setReports), + withLatestFrom(this.store.select(selectSystemStatus)), + filter(([, systemStatus]) => { + return ( + systemStatus != null && this.isTestrunFinished(systemStatus.status) + ); + }), + filter(([{ reports }, systemStatus]) => { + return ( + !reports?.some(report => report.report === systemStatus!.report) || + false + ); + }), + map(() => AppActions.setTestrunStatus({ systemStatus: IDLE_STATUS })) + ); + }); + + private isTestrunFinished(status: string) { + return ( + status === StatusOfTestrun.Compliant || + status === StatusOfTestrun.NonCompliant || + status === StatusOfTestrun.Error + ); + } + private showSnackBar() { timer(WAIT_TO_OPEN_SNACKBAR_MS) .pipe( @@ -290,22 +351,40 @@ export class AppEffects { } private pullingSystemStatusData(): void { - this.ngZone.runOutsideAngular(() => { - this.startInterval = true; - interval(5000) - .pipe( - takeUntil(this.destroyInterval$), - tap(() => this.store.dispatch(fetchSystemStatus())) - ) - .subscribe(); - }); + if ( + this.statusSubscription === undefined || + this.statusSubscription?.closed + ) { + this.statusSubscription = this.testrunMqttService + .getStatus() + .subscribe(systemStatus => { + this.store.dispatch(fetchSystemStatusSuccess({ systemStatus })); + }); + } + } + + private fetchInternetConnection() { + if ( + this.internetSubscription === undefined || + this.internetSubscription?.closed + ) { + this.internetSubscription = this.testrunMqttService + .getInternetConnection() + .subscribe((internetConnection: InternetConnection) => { + this.store.dispatch( + updateInternetConnection({ + internetConnection: internetConnection.connection, + }) + ); + }); + } } constructor( private actions$: Actions, private testrunService: TestRunService, + private testrunMqttService: TestRunMqttService, private store: Store, - private ngZone: NgZone, private notificationService: NotificationService ) {} } diff --git a/modules/ui/src/app/store/reducers.spec.ts b/modules/ui/src/app/store/reducers.spec.ts index ad611e9f9..b6fe9d675 100644 --- a/modules/ui/src/app/store/reducers.spec.ts +++ b/modules/ui/src/app/store/reducers.spec.ts @@ -25,16 +25,22 @@ import { setIsOpenAddDevice, setIsOpenStartTestrun, setIsOpenWaitSnackBar, + setReports, setRiskProfiles, setStatus, + setTestModules, setTestrunStatus, toggleMenu, + updateAdapters, updateError, updateFocusNavigation, + updateInternetConnection, } from './actions'; -import { device } from '../mocks/device.mock'; +import { device, MOCK_TEST_MODULES } from '../mocks/device.mock'; import { MOCK_PROGRESS_DATA_CANCELLING } from '../mocks/testrun.mock'; import { PROFILE_MOCK } from '../mocks/profile.mock'; +import { HISTORY } from '../mocks/reports.mock'; +import { MOCK_ADAPTERS } from '../mocks/settings.mock'; describe('Reducer', () => { describe('unknown action', () => { @@ -258,4 +264,67 @@ describe('Reducer', () => { expect(state).not.toBe(initialState); }); }); + + describe('setReports action', () => { + it('should update state', () => { + const initialState = initialSharedState; + const action = setReports({ + reports: HISTORY, + }); + const state = fromReducer.sharedReducer(initialState, action); + const newState = { + ...initialState, + ...{ reports: HISTORY }, + }; + + expect(state).toEqual(newState); + expect(state).not.toBe(initialState); + }); + }); + + describe('setTestModules action', () => { + it('should update state', () => { + const initialState = initialSharedState; + const action = setTestModules({ + testModules: MOCK_TEST_MODULES, + }); + const state = fromReducer.sharedReducer(initialState, action); + const newState = { + ...initialState, + ...{ testModules: MOCK_TEST_MODULES }, + }; + + expect(state).toEqual(newState); + expect(state).not.toBe(initialState); + }); + }); + + describe('updateAdapters action', () => { + it('should update state', () => { + const initialState = initialSharedState; + const action = updateAdapters({ + adapters: MOCK_ADAPTERS, + }); + const state = fromReducer.sharedReducer(initialState, action); + const newState = { + ...initialState, + ...{ adapters: MOCK_ADAPTERS }, + }; + + expect(state).toEqual(newState); + expect(state).not.toBe(initialState); + }); + }); + + describe('updateInternetConnection action', () => { + it('should update state', () => { + const initialState = initialSharedState; + const action = updateInternetConnection({ internetConnection: true }); + const state = fromReducer.sharedReducer(initialState, action); + const newState = { ...initialState, ...{ internetConnection: true } }; + + expect(state).toEqual(newState); + expect(state).not.toBe(initialState); + }); + }); }); diff --git a/modules/ui/src/app/store/reducers.ts b/modules/ui/src/app/store/reducers.ts index 501c231a5..dfc54b11f 100644 --- a/modules/ui/src/app/store/reducers.ts +++ b/modules/ui/src/app/store/reducers.ts @@ -106,6 +106,30 @@ export const sharedReducer = createReducer( ...state, status, }; + }), + on(Actions.setReports, (state, { reports }) => { + return { + ...state, + reports, + }; + }), + on(Actions.setTestModules, (state, { testModules }) => { + return { + ...state, + testModules, + }; + }), + on(Actions.updateAdapters, (state, { adapters }) => { + return { + ...state, + adapters, + }; + }), + on(Actions.updateInternetConnection, (state, { internetConnection }) => { + return { + ...state, + internetConnection, + }; }) ); diff --git a/modules/ui/src/app/store/selectors.spec.ts b/modules/ui/src/app/store/selectors.spec.ts index e8d31efc8..facc8bb74 100644 --- a/modules/ui/src/app/store/selectors.spec.ts +++ b/modules/ui/src/app/store/selectors.spec.ts @@ -16,6 +16,7 @@ import { AppState } from './state'; import { + selectAdapters, selectDeviceInProgress, selectDevices, selectError, @@ -27,9 +28,12 @@ import { selectIsOpenStartTestrun, selectIsOpenWaitSnackBar, selectMenuOpened, + selectReports, selectRiskProfiles, selectStatus, selectSystemStatus, + selectTestModules, + selectInternetConnection, } from './selectors'; describe('Selectors', () => { @@ -55,6 +59,10 @@ describe('Selectors', () => { systemStatus: null, deviceInProgress: null, status: null, + reports: [], + testModules: [], + adapters: {}, + internetConnection: null, }, }; @@ -127,4 +135,24 @@ describe('Selectors', () => { const result = selectStatus.projector(initialState); expect(result).toEqual(null); }); + + it('should select status', () => { + const result = selectReports.projector(initialState); + expect(result).toEqual([]); + }); + + it('should select testModules', () => { + const result = selectTestModules.projector(initialState); + expect(result).toEqual([]); + }); + + it('should select adapters', () => { + const result = selectAdapters.projector(initialState); + expect(result).toEqual({}); + }); + + it('should select internetConnection', () => { + const result = selectInternetConnection.projector(initialState); + expect(result).toEqual(null); + }); }); diff --git a/modules/ui/src/app/store/selectors.ts b/modules/ui/src/app/store/selectors.ts index 2f42db3d6..383fee1b9 100644 --- a/modules/ui/src/app/store/selectors.ts +++ b/modules/ui/src/app/store/selectors.ts @@ -93,3 +93,23 @@ export const selectStatus = createSelector( selectAppState, (state: AppState) => state.shared.status ); + +export const selectReports = createSelector( + selectAppState, + (state: AppState) => state.shared.reports +); + +export const selectTestModules = createSelector( + selectAppState, + (state: AppState) => state.shared.testModules +); + +export const selectAdapters = createSelector( + selectAppState, + (state: AppState) => state.shared.adapters +); + +export const selectInternetConnection = createSelector( + selectAppState, + (state: AppState) => state.shared.internetConnection +); diff --git a/modules/ui/src/app/store/state.ts b/modules/ui/src/app/store/state.ts index e2528c5a0..76e2d3254 100644 --- a/modules/ui/src/app/store/state.ts +++ b/modules/ui/src/app/store/state.ts @@ -14,8 +14,12 @@ * limitations under the License. */ import { TestrunStatus } from '../model/testrun-status'; -import { SettingMissedError, SystemInterfaces } from '../model/setting'; -import { Device } from '../model/device'; +import { Device, TestModule } from '../model/device'; +import { + Adapters, + SettingMissedError, + SystemInterfaces, +} from '../model/setting'; import { Profile } from '../model/profile'; export interface AppState { @@ -54,6 +58,10 @@ export interface SharedState { isStopTestrun: boolean; isOpenWaitSnackBar: boolean; deviceInProgress: Device | null; + reports: TestrunStatus[]; + testModules: TestModule[]; + adapters: Adapters; + internetConnection: boolean | null; } export const initialAppComponentState: AppComponentState = { @@ -78,4 +86,8 @@ export const initialSharedState: SharedState = { isOpenStartTestrun: false, systemStatus: null, status: null, + reports: [], + testModules: [], + adapters: {}, + internetConnection: null, }; diff --git a/modules/ui/ui.Dockerfile b/modules/ui/ui.Dockerfile index da56be93e..7ecb32dbd 100644 --- a/modules/ui/ui.Dockerfile +++ b/modules/ui/ui.Dockerfile @@ -12,17 +12,10 @@ # See the License for the specific language governing permissions and # limitations under the License. -# Image name: test-run/ui -FROM node@sha256:ffebb4405810c92d267a764b21975fb2d96772e41877248a37bf3abaa0d3b590 as build - -WORKDIR /modules/ui -COPY modules/ui/ /modules/ui -RUN npm install -RUN npm run build - +# Image name: testrun/ui FROM nginx@sha256:4c0fdaa8b6341bfdeca5f18f7837462c80cff90527ee35ef185571e1c327beac -COPY --from=build /modules/ui/dist/ /usr/share/nginx/html +COPY modules/ui/dist/ /usr/share/nginx/html EXPOSE 8080 diff --git a/modules/ws/conf/mosquitto.conf b/modules/ws/conf/mosquitto.conf new file mode 100644 index 000000000..9027ba814 --- /dev/null +++ b/modules/ws/conf/mosquitto.conf @@ -0,0 +1,22 @@ +## Logging + +log_dest stdout +log_type all +log_timestamp true +connection_messages true + +## MQTT Listener + +listener 1883 +protocol mqtt + +## WebSockets Listener + +listener 9001 +protocol websockets + +allow_anonymous true + +## Persistence + +persistence false \ No newline at end of file diff --git a/modules/ws/ws.Dockerfile b/modules/ws/ws.Dockerfile new file mode 100644 index 000000000..7e9408a47 --- /dev/null +++ b/modules/ws/ws.Dockerfile @@ -0,0 +1,4 @@ +FROM eclipse-mosquitto:2.0.18 +RUN mkdir -p /mosquitto/data/ +COPY modules/ws/conf/mosquitto.conf /mosquitto/config/mosquitto.conf +VOLUME /mosquitto/data/ \ No newline at end of file diff --git a/testing/api/profiles/new_profile.json b/testing/api/profiles/new_profile.json new file mode 100644 index 000000000..d63ecd17c --- /dev/null +++ b/testing/api/profiles/new_profile.json @@ -0,0 +1,54 @@ +{ + "name": "New Profile", + "status": "Valid", + "questions": [ + { + "question": "What type of device is this?", + "answer": "IoT Gateway" + }, + { + "question": "How will this device be used at Google?", + "answer": "Monitoring" + }, + { + "question": "Is this device going to be managed by Google or a third party?", + "answer": "Google" + }, + { + "question": "Will the third-party device administrator be able to grant access to authorized Google personnel upon request?", + "answer": "N/A" + }, + { + "question": "Are any of the following statements true about your device?", + "answer": [ + 0 + ] + }, + { + "question": "Which of the following statements are true about this device?", + "answer": [ + 0 + ] + }, + { + "question": "Does the network protocol assure server-to-client identity verification?", + "answer": "Yes" + }, + { + "question": "Click the statements that best describe the characteristics of this device.", + "answer": [ + 0 + ] + }, + { + "question": "Are any of the following statements true about this device?", + "answer": [ + 0 + ] + }, + { + "question": "Comments", + "answer": "" + } + ] + } \ No newline at end of file diff --git a/testing/api/profiles/new_profile_2.json b/testing/api/profiles/new_profile_2.json new file mode 100644 index 000000000..2ac93dc17 --- /dev/null +++ b/testing/api/profiles/new_profile_2.json @@ -0,0 +1,56 @@ +{ + "name": "New Profile 2", + "status": "Draft", + "questions": [ + { + "question": "What type of device is this?", + "answer": "IoT Gateway" + }, + { + "question": "How will this device be used at Google?", + "answer": "Installed in a building" + }, + { + "question": "Is this device going to be managed by Google or a third party?", + "answer": "Google" + }, + { + "question": "Will the third-party device administrator be able to grant access to authorized Google personnel upon request?", + "answer": "Yes" + }, + { + "question": "Are any of the following statements true about your device?", + "answer": [ + 0, + 2 + ] + }, + { + "question": "Which of the following statements are true about this device?", + "answer": [ + 0, + 1, + 5 + ] + }, + { + "question": "Does the network protocol assure server-to-client identity verification?", + "answer": "Yes" + }, + { + "question": "Click the statements that best describe the characteristics of this device.", + "answer": [ + 0, + 1, + 2 + ] + }, + { + "question": "Are any of the following statements true about this device?", + "answer": [ + 2, + 3 + ] + } + ] +} \ No newline at end of file diff --git a/testing/api/profiles/updated_profile.json b/testing/api/profiles/updated_profile.json new file mode 100644 index 000000000..91714bcfa --- /dev/null +++ b/testing/api/profiles/updated_profile.json @@ -0,0 +1,57 @@ +{ + "name": "New Profile", + "rename": "Updated Profile", + "status": "Draft", + "questions": [ + { + "question": "What type of device is this?", + "answer": "IoT Gateway" + }, + { + "question": "How will this device be used at Google?", + "answer": "Installed in a building" + }, + { + "question": "Is this device going to be managed by Google or a third party?", + "answer": "Google" + }, + { + "question": "Will the third-party device administrator be able to grant access to authorized Google personnel upon request?", + "answer": "Yes" + }, + { + "question": "Are any of the following statements true about your device?", + "answer": [ + 0, + 2 + ] + }, + { + "question": "Which of the following statements are true about this device?", + "answer": [ + 0, + 1, + 5 + ] + }, + { + "question": "Does the network protocol assure server-to-client identity verification?", + "answer": "Yes" + }, + { + "question": "Click the statements that best describe the characteristics of this device.", + "answer": [ + 0, + 1, + 2 + ] + }, + { + "question": "Are any of the following statements true about this device?", + "answer": [ + 2, + 3 + ] + } + ] +} \ No newline at end of file diff --git a/testing/api/test_api.py b/testing/api/test_api.py index 75811e3bb..70c1a617f 100644 --- a/testing/api/test_api.py +++ b/testing/api/test_api.py @@ -29,6 +29,7 @@ import pytest import requests + ALL_DEVICES = "*" API = "http://127.0.0.1:8000" LOG_PATH = "/tmp/testrun.log" @@ -36,7 +37,10 @@ DEVICES_DIRECTORY = "local/devices" TESTING_DEVICES = "../device_configs" +PROFILES_DIRECTORY = "local/risk_profiles" SYSTEM_CONFIG_PATH = "local/system.json" +SYSTEM_CONFIG_RESTORE_PATH = "testing/api/system.json" +PROFILES_PATH = "testing/api/profiles" BASELINE_MAC_ADDR = "02:42:aa:00:01:01" ALL_MAC_ADDR = "02:42:aa:00:00:01" @@ -45,21 +49,18 @@ def pretty_print(dictionary: dict): """ Pretty print dictionary """ print(json.dumps(dictionary, indent=4)) - def query_system_status() -> str: """Query system status from API and returns this""" r = requests.get(f"{API}/system/status", timeout=5) - response = json.loads(r.text) + response = r.json() return response["status"] - def query_test_count() -> int: """Queries status and returns number of test results""" r = requests.get(f"{API}/system/status", timeout=5) - response = json.loads(r.text) + response = r.json() return len(response["tests"]["results"]) - def start_test_device( device_name, mac_address, image_name="test-run/ci_device_1", args="" ): @@ -74,7 +75,6 @@ def start_test_device( ) print(cmd.stdout) - def stop_test_device(device_name): """ Stop docker container with given name """ cmd = subprocess.run( @@ -88,7 +88,6 @@ def stop_test_device(device_name): ) print(cmd.stdout) - def docker_logs(device_name): """ Print docker logs from given docker container name """ cmd = subprocess.run( @@ -97,13 +96,23 @@ def docker_logs(device_name): ) print(cmd.stdout) +def load_json(file_name, directory): + """Utility method to load json files' """ + # Construct the base path relative to the main folder + base_path = Path(__file__).resolve().parent.parent.parent + # Construct the full file path + file_path = base_path / directory / file_name + + # Open the file in read mode + with open(file_path, "r", encoding="utf-8") as file: + # Return the file content + return json.load(file) @pytest.fixture def empty_devices_dir(): """ Use e,pty devices directory """ local_delete_devices(ALL_DEVICES) - @pytest.fixture def testing_devices(): """ Use devices from the testing/device_configs directory """ @@ -115,10 +124,10 @@ def testing_devices(): ) return local_get_devices() - @pytest.fixture def testrun(request): # pylint: disable=W0613 """ Start intstance of testrun """ + # pylint: disable=W1509 with subprocess.Popen( "bin/testrun", stdout=subprocess.PIPE, @@ -165,7 +174,6 @@ def testrun(request): # pylint: disable=W0613 ) print(cmd.stdout) - def until_true(func: Callable, message: str, timeout: int): """ Blocks until given func returns True @@ -179,7 +187,6 @@ def until_true(func: Callable, message: str, timeout: int): time.sleep(1) raise TimeoutError(f"Timed out waiting {timeout}s for {message}") - def dict_paths(thing: dict, stem: str = "") -> Iterator[str]: """Returns json paths (in dot notation) from a given dictionary""" for k, v in thing.items(): @@ -189,7 +196,6 @@ def dict_paths(thing: dict, stem: str = "") -> Iterator[str]: else: yield path - def get_network_interfaces(): """return list of network interfaces on machine @@ -204,7 +210,6 @@ def get_network_interfaces(): ifaces.append(i.stem) return ifaces - def local_delete_devices(path): """ Deletes all local devices """ @@ -214,7 +219,6 @@ def local_delete_devices(path): else: shutil.rmtree(thing) - def local_get_devices(): """ Returns path to device configs of devices in local/devices directory""" return sorted( @@ -223,25 +227,240 @@ def local_get_devices(): ) ) +# Tests for system endpoints + +@pytest.fixture() +def restore_config(): + """Restore the original configuration (system.json) after the test""" + yield + + # Restore system.json from 'testing/api/' after the test + if os.path.exists(SYSTEM_CONFIG_RESTORE_PATH): + shutil.copy(SYSTEM_CONFIG_RESTORE_PATH, SYSTEM_CONFIG_PATH) def test_get_system_interfaces(testrun): # pylint: disable=W0613 """Tests API system interfaces against actual local interfaces""" + + # Send a GET request to the API to retrieve system interfaces r = requests.get(f"{API}/system/interfaces", timeout=5) - response = json.loads(r.text) + + # Check if status code is 200 (OK) + assert r.status_code == 200 + + # Parse the JSON response + response = r.json() + + # Retrieve the actual network interfaces local_interfaces = get_network_interfaces() - assert set(response.keys()) == set(local_interfaces) - # schema expects a flat list + # Check if the key are in the response + assert set(response.keys()) == set(local_interfaces) + # Ensure that all values in the response are strings assert all(isinstance(x, str) for x in response) +def test_update_system_config(testrun, restore_config): # pylint: disable=W0613 + """Test update system configuration endpoint ('/system/config')""" -def test_status_idle(testrun): # pylint: disable=W0613 - until_true( - lambda: query_system_status().lower() == "idle", - "system status is `idle`", - 30, + # Configuration data to update + updated_system_config = { + "network": { + "device_intf": "updated_endev0a", + "internet_intf": "updated_wlan1" + }, + "log_level": "DEBUG" + } + + # Send the post request to update the system configuration + r = requests.post(f"{API}/system/config", + data=json.dumps(updated_system_config), + timeout=5) + + # Check if status code is 200 (OK) + assert r.status_code == 200 + + # Parse the JSON response + response = r.json() + + # Check if the response["network"]["device_intf"] has been updated + assert ( + response["network"]["device_intf"] + == updated_system_config["network"]["device_intf"] + ) + + # Check if the response["network"]["internet_intf"] has been updated + assert ( + response["network"]["internet_intf"] + == updated_system_config["network"]["internet_intf"] + ) + + # Check if the response["log_level"] has been updated + assert ( + response["log_level"] + == updated_system_config["log_level"] + ) + +def test_update_system_config_invalid_config(testrun, restore_config): # pylint: disable=W0613 + """Test invalid configuration file for update system configuration""" + + # Configuration data to update with missing "log_level" field + updated_system_config = { + "network": { + "device_intf": "updated_endev0a", + "internet_intf": "updated_wlan1" + } + } + + # Send the post request to update the system configuration + r = requests.post(f"{API}/system/config", + data=json.dumps(updated_system_config), + timeout=5) + + # Check if status code is 400 (Invalid config) + assert r.status_code == 400 + +def test_get_system_config(testrun): # pylint: disable=W0613 + """Tests get system configuration endpoint ('/system/config')""" + + # Send a GET request to the API to retrieve system configuration + r = requests.get(f"{API}/system/config", timeout=5) + + # Load system configuration file + local_config = load_json("system.json", directory="local") + + # Parse the JSON response + api_config = r.json() + + # Check if status code is 200 (OK) + assert r.status_code == 200 + + # Validate structure + assert set(dict_paths(api_config)) | set(dict_paths(local_config)) == set( + dict_paths(api_config) + ) + + # Check if the device interface in the local config matches the API config + assert ( + local_config["network"]["device_intf"] + == api_config["network"]["device_intf"] + ) + + # Check if the internet interface in the local config matches the API config + assert ( + local_config["network"]["internet_intf"] + == api_config["network"]["internet_intf"] ) +def test_start_testrun_started_successfully(testing_devices, testrun): # pylint: disable=W0613 + """Test for testrun started successfully """ + + # Payload with device details + payload = {"device": { + "mac_addr": BASELINE_MAC_ADDR, + "firmware": "asd", + "test_modules": { + "dns": {"enabled": False}, + "connection": {"enabled": True}, + "ntp": {"enabled": False}, + "baseline": {"enabled": False}, + "nmap": {"enabled": False} + }}} + + # Send the post request + r = requests.post(f"{API}/system/start", data=json.dumps(payload), timeout=10) + + # Check if the response status code is 200 (OK) + assert r.status_code == 200 + + # Parse the json response + response = r.json() + + # Check that device is in response + assert "device" in response + + # Check that mac_addr in response + assert "mac_addr" in response["device"] + + # Check that firmware in response + assert "firmware" in response["device"] + +def test_start_testrun_missing_device(testing_devices, testrun): # pylint: disable=W0613 + """Test for missing device when testrun is started """ + + # Payload empty dict (no device) + payload = {} + + # Send the post request + r = requests.post(f"{API}/system/start", data=json.dumps(payload), timeout=10) + + # Check if the response status code is 400 (bad request) + assert r.status_code == 400 + + # Parse the json response + response = r.json() + + # Check if 'error' in response + assert "error" in response + +def test_start_testrun_already_started(testing_devices, testrun): # pylint: disable=W0613 + """Test for testrun already started """ + + # Payload with device details + payload = {"device": { + "mac_addr": BASELINE_MAC_ADDR, + "firmware": "asd", + "test_modules": { + "dns": {"enabled": False}, + "connection": {"enabled": True}, + "ntp": {"enabled": False}, + "baseline": {"enabled": False}, + "nmap": {"enabled": False} + }}} + + # Send the post request (start test) + r = requests.post(f"{API}/system/start", data=json.dumps(payload), timeout=10) + + # Check if the response status code is 200 (OK) + assert r.status_code == 200 + + # Send the second post request (start test again) + r = requests.post(f"{API}/system/start", data=json.dumps(payload), timeout=10) + + # Parse the json response + response = r.json() + + # Check if the response status code is 409 (Conflict) + assert r.status_code == 409 + + # Check if 'error' in response + assert "error" in response + +def test_start_testrun_device_not_found(testing_devices, testrun): # pylint: disable=W0613 + """Test for start testrun device not found """ + + # Payload with device details with no mac address assigned + payload = {"device": { + "mac_addr": "", + "firmware": "asd", + "test_modules": { + "dns": {"enabled": False}, + "connection": {"enabled": True}, + "ntp": {"enabled": False}, + "baseline": {"enabled": False}, + "nmap": {"enabled": False} + }}} + + # Send the post request + r = requests.post(f"{API}/system/start", data=json.dumps(payload), timeout=10) + + # Check if the response status code is 404 (not found) + assert r.status_code == 404 + + # Parse the json response + response = r.json() + + # Check if 'error' in response + assert "error" in response + # Currently not working due to blocking during monitoring period @pytest.mark.skip() def test_status_in_progress(testing_devices, testrun): # pylint: disable=W0613 @@ -264,22 +483,35 @@ def test_status_in_progress(testing_devices, testrun): # pylint: disable=W0613 600, ) - +# Currently not working due to blocking during monitoring period @pytest.mark.skip() -def test_status_non_compliant(testing_devices, testrun): # pylint: disable=W0613 +def test_start_testrun_already_in_progress( + testing_devices, # pylint: disable=W0613 + testrun): # pylint: disable=W0613 + payload = {"device": {"mac_addr": BASELINE_MAC_ADDR, "firmware": "asd"}} + r = requests.post(f"{API}/system/start", data=json.dumps(payload), timeout=10) - r = requests.get(f"{API}/devices", timeout=5) - all_devices = json.loads(r.text) - payload = { - "device": { - "mac_addr": all_devices[0]["mac_addr"], - "firmware": "asd" - } - } - r = requests.post(f"{API}/system/start", data=json.dumps(payload), - timeout=10) + until_true( + lambda: query_system_status().lower() == "waiting for device", + "system status is `waiting for device`", + 30, + ) + + start_test_device("x123", BASELINE_MAC_ADDR) + + until_true( + lambda: query_system_status().lower() == "in progress", + "system status is `in progress`", + 600, + ) + r = requests.post(f"{API}/system/start", data=json.dumps(payload), timeout=10) + assert r.status_code == 409 + +@pytest.mark.skip() +def test_trigger_run(testing_devices, testrun): # pylint: disable=W0613 + payload = {"device": {"mac_addr": BASELINE_MAC_ADDR, "firmware": "asd"}} + r = requests.post(f"{API}/system/start", data=json.dumps(payload), timeout=10) assert r.status_code == 200 - print(r.text) until_true( lambda: query_system_status().lower() == "waiting for device", @@ -287,102 +519,350 @@ def test_status_non_compliant(testing_devices, testrun): # pylint: disable=W0613 30, ) - start_test_device("x123", all_devices[0]["mac_addr"]) + start_test_device("x123", BASELINE_MAC_ADDR) until_true( - lambda: query_system_status().lower() == "non-compliant", - "system status is `complete", + lambda: query_system_status().lower() == "compliant", + "system status is `complete`", 600, ) stop_test_device("x123") -def test_create_get_devices(empty_devices_dir, testrun): # pylint: disable=W0613 - device_1 = { - "manufacturer": "Google", - "model": "First", - "mac_addr": "00:1e:42:35:73:c4", - "test_modules": { - "dns": {"enabled": True}, - "connection": {"enabled": True}, - "ntp": {"enabled": True}, - "baseline": {"enabled": True}, - "nmap": {"enabled": True}, - }, - } - - r = requests.post(f"{API}/device", data=json.dumps(device_1), - timeout=5) - print(r.text) - assert r.status_code == 201 - assert len(local_get_devices()) == 1 - - device_2 = { - "manufacturer": "Google", - "model": "Second", - "mac_addr": "00:1e:42:35:73:c6", - "test_modules": { - "dns": {"enabled": True}, - "connection": {"enabled": True}, - "ntp": {"enabled": True}, - "baseline": {"enabled": True}, - "nmap": {"enabled": True}, - }, - } - r = requests.post(f"{API}/device", data=json.dumps(device_2), - timeout=5) - assert r.status_code == 201 - assert len(local_get_devices()) == 2 + # Validate response + r = requests.get(f"{API}/system/status", timeout=5) + response = r.json() + pretty_print(response) - # Test that returned devices API endpoint matches expected structure - r = requests.get(f"{API}/devices", timeout=5) - all_devices = json.loads(r.text) - pretty_print(all_devices) + # Validate results + results = {x["name"]: x for x in response["tests"]["results"]} + print(results) + # there are only 3 baseline tests + assert len(results) == 3 + # Validate structure with open( - os.path.join(os.path.dirname(__file__), "mockito/get_devices.json"), - encoding="utf-8" + os.path.join( + os.path.dirname(__file__), "mockito/running_system_status.json" + ), encoding="utf-8" ) as f: mockito = json.load(f) - print(mockito) - - # Validate structure - assert all(isinstance(x, dict) for x in all_devices) + # validate structure + assert set(dict_paths(mockito)).issubset(set(dict_paths(response))) - # TOOO uncomment when is done - # assert set(dict_paths(mockito[0])) == set(dict_paths(all_devices[0])) + # Validate results structure + assert set(dict_paths(mockito["tests"]["results"][0])).issubset( + set(dict_paths(response["tests"]["results"][0])) + ) - # Validate contents of given keys matches - for key in ["mac_addr", "manufacturer", "model"]: - assert set([all_devices[0][key], all_devices[1][key]]) == set( - [device_1[key], device_2[key]] - ) + # Validate a result + assert results["baseline.compliant"]["result"] == "Compliant" +@pytest.mark.skip() +def test_stop_running_test(testing_devices, testrun): # pylint: disable=W0613 + payload = {"device": {"mac_addr": ALL_MAC_ADDR, "firmware": "asd"}} + r = requests.post(f"{API}/system/start", data=json.dumps(payload), + timeout=10) + assert r.status_code == 200 -def test_delete_device_success(empty_devices_dir, testrun): # pylint: disable=W0613 - device_1 = { - "manufacturer": "Google", - "model": "First", - "mac_addr": "00:1e:42:35:73:c4", - "test_modules": { - "dns": {"enabled": True}, - "connection": {"enabled": True}, - "ntp": {"enabled": True}, - "baseline": {"enabled": True}, - "nmap": {"enabled": True}, - }, - } + until_true( + lambda: query_system_status().lower() == "waiting for device", + "system status is `waiting for device`", + 30, + ) - # Send create device request - r = requests.post(f"{API}/device", - data=json.dumps(device_1), - timeout=5) - print(r.text) + start_test_device("x12345", ALL_MAC_ADDR) - # Check device has been created - assert r.status_code == 201 - assert len(local_get_devices()) == 1 + until_true( + lambda: query_test_count() > 1, + "system status is `complete`", + 1000, + ) + + stop_test_device("x12345") + + # Validate response + r = requests.post(f"{API}/system/stop", timeout=5) + response = r.json() + pretty_print(response) + assert response == {"success": "Testrun stopped"} + time.sleep(1) + + # Validate response + r = requests.get(f"{API}/system/status", timeout=5) + response = r.json() + pretty_print(response) + + assert response["status"] == "Cancelled" + +def test_stop_running_not_running(testrun): # pylint: disable=W0613 + # Validate response + r = requests.post(f"{API}/system/stop", + timeout=10) + response = r.json() + pretty_print(response) + + assert r.status_code == 404 + assert response["error"] == "Testrun is not currently running" + +@pytest.mark.skip() +def test_multiple_runs(testing_devices, testrun): # pylint: disable=W0613 + payload = {"device": {"mac_addr": BASELINE_MAC_ADDR, "firmware": "asd"}} + r = requests.post(f"{API}/system/start", data=json.dumps(payload), + timeout=10) + assert r.status_code == 200 + print(r.text) + + until_true( + lambda: query_system_status().lower() == "waiting for device", + "system status is `waiting for device`", + 30, + ) + + start_test_device("x123", BASELINE_MAC_ADDR) + + until_true( + lambda: query_system_status().lower() == "compliant", + "system status is `complete`", + 900, + ) + + stop_test_device("x123") + + # Validate response + r = requests.get(f"{API}/system/status", timeout=5) + response = r.json() + pretty_print(response) + + # Validate results + results = {x["name"]: x for x in response["tests"]["results"]} + print(results) + # there are only 3 baseline tests + assert len(results) == 3 + + payload = {"device": {"mac_addr": BASELINE_MAC_ADDR, "firmware": "asd"}} + r = requests.post(f"{API}/system/start", data=json.dumps(payload), + timeout=10) + # assert r.status_code == 200 + # returns 409 + print(r.text) + + until_true( + lambda: query_system_status().lower() == "waiting for device", + "system status is `waiting for device`", + 30, + ) + + start_test_device("x123", BASELINE_MAC_ADDR) + + until_true( + lambda: query_system_status().lower() == "compliant", + "system status is `complete`", + 900, + ) + + stop_test_device("x123") + +def test_status_idle(testrun): # pylint: disable=W0613 + """Test system status 'idle' endpoint (/system/status)""" + until_true( + lambda: query_system_status().lower() == "idle", + "system status is `idle`", + 30, + ) + +def test_system_shutdown(testrun): # pylint: disable=W0613 + """Test the shutdown system endpoint""" + # Send a POST request to initiate the system shutdown + r = requests.post(f"{API}/system/shutdown", timeout=5) + + # Check if the response status code is 200 (OK) + assert r.status_code == 200, f"Expected status code 200, got {r.status_code}" + +def test_system_shutdown_in_progress(testrun): # pylint: disable=W0613 + """Test system shutdown during an in-progress test""" + # Payload with device details to start a test + payload = { + "device": { + "mac_addr": BASELINE_MAC_ADDR, + "firmware": "asd", + "test_modules": { + "dns": {"enabled": False}, + "connection": {"enabled": True}, + "ntp": {"enabled": False}, + "baseline": {"enabled": False}, + "nmap": {"enabled": False} + } + } + } + + # Start a test + r = requests.post(f"{API}/system/start", data=json.dumps(payload), timeout=10) + + # Check if status code is not 200 (OK) + if r.status_code != 200: + raise ValueError(f"Api request failed with code: {r.status_code}") + + # Attempt to shutdown while the test is running + r = requests.post(f"{API}/system/shutdown", timeout=5) + + # Check if the response status code is 400 (test in progress) + assert r.status_code == 400 + +def test_system_latest_version(testrun): # pylint: disable=W0613 + """Test for testrun version when the latest version is installed""" + + # Send the get request to the API + r = requests.get(f"{API}/system/version", timeout=5) + + # Parse the response + response = r.json() + + # Check if status code is 200 (update available) + assert r.status_code == 200 + # Check if an update is available + assert response["update_available"] is False + +# Tests for reports endpoints + +def test_get_reports_no_reports(testrun): # pylint: disable=W0613 + """Test get reports when no reports exist.""" + + # Send a GET request to the /reports endpoint + r = requests.get(f"{API}/reports", timeout=5) + + # Check if the status code is 200 (OK) + assert r.status_code == 200 + + # Parse the JSON response + response = r.json() + + # Check if the response is a list + assert isinstance(response, list) + + # Check if the response is an empty list + assert response == [] + +# Tests for device endpoints + +@pytest.mark.skip() +def test_status_non_compliant(testing_devices, testrun): # pylint: disable=W0613 + + r = requests.get(f"{API}/devices", timeout=5) + all_devices = r.json() + payload = { + "device": { + "mac_addr": all_devices[0]["mac_addr"], + "firmware": "asd" + } + } + r = requests.post(f"{API}/system/start", data=json.dumps(payload), + timeout=10) + assert r.status_code == 200 + print(r.text) + + until_true( + lambda: query_system_status().lower() == "waiting for device", + "system status is `waiting for device`", + 30, + ) + + start_test_device("x123", all_devices[0]["mac_addr"]) + + until_true( + lambda: query_system_status().lower() == "non-compliant", + "system status is `complete", + 600, + ) + + stop_test_device("x123") + +def test_create_get_devices(empty_devices_dir, testrun): # pylint: disable=W0613 + device_1 = { + "manufacturer": "Google", + "model": "First", + "mac_addr": "00:1e:42:35:73:c4", + "test_modules": { + "dns": {"enabled": True}, + "connection": {"enabled": True}, + "ntp": {"enabled": True}, + "baseline": {"enabled": True}, + "nmap": {"enabled": True}, + }, + } + + r = requests.post(f"{API}/device", data=json.dumps(device_1), + timeout=5) + print(r.text) + assert r.status_code == 201 + assert len(local_get_devices()) == 1 + + device_2 = { + "manufacturer": "Google", + "model": "Second", + "mac_addr": "00:1e:42:35:73:c6", + "test_modules": { + "dns": {"enabled": True}, + "connection": {"enabled": True}, + "ntp": {"enabled": True}, + "baseline": {"enabled": True}, + "nmap": {"enabled": True}, + }, + } + r = requests.post(f"{API}/device", data=json.dumps(device_2), + timeout=5) + assert r.status_code == 201 + assert len(local_get_devices()) == 2 + + # Test that returned devices API endpoint matches expected structure + r = requests.get(f"{API}/devices", timeout=5) + all_devices = r.json() + pretty_print(all_devices) + + with open( + os.path.join(os.path.dirname(__file__), "mockito/get_devices.json"), + encoding="utf-8" + ) as f: + mockito = json.load(f) + + print(mockito) + + # Validate structure + assert all(isinstance(x, dict) for x in all_devices) + + # TOOO uncomment when is done + # assert set(dict_paths(mockito[0])) == set(dict_paths(all_devices[0])) + + # Validate contents of given keys matches + for key in ["mac_addr", "manufacturer", "model"]: + assert set([all_devices[0][key], all_devices[1][key]]) == set( + [device_1[key], device_2[key]] + ) + +def test_delete_device_success(empty_devices_dir, testrun): # pylint: disable=W0613 + device_1 = { + "manufacturer": "Google", + "model": "First", + "mac_addr": "00:1e:42:35:73:c4", + "test_modules": { + "dns": {"enabled": True}, + "connection": {"enabled": True}, + "ntp": {"enabled": True}, + "baseline": {"enabled": True}, + "nmap": {"enabled": True}, + }, + } + + # Send create device request + r = requests.post(f"{API}/device", + data=json.dumps(device_1), + timeout=5) + print(r.text) + + # Check device has been created + assert r.status_code == 201 + assert len(local_get_devices()) == 1 device_2 = { "manufacturer": "Google", @@ -413,7 +893,7 @@ def test_delete_device_success(empty_devices_dir, testrun): # pylint: disable=W0 # Test that returned devices API endpoint matches expected structure r = requests.get(f"{API}/devices", timeout=5) - all_devices = json.loads(r.text) + all_devices = r.json() pretty_print(all_devices) with open( @@ -437,7 +917,6 @@ def test_delete_device_success(empty_devices_dir, testrun): # pylint: disable=W0 [device_2[key]] ) - def test_delete_device_not_found(empty_devices_dir, testrun): # pylint: disable=W0613 device_1 = { "manufacturer": "Google", @@ -476,7 +955,6 @@ def test_delete_device_not_found(empty_devices_dir, testrun): # pylint: disable= assert r.status_code == 404 assert len(local_get_devices()) == 0 - def test_delete_device_no_mac(empty_devices_dir, testrun): # pylint: disable=W0613 device_1 = { "manufacturer": "Google", @@ -510,7 +988,6 @@ def test_delete_device_no_mac(empty_devices_dir, testrun): # pylint: disable=W06 assert r.status_code == 400 assert len(local_get_devices()) == 1 - # Currently not working due to blocking during monitoring period @pytest.mark.skip() def test_delete_device_testrun_running(testing_devices, testrun): # pylint: disable=W0613 @@ -550,41 +1027,8 @@ def test_delete_device_testrun_running(testing_devices, testrun): # pylint: disa timeout=5) assert r.status_code == 403 - -def test_start_testrun_started_successfully( - testing_devices, # pylint: disable=W0613 - testrun): # pylint: disable=W0613 - payload = {"device": {"mac_addr": BASELINE_MAC_ADDR, "firmware": "asd"}} - r = requests.post(f"{API}/system/start", data=json.dumps(payload), timeout=10) - assert r.status_code == 200 - - -# Currently not working due to blocking during monitoring period -@pytest.mark.skip() -def test_start_testrun_already_in_progress( - testing_devices, # pylint: disable=W0613 - testrun): # pylint: disable=W0613 - payload = {"device": {"mac_addr": BASELINE_MAC_ADDR, "firmware": "asd"}} - r = requests.post(f"{API}/system/start", data=json.dumps(payload), timeout=10) - - until_true( - lambda: query_system_status().lower() == "waiting for device", - "system status is `waiting for device`", - 30, - ) - - start_test_device("x123", BASELINE_MAC_ADDR) - - until_true( - lambda: query_system_status().lower() == "in progress", - "system status is `in progress`", - 600, - ) - r = requests.post(f"{API}/system/start", data=json.dumps(payload), timeout=10) - assert r.status_code == 409 - -def test_start_system_not_configured_correctly( - empty_devices_dir, # pylint: disable=W0613 +def test_start_system_not_configured_correctly( + empty_devices_dir, # pylint: disable=W0613 testrun): # pylint: disable=W0613 device_1 = { "manufacturer": "Google", @@ -611,7 +1055,6 @@ def test_start_system_not_configured_correctly( timeout=10) assert r.status_code == 500 - def test_start_device_not_found(empty_devices_dir, # pylint: disable=W0613 testrun): # pylint: disable=W0613 device_1 = { @@ -644,7 +1087,6 @@ def test_start_device_not_found(empty_devices_dir, # pylint: disable=W0613 timeout=10) assert r.status_code == 404 - def test_start_missing_device_information( empty_devices_dir, # pylint: disable=W0613 testrun): # pylint: disable=W0613 @@ -673,7 +1115,6 @@ def test_start_missing_device_information( timeout=10) assert r.status_code == 400 - def test_create_device_already_exists( empty_devices_dir, # pylint: disable=W0613 testrun): # pylint: disable=W0613 @@ -703,7 +1144,6 @@ def test_create_device_already_exists( print(r.text) assert r.status_code == 409 - def test_create_device_invalid_json( empty_devices_dir, # pylint: disable=W0613 testrun): # pylint: disable=W0613 @@ -716,7 +1156,6 @@ def test_create_device_invalid_json( print(r.text) assert r.status_code == 400 - def test_create_device_invalid_request( empty_devices_dir, # pylint: disable=W0613 testrun): # pylint: disable=W0613 @@ -727,7 +1166,6 @@ def test_create_device_invalid_request( print(r.text) assert r.status_code == 400 - def test_device_edit_device( testing_devices, # pylint: disable=W0613 testrun): # pylint: disable=W0613 @@ -740,7 +1178,7 @@ def test_device_edit_device( new_model = "Alphabet" r = requests.get(f"{API}/devices", timeout=5) - all_devices = json.loads(r.text) + all_devices = r.json() api_device = next(x for x in all_devices if x["mac_addr"] == mac_addr) @@ -770,13 +1208,12 @@ def test_device_edit_device( assert r.status_code == 200 r = requests.get(f"{API}/devices", timeout=5) - all_devices = json.loads(r.text) + all_devices = r.json() updated_device_api = next(x for x in all_devices if x["mac_addr"] == mac_addr) assert updated_device_api["model"] == new_model assert updated_device_api["test_modules"] == new_test_modules - def test_device_edit_device_not_found( empty_devices_dir, # pylint: disable=W0613 testrun): # pylint: disable=W0613 @@ -814,7 +1251,6 @@ def test_device_edit_device_not_found( assert r.status_code == 404 - def test_device_edit_device_incorrect_json_format( empty_devices_dir, # pylint: disable=W0613 testrun): # pylint: disable=W0613 @@ -847,7 +1283,6 @@ def test_device_edit_device_incorrect_json_format( assert r.status_code == 400 - def test_device_edit_device_with_mac_already_exists( empty_devices_dir, # pylint: disable=W0613 testrun): # pylint: disable=W0613 @@ -904,42 +1339,9 @@ def test_device_edit_device_with_mac_already_exists( assert r.status_code == 409 - -def test_system_latest_version(testrun): # pylint: disable=W0613 - r = requests.get(f"{API}/system/version", timeout=5) - assert r.status_code == 200 - updated_system_version = json.loads(r.text)["update_available"] - assert updated_system_version is False - -def test_get_system_config(testrun): # pylint: disable=W0613 - r = requests.get(f"{API}/system/config", timeout=5) - - with open( - SYSTEM_CONFIG_PATH, - encoding="utf-8" - ) as f: - local_config = json.load(f) - - api_config = json.loads(r.text) - - # validate structure - assert set(dict_paths(api_config)) | set(dict_paths(local_config)) == set( - dict_paths(api_config) - ) - - assert ( - local_config["network"]["device_intf"] - == api_config["network"]["device_intf"] - ) - assert ( - local_config["network"]["internet_intf"] - == api_config["network"]["internet_intf"] - ) - - def test_invalid_path_get(testrun): # pylint: disable=W0613 r = requests.get(f"{API}/blah/blah", timeout=5) - response = json.loads(r.text) + response = r.json() assert r.status_code == 404 with open( os.path.join(os.path.dirname(__file__), "mockito/invalid_request.json"), @@ -950,188 +1352,484 @@ def test_invalid_path_get(testrun): # pylint: disable=W0613 # validate structure assert set(dict_paths(mockito)) == set(dict_paths(response)) +def test_create_invalid_chars(empty_devices_dir, testrun): # pylint: disable=W0613 + # local_delete_devices(ALL_DEVICES) + # We must start test run with no devices in local/devices for this test + # to function as expected + assert len(local_get_devices()) == 0 -@pytest.mark.skip() -def test_trigger_run(testing_devices, testrun): # pylint: disable=W0613 - payload = {"device": {"mac_addr": BASELINE_MAC_ADDR, "firmware": "asd"}} - r = requests.post(f"{API}/system/start", data=json.dumps(payload), timeout=10) + # Test adding device + device_1 = { + "manufacturer": "/'disallowed characters///", + "model": "First", + "mac_addr": BASELINE_MAC_ADDR, + "test_modules": { + "dns": {"enabled": False}, + "connection": {"enabled": True}, + "ntp": {"enabled": True}, + "baseline": {"enabled": True}, + "nmap": {"enabled": True}, + }, + } + + r = requests.post(f"{API}/device", data=json.dumps(device_1), + timeout=5) + print(r.text) + print(r.status_code) + +def test_get_test_modules(testrun): # pylint: disable=W0613 + """Test the /system/modules endpoint to get the test modules""" + + # Send a GET request to the API endpoint + r = requests.get(f"{API}/system/modules", timeout=5) + + # Check if status code is 200 (OK) assert r.status_code == 200 - until_true( - lambda: query_system_status().lower() == "waiting for device", - "system status is `waiting for device`", - 30, - ) + # Parse the JSON response + response = r.json() + + # Check if the response is a list + assert isinstance(response, list) + +# Tests for profile endpoints +def delete_all_profiles(): + """Utility method to delete all profiles from risk_profiles folder""" + + # Assign the profiles directory + profiles_path = Path(PROFILES_DIRECTORY) + + try: + # Check if the profile_path (local/risk_profiles) exists and is a folder + if profiles_path.exists() and profiles_path.is_dir(): + # Iterate over all profiles from risk_profiles folder + for item in profiles_path.iterdir(): + # Check if item is a file + if item.is_file(): + #If True remove file + item.unlink() + else: + # If item is a folder remove it + shutil.rmtree(item) + + except PermissionError: + # Permission related issues + print(f"Permission Denied: {item}") + except OSError as err: + # System related issues + print(f"Error removing {item}: {err}") + +def create_profile(file_name): + """Utility method to create the profile""" + + # Load the profile + new_profile = load_json(file_name, directory=PROFILES_PATH) + + # Assign the profile name to profile_name + profile_name = new_profile["name"] + + # Exception if the profile already exists + if profile_exists(profile_name): + raise ValueError(f"Profile: {profile_name} exists") + + # Send the post request + r = requests.post(f"{API}/profiles", data=json.dumps(new_profile), timeout=5) + + # Exception if status code is not 201 + if r.status_code != 201: + raise ValueError(f"API request failed with code: {r.status_code}") + + # Return the profile + return new_profile + +@pytest.fixture() +def reset_profiles(): + """Delete the profiles before and after each test""" + + # Delete before the test + delete_all_profiles() + + yield + + # Delete after the test + delete_all_profiles() + +@pytest.fixture() +def add_profile(): + """Fixture to create profiles during tests.""" + # Returning the reference to create_profile + return create_profile + +def profile_exists(profile_name): + """Utility method to check if profile exists""" + # Send the get request + r = requests.get(f"{API}/profiles", timeout=5) + # Check if status code is not 200 (OK) + if r.status_code != 200: + raise ValueError(f"Api request failed with code: {r.status_code}") + # Parse the JSON response to get the list of profiles + profiles = r.json() + # Return if name is in the list of profiles + return any(p["name"] == profile_name for p in profiles) + +def test_get_profiles_format(testrun): # pylint: disable=W0613 + """Test profiles format""" + + # Send the get request + r = requests.get(f"{API}/profiles/format", timeout=5) + + # Check if status code is 200 (OK) + assert r.status_code == 200 - start_test_device("x123", BASELINE_MAC_ADDR) + # Parse the response + response = r.json() - until_true( - lambda: query_system_status().lower() == "compliant", - "system status is `complete`", - 600, - ) + # Check if the response is a list + assert isinstance(response, list) - stop_test_device("x123") + # Check that each item in the response has keys "questions" and "type" + for item in response: + assert "question" in item + assert "type" in item - # Validate response - r = requests.get(f"{API}/system/status", timeout=5) - response = json.loads(r.text) - pretty_print(response) +def test_get_profiles(testrun, reset_profiles, add_profile): # pylint: disable=W0613 + """Test for get profiles (no profile, one profile, two profiles)""" - # Validate results - results = {x["name"]: x for x in response["tests"]["results"]} - print(results) - # there are only 3 baseline tests - assert len(results) == 3 + # Test for no profiles - # Validate structure - with open( - os.path.join( - os.path.dirname(__file__), "mockito/running_system_status.json" - ), encoding="utf-8" - ) as f: - mockito = json.load(f) + # Send the get request to "/profiles" endpoint + r = requests.get(f"{API}/profiles", timeout=5) - # validate structure - assert set(dict_paths(mockito)).issubset(set(dict_paths(response))) + # Check if status code is 200 (OK) + assert r.status_code == 200 - # Validate results structure - assert set(dict_paths(mockito["tests"]["results"][0])).issubset( - set(dict_paths(response["tests"]["results"][0])) - ) + # Parse the response (profiles) + response = r.json() - # Validate a result - assert results["baseline.compliant"]["result"] == "Compliant" + # Check if response is a list + assert isinstance(response, list) + # Check if the list is empty + assert len(response) == 0 -@pytest.mark.skip() -def test_stop_running_test(testing_devices, testrun): # pylint: disable=W0613 - payload = {"device": {"mac_addr": ALL_MAC_ADDR, "firmware": "asd"}} - r = requests.post(f"{API}/system/start", data=json.dumps(payload), - timeout=10) + # Test for one profile + + # Load the profile using add_profile fixture + add_profile("new_profile.json") + + # Send get request to the "/profiles" endpoint + r = requests.get(f"{API}/profiles", timeout=5) + + # Check if status code is 200 (OK) assert r.status_code == 200 - until_true( - lambda: query_system_status().lower() == "waiting for device", - "system status is `waiting for device`", - 30, - ) + # Parse the response (profiles) + response = r.json() - start_test_device("x12345", ALL_MAC_ADDR) + # Check if response is a list + assert isinstance(response, list) - until_true( - lambda: query_test_count() > 1, - "system status is `complete`", - 1000, - ) + # Check if response contains one profile + assert len(response) == 1 - stop_test_device("x12345") + # Check that each profile has the expected fields + for profile in response: + for field in ["name", "status", "created", "version", "questions", "risk"]: + assert field in profile - # Validate response - r = requests.post(f"{API}/system/stop", timeout=5) - response = json.loads(r.text) - pretty_print(response) - assert response == {"success": "Testrun stopped"} - time.sleep(1) + # Check if "questions" value is a list + assert isinstance(profile["questions"], list) - # Validate response - r = requests.get(f"{API}/system/status", timeout=5) - response = json.loads(r.text) - pretty_print(response) + # Check that "questions" value has the expected fields + for element in profile["questions"]: + # Check if each element is dict + assert isinstance(element, dict) - assert response["status"] == "Cancelled" + # Check if "question" key is in dict element + assert "question" in element + # Check if "asnswer" key is in dict element + assert "answer" in element -def test_stop_running_not_running(testrun): # pylint: disable=W0613 - # Validate response - r = requests.post(f"{API}/system/stop", - timeout=10) - response = json.loads(r.text) - pretty_print(response) + # Test for two profiles - assert r.status_code == 404 - assert response["error"] == "Testrun is not currently running" + # Load the profile using add_profile fixture + add_profile("new_profile_2.json") -@pytest.mark.skip() -def test_multiple_runs(testing_devices, testrun): # pylint: disable=W0613 - payload = {"device": {"mac_addr": BASELINE_MAC_ADDR, "firmware": "asd"}} - r = requests.post(f"{API}/system/start", data=json.dumps(payload), - timeout=10) + # Send the get request to "/profiles" endpoint + r = requests.get(f"{API}/profiles", timeout=5) + + # Parse the response (profiles) + response = r.json() + + # Check if status code is 200 (OK) assert r.status_code == 200 - print(r.text) - until_true( - lambda: query_system_status().lower() == "waiting for device", - "system status is `waiting for device`", - 30, - ) + # Check if response is a list + assert isinstance(response, list) - start_test_device("x123", BASELINE_MAC_ADDR) + # Check if response contains two profiles + assert len(response) == 2 - until_true( - lambda: query_system_status().lower() == "compliant", - "system status is `complete`", - 900, +def test_create_profile(testrun, reset_profiles): # pylint: disable=W0613 + """Test for create profile if not exists""" + + # Load the profile + new_profile = load_json("new_profile.json", directory=PROFILES_PATH) + + # Assign the profile name to profile_name + profile_name = new_profile["name"] + + # Check if the profile already exists + if profile_exists(profile_name): + raise ValueError(f"Profile: {profile_name} exists") + + # Send the post request + r = requests.post(f"{API}/profiles", data=json.dumps(new_profile), timeout=5) + + # Check if status code is 201 (Created) + assert r.status_code == 201 + + # Parse the response + response = r.json() + + # Check if "success" key in response + assert "success" in response + + # Verify profile creation + r = requests.get(f"{API}/profiles", timeout=5) + + # Check if status code is 200 (OK) + assert r.status_code == 200 + + # Parse the response + profiles = r.json() + + # Iterate through all the profiles to find the profile based on the "name" + created_profile = next( + (p for p in profiles if p["name"] == profile_name), None ) - stop_test_device("x123") + # Check if profile was created + assert created_profile is not None - # Validate response - r = requests.get(f"{API}/system/status", timeout=5) - response = json.loads(r.text) - pretty_print(response) +def test_update_profile(testrun, reset_profiles, add_profile): # pylint: disable=W0613 + """Test for update profile when exists""" - # Validate results - results = {x["name"]: x for x in response["tests"]["results"]} - print(results) - # there are only 3 baseline tests - assert len(results) == 3 + # Load the new profile using add_profile fixture + new_profile = add_profile("new_profile.json") - payload = {"device": {"mac_addr": BASELINE_MAC_ADDR, "firmware": "asd"}} - r = requests.post(f"{API}/system/start", data=json.dumps(payload), - timeout=10) - # assert r.status_code == 200 - # returns 409 - print(r.text) + # Load the updated profile using load_json utility method + updated_profile = load_json("updated_profile.json", + directory=PROFILES_PATH) - until_true( - lambda: query_system_status().lower() == "waiting for device", - "system status is `waiting for device`", - 30, + # Assign the new_profile name + profile_name = new_profile["name"] + + # Assign the updated_profile name + updated_profile_name = updated_profile["rename"] + + # Exception if the profile does't exists + if not profile_exists(profile_name): + raise ValueError(f"Profile: {profile_name} exists") + + # Send the post request to update the profile + r = requests.post( + f"{API}/profiles", + data=json.dumps(updated_profile), + timeout=5) + + # Check if status code is 200 (OK) + assert r.status_code == 200 + + # Parse the response + response = r.json() + + # Check if "success" key in response + assert "success" in response + + # Get request to verify profile update + r = requests.get(f"{API}/profiles", timeout=5) + + # Check if status code is 200 (OK) + assert r.status_code == 200 + + # Parse the response + profiles = r.json() + + # Iterate through the profiles to find the profile based on the updated "name" + updated_profile_check = next( + (p for p in profiles if p["name"] == updated_profile_name), + None ) + # Check if profile was updated + assert updated_profile_check is not None - start_test_device("x123", BASELINE_MAC_ADDR) +def test_update_profile_invalid_json(testrun, reset_profiles, add_profile): # pylint: disable=W0613 + """Test for update profile invalid JSON payload (no 'name')""" - until_true( - lambda: query_system_status().lower() == "compliant", - "system status is `complete`", - 900, + # Load the new profile using add_profile fixture + add_profile("new_profile.json") + + # invalid JSON + updated_profile = {} + + # Send the post request to update the profile + r = requests.post( + f"{API}/profiles", + data=json.dumps(updated_profile), + timeout=5) + + # Parse the response + response = r.json() + + # Check if status code is 400 (Bad request) + assert r.status_code == 400 + + # Check if "error" key in response + assert "error" in response + +def test_create_profile_invalid_json(testrun, reset_profiles): # pylint: disable=W0613 + """Test for create profile invalid JSON payload """ + + # invalid JSON + new_profile = {} + + # Send the post request to update the profile + r = requests.post( + f"{API}/profiles", + data=json.dumps(new_profile), + timeout=5) + + # Parse the response + response = r.json() + + # Check if status code is 400 (Bad request) + assert r.status_code == 400 + + # Check if "error" key in response + assert "error" in response + +def test_delete_profile(testrun, reset_profiles, add_profile): # pylint: disable=W0613 + """Test for delete profile""" + + # Assign the profile from the fixture + profile_to_delete = add_profile("new_profile.json") + + # Assign the profile name + profile_name = profile_to_delete["name"] + + # Delete the profile + r = requests.delete( + f"{API}/profiles", + data=json.dumps(profile_to_delete), + timeout=5) + + # Check if status code is 200 (OK) + assert r.status_code == 200 + + # Parse the JSON response + response = r.json() + + # Check if the response contains "success" key + assert "success" in response + + # Check if the profile has been deleted + r = requests.get(f"{API}/profiles", timeout=5) + + # Check if status code is 200 (OK) + assert r.status_code == 200 + + # Parse the JSON response + profiles = r.json() + + # Iterate through the profiles to find the profile based on the "name" + deleted_profile = next( + (p for p in profiles if p["name"] == profile_name), + None ) + # Check if profile was deleted + assert deleted_profile is None - stop_test_device("x123") +def test_delete_profile_no_profile(testrun, reset_profiles): # pylint: disable=W0613 + """Test delete profile if the profile does not exists""" + # Assign the profile to delete + profile_to_delete = {"name": "New Profile"} -def test_create_invalid_chars(empty_devices_dir, testrun): # pylint: disable=W0613 - # local_delete_devices(ALL_DEVICES) - # We must start test run with no devices in local/devices for this test - # to function as expected - assert len(local_get_devices()) == 0 + # Delete the profile + r = requests.delete( + f"{API}/profiles", + data=json.dumps(profile_to_delete), + timeout=5) - # Test adding device - device_1 = { - "manufacturer": "/'disallowed characters///", - "model": "First", - "mac_addr": BASELINE_MAC_ADDR, - "test_modules": { - "dns": {"enabled": False}, - "connection": {"enabled": True}, - "ntp": {"enabled": True}, - "baseline": {"enabled": True}, - "nmap": {"enabled": True}, - }, - } + # Check if status code is 404 (Profile does not exist) + assert r.status_code == 404 - r = requests.post(f"{API}/device", data=json.dumps(device_1), - timeout=5) - print(r.text) - print(r.status_code) +def test_delete_profile_invalid_json(testrun, reset_profiles): # pylint: disable=W0613 + """Test for delete profile wrong JSON payload""" + + profile_to_delete = {} + + # Delete the profile + r = requests.delete( + f"{API}/profiles", + data=json.dumps(profile_to_delete), + timeout=5) + + # Parse the response + response = r.json() + + # Check if status code is 400 (bad request) + assert r.status_code == 400 + + # Check if "error" key in response + assert "error" in response + + profile_to_delete_2 = {"status": "Draft"} + # Delete the profile + r = requests.delete( + f"{API}/profiles", + data=json.dumps(profile_to_delete_2), + timeout=5) + + # Parse the response + response = r.json() + + # Check if status code is 400 (bad request) + assert r.status_code == 400 + + # Check if "error" key in response + assert "error" in response + +def test_delete_profile_internal_server_error(testrun, # pylint: disable=W0613 + reset_profiles, # pylint: disable=W0613 + add_profile ): + """Test for delete profile causing internal server error""" + + # Assign the profile from the fixture + profile_to_delete = add_profile("new_profile.json") + + # Assign the profile name to profile_name + profile_name = profile_to_delete["name"] + + # Construct the path to the profile JSON file in local/risk_profiles + risk_profile_path = os.path.join(PROFILES_DIRECTORY, f"{profile_name}.json") + + # Delete the profile JSON file before making the DELETE request + if os.path.exists(risk_profile_path): + os.remove(risk_profile_path) + + # Send the DELETE request to delete the profile + r = requests.delete(f"{API}/profiles", + json={"name": profile_to_delete["name"]}, + timeout=5) + + # Check if status code is 500 (Internal Server Error) + assert r.status_code == 500 + + # Parse the json response + response = r.json() + + # Check if error in response + assert "error" in response diff --git a/testing/pylint/test_pylint b/testing/pylint/test_pylint index 1f71482e5..7e102c7f8 100755 --- a/testing/pylint/test_pylint +++ b/testing/pylint/test_pylint @@ -14,27 +14,35 @@ # See the License for the specific language governing permissions and # limitations under the License. -ERROR_LIMIT=25 - -sudo cmd/install +# Install python venv +python3 -m venv venv +# Activate the venv source venv/bin/activate -sudo pip3 install pylint==3.0.3 +# Install pylint +pip install pylint==3.2.6 + +# Declare the applicable files files=$(find . -path ./venv -prune -o -name '*.py' -print) +# Define the pylint output file OUT=pylint.out -rm -f $OUT && touch $OUT +# Remove it if it already exists +rm -f $OUT +# Run pylint against the target files +# Change the evaluation to total the number of errors +# Output to the specified output file pylint $files -ry --extension-pkg-allow-list=docker --evaluation="error + warning + refactor + convention" 2>/dev/null | tee -a $OUT -new_errors=$(cat $OUT | grep -oP "(?!=^Your code has been rated at)([0-9]+)(?=\.00/10[ \(]?)" ) +# Obtain the total number of errors from the pylint out file +errors=$(cat $OUT | grep -oP "(?!=^Your code has been rated at)([0-9]+)(?=\.00/10[ \(]?)" ) -echo "$new_errors > $ERROR_LIMIT?" -if (( $new_errors > $ERROR_LIMIT)); then - echo new errors $new_errors > error limit $ERROR_LIMIT - echo failing .. +# Check if any errors exist +if (( $errors > 0 )); then + echo "$errors pylint issues have been identified. These must be resolved before merging." exit 1 fi diff --git a/testing/tests/test_tests.py b/testing/tests/test_tests.py index aaae1a09d..21be6b7de 100644 --- a/testing/tests/test_tests.py +++ b/testing/tests/test_tests.py @@ -96,7 +96,7 @@ def test_list_tests(capsys, results, test_matrix): print('============') print('============') print('tests seen:') - print('\n'.join(set([x.name for x in all_tests]))) + print('\n'.join(set(x.name for x in all_tests))) print('\ntesting for pass:') print('\n'.join(ci_pass)) print('\ntesting for fail:') diff --git a/testing/unit/conn/captures/monitor.pcap b/testing/unit/conn/captures/monitor.pcap new file mode 100644 index 0000000000000000000000000000000000000000..0dfb85ff4a04427badd206f3110992dfdfcd51d4 GIT binary patch literal 389089 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 nn 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

tD_>x2GWaDj0FGA@yu! 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 literal 0 HcmV?d00001 diff --git a/testing/unit/conn/conn_module_test.py b/testing/unit/conn/conn_module_test.py index d31a8051f..906abb754 100644 --- a/testing/unit/conn/conn_module_test.py +++ b/testing/unit/conn/conn_module_test.py @@ -13,13 +13,17 @@ # limitations under the License. """Module run all the Connection module related unit tests""" from port_stats_util import PortStatsUtil +from connection_module import ConnectionModule import os import unittest from common import logger MODULE = 'conn' -# Define the file paths -TEST_FILES_DIR = 'testing/unit/' + MODULE +# Define the directories +TEST_FILES_DIR = '/testing/unit/' + MODULE +OUTPUT_DIR = os.path.join(TEST_FILES_DIR, 'output/') +CAPTURES_DIR = os.path.join(TEST_FILES_DIR, 'captures/') + ETHTOOL_RESULTS_COMPLIANT_FILE = os.path.join(TEST_FILES_DIR, 'ethtool', 'ethtool_results_compliant.txt') ETHTOOL_RESULTS_NONCOMPLIANT_FILE = os.path.join( @@ -34,8 +38,12 @@ ETHTOOL_PORT_STATS_POST_NONCOMPLIANT_FILE = os.path.join( TEST_FILES_DIR, 'ethtool', 'ethtool_port_stats_post_monitor_noncompliant.txt') -LOGGER = None +# Define the capture files to be used for the test +STARTUP_CAPTURE_FILE = os.path.join(CAPTURES_DIR, 'startup.pcap') +MONITOR_CAPTURE_FILE = os.path.join(CAPTURES_DIR, 'monitor.pcap') + +LOGGER = None class ConnectionModuleTest(unittest.TestCase): """Contains and runs all the unit tests concerning Connection @@ -46,6 +54,9 @@ def setUpClass(cls): global LOGGER LOGGER = logger.get_logger('unit_test_' + MODULE) + # Set the MAC address for device in capture files + os.environ['DEVICE_MAC'] = '98:f0:7b:d1:87:06' + # Test the port link status def connection_port_link_compliant_test(self): LOGGER.info('connection_port_link_compliant_test') @@ -117,6 +128,17 @@ def connection_port_speed_autonegotiation_fail_test(self): LOGGER.info(result) self.assertEqual(result[0], False) + # Test proper filtering for ICMP protocol in DHCP packets + def connection_switch_dhcp_snooping_icmp_test(self): + LOGGER.info('connection_switch_dhcp_snooping_icmp_test') + conn_module = ConnectionModule(module=MODULE, + log_dir=OUTPUT_DIR, + results_dir=OUTPUT_DIR, + startup_capture_file=STARTUP_CAPTURE_FILE, + monitor_capture_file=MONITOR_CAPTURE_FILE) + result = conn_module._connection_switch_dhcp_snooping() # pylint: disable=W0212 + LOGGER.info(result) + self.assertEqual(result[0], True) if __name__ == '__main__': suite = unittest.TestSuite() @@ -136,5 +158,9 @@ def connection_port_speed_autonegotiation_fail_test(self): suite.addTest( ConnectionModuleTest('connection_port_speed_autonegotiation_fail_test')) + # DHCP Snooping related tests + suite.addTest( + ConnectionModuleTest('connection_switch_dhcp_snooping_icmp_test')) + runner = unittest.TextTestRunner() runner.run(suite) diff --git a/testing/unit/dns/dns_module_test.py b/testing/unit/dns/dns_module_test.py index 6c3dec74d..a4b7a81e9 100644 --- a/testing/unit/dns/dns_module_test.py +++ b/testing/unit/dns/dns_module_test.py @@ -16,7 +16,6 @@ import unittest from scapy.all import rdpcap, DNS, wrpcap import os -from testreport import TestReport MODULE = 'dns' @@ -28,7 +27,6 @@ LOCAL_REPORT = os.path.join(REPORTS_DIR, 'dns_report_local.html') LOCAL_REPORT_NO_DNS = os.path.join(REPORTS_DIR, 'dns_report_local_no_dns.html') -CONF_FILE = 'modules/test/' + MODULE + '/conf/module_config.json' # Define the capture files to be used for the test DNS_SERVER_CAPTURE_FILE = os.path.join(CAPTURES_DIR, 'dns.pcap') @@ -44,11 +42,13 @@ def setUpClass(cls): # Create the output directories and ignore errors if it already exists os.makedirs(OUTPUT_DIR, exist_ok=True) + # Set the MAC address for device in capture files + os.environ['DEVICE_MAC'] = '38:d1:35:01:17:fe' + # Test the module report generation def dns_module_report_test(self): dns_module = DNSModule(module=MODULE, log_dir=OUTPUT_DIR, - conf_file=CONF_FILE, results_dir=OUTPUT_DIR, dns_server_capture_file=DNS_SERVER_CAPTURE_FILE, startup_capture_file=STARTUP_CAPTURE_FILE, @@ -59,12 +59,6 @@ def dns_module_report_test(self): # Read the generated report with open(report_out_path, 'r', encoding='utf-8') as file: report_out = file.read() - formatted_report = self.add_formatting(report_out) - - # Write back the new formatted_report value - out_report_path = os.path.join(OUTPUT_DIR, 'dns_report_with_dns.html') - with open(out_report_path, 'w', encoding='utf-8') as file: - file.write(formatted_report) # Read the local good report with open(LOCAL_REPORT, 'r', encoding='utf-8') as file: @@ -104,7 +98,6 @@ def dns_module_report_no_dns_test(self): dns_module = DNSModule(module='dns', log_dir=OUTPUT_DIR, - conf_file=CONF_FILE, results_dir=OUTPUT_DIR, dns_server_capture_file=dns_server_cap_file, startup_capture_file=startup_cap_file, @@ -115,12 +108,6 @@ def dns_module_report_no_dns_test(self): # Read the generated report with open(report_out_path, 'r', encoding='utf-8') as file: report_out = file.read() - formatted_report = self.add_formatting(report_out) - - # Write back the new formatted_report value - out_report_path = os.path.join(OUTPUT_DIR, 'dns_report_no_dns.html') - with open(out_report_path, 'w', encoding='utf-8') as file: - file.write(formatted_report) # Read the local good report with open(LOCAL_REPORT_NO_DNS, 'r', encoding='utf-8') as file: @@ -128,17 +115,6 @@ def dns_module_report_no_dns_test(self): self.assertEqual(report_out, report_local) - def add_formatting(self, body): - return f''' - - - {TestReport().generate_head()} - - {body} - - DNS Module

Requests to local DNS server Requests to external DNS servers Total DNS requests Total DNS responses
71 6 77 91
Source Destination Type URL
10.10.10.14 10.10.10.4 Query mqtt.googleapis.com
10.10.10.14 10.10.10.4 Query mqtt.googleapis.com
10.10.10.14 10.10.10.4 Query mqtt.googleapis.com
10.10.10.14 10.10.10.4 Query mqtt.googleapis.com
10.10.10.4 8.8.8.8 Query mqtt.googleapis.com
10.10.10.4 8.8.8.8 Query mqtt.googleapis.com
8.8.8.8 10.10.10.4 Response mqtt.googleapis.com
10.10.10.4 10.10.10.14 Response mqtt.googleapis.com
10.10.10.4 10.10.10.14 Response mqtt.googleapis.com
8.8.8.8 10.10.10.4 Response mqtt.googleapis.com
10.10.10.4 10.10.10.14 Response mqtt.googleapis.com
10.10.10.4 10.10.10.14 Response mqtt.googleapis.com
10.10.10.14 10.10.10.4 Query pool.ntp.org
10.10.10.14 10.10.10.4 Query pool.ntp.org
10.10.10.4 8.8.8.8 Query pool.ntp.org
10.10.10.4 8.8.8.8 Query pool.ntp.org
8.8.8.8 10.10.10.4 Response pool.ntp.org
10.10.10.4 10.10.10.14 Response pool.ntp.org
8.8.8.8 10.10.10.4 Response pool.ntp.org
10.10.10.4 10.10.10.14 Response pool.ntp.org
10.10.10.14 10.10.10.4 Query mqtt.googleapis.com
10.10.10.14 10.10.10.4 Query mqtt.googleapis.com
10.10.10.4 10.10.10.14 Response mqtt.googleapis.com
10.10.10.4 10.10.10.14 Response mqtt.googleapis.com
10.10.10.14 10.10.10.4 Query mqtt.googleapis.com
10.10.10.14 10.10.10.4 Query mqtt.googleapis.com
10.10.10.4 10.10.10.14 Response mqtt.googleapis.com
10.10.10.4 10.10.10.14 Response mqtt.googleapis.com
10.10.10.14 10.10.10.4 Query mqtt.googleapis.com
10.10.10.14 10.10.10.4 Query mqtt.googleapis.com
10.10.10.4 10.10.10.14 Response mqtt.googleapis.com
10.10.10.4 10.10.10.14 Response mqtt.googleapis.com
10.10.10.14 10.10.10.4 Query mqtt.googleapis.com
10.10.10.14 10.10.10.4 Query mqtt.googleapis.com
10.10.10.4 10.10.10.14 Response mqtt.googleapis.com
10.10.10.4 10.10.10.14 Response mqtt.googleapis.com
10.10.10.14 10.10.10.4 Query mqtt.googleapis.com
10.10.10.14 10.10.10.4 Query mqtt.googleapis.com
10.10.10.4 10.10.10.14 Response mqtt.googleapis.com
10.10.10.4 10.10.10.14 Response mqtt.googleapis.com
10.10.10.14 10.10.10.4 Query mqtt.googleapis.com
10.10.10.14 10.10.10.4 Query mqtt.googleapis.com
10.10.10.4 10.10.10.14 Response mqtt.googleapis.com
10.10.10.4 10.10.10.14 Response mqtt.googleapis.com
10.10.10.14 10.10.10.4 Query mqtt.googleapis.com
10.10.10.14 10.10.10.4 Query mqtt.googleapis.com
10.10.10.4 10.10.10.14 Response mqtt.googleapis.com
10.10.10.4 10.10.10.14 Response mqtt.googleapis.com
10.10.10.14 10.10.10.4 Query mqtt.googleapis.com
10.10.10.14 10.10.10.4 Query mqtt.googleapis.com
10.10.10.4 10.10.10.14 Response mqtt.googleapis.com
10.10.10.4 10.10.10.14 Response mqtt.googleapis.com
10.10.10.14 10.10.10.4 Query pool.ntp.org
10.10.10.14 10.10.10.4 Query pool.ntp.org
10.10.10.4 8.8.8.8 Query pool.ntp.org
10.10.10.4 10.10.10.14 Response pool.ntp.org
10.10.10.4 10.10.10.14 Response pool.ntp.org
8.8.8.8 10.10.10.4 Response pool.ntp.org
10.10.10.4 8.8.8.8 Response pool.ntp.org
10.10.10.14 10.10.10.4 Query mqtt.googleapis.com
10.10.10.14 10.10.10.4 Query mqtt.googleapis.com
10.10.10.4 10.10.10.14 Response mqtt.googleapis.com
10.10.10.4 10.10.10.14 Response mqtt.googleapis.com
10.10.10.14 10.10.10.4 Query mqtt.googleapis.com
10.10.10.14 10.10.10.4 Query mqtt.googleapis.com
10.10.10.4 10.10.10.14 Response mqtt.googleapis.com
10.10.10.4 10.10.10.14 Response mqtt.googleapis.com
10.10.10.14 10.10.10.4 Query mqtt.googleapis.com
10.10.10.14 10.10.10.4 Query mqtt.googleapis.com
10.10.10.4 10.10.10.14 Response mqtt.googleapis.com
10.10.10.4 10.10.10.14 Response mqtt.googleapis.com
10.10.10.14 10.10.10.4 Query mqtt.googleapis.com
10.10.10.14 10.10.10.4 Query mqtt.googleapis.com
10.10.10.4 10.10.10.14 Response mqtt.googleapis.com
10.10.10.4 10.10.10.14 Response mqtt.googleapis.com
10.10.10.14 10.10.10.4 Query mqtt.googleapis.com
10.10.10.14 10.10.10.4 Query mqtt.googleapis.com
10.10.10.4 10.10.10.14 Response mqtt.googleapis.com
10.10.10.4 10.10.10.14 Response mqtt.googleapis.com
10.10.10.14 10.10.10.4 Query mqtt.googleapis.com
10.10.10.14 10.10.10.4 Query mqtt.googleapis.com
10.10.10.4 10.10.10.14 Response mqtt.googleapis.com
10.10.10.4 10.10.10.14 Response mqtt.googleapis.com
10.10.10.14 10.10.10.4 Query mqtt.googleapis.com
10.10.10.14 10.10.10.4 Query mqtt.googleapis.com
10.10.10.4 8.8.8.8 Query mqtt.googleapis.com
8.8.8.8 10.10.10.4 Response mqtt.googleapis.com
10.10.10.4 10.10.10.14 Response mqtt.googleapis.com
10.10.10.4 10.10.10.14 Response mqtt.googleapis.com
10.10.10.14 10.10.10.4 Query mqtt.googleapis.com
10.10.10.14 10.10.10.4 Query mqtt.googleapis.com
10.10.10.4 10.10.10.14 Response mqtt.googleapis.com
10.10.10.4 10.10.10.14 Response mqtt.googleapis.com
10.10.10.14 10.10.10.4 Query mqtt.googleapis.com
10.10.10.14 10.10.10.4 Query mqtt.googleapis.com
10.10.10.4 10.10.10.14 Response mqtt.googleapis.com
10.10.10.4 10.10.10.14 Response mqtt.googleapis.com
10.10.10.14 10.10.10.4 Query mqtt.googleapis.com
10.10.10.14 10.10.10.4 Query mqtt.googleapis.com
10.10.10.4 10.10.10.14 Response mqtt.googleapis.com
10.10.10.4 10.10.10.14 Response mqtt.googleapis.com
10.10.10.14 10.10.10.4 Query mqtt.googleapis.com
10.10.10.14 10.10.10.4 Query mqtt.googleapis.com
10.10.10.4 10.10.10.14 Response mqtt.googleapis.com
10.10.10.4 10.10.10.14 Response mqtt.googleapis.com
10.10.10.14 10.10.10.4 Query mqtt.googleapis.com
10.10.10.14 10.10.10.4 Query mqtt.googleapis.com
10.10.10.4 10.10.10.14 Response mqtt.googleapis.com
10.10.10.4 10.10.10.14 Response mqtt.googleapis.com
10.10.10.14 10.10.10.4 Query mqtt.googleapis.com
10.10.10.14 10.10.10.4 Query mqtt.googleapis.com
10.10.10.4 10.10.10.14 Response mqtt.googleapis.com
10.10.10.4 10.10.10.14 Response mqtt.googleapis.com
10.10.10.4 10.10.10.14 Response mqtt.googleapis.com
10.10.10.4 10.10.10.14 Response mqtt.googleapis.com
10.10.10.14 10.10.10.4 Query pool.ntp.org
10.10.10.14 10.10.10.4 Query pool.ntp.org
10.10.10.4 10.10.10.14 Response pool.ntp.org
10.10.10.4 10.10.10.14 Response pool.ntp.org
10.10.10.14 10.10.10.4 Query mqtt.googleapis.com
10.10.10.4 10.10.10.14 Response mqtt.googleapis.com
10.10.10.4 10.10.10.14 Response mqtt.googleapis.com
10.10.10.14 10.10.10.4 Query mqtt.googleapis.com
10.10.10.4 10.10.10.14 Response mqtt.googleapis.com
10.10.10.4 10.10.10.14 Response mqtt.googleapis.com
10.10.10.14 10.10.10.4 Query mqtt.googleapis.com
10.10.10.4 10.10.10.14 Response mqtt.googleapis.com
10.10.10.4 10.10.10.14 Response mqtt.googleapis.com
10.10.10.14 10.10.10.4 Query mqtt.googleapis.com
10.10.10.4 10.10.10.14 Response mqtt.googleapis.com
10.10.10.4 10.10.10.14 Response mqtt.googleapis.com
10.10.10.14 10.10.10.4 Query mqtt.googleapis.com
10.10.10.4 10.10.10.14 Response mqtt.googleapis.com
10.10.10.4 10.10.10.14 Response mqtt.googleapis.com
10.10.10.14 10.10.10.4 Query mqtt.googleapis.com
10.10.10.4 10.10.10.14 Response mqtt.googleapis.com
10.10.10.4 10.10.10.14 Response mqtt.googleapis.com
10.10.10.14 10.10.10.4 Query mqtt.googleapis.com
10.10.10.4 10.10.10.14 Response mqtt.googleapis.com
10.10.10.4 10.10.10.14 Response mqtt.googleapis.com
10.10.10.14 10.10.10.4 Query mqtt.googleapis.com
10.10.10.14 10.10.10.4 Query mqtt.googleapis.com
10.10.10.4 10.10.10.14 Response mqtt.googleapis.com
10.10.10.4 10.10.10.14 Response mqtt.googleapis.com
10.10.10.14 10.10.10.4 Query pool.ntp.org
10.10.10.4 10.10.10.14 Response pool.ntp.org
10.10.10.4 10.10.10.14 Response pool.ntp.org
10.10.10.14 10.10.10.4 Query mqtt.googleapis.com
10.10.10.4 10.10.10.14 Response mqtt.googleapis.com
10.10.10.4 10.10.10.14 Response mqtt.googleapis.com
10.10.10.14 10.10.10.4 Query mqtt.googleapis.com
10.10.10.4 10.10.10.14 Response mqtt.googleapis.com
10.10.10.4 10.10.10.14 Response mqtt.googleapis.com
10.10.10.14 10.10.10.4 Query mqtt.googleapis.com
10.10.10.14 10.10.10.4 Query mqtt.googleapis.com
10.10.10.4 10.10.10.14 Response mqtt.googleapis.com
10.10.10.4 10.10.10.14 Response mqtt.googleapis.com
10.10.10.14 10.10.10.4 Query mqtt.googleapis.com
10.10.10.4 10.10.10.14 Response mqtt.googleapis.com
10.10.10.4 10.10.10.14 Response mqtt.googleapis.com
10.10.10.14 10.10.10.4 Query mqtt.googleapis.com
10.10.10.14 10.10.10.4 Query mqtt.googleapis.com
10.10.10.4 10.10.10.14 Response mqtt.googleapis.com
10.10.10.4 10.10.10.14 Response mqtt.googleapis.com
10.10.10.14 10.10.10.4 Query mqtt.googleapis.com
10.10.10.14 10.10.10.4 Query mqtt.googleapis.com
10.10.10.4 10.10.10.14 Response mqtt.googleapis.com
10.10.10.4 10.10.10.14 Response mqtt.googleapis.com
\ No newline at end of file +

DNS Module

Requests to local DNS server Requests to external DNS servers Total DNS requests Total DNS responses
71 0 71 84
Source Destination Type URL Count
10.10.10.14 10.10.10.4 Query mqtt.googleapis.com 64
10.10.10.4 10.10.10.14 Response mqtt.googleapis.com 76
10.10.10.14 10.10.10.4 Query pool.ntp.org 7
10.10.10.4 10.10.10.14 Response pool.ntp.org 8
\ No newline at end of file diff --git a/testing/unit/framework/session_test.py b/testing/unit/framework/session_test.py new file mode 100644 index 000000000..1045457f3 --- /dev/null +++ b/testing/unit/framework/session_test.py @@ -0,0 +1,57 @@ +# Copyright 2024 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Session methods tests""" + +from unittest.mock import patch +from common import session + + +class MockUtil: + """mock util functions""" + + @staticmethod + def get_sys_interfaces(): + return {"eth0": "00:1A:2B:3C:4D:5E", "eth1": "66:77:88:99:AA:BB"} + + @staticmethod + def diff_dicts(d1, d2): # pylint: disable=W0613 + return { + "items_added": {"eth1": "66:77:88:99:AA:BB"}, + "items_removed": {"eth2": "00:1B:2C:3D:4E:5F"}, + } + + +class TestrunSessionMock(session.TestrunSession): + def __init__(self): # pylint: disable=W0231 + self._ifaces = {"eth0": "00:1A:2B:3C:4D:5E", "eth2": "66:77:88:99:AA:BB"} + + +util = MockUtil() + + +@patch("common.util.get_sys_interfaces", side_effect=util.get_sys_interfaces) +@patch("common.util.diff_dicts", side_effect=util.diff_dicts) +def test_detect_network_adapters_change( + mock_get_sys_interfaces, # pylint: disable=W0613 + mock_diff_dicts, # pylint: disable=W0613 +): + testrun_session = TestrunSessionMock() + + # Test added and removed + result = testrun_session.detect_network_adapters_change() + assert result == { + "adapters_added": {"eth1": "66:77:88:99:AA:BB"}, + "adapters_removed": {"eth2": "00:1B:2C:3D:4E:5F"}, + } diff --git a/testing/unit/framework/util_test.py b/testing/unit/framework/util_test.py new file mode 100644 index 000000000..ec8fd48fc --- /dev/null +++ b/testing/unit/framework/util_test.py @@ -0,0 +1,61 @@ +# Copyright 2024 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Util tests""" + +from collections import namedtuple +from unittest.mock import patch +from common import util +from net_orc import ip_control + +Snicaddr = namedtuple('snicaddr', + ['family', 'address']) + +mock_addrs = { + 'eth0': [Snicaddr(17, '00:1A:2B:3C:4D:5E')], + 'wlan0': [Snicaddr(17, '66:77:88:99:AA:BB')], + 'enp0s3': [Snicaddr(17, '11:22:33:44:55:66')] +} + +@patch('psutil.net_if_addrs') +def test_get_sys_interfaces(mock_net_if_addrs): + mock_net_if_addrs.return_value = mock_addrs + # Expected result + expected = { + 'eth0': '00:1A:2B:3C:4D:5E', + 'enp0s3': '11:22:33:44:55:66' + } + + result = ip_control.IPControl.get_sys_interfaces() + # Assert the result + assert result == expected + + +def test_diff_dicts(): + d1 = {'a': 1, 'b': 2} + d2 = {'a': 1, 'b': 2} + #Assert equal dicts + assert not util.diff_dicts(d1, d2) + d2 = {'a': 1, 'c': 3} + expected = {'items_removed': {'b': 2},'items_added': {'c': 3}} + #Assert items added adn removed + assert util.diff_dicts(d1, d2) == expected + d1 = {'a': 1} + d2 = {'b': 2} + expected = { + 'items_removed': {'a': 1}, + 'items_added': {'b': 2} + } + #Assert completely different dicts + assert util.diff_dicts(d1, d2) == expected diff --git a/testing/unit/ntp/ntp_module_test.py b/testing/unit/ntp/ntp_module_test.py index 20dd88ef1..ac10bb46c 100644 --- a/testing/unit/ntp/ntp_module_test.py +++ b/testing/unit/ntp/ntp_module_test.py @@ -11,12 +11,11 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. -"""Module run all the DNS related unit tests""" +"""Module run all the NTP related unit tests""" from ntp_module import NTPModule import unittest from scapy.all import rdpcap, NTP, wrpcap import os -from testreport import TestReport MODULE = 'ntp' @@ -28,7 +27,6 @@ LOCAL_REPORT = os.path.join(REPORTS_DIR,'ntp_report_local.html') LOCAL_REPORT_NO_NTP = os.path.join(REPORTS_DIR,'ntp_report_local_no_ntp.html') -CONF_FILE = 'modules/test/' + MODULE + '/conf/module_config.json' # Define the capture files to be used for the test NTP_SERVER_CAPTURE_FILE = os.path.join(CAPTURES_DIR,'ntp.pcap') @@ -49,7 +47,6 @@ def setUpClass(cls): def ntp_module_report_test(self): ntp_module = NTPModule(module=MODULE, log_dir=OUTPUT_DIR, - conf_file=CONF_FILE, results_dir=OUTPUT_DIR, ntp_server_capture_file=NTP_SERVER_CAPTURE_FILE, startup_capture_file=STARTUP_CAPTURE_FILE, @@ -60,12 +57,6 @@ def ntp_module_report_test(self): # Read the generated report with open(report_out_path, 'r', encoding='utf-8') as file: report_out = file.read() - formatted_report = self.add_formatting(report_out) - - # Write back the new formatted_report value - out_report_path = os.path.join(OUTPUT_DIR, 'ntp_report_with_ntp.html') - with open(out_report_path, 'w', encoding='utf-8') as file: - file.write(formatted_report) # Read the local good report with open(LOCAL_REPORT, 'r', encoding='utf-8') as file: @@ -105,7 +96,6 @@ def ntp_module_report_no_ntp_test(self): ntp_module = NTPModule(module='dns', log_dir=OUTPUT_DIR, - conf_file=CONF_FILE, results_dir=OUTPUT_DIR, ntp_server_capture_file=ntp_server_cap_file, startup_capture_file=startup_cap_file, @@ -116,12 +106,6 @@ def ntp_module_report_no_ntp_test(self): # Read the generated report with open(report_out_path, 'r', encoding='utf-8') as file: report_out = file.read() - formatted_report = self.add_formatting(report_out) - - # Write back the new formatted_report value - out_report_path = os.path.join(OUTPUT_DIR,'ntp_report_no_ntp.html') - with open(out_report_path, 'w', encoding='utf-8') as file: - file.write(formatted_report) # Read the local good report with open(LOCAL_REPORT_NO_NTP, 'r', encoding='utf-8') as file: @@ -129,16 +113,6 @@ def ntp_module_report_no_ntp_test(self): self.assertEqual(report_out, report_local) - def add_formatting(self,body): - return f''' - - - {TestReport().generate_head()} - - {body} - - NTP Module 101 104 + @@ -24,1444 +25,90 @@

NTP Module

- + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + - + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + - + + - + + - + + - - - - - - - - - - - - - - - + + - + + - + +
Destination Type VersionTimestampCountSync Request Average
10.10.10.1510.10.10.5Client4Feb 15, 2024 22:12:29.447
10.10.10.510.10.10.15Server4Feb 15, 2024 22:12:29.448
10.10.10.1510.10.10.5Client4Feb 15, 2024 22:12:31.577
10.10.10.510.10.10.15Server4Feb 15, 2024 22:12:31.577
10.10.10.1510.10.10.5Client4Feb 15, 2024 22:12:33.694
10.10.10.510.10.10.15Server4Feb 15, 2024 22:12:33.694
10.10.10.1510.10.10.5Client4Feb 15, 2024 22:12:35.785
10.10.10.510.10.10.15Server4Feb 15, 2024 22:12:35.786
10.10.10.1510.10.10.5Client4Feb 15, 2024 22:12:37.806
10.10.10.510.10.10.15Server4Feb 15, 2024 22:12:37.806
10.10.10.1510.10.10.5Client4Feb 15, 2024 22:12:39.856
10.10.10.510.10.10.15Server4Feb 15, 2024 22:12:39.856
10.10.10.1510.10.10.5Client4Feb 15, 2024 22:12:41.931
10.10.10.510.10.10.15Server4Feb 15, 2024 22:12:41.932
10.10.10.1510.10.10.5Client4Feb 15, 2024 22:12:43.954
10.10.10.510.10.10.15Server4Feb 15, 2024 22:12:43.956
10.10.10.1510.10.10.5Client4Feb 15, 2024 22:13:06.439
10.10.10.510.10.10.15Server4Feb 15, 2024 22:13:06.439
10.10.10.1510.10.10.5Client4Feb 15, 2024 22:13:08.492
10.10.10.510.10.10.15Server4Feb 15, 2024 22:13:08.494
10.10.10.1510.10.10.5Client4Feb 15, 2024 22:13:40.536
10.10.10.510.10.10.15Server4Feb 15, 2024 22:13:40.541
10.10.10.1510.10.10.5Client4Feb 15, 2024 22:13:48.274
10.10.10.510.10.10.15Server4Feb 15, 2024 22:13:48.277
10.10.10.1510.10.10.5Client4Feb 15, 2024 22:14:12.619
10.10.10.510.10.10.15Server4Feb 15, 2024 22:14:12.624
10.10.10.1510.10.10.5Client4Feb 15, 2024 22:14:44.702
10.10.10.510.10.10.15Server4Feb 15, 2024 22:14:44.703
10.10.10.1510.10.10.5Client4Feb 15, 2024 22:14:53.026
10.10.10.510.10.10.15Server4Feb 15, 2024 22:14:53.029
10.10.10.1510.10.10.5Client4Feb 15, 2024 22:15:16.786
10.10.10.510.10.10.15Server4Feb 15, 2024 22:15:16.791
10.10.10.1510.10.10.5Client4Feb 15, 2024 22:15:48.884
10.10.10.510.10.10.15Server4Feb 15, 2024 22:15:48.887
10.10.10.1510.10.10.5Client4Feb 15, 2024 22:15:57.829
10.10.10.510.10.10.15Server4Feb 15, 2024 22:15:57.829
10.10.10.1510.10.10.5Client4Feb 15, 2024 22:16:20.970
10.10.10.510.10.10.15Server4Feb 15, 2024 22:16:20.970
10.10.10.1510.10.10.5Client4Feb 15, 2024 22:16:54.054
10.10.10.510.10.10.15Server4Feb 15, 2024 22:16:54.054
10.10.10.1510.10.10.5Client4Feb 15, 2024 22:17:02.738
10.10.10.510.10.10.15Server4Feb 15, 2024 22:17:02.740
10.10.10.1510.10.10.5Client4Feb 15, 2024 22:17:26.136
10.10.10.510.10.10.15Server4Feb 15, 2024 22:17:26.139
10.10.10.1510.10.10.5Client4Feb 15, 2024 22:17:59.293
10.10.10.510.10.10.15Server4Feb 15, 2024 22:17:59.293
10.10.10.1510.10.10.5Client4Feb 15, 2024 22:18:07.242
10.10.10.510.10.10.15Server4Feb 15, 2024 22:18:07.242
10.10.10.1510.10.10.5Client4Feb 15, 2024 22:18:32.379
10.10.10.510.10.10.15Server4Feb 15, 2024 22:18:32.379
10.10.10.1510.10.10.5Client4Feb 15, 2024 22:20:06.908
10.10.10.510.10.10.15Server4Feb 15, 2024 22:20:06.908
10.10.10.1510.10.10.5Client4Feb 15, 2024 22:20:08.936
10.10.10.510.10.10.15Server4Feb 15, 2024 22:20:08.937
10.10.10.1510.10.10.5Client4Feb 15, 2024 22:20:10.974
10.10.10.510.10.10.15Server4Feb 15, 2024 22:20:10.974
10.10.10.1510.10.10.5Client4Feb 15, 2024 22:20:12.998
10.10.10.510.10.10.15Server4Feb 15, 2024 22:20:12.999
10.10.10.1510.10.10.5Client4Feb 15, 2024 22:20:59.581
10.10.10.510.10.10.15Server4Feb 15, 2024 22:20:59.582
10.10.10.1510.10.10.5Client4Feb 15, 2024 22:21:34.063
10.10.10.510.10.10.15Server4Feb 15, 2024 22:21:34.063
10.10.10.1510.10.10.5Client4Feb 15, 2024 22:21:36.121
10.10.10.510.10.10.15Server4Feb 15, 2024 22:21:36.121
10.10.10.1510.10.10.5Client4Feb 15, 2024 22:21:38.176
10.10.10.510.10.10.15Server4Feb 15, 2024 22:21:38.176
10.10.10.1510.10.10.5Client4Feb 15, 2024 22:21:40.277
10.10.10.510.10.10.15Server4Feb 15, 2024 22:21:40.277
10.10.10.1510.10.10.5Client4Feb 15, 2024 22:22:05.704
10.10.10.510.10.10.15Server4Feb 15, 2024 22:22:05.706
10.10.10.1510.10.10.5Client4Feb 15, 2024 22:22:45.469
10.10.10.510.10.10.15Server4Feb 15, 2024 22:22:45.470
10.10.10.1510.10.10.5Client4Feb 15, 2024 22:23:09.826
10.10.10.510.10.10.15Server4Feb 15, 2024 22:23:09.828
10.10.10.1510.10.10.5Client4Feb 15, 2024 22:23:50.337
10.10.10.510.10.10.15Server4Feb 15, 2024 22:23:50.343
10.10.10.1510.10.10.5Client4Feb 15, 2024 22:24:13.945
10.10.10.510.10.10.15Server4Feb 15, 2024 22:24:13.946
10.10.10.1510.10.10.5Client4Feb 15, 2024 22:24:54.876
10.10.10.510.10.10.15Server4Feb 15, 2024 22:24:54.877
10.10.10.1510.10.10.5Client4Feb 15, 2024 22:25:59.000
10.10.10.510.10.10.15Server4Feb 15, 2024 22:25:59.001
10.10.10.15216.239.35.12Client4Feb 15, 2024 22:12:28.681
216.239.35.1210.10.10.15Server4Feb 15, 2024 22:12:28.728
10.10.10.15216.239.35.4Client4Feb 15, 2024 22:12:28.842
216.239.35.410.10.10.15Server4Feb 15, 2024 22:12:28.888
10.10.10.15216.239.35.8Client4Feb 15, 2024 22:12:29.042
216.239.35.810.10.10.15Server4Feb 15, 2024 22:12:29.089
10.10.10.15216.239.35.0Client4Feb 15, 2024 22:12:29.243
216.239.35.010.10.10.15Server4Feb 15, 2024 22:12:29.290
10.10.10.1510.10.10.5Client4Feb 15, 2024 22:12:29.447
10.10.10.510.10.10.15Server4Feb 15, 2024 22:12:29.448
10.10.10.15216.239.35.12Client4Feb 15, 2024 22:12:30.802
216.239.35.1210.10.10.15Server4Feb 15, 2024 22:12:30.850
10.10.10.15216.239.35.4Client4Feb 15, 2024 22:12:30.973
216.239.35.410.10.10.15Server4Feb 15, 2024 22:12:31.032
10.10.10.15216.239.35.8Client4Feb 15, 2024 22:12:31.173
216.239.35.810.10.10.15Server4Feb 15, 2024 22:12:31.220
10.10.10.15216.239.35.0Client4Feb 15, 2024 22:12:31.376
216.239.35.010.10.10.15Server4Feb 15, 2024 22:12:31.423
10.10.10.1510.10.10.5Client4Feb 15, 2024 22:12:31.577
10.10.10.510.10.10.15Server4Feb 15, 2024 22:12:31.577
10.10.10.15216.239.35.12Client4Feb 15, 2024 22:12:32.867
216.239.35.1210.10.10.15Server4Feb 15, 2024 22:12:32.914
10.10.10.15216.239.35.4Client4Feb 15, 2024 22:12:33.112
216.239.35.410.10.10.15Server4Feb 15, 2024 22:12:33.159
10.10.10.15216.239.35.8Client4Feb 15, 2024 22:12:33.271
216.239.35.810.10.10.15Server4Feb 15, 2024 22:12:33.318
10.10.10.15216.239.35.0Client4Feb 15, 2024 22:12:33.475
216.239.35.010.10.10.15Server4Feb 15, 2024 22:12:33.522
10.10.10.1510.10.10.5Client4Feb 15, 2024 22:12:33.694
10.10.10.510.10.10.15Server4Feb 15, 2024 22:12:33.694
10.10.10.15216.239.35.12Client4Feb 15, 2024 22:12:34.956
216.239.35.1210.10.10.15Server4Feb 15, 2024 22:12:35.002
10.10.10.15216.239.35.4Client4Feb 15, 2024 22:12:35.182
216.239.35.410.10.10.15Server4Feb 15, 2024 22:12:35.228
10.10.10.15216.239.35.8Client4Feb 15, 2024 22:12:35.398
216.239.35.810.10.10.15Server4Feb 15, 2024 22:12:35.445
10.10.10.15216.239.35.0Client4Feb 15, 2024 22:12:35.625
216.239.35.010.10.10.15Server4Feb 15, 2024 22:12:35.673
10.10.10.1510.10.10.5Client4Feb 15, 2024 22:12:35.785
10.10.10.510.10.10.15Server4Feb 15, 2024 22:12:35.786
10.10.10.1510.10.10.5Client4Feb 15, 2024 22:12:37.806
10.10.10.510.10.10.15Server4Feb 15, 2024 22:12:37.806
10.10.10.1510.10.10.5Client4Feb 15, 2024 22:12:39.856
10.10.10.510.10.10.15Server4Feb 15, 2024 22:12:39.856
10.10.10.1510.10.10.5Client4Feb 15, 2024 22:12:41.931
10.10.10.510.10.10.15Server4Feb 15, 2024 22:12:41.932
10.10.10.1510.10.10.5Client4Feb 15, 2024 22:12:43.954
10.10.10.510.10.10.15Server4Feb 15, 2024 22:12:43.956
10.10.10.1510.10.10.5Client4Feb 15, 2024 22:13:06.439
10.10.10.15216.239.35.0Client4Feb 15, 2024 22:13:06.439
10.10.10.510.10.10.15Server4Feb 15, 2024 22:13:06.439
216.239.35.010.10.10.15Server4Feb 15, 2024 22:13:06.489
10.10.10.1510.10.10.5Client4Feb 15, 2024 22:13:08.492
10.10.10.510.10.10.15Server4Feb 15, 2024 22:13:08.494
216.239.35.010.10.10.15Server4Feb 15, 2024 22:13:08.543
10.10.10.15216.239.35.12Client4Feb 15, 2024 22:13:40.310
216.239.35.1210.10.10.15Server4Feb 15, 2024 22:13:40.357
10.10.10.15216.239.35.4Client4Feb 15, 2024 22:13:40.512
10.10.10.1510.10.10.5Client4Feb 15, 2024 22:13:40.536
10.10.10.510.10.10.15Server4Feb 15, 2024 22:13:40.542
216.239.35.410.10.10.15Server4Feb 15, 2024 22:13:40.574
216.239.35.010.10.10.15Server4Feb 15, 2024 22:13:40.583
10.10.10.15216.239.35.8Client4Feb 15, 2024 22:13:40.714
216.239.35.810.10.10.15Server4Feb 15, 2024 22:13:40.764
10.10.10.15216.239.35.0Client4Feb 15, 2024 22:13:40.917
216.239.35.010.10.10.15Server4Feb 15, 2024 22:13:40.965
10.10.10.1510.10.10.5Client4Feb 15, 2024 22:13:48.274
10.10.10.510.10.10.15Server4Feb 15, 2024 22:13:48.277
10.10.10.1510.10.10.5Client4Feb 15, 2024 22:14:12.619
10.10.10.510.10.10.15Server4Feb 15, 2024 22:14:12.624
216.239.35.010.10.10.15Server4Feb 15, 2024 22:14:12.668
10.10.10.15216.239.35.12Client4Feb 15, 2024 22:14:44.515
216.239.35.1210.10.10.15Server4Feb 15, 2024 22:14:44.562
10.10.10.1510.10.10.5Client4Feb 15, 2024 22:14:44.702
10.10.10.510.10.10.15Server4Feb 15, 2024 22:14:44.704
10.10.10.15216.239.35.4Client4Feb 15, 2024 22:14:45.158
216.239.35.410.10.10.15Server4Feb 15, 2024 22:14:45.219
10.10.10.15216.239.35.0Client4Feb 15, 2024 22:14:45.359
216.239.35.010.10.10.15Server4Feb 15, 2024 22:14:45.406
10.10.10.15216.239.35.0Client4Feb 15, 2024 22:14:45.707
216.239.35.010.10.10.15Server4Feb 15, 2024 22:14:45.755
10.10.10.15216.239.35.8Client4Feb 15, 2024 22:14:45.980
216.239.35.810.10.10.15Server4Feb 15, 2024 22:14:46.027
10.10.10.1510.10.10.5Client4Feb 15, 2024 22:14:53.026
10.10.10.510.10.10.15Server4Feb 15, 2024 22:14:53.029
10.10.10.1510.10.10.5Client4Feb 15, 2024 22:15:16.786
10.10.10.510.10.10.15Server4Feb 15, 2024 22:15:16.791
10.10.10.15216.239.35.0Client4Feb 15, 2024 22:15:18.794
216.239.35.010.10.10.15Server4Feb 15, 2024 22:15:18.843
10.10.10.1510.10.10.5Client4Feb 15, 2024 22:15:48.884
10.10.10.510.10.10.15Server4Feb 15, 2024 22:15:48.887
10.10.10.15 216.239.35.12 Client 4Feb 15, 2024 22:15:49.063837.942 seconds
216.239.35.12 10.10.10.15 Server 4Feb 15, 2024 22:15:49.110
10.10.10.15216.239.35.4Client4Feb 15, 2024 22:15:49.462
216.239.35.410.10.10.15Server4Feb 15, 2024 22:15:49.509
10.10.10.15216.239.35.0Client4Feb 15, 2024 22:15:50.127
216.239.35.010.10.10.15Server4Feb 15, 2024 22:15:50.175
10.10.10.15216.239.35.8Client4Feb 15, 2024 22:15:51.107
216.239.35.810.10.10.15Server4Feb 15, 2024 22:15:51.154
10.10.10.15216.239.35.0Client4Feb 15, 2024 22:15:51.890
216.239.35.010.10.10.15Server4Feb 15, 2024 22:15:51.938
10.10.10.1510.10.10.5Client4Feb 15, 2024 22:15:57.829
10.10.10.510.10.10.15Server4Feb 15, 2024 22:15:57.829
10.10.10.1510.10.10.5Client4Feb 15, 2024 22:16:20.970
10.10.10.510.10.10.15Server4Feb 15, 2024 22:16:20.971
10.10.10.15216.239.35.0Client4Feb 15, 2024 22:16:24.975
216.239.35.010.10.10.15Server4Feb 15, 2024 22:16:25.0238N/A
10.10.10.15 216.239.35.4 Client 4Feb 15, 2024 22:16:53.677837.834 seconds
216.239.35.4 10.10.10.15 Server 4Feb 15, 2024 22:16:53.739
10.10.10.1510.10.10.5Client4Feb 15, 2024 22:16:54.054
10.10.10.510.10.10.15Server4Feb 15, 2024 22:16:54.054
10.10.10.15216.239.35.12Client4Feb 15, 2024 22:16:54.276
216.239.35.1210.10.10.15Server4Feb 15, 2024 22:16:54.322
10.10.10.15216.239.35.0Client4Feb 15, 2024 22:16:54.593
216.239.35.010.10.10.15Server4Feb 15, 2024 22:16:54.6488N/A
10.10.10.15 216.239.35.8 Client 4Feb 15, 2024 22:16:55.435838.056 seconds
216.239.35.8 10.10.10.15 Server 4Feb 15, 2024 22:16:55.4818N/A
10.10.10.15 216.239.35.0 Client 4Feb 15, 2024 22:16:57.0591420.601 seconds
216.239.35.0 10.10.10.15 Server 4Feb 15, 2024 22:16:57.107
10.10.10.1510.10.10.5Client4Feb 15, 2024 22:17:02.738
10.10.10.510.10.10.15Server4Feb 15, 2024 22:17:02.74017N/A
10.10.10.15 10.10.10.5 Client 4Feb 15, 2024 22:17:26.1366313.057 seconds
10.10.10.5 10.10.10.15 Server 4Feb 15, 2024 22:17:26.13963N/A
diff --git a/testing/unit/ntp/reports/ntp_report_local_no_ntp.html b/testing/unit/ntp/reports/ntp_report_local_no_ntp.html index 7df0fbd87..7fe2e6ab5 100644 --- a/testing/unit/ntp/reports/ntp_report_local_no_ntp.html +++ b/testing/unit/ntp/reports/ntp_report_local_no_ntp.html @@ -15,6 +15,7 @@

NTP Module

0 0 +
diff --git a/testing/unit/protocol/protocol_module_test.py b/testing/unit/protocol/protocol_module_test.py index 32a0021cd..6ba3143c0 100644 --- a/testing/unit/protocol/protocol_module_test.py +++ b/testing/unit/protocol/protocol_module_test.py @@ -46,7 +46,6 @@ def setUpClass(cls): BACNET = BACnet(log=LOGGER, captures_dir=CAPTURES_DIR, capture_file='bacnet.pcap', - bin_dir='modules/test/protocol/bin', device_hw_addr=HW_ADDR) # Test the BACNet traffic for a matching Object ID and HW address diff --git a/testing/unit/report/report_test.py b/testing/unit/report/report_test.py index f92666b2c..c67d81b1e 100644 --- a/testing/unit/report/report_test.py +++ b/testing/unit/report/report_test.py @@ -16,6 +16,7 @@ from testreport import TestReport import os import json +import shutil MODULE = 'report' @@ -30,6 +31,10 @@ class ReportTest(unittest.TestCase): @classmethod def setUpClass(cls): + # Delete old files + if os.path.exists(OUTPUT_DIR) and os.path.isdir(OUTPUT_DIR): + shutil.rmtree(OUTPUT_DIR) + # Create the output directories and ignore errors if it already exists os.makedirs(OUTPUT_DIR, exist_ok=True) @@ -59,6 +64,47 @@ def report_compliant_test(self): def report_noncompliant_test(self): self.create_report(os.path.join(TEST_FILES_DIR, 'report_noncompliant.json')) + # Generate formatted reports for each report generated from + # the test containers. + # Not a unit test but can't run from within the test module container and must + # be done through the venv. Useful for doing visual inspections + # of report formatting changes without having to re-run a new device test. + def report_formatting(self): + test_modules = ['conn','dns','ntp','protocol','services','tls'] + unit_tests = os.listdir(UNIT_TEST_DIR) + for test in unit_tests: + if test in test_modules: + output_dir = os.path.join(UNIT_TEST_DIR,test,'output') + if os.path.isdir(output_dir): + output_files = os.listdir(output_dir) + for file in output_files: + if file.endswith('.html'): + + # Read the generated report and add formatting + report_out_path = os.path.join(output_dir,file) + with open(report_out_path, 'r', encoding='utf-8') as f: + report_out = f.read() + formatted_report = self.add_formatting(report_out) + + # Write back the new formatted_report value + out_report_dir = os.path.join(OUTPUT_DIR, test) + os.makedirs(out_report_dir, exist_ok=True) + + with open(os.path.join( + out_report_dir,file), 'w', + encoding='utf-8') as f: + f.write(formatted_report) + + def add_formatting(self, body): + return f''' + + + {TestReport().generate_head()} + + {body} + + /dev/null 2>&1 - -echo "Root dir: $PWD" - -# Add the framework sources -PYTHONPATH="$PWD/framework/python/src:$PWD/framework/python/src/common" - -# Add the test module sources -PYTHONPATH="$PYTHONPATH:$PWD/modules/test/base/python/src" -PYTHONPATH="$PYTHONPATH:$PWD/modules/test/conn/python/src" -PYTHONPATH="$PYTHONPATH:$PWD/modules/test/tls/python/src" -PYTHONPATH="$PYTHONPATH:$PWD/modules/test/dns/python/src" -PYTHONPATH="$PYTHONPATH:$PWD/modules/test/services/python/src" -PYTHONPATH="$PYTHONPATH:$PWD/modules/test/ntp/python/src" -PYTHONPATH="$PYTHONPATH:$PWD/modules/test/protocol/python/src" - - -# Set the python path with all sources -export PYTHONPATH - -# Run the DHCP Unit tests -python3 -u $PWD/modules/network/dhcp-1/python/src/grpc_server/dhcp_config_test.py -python3 -u $PWD/modules/network/dhcp-2/python/src/grpc_server/dhcp_config_test.py - -# Run the Conn Module Unit Tests -python3 -u $PWD/testing/unit/conn/conn_module_test.py - -# Run the TLS Module Unit Tests -python3 -u $PWD/testing/unit/tls/tls_module_test.py - -# Run the DNS Module Unit Tests -python3 -u $PWD/testing/unit/dns/dns_module_test.py - -# Run the NMAP Module Unit Tests -python3 -u $PWD/testing/unit/services/services_module_test.py - -# Run the NTP Module Unit Tests -python3 -u $PWD/testing/unit/ntp/ntp_module_test.py - -# Run the Report Unit Tests -python3 -u $PWD/testing/unit/report/report_test.py - -# Run the RiskProfile Unit Tests -python3 -u $PWD/testing/unit/risk_profile/risk_profile_test.py - -# Run the RiskProfile Unit Tests -python3 -u $PWD/testing/unit/protocol/protocol_module_test.py - -popd >/dev/null 2>&1 diff --git a/testing/unit/services/output/services.log b/testing/unit/services/output/services.log deleted file mode 100644 index 7df3f745b..000000000 --- a/testing/unit/services/output/services.log +++ /dev/null @@ -1,6 +0,0 @@ -Jun 17 09:23:01 test_services INFO Module report generated at: testing/unit/services/output/services_report.html -Jun 17 09:23:01 test_services INFO Module report generated at: testing/unit/services/output/services_report.html -Jun 17 09:23:01 test_services INFO Module report generated at: testing/unit/services/output/services_report.html -Jun 17 09:32:48 test_services INFO Module report generated at: testing/unit/services/output/services_report.html -Jun 17 09:32:48 test_services INFO Module report generated at: testing/unit/services/output/services_report.html -Jun 17 09:32:48 test_services INFO Module report generated at: testing/unit/services/output/services_report.html diff --git a/testing/unit/services/services_module_test.py b/testing/unit/services/services_module_test.py index 30c4928bf..a8c60262b 100644 --- a/testing/unit/services/services_module_test.py +++ b/testing/unit/services/services_module_test.py @@ -16,7 +16,7 @@ import unittest import os import shutil -from testreport import TestReport +# from testreport import TestReport MODULE = 'services' @@ -29,8 +29,6 @@ LOCAL_REPORT = os.path.join(REPORTS_DIR, 'services_report_local.html') LOCAL_REPORT_ALL_CLOSED = os.path.join(REPORTS_DIR, 'services_report_all_closed_local.html') -CONF_FILE = 'modules/test/' + MODULE + '/conf/module_config.json' - class ServicesTest(unittest.TestCase): """Contains and runs all the unit tests concerning DNS behaviors""" @@ -51,7 +49,6 @@ def services_module_ports_open_report_test(self): services_module = ServicesModule(module=MODULE, log_dir=OUTPUT_DIR, - conf_file=CONF_FILE, results_dir=OUTPUT_DIR, run=False, nmap_scan_results_path=OUTPUT_DIR) @@ -61,13 +58,6 @@ def services_module_ports_open_report_test(self): # Read the generated report with open(report_out_path, 'r', encoding='utf-8') as file: report_out = file.read() - formatted_report = self.add_formatting(report_out) - - # Write back the new formatted_report value - out_report_path = os.path.join( - OUTPUT_DIR, 'services_report_ports_open.html') - with open(out_report_path, 'w', encoding='utf-8') as file: - file.write(formatted_report) # Read the local good report with open(LOCAL_REPORT, 'r', encoding='utf-8') as file: @@ -85,7 +75,6 @@ def services_module_report_all_closed_test(self): services_module = ServicesModule(module=MODULE, log_dir=OUTPUT_DIR, - conf_file=CONF_FILE, results_dir=OUTPUT_DIR, run=False, nmap_scan_results_path=OUTPUT_DIR) @@ -95,13 +84,6 @@ def services_module_report_all_closed_test(self): # Read the generated report with open(report_out_path, 'r', encoding='utf-8') as file: report_out = file.read() - formatted_report = self.add_formatting(report_out) - - # Write back the new formatted_report value - out_report_path = os.path.join( - OUTPUT_DIR, 'services_report_all_closed.html') - with open(out_report_path, 'w', encoding='utf-8') as file: - file.write(formatted_report) # Read the local good report with open(LOCAL_REPORT_ALL_CLOSED, 'r', encoding='utf-8') as file: @@ -109,17 +91,6 @@ def services_module_report_all_closed_test(self): self.assertEqual(report_out, report_local) - def add_formatting(self, body): - return f''' - - - {TestReport().generate_head()} - - {body} - - Date: Fri, 11 Oct 2024 13:56:34 +0100 Subject: [PATCH 2/9] Merge release v2.0 into main (#876) --- .github/workflows/package.yml | 2 +- .github/workflows/testing.yml | 45 +- README.md | 125 +- cmd/build | 9 +- cmd/install | 6 +- cmd/prune | 11 +- docs/README.md | 30 +- docs/configure_device.md | 31 - docs/dev/README.md | 38 +- docs/dev/code_quality.md | 16 - docs/get_started.md | 158 +- docs/network/README.md | 41 +- docs/network/add_new_service.md | 79 +- docs/network/addresses.md | 27 +- docs/network/identify_interfaces.md | 20 - docs/roadmap.pdf | Bin 515039 -> 817448 bytes docs/test/README.md | 6 +- docs/test/modules.md | 34 +- docs/test/statuses.md | 47 +- docs/ui/accessibility.md | 12 + docs/ui/device_icon.png | Bin 933 -> 0 bytes docs/ui/getstarted--2dn8vrzsspe.png | Bin 0 -> 36356 bytes docs/ui/getstarted--3d9k3si3ul1.png | Bin 0 -> 10189 bytes docs/ui/getstarted--7cfvdpdnc5o.png | Bin 0 -> 17881 bytes docs/ui/getstarted--m4si1otdu5d.png | Bin 0 -> 13909 bytes docs/ui/getstarted--q5uw26tfod.png | Bin 0 -> 13929 bytes docs/ui/getstarted--w09wecsry3.png | Bin 0 -> 14001 bytes docs/ui/history_icon.png | Bin 538 -> 0 bytes docs/ui/progress_icon.png | Bin 989 -> 0 bytes docs/ui/settings_icon.png | Bin 539 -> 0 bytes docs/ui/settings_menu.png | Bin 37230 -> 0 bytes docs/ui/test_name.png | Bin 10221 -> 0 bytes docs/virtual_machine.md | 51 +- framework/python/src/api/api.py | 270 +- framework/python/src/common/device.py | 19 +- framework/python/src/common/docker_util.py | 35 + framework/python/src/common/mqtt.py | 21 +- framework/python/src/common/risk_profile.py | 471 +- framework/python/src/common/statuses.py | 36 + framework/python/src/common/testreport.py | 1012 +- framework/python/src/common/util.py | 9 +- .../python/src/core/docker/docker_module.py | 163 + .../src/core/docker/network_docker_module.py | 98 + .../src/core/docker/test_docker_module.py | 135 + .../python/src/{common => core}/session.py | 210 +- framework/python/src/core/tasks.py | 78 + framework/python/src/core/test_runner.py | 4 +- framework/python/src/core/testrun.py | 125 +- framework/python/src/net_orc/ip_control.py | 19 + .../src/net_orc/network_orchestrator.py | 274 +- .../python/src/net_orc/network_validator.py | 2 +- framework/python/src/test_orc/test_case.py | 35 +- .../python/src/test_orc/test_orchestrator.py | 423 +- framework/python/src/test_orc/test_pack.py | 42 + framework/requirements.txt | 3 + local/system.json.example | 7 +- make/DEBIAN/control | 2 +- .../faux-dev/bin/start_network_service | 2 +- modules/devices/faux-dev/faux-dev.Dockerfile | 9 +- modules/devices/faux-dev/python/src/logger.py | 61 - modules/devices/faux-dev/python/src/util.py | 48 - modules/network/base/base.Dockerfile | 11 +- modules/network/base/bin/start_module | 20 +- .../python/src/grpc_server/start_server.py | 1 - modules/network/dhcp-1/dhcp-1.Dockerfile | 4 +- modules/network/dhcp-2/dhcp-2.Dockerfile | 4 +- modules/network/dns/dns.Dockerfile | 4 +- modules/network/gateway/gateway.Dockerfile | 4 +- .../network/host/bin/start_network_service | 22 + modules/network/host/conf/module_config.json | 24 + modules/network/host/host.Dockerfile | 34 + .../python/src/grpc_server/network_service.py | 120 + .../python/src/grpc_server/proto/grpc.proto | 37 + .../python/src/grpc_server/start_server.py | 50 + modules/network/ntp/ntp.Dockerfile | 4 +- modules/network/ntp/python/src/ntp_server.py | 2 +- modules/network/radius/radius.Dockerfile | 10 +- modules/network/template/template.Dockerfile | 4 +- modules/test/base/base.Dockerfile | 35 +- modules/test/base/bin/setup | 2 +- .../base/python/src/grpc/proto/host/client.py | 63 + modules/test/base/python/src/logger.py | 61 - modules/test/base/python/src/test_module.py | 35 +- modules/test/base/python/src/util.py | 47 - modules/test/baseline/baseline.Dockerfile | 4 +- modules/test/baseline/conf/module_config.json | 9 +- modules/test/conn/conf/module_config.json | 37 +- modules/test/conn/conn.Dockerfile | 6 +- .../test/conn/python/src/connection_module.py | 171 +- modules/test/dns/conf/module_config.json | 5 +- modules/test/dns/dns.Dockerfile | 6 +- modules/test/dns/python/src/dns_module.py | 37 +- modules/test/ntp/conf/module_config.json | 4 +- modules/test/ntp/ntp.Dockerfile | 20 +- modules/test/ntp/python/src/ntp_module.py | 8 +- modules/test/protocol/conf/module_config.json | 7 +- modules/test/protocol/protocol.Dockerfile | 6 +- modules/test/services/conf/module_config.json | 11 - .../services/python/src/services_module.py | 2 +- modules/test/services/services.Dockerfile | 6 +- .../test/tls/bin/get_client_hello_packets.sh | 12 +- .../test/tls/bin/get_handshake_complete.sh | 72 +- modules/test/tls/conf/module_config.json | 32 +- modules/test/tls/python/requirements.txt | 4 +- modules/test/tls/python/src/tls_module.py | 111 +- modules/test/tls/python/src/tls_util.py | 80 +- modules/test/tls/tls.Dockerfile | 9 +- modules/ui/angular.json | 4 +- modules/ui/build.Dockerfile | 3 +- modules/ui/package-lock.json | 10555 ++++++++-------- modules/ui/package.json | 52 +- modules/ui/src/app/app.component.html | 240 +- modules/ui/src/app/app.component.scss | 10 +- modules/ui/src/app/app.component.spec.ts | 71 +- modules/ui/src/app/app.component.ts | 64 +- modules/ui/src/app/app.module.ts | 8 +- modules/ui/src/app/app.store.spec.ts | 41 +- modules/ui/src/app/app.store.ts | 48 + .../components/callout/callout.component.html | 11 + .../components/callout/callout.component.scss | 40 +- .../callout/callout.component.spec.ts | 26 +- .../components/callout/callout.component.ts | 28 +- .../components/component-with-announcement.ts | 35 + .../device-item/device-item.component.html | 61 +- .../device-item/device-item.component.scss | 70 +- .../device-item/device-item.component.spec.ts | 56 +- .../device-item/device-item.component.ts | 25 +- .../download-report-zip.component.spec.ts | 18 +- .../download-report-zip.component.ts | 14 +- .../download-report.component.html | 1 + .../download-report.component.scss | 1 + .../download-report.component.ts | 4 + .../download-zip-modal.component.html | 85 +- .../download-zip-modal.component.scss | 87 + .../download-zip-modal.component.ts | 10 +- .../dynamic-form/dynamic-form.component.html | 268 + .../dynamic-form/dynamic-form.component.scss | 67 + .../dynamic-form.component.spec.ts | 235 + .../dynamic-form/dynamic-form.component.ts | 171 + .../program-type-con.component.spec.ts | 46 + .../program-type-icon.component.ts | 39 + .../report-action/report-action.component.ts | 3 + .../simple-dialog.component.html | 2 + .../simple-dialog.component.scss | 3 +- .../simple-dialog.component.spec.ts | 2 + .../simple-dialog/simple-dialog.component.ts | 14 +- .../components/spinner/spinner.component.scss | 2 +- .../stepper/stepper-test.component.html | 39 + .../components/stepper/stepper.component.html | 56 + .../components/stepper/stepper.component.scss | 91 + .../stepper/stepper.component.spec.ts | 101 + .../components/stepper/stepper.component.ts | 72 + .../testing-complete.component.spec.ts | 91 + .../testing-complete.component.ts | 99 + .../consent-dialog.component.html | 24 +- .../consent-dialog.component.scss | 11 + .../consent-dialog.component.spec.ts | 89 +- .../consent-dialog.component.ts | 10 +- .../components/version/version.component.html | 2 +- .../version/version.component.spec.ts | 32 +- .../components/version/version.component.ts | 34 +- .../app/guards/can-deactivate.guard.spec.ts | 31 + .../ui/src/app/guards/can-deactivate.guard.ts | 38 + modules/ui/src/app/mocks/device.mock.ts | 77 +- modules/ui/src/app/mocks/profile.mock.ts | 8 +- modules/ui/src/app/mocks/reports.mock.ts | 28 + modules/ui/src/app/mocks/testrun.mock.ts | 36 +- modules/ui/src/app/model/callout-type.ts | 1 + modules/ui/src/app/model/device.ts | 29 + modules/ui/src/app/model/profile.ts | 24 +- modules/ui/src/app/model/program-type.ts | 19 + modules/ui/src/app/model/question.ts | 38 + modules/ui/src/app/model/setting.ts | 1 + modules/ui/src/app/model/testrun-status.ts | 5 + modules/ui/src/app/model/version.ts | 1 - .../certificates/certificate.validator.ts | 1 - .../certificates/certificates.component.html | 9 - .../certificates.component.spec.ts | 18 +- .../certificates/certificates.component.ts | 2 +- .../device-form/device-form.component.html | 130 - .../device-form/device-form.component.scss | 76 - .../device-form/device-form.component.spec.ts | 459 - .../device-form/device-form.component.ts | 211 - .../device-form/device.validators.ts | 10 + .../device-qualification-from.component.html | 347 + .../device-qualification-from.component.scss | 359 + ...evice-qualification-from.component.spec.ts | 760 ++ .../device-qualification-from.component.ts | 574 + .../pages/devices/devices-routing.module.ts | 9 +- .../app/pages/devices/devices.component.html | 7 +- .../app/pages/devices/devices.component.scss | 3 + .../pages/devices/devices.component.spec.ts | 163 +- .../app/pages/devices/devices.component.ts | 242 +- .../src/app/pages/devices/devices.module.ts | 3 +- .../ui/src/app/pages/devices/devices.store.ts | 23 +- .../delete-report.component.html | 4 +- .../filter-dialog.component.scss | 2 +- .../app/pages/reports/reports.component.html | 30 +- .../app/pages/reports/reports.component.scss | 6 +- .../pages/reports/reports.component.spec.ts | 20 +- .../app/pages/reports/reports.component.ts | 10 +- .../app/pages/reports/reports.store.spec.ts | 1 + .../ui/src/app/pages/reports/reports.store.ts | 2 + .../src/app/pages/reports/reportscomponent.ts | 173 - .../success-dialog.component.html | 45 + .../success-dialog.component.scss | 73 + .../success-dialog.component.spec.ts | 78 + .../success-dialog.component.ts | 65 + .../profile-form/profile-form.component.html | 256 +- .../profile-form/profile-form.component.scss | 53 +- .../profile-form.component.spec.ts | 171 +- .../profile-form/profile-form.component.ts | 74 +- .../profile-item/profile-item.component.html | 12 +- .../profile-item/profile-item.component.ts | 16 +- .../risk-assessment.component.scss | 2 + .../risk-assessment.component.spec.ts | 49 +- .../risk-assessment.component.ts | 108 +- .../risk-assessment.store.spec.ts | 31 +- .../risk-assessment/risk-assessment.store.ts | 46 +- .../pages/settings/settings.component.scss | 7 + .../app/pages/settings/settings.component.ts | 6 +- .../download-options.component.scss | 8 +- .../download-options.component.ts | 3 + .../testrun-initiate-form.component.html | 1 + .../testrun-initiate-form.component.spec.ts | 21 +- .../testrun-initiate-form.component.ts | 18 +- .../testrun-status-card.component.html | 8 +- .../testrun-status-card.component.scss | 4 +- .../testrun-status-card.component.spec.ts | 11 +- .../testrun-status-card.component.ts | 13 +- .../testrun-table.component.spec.ts | 1 + .../app/pages/testrun/testrun.component.html | 26 +- .../app/pages/testrun/testrun.component.scss | 33 +- .../pages/testrun/testrun.component.spec.ts | 33 +- .../app/pages/testrun/testrun.component.ts | 13 +- .../src/app/pages/testrun/testrun.module.ts | 2 + .../app/pages/testrun/testrun.store.spec.ts | 3 + .../ui/src/app/pages/testrun/testrun.store.ts | 3 + .../src/app/services/notification.service.ts | 17 +- .../src/app/services/test-run.service.spec.ts | 32 +- .../ui/src/app/services/test-run.service.ts | 43 +- modules/ui/src/app/store/actions.ts | 15 + modules/ui/src/app/store/effects.spec.ts | 26 +- modules/ui/src/app/store/effects.ts | 67 +- modules/ui/src/app/store/reducers.spec.ts | 44 + modules/ui/src/app/store/reducers.ts | 18 + modules/ui/src/app/store/selectors.spec.ts | 21 + modules/ui/src/app/store/selectors.ts | 14 + modules/ui/src/app/store/state.ts | 6 + .../src/assets/icons/create_device_header.svg | 64 + modules/ui/src/assets/icons/pilot.svg | 13 + modules/ui/src/assets/icons/qualification.svg | 6 + modules/ui/src/index.html | 18 +- modules/ui/src/styles.scss | 187 +- modules/ui/src/theming/colors.scss | 1 + modules/ui/src/theming/theme.scss | 21 +- modules/ui/src/theming/variables.scss | 3 + modules/ws/conf/mosquitto.conf | 2 +- resources/devices/device_profile.json | 420 + resources/report/header_macros.jinja | 43 + resources/report/pilot-icon.png | Bin 0 -> 536 bytes resources/report/qualification-icon.png | Bin 0 -> 428 bytes resources/report/risk_report_styles.css | 211 + resources/report/risk_report_template.html | 75 + resources/report/test_report_styles.css | 633 + resources/report/test_report_template.html | 241 + resources/risk_assessment.json | 200 - resources/test_packs/pilot.json | 169 + resources/test_packs/qualification.json | 177 + testing/api/certificates/WR2.pem | 29 + testing/api/certificates/crt.pem | 31 + testing/api/certificates/invalid.pem | 1 + .../certificates/invalidname1234567891234.pem | 31 + .../api/devices/device_1/device_config.json | 54 + .../api/devices/device_2/device_config.json | 54 + testing/api/profiles/draft_profile.json | 35 + testing/api/profiles/new_profile.json | 54 - testing/api/profiles/new_profile_2.json | 56 - testing/api/profiles/updated_profile.json | 57 - testing/api/profiles/valid_profile.json | 39 + testing/api/reports/report.json | 134 + testing/api/reports/report.pdf | Bin 0 -> 29659 bytes testing/api/{ => sys_config}/system.json | 0 testing/api/sys_config/updated_system.json | 7 + testing/api/test_api | 5 +- testing/api/test_api.py | 3762 ++++-- testing/baseline/test_baseline | 9 +- testing/docker/ci_baseline/Dockerfile | 2 +- testing/unit/conn/conn_module_test.py | 8 +- testing/unit/dns/dns_module_test.py | 8 +- .../unit/dns/reports/dns_report_local.html | 98 +- .../dns/reports/dns_report_local_no_dns.html | 2 +- testing/unit/framework/session_test.py | 2 +- testing/unit/ntp/ntp_module_test.py | 9 +- .../unit/ntp/reports/ntp_report_local.html | 2 +- .../ntp/reports/ntp_report_local_no_ntp.html | 2 +- testing/unit/protocol/protocol_module_test.py | 8 +- testing/unit/report/report_test.py | 92 +- .../profiles/risk_profile_valid_high.json | 42 +- .../profiles/risk_profile_valid_limited.json | 33 +- .../unit/risk_profile/risk_profile_test.py | 16 +- testing/unit/run.sh | 2 +- testing/unit/run_report_test.sh | 67 + testing/unit/run_test_module.sh | 80 + .../services_report_all_closed_local.html | 2 +- .../reports/services_report_local.html | 2 +- testing/unit/services/services_module_test.py | 9 +- testing/unit/tls/tls_module_test.py | 49 +- 308 files changed, 19563 insertions(+), 12329 deletions(-) delete mode 100644 docs/configure_device.md delete mode 100644 docs/dev/code_quality.md delete mode 100644 docs/network/identify_interfaces.md create mode 100644 docs/ui/accessibility.md delete mode 100644 docs/ui/device_icon.png create mode 100644 docs/ui/getstarted--2dn8vrzsspe.png create mode 100644 docs/ui/getstarted--3d9k3si3ul1.png create mode 100644 docs/ui/getstarted--7cfvdpdnc5o.png create mode 100644 docs/ui/getstarted--m4si1otdu5d.png create mode 100644 docs/ui/getstarted--q5uw26tfod.png create mode 100644 docs/ui/getstarted--w09wecsry3.png delete mode 100644 docs/ui/history_icon.png delete mode 100644 docs/ui/progress_icon.png delete mode 100644 docs/ui/settings_icon.png delete mode 100644 docs/ui/settings_menu.png delete mode 100644 docs/ui/test_name.png create mode 100644 framework/python/src/common/docker_util.py create mode 100644 framework/python/src/common/statuses.py create mode 100644 framework/python/src/core/docker/docker_module.py create mode 100644 framework/python/src/core/docker/network_docker_module.py create mode 100644 framework/python/src/core/docker/test_docker_module.py rename framework/python/src/{common => core}/session.py (76%) create mode 100644 framework/python/src/core/tasks.py create mode 100644 framework/python/src/test_orc/test_pack.py delete mode 100644 modules/devices/faux-dev/python/src/logger.py delete mode 100644 modules/devices/faux-dev/python/src/util.py create mode 100644 modules/network/host/bin/start_network_service create mode 100644 modules/network/host/conf/module_config.json create mode 100644 modules/network/host/host.Dockerfile create mode 100644 modules/network/host/python/src/grpc_server/network_service.py create mode 100644 modules/network/host/python/src/grpc_server/proto/grpc.proto create mode 100644 modules/network/host/python/src/grpc_server/start_server.py create mode 100644 modules/test/base/python/src/grpc/proto/host/client.py delete mode 100644 modules/test/base/python/src/logger.py delete mode 100644 modules/test/base/python/src/util.py create mode 100644 modules/ui/src/app/components/component-with-announcement.ts create mode 100644 modules/ui/src/app/components/dynamic-form/dynamic-form.component.html create mode 100644 modules/ui/src/app/components/dynamic-form/dynamic-form.component.scss create mode 100644 modules/ui/src/app/components/dynamic-form/dynamic-form.component.spec.ts create mode 100644 modules/ui/src/app/components/dynamic-form/dynamic-form.component.ts create mode 100644 modules/ui/src/app/components/program-type-icon/program-type-con.component.spec.ts create mode 100644 modules/ui/src/app/components/program-type-icon/program-type-icon.component.ts create mode 100644 modules/ui/src/app/components/stepper/stepper-test.component.html create mode 100644 modules/ui/src/app/components/stepper/stepper.component.html create mode 100644 modules/ui/src/app/components/stepper/stepper.component.scss create mode 100644 modules/ui/src/app/components/stepper/stepper.component.spec.ts create mode 100644 modules/ui/src/app/components/stepper/stepper.component.ts create mode 100644 modules/ui/src/app/components/testing-complete/testing-complete.component.spec.ts create mode 100644 modules/ui/src/app/components/testing-complete/testing-complete.component.ts create mode 100644 modules/ui/src/app/guards/can-deactivate.guard.spec.ts create mode 100644 modules/ui/src/app/guards/can-deactivate.guard.ts create mode 100644 modules/ui/src/app/model/program-type.ts create mode 100644 modules/ui/src/app/model/question.ts delete mode 100644 modules/ui/src/app/pages/devices/components/device-form/device-form.component.html delete mode 100644 modules/ui/src/app/pages/devices/components/device-form/device-form.component.scss delete mode 100644 modules/ui/src/app/pages/devices/components/device-form/device-form.component.spec.ts delete mode 100644 modules/ui/src/app/pages/devices/components/device-form/device-form.component.ts create mode 100644 modules/ui/src/app/pages/devices/components/device-qualification-from/device-qualification-from.component.html create mode 100644 modules/ui/src/app/pages/devices/components/device-qualification-from/device-qualification-from.component.scss create mode 100644 modules/ui/src/app/pages/devices/components/device-qualification-from/device-qualification-from.component.spec.ts create mode 100644 modules/ui/src/app/pages/devices/components/device-qualification-from/device-qualification-from.component.ts delete mode 100644 modules/ui/src/app/pages/reports/reportscomponent.ts create mode 100644 modules/ui/src/app/pages/risk-assessment/components/success-dialog/success-dialog.component.html create mode 100644 modules/ui/src/app/pages/risk-assessment/components/success-dialog/success-dialog.component.scss create mode 100644 modules/ui/src/app/pages/risk-assessment/components/success-dialog/success-dialog.component.spec.ts create mode 100644 modules/ui/src/app/pages/risk-assessment/components/success-dialog/success-dialog.component.ts create mode 100644 modules/ui/src/assets/icons/create_device_header.svg create mode 100644 modules/ui/src/assets/icons/pilot.svg create mode 100644 modules/ui/src/assets/icons/qualification.svg create mode 100644 resources/devices/device_profile.json create mode 100644 resources/report/header_macros.jinja create mode 100644 resources/report/pilot-icon.png create mode 100644 resources/report/qualification-icon.png create mode 100644 resources/report/risk_report_styles.css create mode 100644 resources/report/risk_report_template.html create mode 100644 resources/report/test_report_styles.css create mode 100644 resources/report/test_report_template.html create mode 100644 resources/test_packs/pilot.json create mode 100644 resources/test_packs/qualification.json create mode 100644 testing/api/certificates/WR2.pem create mode 100644 testing/api/certificates/crt.pem create mode 100644 testing/api/certificates/invalid.pem create mode 100644 testing/api/certificates/invalidname1234567891234.pem create mode 100644 testing/api/devices/device_1/device_config.json create mode 100644 testing/api/devices/device_2/device_config.json create mode 100644 testing/api/profiles/draft_profile.json delete mode 100644 testing/api/profiles/new_profile.json delete mode 100644 testing/api/profiles/new_profile_2.json delete mode 100644 testing/api/profiles/updated_profile.json create mode 100644 testing/api/profiles/valid_profile.json create mode 100644 testing/api/reports/report.json create mode 100644 testing/api/reports/report.pdf rename testing/api/{ => sys_config}/system.json (100%) create mode 100644 testing/api/sys_config/updated_system.json mode change 100644 => 100755 testing/unit/run.sh create mode 100644 testing/unit/run_report_test.sh create mode 100644 testing/unit/run_test_module.sh diff --git a/.github/workflows/package.yml b/.github/workflows/package.yml index 8c4b5bcbe..99bfa4e96 100644 --- a/.github/workflows/package.yml +++ b/.github/workflows/package.yml @@ -17,7 +17,7 @@ jobs: permissions: {} name: Package runs-on: ubuntu-22.04 - timeout-minutes: 5 + timeout-minutes: 10 steps: - name: Checkout source uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 diff --git a/.github/workflows/testing.yml b/.github/workflows/testing.yml index d6deb1ab0..2aaa55b25 100644 --- a/.github/workflows/testing.yml +++ b/.github/workflows/testing.yml @@ -10,7 +10,7 @@ jobs: testrun_baseline: permissions: {} name: Baseline - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 timeout-minutes: 20 steps: - name: Checkout source @@ -29,7 +29,7 @@ jobs: testrun_api: permissions: {} name: API - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 timeout-minutes: 60 steps: - name: Checkout source @@ -58,7 +58,7 @@ jobs: testrun_unit: permissions: {} name: Unit - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 timeout-minutes: 15 steps: - name: Checkout source @@ -73,9 +73,40 @@ jobs: shell: bash {0} run: cmd/build timeout-minutes: 10 - - name: Run tests + - name: Run tests for conn module + shell: bash {0} + run: bash testing/unit/run_test_module.sh conn captures ethtool output + - name: Run tests for dns module + shell: bash {0} + run: bash testing/unit/run_test_module.sh dns captures reports output + - name: Run tests for ntp module + shell: bash {0} + run: bash testing/unit/run_test_module.sh ntp captures reports output + - name: Run tests for protocol module + shell: bash {0} + run: bash testing/unit/run_test_module.sh protocol captures output + - name: Run tests for services module shell: bash {0} - run: bash testing/unit/run.sh + run: bash testing/unit/run_test_module.sh services reports results output + - name: Run tests for tls module + shell: bash {0} + run: bash testing/unit/run_test_module.sh tls captures certAuth certs reports root_certs output + - name: Run tests for risk profiles + shell: bash {0} + run: bash testing/unit/run_report_test.sh testing/unit/risk_profile/risk_profile_test.py + - name: Run tests for reports + shell: bash {0} + run: bash testing/unit/run_report_test.sh testing/unit/report/report_test.py + - name: Archive HTML reports for modules + if: ${{ always() }} + run: sudo tar --exclude-vcs -czf html_reports.tgz testing/unit/report/output/ + - name: Upload HTML reports + uses: actions/upload-artifact@694cdabd8bdb0f10b2cea11669e1bf5453eed0a6 # v4.2.0 + if: ${{ always() }} + with: + if-no-files-found: error + name: html-reports_${{ github.run_id }} + path: html_reports.tgz pylint: permissions: {} @@ -98,7 +129,7 @@ jobs: - name: Install Node uses: actions/setup-node@b39b52d1213e96004bfcb1c61a8a6fa8ab84f3e8 # v4.0.1 with: - node-version: 18.18.0 + node-version: 18.19.0 - name: Install Chromium Browser run: sudo apt install chromium-browser - name: Install dependencies @@ -121,7 +152,7 @@ jobs: - name: Install Node uses: actions/setup-node@b39b52d1213e96004bfcb1c61a8a6fa8ab84f3e8 # v4.0.1 with: - node-version: 18.18.0 + node-version: 18.19.0 - name: Install dependencies run: npm install && npm ci working-directory: ./modules/ui diff --git a/README.md b/README.md index 23fd843ca..ce9602637 100644 --- a/README.md +++ b/README.md @@ -4,84 +4,93 @@ [![CodeQL](https://github.com/google/testrun/actions/workflows/github-code-scanning/codeql/badge.svg?branch=main)](https://github.com/google/testrun/actions/workflows/github-code-scanning/codeql) [![Testrun test suite](https://github.com/google/testrun/actions/workflows/testing.yml/badge.svg?branch=main&event=push)](https://github.com/google/testrun/actions/workflows/testing.yml) -## Introduction :wave: -Testrun automates specific test cases to verify network and security functionality in IoT devices. It is an open source tool which allows manufacturers of IP capable devices to test their devices for the purposes of Device Qualification within the BOS program. +# Introduction :wave: -## Motivation :bulb: -Without tools like Testrun, test labs and engineers may need to maintain a large and complex network coupled with dynamic configuration files and constant software updates. The major issues which can and should be solved are: - 1) The complexity of managing a testing network - 2) The time required to perform testing of network functionality - 3) The accuracy and consistency of testing network functionality +Testrun automates specific test cases to verify network and security functionality in IoT devices. It's an open-source tool that manufacturers use to test their IP-capable devices for the purpose of device qualification within Google's Building Operating System (BOS) program. -## How it works :triangular_ruler: -Testrun creates an isolated and controlled network environment on a linux machine. This removes the necessity for complex hardware, advanced knowledge and networking experience whilst enabling test engineers to validate device behaviour against Google’s Building Operating System requirements. +# Motivation :bulb: -Two modes are supported by Testrun: +Test labs and engineers often need to maintain a large and complex network coupled with dynamic configuration files and constant software updates. Testrun helps address major issues like: -
- - Automated testing - +- The complexity of managing a testing network +- The time required to perform testing of network functionality +- The accuracy and consistency of testing network functionality -Once the device has become operational (steady state), automated testing of the DUT (device under test) will begin. Containerized test modules will then execute against the device, one module at a time. Once all test modules have been executed, a report will be produced - presenting the results. -
+# How it works :triangular_ruler: -
+Testrun creates an isolated and controlled network environment on a Linux machine. This removes the necessity for complex hardware, advanced knowledge, and networking experience while enabling test engineers to validate device behavior against Google's BOS requirements. - - Lab network - +Testrun supports two modes: automated testing and lab network. -When manual testing or configuration changes are required, Testrun will provide the network and some tools to assist an engineer performing the additional testing. This reduces the need to maintain a separate but identical lab network. Testrun will take care of packet captures and logs for each network service for further debugging. +## Automated testing -
+Automated testing of the device under test (DUT) begins once the device is operational (steady state). Containerized test modules execute against the device one module at a time. Testrun produces a report with the results after all modules are executed. -## Minimum requirements :computer: -### Hardware - - PC running Ubuntu LTS 20.04, 22.04 or 24.04 (laptop or desktop) - - 2x USB ethernet adapter (One may be built in ethernet) - - Internet connection -### Software -- Docker - installation guide: [https://docs.docker.com/engine/install/ubuntu/#install-using-the-repository](https://docs.docker.com/engine/install/ubuntu/#install-using-the-repository) -### Device under test (DUT) - - DHCP client - The device must be able to obtain an IP address via DHCP +## Lab network -## Get started ▶️ -Once you have met the hardware and software requirements, you can get started with Testrun by following the [Get started guide](docs/get_started.md). Further docs are available in the [docs directory](docs) +Testrun provides the network and assistive tools for engineers when manual testing or configuration changes are required, reducing the need to maintain a separate but identical lab network. Testrun handles packet captures and logs for each network service for further debugging. -## Roadmap :chart_with_upwards_trend: -Testrun will constantly evolve to further support end-users by automating device network behaviour against industry standards. For further information on upcoming features, check out the [Roadmap](docs/roadmap.pdf). +# Minimum requirements :computer: -## Accessibility :busts_in_silhouette: -We are proud of our tool and strive to provide an enjoyable experience for all of our users. Testrun goes through rigorous accessibility testing at each release. You can read more about [Google and Accessibility here](https://www.google.co.uk/accessibility). You are welcome to submit a new issue and provide feedback on our implementations. To find out how Testrun implements accessibility features, you can view a [short video here](docs/ui/accessibility.mp4). +## Hardware -## Issue reporting :triangular_flag_on_post: -If the application has come across a problem at any point during setup or use, please raise an issue under the [issues tab](https://github.com/google/testrun/issues). Issue templates exist for both bug reports and feature requests. If neither of these are appropriate for your issue, raise a blank issue instead. +- PC running Ubuntu LTS 20.04, 22.04, or 24.04 (laptop or desktop) +- 2x USB Ethernet adapter (one may be built-in Ethernet) +- Internet connection -## Contributing :keyboard: -The contributing requirements can be found in [CONTRIBUTING.md](CONTRIBUTING.md). In short, checkout the [Google CLA](https://cla.developers.google.com/) site to get started. After that, check out our [developer documentation](docs/dev/README.md). +## Software -## FAQ :raising_hand: -1) I have an issue whilst installing/upgrading Testrun, what do I do? +Testrun requires Docker. Refer to the [installation guide](https://docs.docker.com/engine/install/ubuntu/#install-using-the-repository) for more information. - Sometimes, issues may arise when installing or upgrading Testrun - this may happen due to one of many reasons due to the nature of the application. However, most of the time, it can be resolved by following a full Testrun re-install by using these commands: - - ```sudo docker system prune -a``` - - ```sudo apt install ./testrun-*.deb``` +## Device under test (DUT) -2) What device networking functionality is validated by Testrun? +The DUT must be able to obtain an IP address via DHCP. - Best practices and requirements for IoT devices are constantly changing due to technological advances and discovery of vulnerabilities. - The current expectations for IoT devices on Google deployments can be found in the [Application Security Requirements for IoT Devices](https://partner-security.withgoogle.com/docs/iot_requirements). - Testrun aims to automate as much of the Application Security Requirements as possible. +# Get started :arrow_forward: -3) What services are provided on the virtual network? +Once you meet the hardware and software requirements, follow the Testrun [Get started guide](/docs/get_started.md). Additional guidance is available in the [docs directory](/docs). - The following are network services that are containerized and accessible to the device under test though are likely to change over time: - - DHCP in failover configuration with internet connectivity - - IPv6 SLAAC - - DNS - - NTPv4 +# Roadmap :chart_with_upwards_trend: -4) Can I run Testrun on a virtual machine? +Testrun continually evolves to further support end users by automating device network behavior against industry standards. For information on upcoming features, check out the [Roadmap](/docs/roadmap.pdf). - Testrun can be virtualized if the 2x ethernet adapters are passed through to a VirtualBox VM as a USB device rather than managed network adapters. You can view the guide to working on a [virtual machine here](docs/virtual_machine.md). +# Accessibility :busts_in_silhouette: + +We're proud of our tool and strive to provide an enjoyable experience for everyone. Testrun goes through rigorous accessibility testing at each release. Download the [Testrun: Accessible features](https://github.com/google/testrun/raw/refs/heads/main/docs/ui/accessibility.mp4) video to learn more.You're welcome to [submit a new issue](https://github.com/google/testrun/issues) and provide feedback on our implementations. To learn more about Google's [Belonging initiative](https://www.google.co.uk/accessibility) and their approach to accessibility, visit their site. + +# Issue reporting :triangular_flag_on_post: + +If you encounter a problem during setup or use, raise an issue under the [Issues tab](https://github.com/google/testrun/issues). Issue templates exist for both bug reports and feature requests. If neither of these apply, raise a blank issue instead. + +# Contributing :keyboard: + +We strongly encourage contributions from the community. Review the requirements on the ["How to Contribute" page](CONTRIBUTING.md), then follow the [developer guidelines](/docs/dev/README.md). + +# FAQ :raising_hand: + +#### 1. What should I do if I have an issue while installing or upgrading Testrun? + + You can resolve most issues by reinstalling Testrun using these commands: +- `sudo docker system prune -a` +- `sudo apt install ./testrun-*.deb` + +If this doesn't resolve the problem, [raise an issue](https://github.com/google/testrun/issues). + +#### 2. What device networking functionality does Testrun validate? + +Best practices and requirements for IoT devices change often due to technological advances and discovery of vulnerabilities. You can find the current expectations for IoT devices on Google deployments in the [Application Security Requirements for IoT Devices](https://partner-security.withgoogle.com/docs/iot_requirements). Testrun aims to automate as much of the Application Security Requirements as possible. + +#### 3. What services are provided on the virtual network? + +The following network services are containerized and accessible to the DUT: + +- DHCP in failover configuration with internet connectivity +- IPv6 SLAAC +- DNS +- NTPv4 + +Note that this list is likely to change over time. + +#### 4. Can I run Testrun on a virtual machine? + +Testrun can be virtualized if the 2x Ethernet adapters are passed through to a VirtualBox VM as a USB device rather than managed network adapters. Visit the [virtual machine guide](/docs/virtual_machine.md) for additional details. \ No newline at end of file diff --git a/cmd/build b/cmd/build index d3294a681..8ecccb5ef 100755 --- a/cmd/build +++ b/cmd/build @@ -60,11 +60,10 @@ fi # Build network modules echo Building network modules -mkdir -p build/network for dir in modules/network/* ; do module=$(basename $dir) echo Building network module $module... - if docker build -f modules/network/$module/$module.Dockerfile -t test-run/$module . ; then + if docker build -f modules/network/$module/$module.Dockerfile -t testrun/$module . ; then echo Successfully built container for network $module else echo An error occured whilst building container for network module $module @@ -74,11 +73,10 @@ done # Build validators echo Building network validators -mkdir -p build/devices for dir in modules/devices/* ; do module=$(basename $dir) echo Building validator module $module... - if docker build -f modules/devices/$module/$module.Dockerfile -t test-run/$module . ; then + if docker build -f modules/devices/$module/$module.Dockerfile -t testrun/$module . ; then echo Successfully built container for device module $module else echo An error occured whilst building container for device module $module @@ -88,11 +86,10 @@ done # Build test modules echo Building test modules -mkdir -p build/test for dir in modules/test/* ; do module=$(basename $dir) echo Building test module $module... - if docker build -f modules/test/$module/$module.Dockerfile -t test-run/$module-test . ; then + if docker build -f modules/test/$module/$module.Dockerfile -t testrun/$module-test . ; then echo Successfully built container for test module $module else echo An error occured whilst building container for test module $module diff --git a/cmd/install b/cmd/install index c350a969f..906550abf 100755 --- a/cmd/install +++ b/cmd/install @@ -68,15 +68,13 @@ deactivate cmd/build # Create local folders -mkdir -p local/devices -mkdir -p local/root_certs -mkdir -p local/risk_profiles +mkdir -p local/{devices,root_certs,risk_profiles} # Set file permissions on local # This does not work on GitHub actions if logname ; then USER_NAME=$(logname) - sudo chown -R "$USER_NAME" local + sudo chown -R "$USER_NAME" local || true fi echo Finished installing Testrun diff --git a/cmd/prune b/cmd/prune index 9f471897d..4c2796460 100755 --- a/cmd/prune +++ b/cmd/prune @@ -25,17 +25,16 @@ fi # Remove docker images echo Removing docker images -docker_images=$(sudo docker images --filter=reference="test-run/*" -q) +docker_images=$(sudo docker images --filter=reference="testrun/*" -q) if [ -z "$docker_images" ]; then echo No docker images to delete else - sudo docker rmi $docker_images > /dev/null + sudo docker rmi $docker_images fi # Remove docker networks echo Removing docker networks -sudo docker network rm endev0 > /dev/null -# Private network not used, add cleanup -# back in if/when implemented -#sudo docker network rm tr-private-net > /dev/null \ No newline at end of file +sudo docker network rm endev0 || true + +echo Successfully pruned Testrun resources diff --git a/docs/README.md b/docs/README.md index 5f055dbb9..efea0d413 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,21 +1,19 @@ Testrun logo +# Contents -## Contents +- [Get started](/docs/get_started.md) + - [Run on a virtual machine](/docs/virtual_machine.md) +- [Network](/docs/network/README.md) + - [Network addresses](/docs/network/addresses.md) + - [Add a new network service](/docs/network/add_new_service.md) +- [Testing](/docs/test/README.md) + - [Test modules](/docs/test/modules.md) + - [Test results](/docs/test/statuses.md) +- [Developer guidelines](/docs/dev/README.md) +- [Accessibility](/docs/ui/accessibility.md) +- [Roadmap](/docs/roadmap.pdf) - - [Get Started](get_started.md) - - [Network](network/README.md) - - [Network Overview](network/README.md) - - [How to identify network interfaces](network/identify_interfaces.md) - - [Addresses](network/addresses.md) - - [Add a new network service](network/add_new_service.md) - - [Testing](test/README.md) - - [Test modules](test/modules.md) - - [Test statuses](test/statuses.md) - - [Development](dev/README.md) - - [Running on a virtual machine](virtual_machine.md) - - [Accessibility](ui/accessibility.mp4) - - [Roadmap](roadmap.pdf) +# Something missing? -## Something missing? -If you feel there is some documentation that you would find useful, or have found an issue with existing documentation, please raise an issue on GitHub by navigating [here](https://github.com/google/testrun/issues/new/choose) \ No newline at end of file +To request additional documentation or report an issue with existing resources, visit [the Issues tab](https://github.com/google/testrun/issues/new/choose). diff --git a/docs/configure_device.md b/docs/configure_device.md deleted file mode 100644 index 1db7155be..000000000 --- a/docs/configure_device.md +++ /dev/null @@ -1,31 +0,0 @@ -Testrun logo - -## Device Configuration (Deprecated) - -The device configuration file allows you to customize the testing behavior for a specific device. This file is located at `local/devices/{Device Name}/device_config.json`. Below is an overview of how to configure the device tests. - -## Device Information - -The device information section includes the manufacturer, model, and MAC address of the device. These details help identify the specific device being tested. - -## Test Modules - -Test modules are groups of tests that can be enabled or disabled as needed. You can choose which test modules to run on your device. - -### Enabling and Disabling Test Modules - -To enable or disable a test module, modify the `enabled` field within the respective module. Setting it to `true` enables the module, while setting it to `false` disables the module. - -## Customizing the Device Configuration - -To customize the device configuration for your specific device, follow these steps: - -1. Copy the default configuration file provided in the `resources/devices/template` folder. - - Create a new folder for your device under `local/devices` directory. - - Copy the `device_config.json` file from `resources/devices/template` to the newly created device folder. - -This ensures that you have a copy of the default configuration file, which you can then modify for your specific device. - -> Note: Ensure that the device configuration file is properly formatted, and the changes made align with the intended test behavior. Incorrect settings or syntax may lead to unexpected results during testing. - -If you encounter any issues or need assistance with the device configuration, refer to the Testrun documentation or ask a question on the Issues page. diff --git a/docs/dev/README.md b/docs/dev/README.md index f11b1b092..076cb827c 100644 --- a/docs/dev/README.md +++ b/docs/dev/README.md @@ -1,25 +1,35 @@ Testrun logo -## Developer docs +# Developer guidelines -## Table of Contents -1) General guidelines (this page) -2) [Code quality](code_quality.md) +## How to contribute -## General guidelines -As an open source project, we absolutely encourage contributions from the community to help Testrun remain an expanding but stable product. However, before contributing there are a number of things to take into consideration. +As an open source project, we encourage contributions from the community to help Testrun remain an expanding but stable product. To contribute, follow the steps below: -1) [Sign the Google CLA](https://cla.developers.google.com/): Whether you are an individual or contributing on behalf of your organisation, you must be covered by a Google CLA. +1. Sign the [Google Contributor License Agreement (CLA)](https://cla.developers.google.com/). + - Whether you're an individual or contributing on behalf of your organization, you must be covered by a Google CLA. -2) Determine the scope of your contribution +1. Determine the scope of your contribution. + - Keep it simple. Your contribution is more likely to be accepted if you change fewer files. + - Ensure your pull request addresses one thing, such as a bug fix, dependency issue, or new framework capability. - - Your contribution is more likely to be accepted if fewer files are changed (keep it simple) - - Are you going to be fixing a bug, dependency issue or a new framework capability? Whatever it is, ensure your pull request fixes or changes just one thing. +1. Reach out to the core maintainers at [testrun-team@googlegroups.com](mailto:testrun-team@googlegroups.com). + - They can provide confirmation that your proposed changes meet our objectives and align with Testrun principles, making them more likely to be accepted. -3) Get in touch to discuss whether your proposed changes are likely to be accepted +1. Fork Testrun and get developing. + - We aim to provide thorough and clear developer documentation to help you contribute successfully. - - It is best to get the opinion from the core maintainers whether your proposed changes meet our objectives and align with Testrun principles. +## Code quality -4) Fork Testrun and get developing +To ensure code quality, use the appropriate style guide when developing code for Testrun: - - We aim to provide thorough and easy to ready developer documentation to help you contribute successfully. \ No newline at end of file +- [Python](https://google.github.io/styleguide/pyguide.html) +- [Angular](https://google.github.io/styleguide/angularjs-google-style.html) +- [Shell](https://google.github.io/styleguide/shellguide.html) +- [HTML/CSS](https://google.github.io/styleguide/htmlcssguide.html) +- [JSON](https://google.github.io/styleguide/jsoncstyleguide.xml) +- [Markdown](https://google.github.io/styleguide/docguide/style.html) + +## Automated actions + +The current code base has zero code lint issues. To maintain this, all lint checks are enforced on pull requests to dev and main. You should ensure these lint checks pass before marking your pull requests as Ready for review. diff --git a/docs/dev/code_quality.md b/docs/dev/code_quality.md deleted file mode 100644 index 47eabcf95..000000000 --- a/docs/dev/code_quality.md +++ /dev/null @@ -1,16 +0,0 @@ -Testrun logo - -## Code quality - -Whilst developing code for Testrun, there are some style guides that you should follow. - - - Python: https://google.github.io/styleguide/pyguide.html - - Angular: https://google.github.io/styleguide/angularjs-google-style.html - - Shell: https://google.github.io/styleguide/shellguide.html - - HTML/CSS: https://google.github.io/styleguide/htmlcssguide.html - - JSON: https://google.github.io/styleguide/jsoncstyleguide.xml - - Markdown: https://google.github.io/styleguide/docguide/style.html - -### Automated actions - -The current code base has been able to achieve 0 code lint issues. To maintain this, all lint checks are enforced on pull requests to dev and main. Please ensure that these lint checks are passing before marking your pull requests as 'Ready for review'. \ No newline at end of file diff --git a/docs/get_started.md b/docs/get_started.md index f9a2aa2f8..dbe5eab43 100644 --- a/docs/get_started.md +++ b/docs/get_started.md @@ -1,123 +1,121 @@ Testrun logo +# Get started -## Getting Started +This page covers the following topics: -It is recommended that you run Testrun on a standalone machine running a fresh install of Ubuntu 20.04, 22.04 or 24.04 LTS (laptop or desktop). +- [Prerequisites](#prerequisites) +- [Installation](#installation) +- [Testing](#testing) +- [Troubleshooting](#troubleshooting) +- [Review the report](#review-the-report) +- [Uninstall](#uninstall) -## Prerequisites +# Prerequisites -### Hardware +We recommend that you run Testrun on a stand-alone machine that has a fresh install of Ubuntu 20.04, 22.04, or 24.04 LTS (laptop or desktop). -Before starting with Testrun, ensure you have the following hardware: +## Hardware -- PC running Ubuntu LTS (laptop or desktop) -- 2x USB Ethernet adapter (one may be a built-in Ethernet port) -- Internet connection +Before you start, ensure you have the following hardware: -![Visual representation of setup](setup/visual.png) +- PC running Ubuntu LTS (laptop or desktop) +- 2x USB Ethernet adapter (one may be a built-in Ethernet port) +- Internet connection -**NOTE: Running in a virtual machine? Checkout the virtual machine documentation [here](/docs/virtual_machine.md).** +![Required hardware for Testrun](/docs/ui/getstarted--2dn8vrzsspe.png) -### Software +Note: If you're using Testrun in a virtual machine, follow the steps on the [Virtual machine page](/docs/virtual_machine.md). -Ensure the following software is installed on your Ubuntu LTS PC: -- Docker - installation guide: [https://docs.docker.com/engine/install/ubuntu/#install-using-the-repository](https://docs.docker.com/engine/install/ubuntu/#install-using-the-repository) -- System dependencies (These will be installed automatically when installing Testrun if not already installed): - - Python3-dev - - Python3-venv - - Openvswitch Common - - Openvswitch Switch - - Build Essential - - Net Tools - - Ethtool +## Software -### Device -Any device with an ethernet connection, and support for IPv4 DHCP can be tested. +Install Docker on your Ubuntu LTS PC using [the installation guide](https://docs.docker.com/engine/install/ubuntu/#install-using-the-repository). The following system dependencies install automatically when you install Testrun: -However, to achieve a compliant test outcome, your device must be configured correctly and implement the required security features. These standards are outlined in the [Application Security Requirements for IoT Devices](https://partner-security.withgoogle.com/docs/iot_requirements). but further detail is available in [documentation for each test module](/docs/test/modules.md). +- Python3-dev +- Python3-venv +- Openvswitch Common +- Openvswitch Switch +- Build Essential +- Net Tools +- Ethtool -## Installation +## Device -1. Download the latest version of the Testrun installer from the [releases page](https://github.com/google/testrun/releases) +You can test any device with an Ethernet connection and support for IPv4 DHCP. However, to achieve a compliant test outcome, you must configure your device correctly and implement the required security features. The [Application Security Requirements for IoT Devices](https://partner-security.withgoogle.com/docs/iot_requirements) outlines these standards. Additional details are available on the [Test modules page](/docs/test/modules.md). -2. Open a terminal and navigate to location of the Testrun installer (most likely your downloads folder) +# Installation -3. Install the package using ``sudo apt install ./testrun*.deb`` +Follow these steps to install Testrun: - - Testrun will be installed under the /usr/local/testrun directory. - - Testing data will be available in the ``local/devices/{device}/reports`` folders +1. Download the latest version of the Testrun installer from the [Releases page](https://github.com/google/testrun/releases). +2. Open a terminal and navigate to the location of the Testrun installer (most likely your Downloads folder). +3. Install the package using `sudo apt install ./testrun*.deb` - **NOTE: Local CA certificates should be uploaded within Testrun to run TLS server testing** +Testrun installs under the `/usr/local/testrun` directory. Testing data is available in the `local/devices/{device}/reports` folders. - ![Installing Testrun in terminal window](setup/install.gif) +Note: Local CA certificates should be uploaded within Testrun to run TLS server testing. -## Start Testrun - -1. Attach network interfaces: - - Connect one USB Ethernet adapter to the internet source (e.g., router or switch) using an ethernet cable. - - Connect the other USB Ethernet adapter directly to the IoT device you want to test using an ethernet cable. - - Some things to remember: - - Both adapters should be disabled in the host system (IPv4, IPv6 and general). You can do this by going to Settings > Network - - The device under test should be powered off until prompted - - Struggling to identify the correct interfaces? See [this guide](network/identify_interfaces.md). - -2. Start Testrun. - -Start Testrun with the command `sudo testrun` - - - To run Testrun in network-only mode (without running any tests), use the `--net-only` option. - - - To run Testrun with just one interface (connected to the device), use the ``--single-intf`` option. +![Terminal during install](/docs/setup/install.gif) -## Test Your Device +# Testing -1. Once Testrun has started, open your browser to http://localhost:8080. - -2. Configure your network interfaces under the settings menu - located in the top right corner of the application. Settings can be changed at any time. - - ![](/docs/ui/settings_icon.png) - -3. Navigate to the device repository icon to add a new device for testing. +## Start Testrun - ![](/docs/ui/device_icon.png) +Follow these steps to start Testrun: +1. Attach the network interfaces. + - Connect one USB Ethernet adapter to the internet source (e.g., router or switch) using an Ethernet cable. + - Connect the other USB Ethernet adapter directly to the IoT device you want to test using an Ethernet cable. -4. Click the button 'Add Device'. +Notes: -5. Enter the MAC address, manufacturer name and model number. +- Disable both adapters in the host system (IPv4, IPv6, and general) by opening **Settings**, then **Network**. +- Keep the DUT powered off until prompted. -6. Select the test modules you wish to enable for this device (Hint: All are required for qualification purposes) and click save. +1. Start Testrun with the command `sudo testrun` + - To run Testrun in network-only mode (without running any tests), use the `--net-only` option. + - To run Testrun with just one interface (connected to the device), use the `--single-intf` option. -7. Navigate to the Testrun progress icon and click the button 'Start New Testrun'. - ![](/docs/ui/progress_icon.png) +## Test your device -8. Select the device you would like to test. +Follow these steps to test your IoT device: -9. Enter the version number of the firmware running on the device. +1. Open Testrun by navigating to [http://localhost:8080](http://localhost:8080/) in your browser. +2. Select the **Settings** menu in the top-right corner, then select your network interfaces. You can change the settings at any time. + ![Settings menu button](/docs/ui/getstarted--7cfvdpdnc5o.png) -10. Click 'Start Testrun' +3. Select the **device repository** icon on the left panel to add a new device for testing. + ![Device repository button](/docs/ui/getstarted--q5uw26tfod.png) - - During testing, if you would like to stop Testrun, click 'Stop' next to the test name. +4. Select the **Add Device** button. +5. Enter the MAC address, manufacturer name, and model number. +6. Select the test modules you want to enable for this device. +Note: For qualification purposes, you must select all. +7. Select **Save**. +8. Select the Testrun progress icon, then select the **Testing** button.![Testing button](/docs/ui/getstarted--w09wecsry3.png) -11. Once the notification 'Waiting for Device' appears, power on the device under test. +9. Select the device you want to test. +10. Enter the version number of the firmware running on the device. +11. Select **Start Testrun**. +- If you need to stop Testrun during testing, select **Stop** next to the test name. +12. Once the Waiting for Device notification appears, power on the device under test. A report appears under the Reports icon once the test sequence is complete. + ![Reports button](/docs/ui/getstarted--m4si1otdu5d.png) -12. On completion of the test sequence, a report will appear under the history icon. +# Troubleshooting - ![](/docs/ui/history_icon.png) +If you encounter any issues, try the following: -# Troubleshooting +- Ensure that your computer meets all hardware and software prerequisites. +- Verify that the network interfaces are connected correctly. +- Check the configuration settings. +- Refer to the [Testrun documentation](/docs). -If you encounter any issues or need assistance, consider the following: +If you still need assistance, ask a question on the [Issues page](https://github.com/google/testrun/issues). -- Ensure that all hardware and software prerequisites are met. -- Verify that the network interfaces are connected correctly. -- Check the configuration settings. -- Refer to the Testrun documentation or ask for assistance in the issues page: https://github.com/google/testrun/issues +# Review the report -# Reviewing -Once you have completed a test attempt, you may want to review the test report provided by Testrun. For more information about what Testrun looks for when testing, and what the output means, take a look at the testing documentation: [Testing](/docs/test/README.md). +Once you complete a test attempt, you can review the test report provided by Testrun. For more information on Testrun requirements and outputs, refer to the [Testing documentation](/docs/test/README.md). # Uninstall -To uninstall Testrun, use the built-in dpkg uninstall command to remove Testrun correctly. For Testrun, this would be: ```sudo apt-get remove testrun```. + +To uninstall Testrun correctly, use the built-in dpkg uninstall command: `sudo apt-get remove testrun` \ No newline at end of file diff --git a/docs/network/README.md b/docs/network/README.md index 0f97ecd7b..0efb5e3ff 100644 --- a/docs/network/README.md +++ b/docs/network/README.md @@ -1,44 +1,41 @@ Testrun logo -## Network Overview +# Network -## Table of Contents -1) Network overview (this page) -2) [How to identify network interfaces](identify_interfaces.md) -3) [Addresses](addresses.md) -4) [Add a new network service](add_new_service.md) +This page provides an overview of Testrun's network services. Visit these pages for additional information: -Testrun provides several built-in network services that can be utilized for testing purposes. These services are already available and can be used without any additional configuration. +- [Network addresses](/docs/network/addresses.md) +- [Add a new network service](/docs/network/add_new_service.md) -The following network services are provided: +Testrun provides several built-in network services you can use for testing purposes. These services don't require any additional configuration. Below is a list and brief description of the network services provided. -### Internet Connectivity (Gateway Service) +# Internet connectivity (gateway service) The gateway service provides internet connectivity to the test network. It allows devices in the network to access external resources and communicate with the internet. -### DHCPv4 Service +# DHCPv4 service The DHCPv4 service provides Dynamic Host Configuration Protocol (DHCP) functionality for IPv4 addressing. It includes the following components: -- Primary DHCP Server: A primary DHCP server is available to assign IPv4 addresses to DHCP clients in the network. -- Secondary DHCP Server (Failover Configuration): A secondary DHCP server operates in failover configuration with the primary server to provide high availability and redundancy. +- Primary DHCP server: Assigns IPv4 addresses to DHCP clients in the network. +- Secondary DHCP server (failover configuration): Operates in failover configuration with the primary server to provide high availability and redundancy. -#### Configuration +## Configuration -The configuration of the DHCPv4 service can be modified using the provided GRPC (gRPC Remote Procedure Call) service. +You can modify the configuration of the DHCPv4 service using the provided Remote Procedure Call (GRPC) service. -### IPv6 SLAAC Addressing +# IPv6 SLAAC addressing -The primary DHCP server also provides IPv6 Stateless Address Autoconfiguration (SLAAC) addressing for devices in the network. IPv6 addresses are automatically assigned to devices using SLAAC where test devices support it. +The primary DHCP server provides IPv6 Stateless Address Autoconfiguration (SLAAC) addressing for devices on the network. It automatically assigns IPv6 addresses to devices using SLAAC where test devices support it. -### NTP Service +# NTP service -The Network Time Protocol (NTP) service provides time synchronization for devices in the network. It ensures that all devices have accurate and synchronized time information. +The Network Time Protocol (NTP) service provides time synchronization for devices on the network. It ensures that all devices have accurate and synchronized time information. -### DNS Service +# DNS service -The DNS (Domain Name System) service resolves domain names to their corresponding IP addresses. It allows devices in the network to access external resources using domain names. +The Domain Name System (DNS) service resolves domain names to their corresponding IP addresses. It allows devices on the network to access external resources using domain names. -### 802.1x Authentication (Radius Module) +# 802.1x authentication (radius module) -The radius module provides 802.1x authentication for devices in the network. It ensures secure and authenticated access to the network. The issuing CA (Certificate Authority) certificate can be specified by the user if required. \ No newline at end of file +The radius module provides 802.1x authentication for devices on the network. It ensures secure and authenticated access to the network. The user can specify the issuing Certificate Authority (CA) certificate if required. \ No newline at end of file diff --git a/docs/network/add_new_service.md b/docs/network/add_new_service.md index b3fa22514..e6b01e102 100644 --- a/docs/network/add_new_service.md +++ b/docs/network/add_new_service.md @@ -1,21 +1,44 @@ Testrun logo -## Adding a New Network Service +# Add a new network service -The Testrun framework allows users to add their own network services with ease. A template network service can be used to get started quickly, this can be found at [modules/network/template](../../modules/network/template). Otherwise, see below for details of the requirements for new network services. +The Testrun framework allows you to easily add your own network services. You can use the template network service at [modules/network/template](/modules/network/template). To add a new network service, follow these steps: -To add a new network service to Testrun, follow the procedure below: +1. Create a folder under `modules/network/` with the name of the network service in lowercase using only alphanumeric characters and hyphens (-). +1. Include the following items in the created folder: + - `{module}.Dockerfile`: Dockerfile builds the network service image. Replace `{module}` with the name of the module. + - `conf/`: Folder containing the module configuration files. + - `bin/`: Folder containing the start-up script for the network service. + - Place any additional application code in its own folder. -1. Create a folder under `modules/network/` with the name of the network service in lowercase, using only alphanumeric characters and hyphens (`-`). -2. Inside the created folder, include the following files and folders: - - `{module}.Dockerfile`: Dockerfile for building the network service image. Replace `{module}` with the name of the module. - - `conf/`: Folder containing the module configuration files. - - `bin/`: Folder containing the startup script for the network service. - - Any additional application code can be placed in its own folder. +Here are some examples: -### Example `module_config.json` +## {module}.Dockerfile -```json +``` +# Image name: test-run/{module} +FROM test-run/base:latest + +ARG MODULE_NAME={module} +ARG MODULE_DIR=modules/network/$MODULE_NAME + +# Install network service dependencies +# ... + +# Copy over all configuration files +COPY $MODULE_DIR/conf /testrun/conf + +# Copy over all binary files +COPY $MODULE_DIR/bin /testrun/bin + +# Copy over all python files +COPY $MODULE_DIR/python /testrun/python + +# Do not specify a CMD or Entrypoint as Testrun will automatically start your service as required by calling the start_network_service script in the bin folder +``` + +## module_config.json +``` { "config": { "meta": { @@ -42,35 +65,11 @@ To add a new network service to Testrun, follow the procedure below: } } } -``` - -### Example of {module}.Dockerfile -```Dockerfile -# Image name: test-run/{module} -FROM test-run/base:latest - -ARG MODULE_NAME={module} -ARG MODULE_DIR=modules/network/$MODULE_NAME - -# Install network service dependencies -# ... - -# Copy over all configuration files -COPY $MODULE_DIR/conf /testrun/conf - -# Copy over all binary files -COPY $MODULE_DIR/bin /testrun/bin - -# Copy over all python files -COPY $MODULE_DIR/python /testrun/python - -# Do not specify a CMD or Entrypoint as Testrun will automatically start your service as required ``` -### Example of start_network_service script - -```bash +## start_network_service script +``` #!/bin/bash CONFIG_FILE=/etc/network_service/config.conf @@ -89,8 +88,4 @@ echo "Starting Network Service..." # Restart the network service when the config changes # ... -``` - - - - +``` \ No newline at end of file diff --git a/docs/network/addresses.md b/docs/network/addresses.md index 7fa71d716..261242687 100644 --- a/docs/network/addresses.md +++ b/docs/network/addresses.md @@ -1,20 +1,19 @@ Testrun logo -## Network Addresses +# Network addresses -Each network service is configured with an IPv4 and IPv6 address. For IPv4 addressing, the last number in the IPv4 address is fixed (ensuring the IP is unique). See below for a table of network addresses: +Each network service is configured with an IPv4 and IPv6 address. For IPv4 addressing, the last number in the IPv4 address is fixed, ensuring the IP is unique. The table below lists network addresses you might need. -| Name | Mac address | IPv4 address | IPv6 address | -|---------------------|----------------------|--------------|------------------------------| -| Internet gateway | 9a:02:57:1e:8f:01 | 10.10.10.1 | fd10:77be:4186::1 | -| DHCP primary | 9a:02:57:1e:8f:02 | 10.10.10.2 | fd10:77be:4186::2 | -| DHCP secondary | 9a:02:57:1e:8f:03 | 10.10.10.3 | fd10:77be:4186::3 | -| DNS server | 9a:02:57:1e:8f:04 | 10.10.10.4 | fd10:77be:4186::4 | -| NTP server | 9a:02:57:1e:8f:05 | 10.10.10.5 | fd10:77be:4186::5 | -| Radius authenticator| 9a:02:57:1e:8f:07 | 10.10.10.7 | fd10:77be:4186::7 | -| Active test module | 9a:02:57:1e:8f:09 | 10.10.10.9 | fd10:77be:4186::9 | +| Name | MAC address | IPv4 address | IPv6 address | +| ----------------- | ----------------- | ------------- | ------------------- | +| Internet gateway | 9a\:02\:57\:1e\:8f\:01 | 10.10.10.1 | fd10\:77be\:4186\:\:1 | +| DHCP primary | 9a\:02\:57\:1e\:8f\:02 | 10.10.10.2 | fd10\:77be\:4186\:\:2 | +| DHCP secondary | 9a\:02\:57\:1e\:8f\:03 | 10.10.10.3 | fd10\:77be\:4186\:\:3 | +| DNS server | 9a\:02\:57\:1e\:8f\:04 | 10.10.10.4 | fd10\:77be\:4186\:\:4 | +| NTP server | 9a\:02\:57\:1e\:8f\:05 | 10.10.10.5 | fd10\:77be\:4186\:\:5 | +| Radius authenticator | 9a\:02\:57\:1e\:8f\:07 | 10.10.10.7 | fd10\:77be\:4186\:\:7 | +| Active test module | 9a\:02\:57\:1e\:8f\:09 | 10.10.10.9 | fd10\:77be\:4186\:\:9 | +The default network range is 10.10.10.0/24 and devices are assigned addresses in that range via DHCP. The range may change when requested by a test module. In that case, network services restart and are accessible on the new range with the same final host ID. The default IPv6 network is fd10:77be:4186::/64 and addresses are assigned to devices on the network using IPv6 SLAAC. -The default network range is 10.10.10.0/24 and devices will be assigned addresses in that range via DHCP. The range may change when requested by a test module. In which case, network services will be restarted and accessible on the new range, with the same final host ID. The default IPv6 network is fd10:77be:4186::/64 and addresses will be assigned to devices on the network using IPv6 SLAAC. - -When creating a new network module, please ensure that the ip_index value in the module_config.json is unique otherwise unexpected behaviour will occur. \ No newline at end of file +When creating a new network module, ensure that the ip_index value in the module_config.json is unique to prevent unexpected behavior. \ No newline at end of file diff --git a/docs/network/identify_interfaces.md b/docs/network/identify_interfaces.md deleted file mode 100644 index 50e62acd3..000000000 --- a/docs/network/identify_interfaces.md +++ /dev/null @@ -1,20 +0,0 @@ -Testrun logo - -## Identifying network interfaces - -For Testrun to operate correctly, you must select the correct network interfaces within the settings panel of the user interface. There are 2 methods to identify the correct network interfaces: - -A) Find the printed MAC address on your interface - -Some USB network interfaces will have the MAC address printed on the interface itself. This will look something like: ```00:e0:4c:02:0f:a8```. - -Compare this printed MAC address against the MAC address provided in the settings panel in the user interface. - -B) Connect your interfaces one at a time - - 1) Ensure both interfaces are disconnected from your PC and open the settings panel in the user interface. - - 2) Connect your internet interface to your PC and refresh the settings panel. One interface, which was not previously present, should now be visibile. - - 3) Repeat the previous step for the devices interface. - diff --git a/docs/roadmap.pdf b/docs/roadmap.pdf index 9f4599ee1c45ade27a0d41342a25864b4dca69d7..0566598e08cc8b383aea4681eb5f087c51bfe507 100644 GIT binary patch delta 389766 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 diff --git a/docs/test/README.md b/docs/test/README.md index 3163b4c84..19fcf3fd5 100644 --- a/docs/test/README.md +++ b/docs/test/README.md @@ -1,7 +1,5 @@ Testrun logo +# Testing -## Testing -The test requirements that are investigated by Testrun can be found in the [test modules documentation](/docs/test/modules.md). - -To understand the testing results, various definitions of test results and requirements are specified in the [statuses documentation](/docs/test/statuses.md). \ No newline at end of file +Testrun provides modules for you to test your own device. You can learn more about the requirements for each on the [Test modules page](/docs/test/modules.md). The [Test results page](/docs/test/statuses.md) outlines possible results and how to interpret them. diff --git a/docs/test/modules.md b/docs/test/modules.md index 2fe5983b1..ff6bb5c53 100644 --- a/docs/test/modules.md +++ b/docs/test/modules.md @@ -1,16 +1,26 @@ Testrun logo -## Test Modules +# Test modules -Testrun provides some pre-built test modules for you to use when testing your own device. These test modules are listed below: +Testrun provides some pre-built modules you can use when testing your own device. The table below lists the test module, its purpose, and a link to additional information. -| Name | Description | Read more | -|---|---|---| -| Base | Template for all test modules | [Base module](/modules/test/base/README.md) | -| Baseline | A sample test module | [Baseline module](/modules/test/baseline/README.md) | -| Connection | Verify IP and DHCP based behavior | [Connection module](/modules/test/conn/README.md) | -| DNS | Verify DNS functionality | [DNS module](/modules/test/dns/README.md) | -| Services | Ensure unsecure services are disabled | [Services module](/modules/test/services/README.md) | -| NTP | Verify NTP functionality | [NTP module](/modules/test/ntp/README.md) | -| Protocol | Inspect BMS protocol implementation | [Protocol Module](/modules/test/protocol/README.md) | -| TLS | Determine TLS client and server behavior | [TLS module](/modules/test/tls/README.md) | +| Module name | Purpose | Additional documentation | +| ------------ | ---------------------------- | ----------------------------- | +| Base | Template for all test modules | [Base module] | +| Baseline | Sample test module | [Baseline module] | +| Connection | Verify IP and DHCP-based behavior | [Connection module] | +| DNS | Verify DNS functionality | [DNS module] | +| Services | Ensure unsecure services are disabled | [Services module] | +| NTP | Verify NTP functionality | [NTP module] | +| Protocol | Inspect BMS protocol implementation | [Protocol Module] | +| TLS | Determine TLS client and server behavior | [TLS module] | + + +[Base module]: /modules/test/base/README.md +[Baseline module]: /modules/test/baseline/README.md +[Connection module]: /modules/test/conn/README.md +[DNS module]: /modules/test/dns/README.md +[Services module]: /modules/test/services/README.md +[NTP module]: /modules/test/ntp/README.md +[Protocol Module]: /modules/test/protocol/README.md +[TLS module]: /modules/test/tls/README.md diff --git a/docs/test/statuses.md b/docs/test/statuses.md index d196fa4af..25c3d77b2 100644 --- a/docs/test/statuses.md +++ b/docs/test/statuses.md @@ -1,33 +1,32 @@ Testrun logo -## Test Statuses -Testrun will output the result and description of each automated test. The test results will be one of the following: +# Test results -| Name | Description | What next? | -|---|---|---| -| Compliant | The device implements the required feature correctly | Nothing | -| Non-Compliant | The device does not support the specified requirements for the test | Modify or implement the required functionality on the device | -| Feature Not Detected | The device does not implement a feature covered by the test | You may implement the functionality (not required) | -| Error | An error occured whilst running the test | Create a bug report requesting additional support to diagnose the issue | +Testrun outputs the result and a description of each automated test. The table below includes the result name, its description, and what your next step should be. -## Test Requirement -Testrun also determines whether each test is required for the device to receive an overall compliant result. These rules are: +| Result name | Description | What next? | +| --------------------- | ------------------------ | ------------------------ | +| Compliant | The device implements the required feature correctly. | Nothing. | +| Non-Compliant | The device doesn’t support the specified requirements for the test. | Modify or implement the required functionality on the device. | +| Informational | Extra information about the device under test | Nothing. | +| Feature Not Detected | The device doesn’t implement a feature covered by the test. | You may implement the functionality but it’s not required. | +| Error | An error occurred while running the test. | Create a bug report requesting additional support to diagnose the issue. | -| Name | Description | -|---|---| -| Required | The device must implement the feature | -| Recommended | The device should implement the feature, but will not receive an overall Non-Compliant if not implemented | -| Roadmap | The device should implement this feature in the future, but is not required at the moment | -| Required If Applicable | If the device implements this feature, it must be implemented correctly (as per the test requirements) | -## Testrun Statuses -Once testing is completed, an overall result for the test attempt will be produced. This is calculated by comparing the result of all tests, and whether they are required or not required. +# Test requirements -### Compliant -All required tests are implemented correctly, and all required if applicable tests are implemented correctly (where the feature has been implemented). +Testrun determines whether the device needs each test to receive an overall compliant result. Here are the rules and what they mean: -### Non-Compliant -One or more of the required tests (or required if applicable tests) have produced a non-compliant result. +- Required: The device must implement the feature. +- Recommended: The device should implement the feature but won't receive an overall Non-Compliant if it's not implemented. +- Roadmap: The device should implement this feature in the future, but it's not required at the moment. +- Required If Applicable: If the device implements this feature, it must be implemented correctly (per the test requirements). -### Error -One of more of the required tests (or required if applicable tests) have not executed correctly. This does not necessarily indicate that the device is compliant or non-compliant. +# Testrun statuses + +Once testing is complete, the program produces an overall status for the test attempt. It's calculated by comparing the results of all tests and whether they're required or not. The possible statuses are: + +- Compliant: All required tests are implemented correctly, and all required if applicable tests are implemented correctly (where the feature is implemented). +- Non-Compliant: One or more of the required tests (or Required If Applicable tests) produced a Non-Compliant result. +- Error: One or more of the required tests (or Required If Applicable tests) didn't execute correctly. This doesn't necessarily indicate that the device is Compliant or Non-Compliant. +- Cancelled: Either the device was disconnected during testing or the user requested to cancel the test attempt. \ No newline at end of file diff --git a/docs/ui/accessibility.md b/docs/ui/accessibility.md new file mode 100644 index 000000000..58d948522 --- /dev/null +++ b/docs/ui/accessibility.md @@ -0,0 +1,12 @@ +Testrun logo + +# Accessibility + +We designed Testrun with accessibility at its core. The application provides full support for: + +- Screen readers +- Keyboard navigation +- Responsive resizing and scaling +- [Helperbird](https://www.helperbird.com/) + +For a more thorough explanation of these features, download the [accessibility video](https://github.com/google/testrun/raw/main/docs/ui/accessibility.mp4). If you require further accommodations when using Testrun, please [raise an issue](https://github.com/google/testrun/issues/new/choose). \ No newline at end of file diff --git a/docs/ui/device_icon.png b/docs/ui/device_icon.png deleted file mode 100644 index 2472f7da223c4b6a29e94e830303095628d3aae6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 933 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 literal 0 HcmV?d00001 diff --git a/docs/ui/getstarted--3d9k3si3ul1.png b/docs/ui/getstarted--3d9k3si3ul1.png new file mode 100644 index 0000000000000000000000000000000000000000..5c762156d2173c891fafb99706b5aee40dfaeac7 GIT binary patch literal 10189 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 SET~Nn@cP-;vugM8-~S6=Z4fX3 literal 0 HcmV?d00001 diff --git a/docs/ui/getstarted--7cfvdpdnc5o.png b/docs/ui/getstarted--7cfvdpdnc5o.png new file mode 100644 index 0000000000000000000000000000000000000000..288e5f876724a11c0e6bd5d49ab2d40061f17dc1 GIT binary patch literal 17881 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 literal 0 HcmV?d00001 diff --git a/docs/ui/getstarted--m4si1otdu5d.png b/docs/ui/getstarted--m4si1otdu5d.png new file mode 100644 index 0000000000000000000000000000000000000000..f6b7eddc93ce83b8b340b1e5679e4db8545bae11 GIT binary patch literal 13909 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 NW@>c>f9X!-{{np0YA^r* literal 0 HcmV?d00001 diff --git a/docs/ui/getstarted--q5uw26tfod.png b/docs/ui/getstarted--q5uw26tfod.png new file mode 100644 index 0000000000000000000000000000000000000000..4b8b2e847a7994704b089d2ff465d8b2c2fd788c GIT binary patch literal 13929 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 literal 0 HcmV?d00001 diff --git a/docs/ui/getstarted--w09wecsry3.png b/docs/ui/getstarted--w09wecsry3.png new file mode 100644 index 0000000000000000000000000000000000000000..e379040b59473f5035bbe2e76c6b1727d78f43ea GIT binary patch literal 14001 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 literal 0 HcmV?d00001 diff --git a/docs/ui/history_icon.png b/docs/ui/history_icon.png deleted file mode 100644 index eb95a8663bd7e574b30466f2fa9461a174490a83..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 538 zcmV+#0_FXQP)ZgXgFbngSdJ^%m#tVu*cR9J=W zmp@R#FcgP>cy8?;X3Lz;z}(^}J20y&?xG`u(1T6x68>o$9h&xcvWPX7*x3Q4}eYd!Cn=!xbzO zg7Dy&+4gTb#S*uUxh-xVcxB0;(`utE@~R7&FXmvrFHH>i?AD=;cTg}7|j9 cvyywk5BJY^UKz0?dH?_b07*qoM6N<$g68$~zW@LL diff --git a/docs/ui/progress_icon.png b/docs/ui/progress_icon.png deleted file mode 100644 index c326d185e93a58a3f2194200052daeb83e209bb3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 989 zcmV<310wv1P)ZgXgFbngSdJ^%m%Z%IT!R9J=W zn7>ckKorNnsk(8@F>{>R_8L2kal!}`sjAefNC*j9q^kb|5@M{{5d^bMTx2HpVhZA4Ok2a#Hyzwr&_xZl}?%i_=gTY{cXJoIQ8em>~0baH(419iZiQT<@ z^rBe0Picakx9@O%aXImfqc2~vySER<*vx(x26kHa=tVKUeg7fdKfAa@pC-7vxs?L> zK?pwx(Tif-weFV-><1zGlmY;HQ7i=tf)D_}7?T1y?T0nj^JezF5HNS9C`Bm{7evU) zL?AAR5CT64XD*9{fIIFG;OOu`3d99{K0J^DbzBd9N>MdUH0&Ax;OgdfIlxZq9*i+G z>>8?;nFC^s2|??Yi8ouDIiQZ$1pru91y#!g00d#U9NYSirq;Cd|;JH5Tl1kCO;KRz8W>|!%VK6=N)r*itTPVOsMm1aY)S?3TILtC zYMK}~D_xsb7Bd6iwbJO?c5aUlg0iZj7sXORSs*_MarEgRx4)4oi^qEM%)qU*zIeTj zo0AHXMUMj!LM8%bv)~6m{|uPFT)L)Vl@RG~+_bk=EmI0aX@as^0%J@%Fj1O_7RgI% zVPGc{-Q$zf91s_zswGgGNP&(|PVw=>djOD5a91}&$Z1dYOu+ozUsDuxTrZb0F6hnH zCa#;!9MEtoi@+EIAw)Vbxn36?w=16NNfX>uU>?0}ZH}jm3o>*K{WQW^ATH=v`T~v* zOx#B=jzs^SG{H>;=FTju0z=nwDdU5Tu4!0P6e$pIRfeuffwCEr`Is3vJIE9;>;z@C z1mGcMe2`gPC2(486bcvt;=@u|Eg=X(&~%Q@0`Uwb>72oR$R@bgj{)~-A`U%1IUU)H z(u?H>!4TZ_Mw&iZgXgFbngSdJ^%m#tw}^dR9J=W znZHxQFcilho?Ehqne3^n&b0g!fPX~66>(Jm34YA#NMWYTGTY8%Z|69ul{*4yV}sXT zK2z`A>+AQDmwc0JLkJo(?s#G{o9dP+Og&rT zeY-t~#c?dn<($Lr+tG@WXCdY?j$<3Wj9|5d*?gWmf1g?!hjN55Lg2aXL43zKY$dHi zDV64zhVE*$yF^a3jiX6OHuaR!EFMJ>e8gV|^(*E8DZ?!GDSOH$vuV~U!7?bjd<|P7 zNfLN@dp()sFh{!Shnr0y6X|z*${e-eG*ufxSME~lFobm&DncrYK)^Q%y$3)002ovPDHLkV1k_3?lu4b diff --git a/docs/ui/settings_menu.png b/docs/ui/settings_menu.png deleted file mode 100644 index 046526b2546214541f5c97f8635f8fb7d8c9bc8b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 37230 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 diff --git a/docs/ui/test_name.png b/docs/ui/test_name.png deleted file mode 100644 index 3d18df19d031cfacd7e1befe745efc57be0109cc..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 10221 zcmd5?by!s0w;ohNkP-x>OG-i|B?W1zp`>x7q@^2FQW_Lc5Re(VyAdftdKh7-0R*Ln z?&fa3?{}a3@4f$Bo@Zcl&YW}h+Gp)|z3W};y_SX&F(EA>1Og#eQI^++K(0%JaRdP# z_{~u+%?JM6aFbKfB>+GE1eOut|GVxA2JSk}*6v;~S1X8(le420pPPlNm6em*D`)r3 z>#g7p%n%iMSzYfh>lkwf`q2!|EpxmfJW~-*Tz4ddn0pg*%w{Ho*XQ4tCcokQT6Fgyl&+v?v#)d!Cxr=bJ3e}9?1X|Uk(Jn!T*f%WZrsWgV)E8RKWEpVyx_wU~U|UI!|bI&MJRMg zda10Fmm7|uWR-ht4^F?yk|V#~Q{?C8x3@o91PsA{M+y#qCkqI0bQceU*q$zaz;-yv z{rBPZ+2)6991zRJ?xDJHObE%pQ)FqQ6zyp`#m8rJ%V0YK*FzvrHdy`_11m=V$S&4& zF_)K?7k8q%ip?MWb8+;i0+=Wm;q~oGPEJm;cDwYr(7T$}EuAK^C&0O~3wZ_x1`^m+ zp4u*rjZsb14mcus`@m5?Ufx?JF&RtHCJe_^?a$q2i}jCIpD7hs6ritQFc_EP4agfd z0%=G*U9uJUj`YF)=;)|coO9ijrQujb+4Ca3M61UzYpvI3;@Y3%gHg#JSkW&&S7?uprj`Ek)?aJ430)sD$D+yplJHw~tjF^@#Xwag1zM zzh>1$wsn3s=d$$g=sAw;u@@Zr6?uL;Ubue|h z&>I;U5eoa>4)K^&N2xhdMk>>vz$+cotZi(5K8V&0#zi7GRnYsL-P@9Jw>p^nD)UF} za&C`|9Gzc-{9$-U+G(AdyJ@Pajw17a!Ig-|)Y@(HxNBfnvQU@jGv?bWO}^{2+>=9>5F$Lxe3-D30UXau%(y-XNGquJ8)nk{f3 zZwwaOYytwL4sc7f3-1o94eXQCPfxs;O@Q4G!t$+eA*lR&98 z8{D#mPkS28nYb`pLhQWs0+vSSM&AMIWLK#&O zfxBpUJXT2**INNj;lM`prbPd~E8Y9p+{4gg)9smO`rGQ1YU7*sZf=75bQ2H32D+t# zfiP2hvq{?we!02tZT4DK@CaCa|Ni|7svk;FUaC-r-{rWF_G&LHt56Y9Q8vsRAw|EX zVeaUto>M;X0NoOOiP3&}6t3e`u4>Nh24#g$sDIt;asGT0oks2zytE)>Hp#v6u zdO;;o9*f0h#pT)3CaK;tGBn&yZy%U6;7?&2O5BnhI&4m4=ia|Y3jL^h4_NV8hE!lr zH8G^+1LSM>zp=x%2VO4w%i;)zdrL^YPjZhe;dKbB>hcPeE(@RPO*?(sG#d{OVg2LR zR3q}OW%;Mm5t9>kHLRN37O?l!c;k$UP)A3{lrkvU8%)PQ+Mr6DASDJ zT`N`HCJfcL6qRe+tE)VR&5?puQPkqQGb_~U3LmW=qdeRp4>EvLy;$4MJ&|Ae!V%!# z3Xo%WclT}7poeK5z%yO(f~KaXZ`}!2kCV7H;TFXuCAiYkTx!`BN*cpqHLyt)b`oZl z*=PFYZx5THN_ak-wll$TuXtGHNFhhEVDc3yKXfy**%Z&;T_a69&@DsjOfdt%7#SQK ztbjub3v)+q>gy#*tWsEK%L#e#4%BeLJw?(6wA$W@zRTfKwcEU>`HRr;1C754`fnJW zVX_Y91vO;ndKdAv^2&nPg?Io%8@yHAJcWgxy(J{tO&F5VS{1{&6(2@N2M6~VQpIE| zZOTw8YU=Ru#^3Yl_Y>LozjQ2&8_s)JcIao`_HbP&)GzwwAQB*v11l=*%CR-FiTlc) z)PBYlIZx->?mp(T>1NbcY+Pfs@5q^!OkFJU|;APG|ZD=$w_LE+@YZk~A)Cbs5~4EaJ43fJ)H6VHyJMSCaB()WQ^+UPTE(Kj7DYkq>OH&i})Y~vQ4mT4x zUKmV%L%t&B9&v|qbK2Qgc7_U-Mtrik!oqB<_~%@eN?z2-i7y8ooJ4ziX9iB5RjkNi zX?Sps;h0zxEa~-!FUS+~Ht%}TUwAI$>+(!*<}mEBNT38V5P9Zk+r{a}vzxOGo=Hx^ zYFAY0J72Km)2)?}S3=rN6xc?7+f+sHT562c{c(B=9c@bmXoR|5@(p-NvgNnLwk0*| zpDP=bVm)~hiLoSwOFlv}GEA#WXZ?eNVS$V?vS|*tp)Woa=obkI3O;Qtja#2J5}i>j zll4Z}$^Q=0gmOpTmN?pyuIkV)BVzSsLfmjdzS3kItP7;0qnjQQQAfGb9qi<4jjen} zcLk(&3z)&L);-hAFDdZM-|q=uaP!lxEc{c42&$PMR)&CGq%RNpl<41Tzzn5HBrC;#Ai5^a%&Z)I!CT{Dad znuayc+G0$HRLwREqoPsOh3rekuRaPos5mepX_NA?=^VBJn#0n12yYU(vJaYfVnZ{^ z^kuR&sm&RV>x_Nla*|CXVl;mtLZ zfVw#=q2P>-WQMQ{BXux#HL0%|ZWKjy z5|Lp3!u$R{dK}AkOWT1ZuxOMJBh9G)Q1#b@7G)@^aY!S#4x zLDfv2d9iTCD%ExBWqF!xvu$Ys+l~H35V;(N-r3nvuDNr>_}H&@>9W=6JpQx_Sby$) zaV_BV=Y9s+jrh(^yv(miN@#e35|nasdYaOc8L)zyni{RBE}ty58}g_{p~`{2GU8O3h{TwyZPvX8vJtI9g(Kwx%VO%H5NJGbN zETw{Ttqye6sl9a>Q&LF|OZ%6>nwK-Q7{JOYN{(Sn?af;hxgXt|FpFmS zw>ly`SQ6RatD)9Dm$oaa{-~M%Sc)z1`n1EmVfHW*J`4hVU8*4!gKokDFO-jLhNyZ0 zQ(5C!{e`;aTgQ-IirQ5sfur*C+_M9CBwKz9wATjPr~yL!bZniKp`{^}ZR*%WL{fmX zMC?<5c8bq@*i|A;EfpzWMRvV}YhUuN<+cbv)deu=EfuouQdjmwHmmrJIEl>K|x7V zu{JXGBcH!@XFR(~GhP2yZVrV%2Ft*TK8m1H?ydZynsvS7K|O=!497=|JarYjpIJG# zYa~40%CNes>S-7(+6uo+mxpEVXxKvvy0WWX(8(}L?kM+|H!!GA#j7Q=6OJoc47s%_ zsG~PJjCccIaR`HD*XBMG;!}lw?7?B{kGavhrNfh?jNb2;BP9>&HM@qiA#iwD6i@)XGW}Kei zNt_K7-K)qvpCc6Ab7!g|t1M*5c4iHZ<8lu%4Wdx2G5YJ7@veQ;Yc`eZ*bm-H$1c=f zjpj2s!oHlV5i#01HA1TQcNP7a#pF8;FQH}VhP08&^9F#g7Uu!@NtY>BkIM;yBVaTj z{o$bl+fx;=@%Mu@Vrf4s?NZi!q=v92)WLFjR)*{k{jUPhWkq?{9b_K1{FPvuy zXe^!Q75za5dgy1U%Stg|NUisgLfvLzKsb}Ke(v}KJ9ka5K_a`0L#^(=N{`6~tEC+lPMqP+YdCM8dxwu$NqnI*5c~pCZZ~C0+~hDUY`*s3hhC zjtm?`sYJg>&{wZSzrd{Qiv2A4$)c##QIuv%D0ZW~aAd}|C)h?~LHn~G-R*oE1qnYc z3e|%k>Z@qEF*Ab*I>$H%-2Z_Av`0E-VJMhO4??)lY%BV= zet~TF)sZB1GwKfOwmuv{_5&o1@4m4Qocujp_;r!xrlC$y_^~{UW zXL}R(1yBrwJ>3h`0?1vafG?^?K6~w<8JCwk1ER|pI6>8ayVut{ei~ux69`dm8G{7XDO7=^&%jQ@p#{GP?J&J1c@Owivg_9BvZgHeUMFA3SlTxa05X=~+Mf3=!f% ze|ma4J;$b!xYdp-nQ-vT^(~i(16^Zey45#KHM=ja3hom$RBJ^fFN*WG?A}6ZENBnZf+z$si0T1pJi6~@oMksblrxdPb6!SfbNa(#tO2}Qph z*y=B#J7(zX9i43hbW&`Bq^AxBvsoOG zL}-ueheJPk_S{iIF-9f&e{N4WxKC+L2b2Js`OQp3H7WbL6AfOSX>oiz*KbolV9)Y zMWd{yXurrC6ubAHrY8gi&4`a3h`dVG)*n*5MW`8YZ@I8F4` z$?O2NgcQ5pjNn*;lIn-so!hUdPgukcV9= zkoxzQkBOwRf+Lp4n9ici0Jvjr>Ig0kBCD88vY2Y?R)BIWp!_k_~TvFBo%Z`s6GL%1X5{v zX^v80fXJ^@1~`Ju<8tne^(<959~|*!=Wm*G$mH7IMH^1Q_9ETh+GZI6n!jS0!n#UF zc~%~m35oloc3qyj4?5aX7HnmxTcBI-g(B{FvbFNTQ|oS5;wmC+-H(-($BMzzRDPs8 zF0DC)k##ZfkR@o$sCF1ng>}1A;EV z`IW0TfGT4uSy&-`;UJ|wzQoCTva5r-V!XCkuhtwO6RjYLsC}<#{X*ZE_>@1#X7u$l zdJd(I64KU7T3lKRn7ux<+f%zf?Rh4TH(GGFX662ZoxOeEAr`9m@=V-#K|GOyT{BBb z$T!_G(m1|2*L-7hv)k7MRDMuGeq$burD-Y^v_rcgbPXJDkr_X)miC{R0v5brWo1xW zOooBJlMrvs0EU^G!WLhuEF8x%9G2kwLGDR$WZS@d4Jo#%^MF2@VE#NF4~X1mWp1rQ zSgl2d&H40$2+iasl;-W3AFZ%F-0f?x7^$_Exc2}ZpP&>~bSJTLS&*wH`GBSx+EvyY zwLtR??LL~weNuf~^NK@(3>u-QCwwT%_WSKszToT}2eMM)d(4*mXC40PbI$YcNH>E& zs#Ev8!#0A7$n7Qy3?O(0yt(>+^K^NLixD#Xc*qGCdVDr%ZWZzQjx^U#*%YFYJCwfT ziba7VU*SY2>G&AT}uv0d%iUmzW+eK5awzlhrp@?e64 z|0ut(cYQA{_p?fOs{wDKmEl#8;)VcGSF6Hc?W4pU^_vlLiP>uRr(+g+CO{I^F#D{i z>VsZ+hJ0YqQ}}KpQ(4vHLaf+)kIpg+?KGvR0yG32AtW;^k2QWLC+U0u>?jBvi#R~5 zXwNk17rD&#RUbaP26+Jp}xuo1AGc9=%vnvK52~|Dqs1WB2?=9j|h?K14)4zIxPnZyNs_| z70u24#IHc^x$}62LccE?dCnxGk)w%AtJ*js{E8~OEklE&l07tRLDxDdk4kxoAGb;Apd z{D;Xq*ez9ij1V1E2NI~Lr z-rtf=WWTvYDw?LAnwhETjwB0Z1k~mJW{0Vs-fxEPmldD53?lf&p<`kZ;*qwPI}Ged zhOzp$C>MIC71IdL7$j}0#hAVAOo}Q9pI*Htr z8g3&X5PY|*UI6}xNdsQ027AAV`LZ_Kx~XZpAj4!f{GRr>sVVo@yLeZDPR9S6)Y{?` z0t&ssM&klqI8d*gaOuZj!nfE)UQ6G%Wag3o%B4CNS^_qMz){8$^6X3!R0mgBfBP`% z@!Z+yQqvn%PA7mW163LTQa%vNBslJ~XW33FJJK5Sr!=%uA7rHE-t{fJs+)$vnD?@y zpI?YNZ2exx`4~P&Q5~iQrA$pvAIRRJMnV(fh5CL+jqPVkb(NUc7~Khs1=3TpRp>Xx z@}ZWm9&*sa&lSL{;iRS5k+n64ycpRf?{SgJ67uZ zkBH6Xb#Qca*<=P4-PBxAvH~SzphbgPK=(%Z`o>10Bd7!GzxXR`nEZ78R#_p>N+W-< zZwG&KYA&=ucOEYR)K7t7Z>g*Dy|xUp2xcCtqu2r3T+{Qi5=7#@reRlEb=d3e2;>Zd z#lyL}XLd0dQ*gmm&0`3+Hsx2nIxhXa=0kpmh(*1Sv=EY%1+mW;n0qT^hgG~$r^liZ z2{?Lspj1n!;vW>`kQ|Ku)I> zJ&B`?&&kq~u6-|8|3jPUv=%Jcdjg^2Y7dLtSbFaYwP5(mrQ_`|wQ)w|7M`&Dijr+Z zK?%LDn^$|wc=)6&5H^H+(DWq)8d|(PwiL1+eGhY;pi;D-+vyx=F^h<2P#NsGL2{C< z#DYmjBW}#Wiz;mhO~`vvX?Z24#py?4V^7x<4>EdC4nWO`-Ac{Pc_fFx z`r4(_&S`Q+?_6+PnbGA$c~qi6`-@UkX3hC=iLmyXo78&3oY2%~eLBMz`x0SrE}C_m zH*fE*2un8;${KoVQz~SE92dA1P?KDk>^raP~peu8p;I{btni zd9(#hP>`(T(5~Z$SJx*41DEtacH4v=epB>m4QH4e@eQo)34+101lRdgYpRONJuBe& zut=Oe9lazfDyl10Pw}pygyG+j{B_-bYJobA7Ki&qemoYb2rL95t3l7m$Vk#ej)*Qv z0~nMpEO<^$rT|q=wn|M)i!!+_epBRLh+Zy5jU#x7PN<`nl&uS9N%k_8 zQQzR@)v1Y_kVFeq_B_>#pGKGaS(kV>Px~!kFAd7FCLCPLEY^1I;~g#S10M{;ECV4p zpmA%YJPjwl0Mu5LIf+1b&40RM3_vTw?P%4lHrlKH9SEdH>Yt95|K*_nsUWJu=^FXJ ztuD6)e#*Vg)re8dBV#7gL7~{(V*hQ1xrG!M%wX`OfIJdBziQ3N`p?57{^{ho>ZU>N zQiuIdHx20j!9D3Jd$@Z2*lnla8X#C;^2oPyR}DxHEQbPKTUn0DLb9*&(UvV_H|W|S z{ZAi`-=8(ZlhIA`KxU>~4RsWdZ`A&=HmCc22l#=BUb`|fVq;?+X;ap$2J#x>tG*+( zR`1x1)4ATBP)62&@08#MB;~6nrt|t1^}|*f8c#P!$ki=@C0FDP)cBTlU9jQ*&Z&K4 z9kz$BIK>saaaD=FdIsp}a`~rW>i@W;|EKl;fBWT@OVa+~s(kj?p!-*gp`xH6Un=+F G-G2dumwj~r diff --git a/docs/virtual_machine.md b/docs/virtual_machine.md index 2f029b296..579a40c93 100644 --- a/docs/virtual_machine.md +++ b/docs/virtual_machine.md @@ -1,38 +1,41 @@ Testrun logo -## Virtual Machine +# Run on a virtual machine -This guide will provide steps to use Testrun within a virtual machine in virtual Box (VMWare and other providers have not yet been tested). You should use this guide alongside the [Get Started guide](/docs/get_started.md) - only differences will be outlined in this guide. +This page provides steps to use Testrun within a virtual machine in VirtualBox. VMWare and other providers haven't been tested yet. You should use these instructions alongside the [Get started guide](/docs/get_started.md). -## Prerequisites +# Prerequisites -### Hardware +## Hardware -Before starting with Testrun, ensure you have the following hardware: -- PC running any OS that supports Virtual Box -- 2x USB Ethernet adapter (built in ethernet connections are not supported) -- Internet connection +Before you start with Testrun, ensure you have the following hardware: -### Software +- PC running any OS that supports VirtualBox +- 2x USB Ethernet adapter (built-in Ethernet connections aren't supported) +- Internet connection -Ensure the following software is installed on the host PC: - - Virtual Box +## Software -Ensure the following software is installed on your virtual machine: -- Ubuntu LTS (22.04 or 20.04) -- Docker - installation guide: [https://docs.docker.com/engine/install/ubuntu/#install-using-the-repository](https://docs.docker.com/engine/install/ubuntu/#install-using-the-repository) +Ensure you have VirtualBox installed on the host PC. Then, install the following software on your virtual machine: -## Installation +- Ubuntu LTS (22.04 or 20.04) +- Docker + - Refer to the [installation guide](https://docs.docker.com/engine/install/ubuntu/#install-using-the-repository) as needed. -In addition to the install steps provided in the Get Started guide, the default user must be added to the sudo group. -1. Open a terminal and run ```sudo su``` to login as root (you will be prompted for your password). -2. Add the default user to the sudo group by running ```adduser {username} sudo```. -3. Restart the virtual machine. -4. Continue the installation as per the Get Started guide. +# Installation -## Start Testrun +As part of installation, you must add the default user to the sudo group: + +1. Open a terminal and run `sudo su` to log in as root. +1. Enter your password when prompted. +1. Add the default user to the sudo group by running `adduser {username} sudo`. +1. Restart the virtual machine. +1. Follow the steps in the [Get started guide](/docs/get_started.md) to complete the installation. + +# Start Testrun + +Follow these steps to start Testrun. Keep in mind that attaching USB Ethernet adapters is different when working in a virtual machine. -Attaching USB ethernet adapters is different when working in a Virtual Machine. 1. Ensure the 2x adapters are attached to the host PC. -2. With the virtual machine running, right click the USB icon in the bottom right of the window. -3. Select the 2x ethernet adapter names and check that these two adapters have now appeared in the virtual machine. \ No newline at end of file +1. With the virtual machine running, right-click the **USB** icon in the bottom-right of the window. +1. Select the 2x Ethernet adapter names. The two adapters should now appear in the virtual machine. \ No newline at end of file diff --git a/framework/python/src/api/api.py b/framework/python/src/api/api.py index e8e87465d..0d633987c 100644 --- a/framework/python/src/api/api.py +++ b/framework/python/src/api/api.py @@ -26,8 +26,10 @@ import uvicorn from urllib.parse import urlparse -from common import logger, tasks +from core import tasks +from common import logger from common.device import Device +from common.statuses import TestrunStatus LOGGER = logger.get_logger("api") @@ -35,8 +37,16 @@ DEVICE_MANUFACTURER_KEY = "manufacturer" DEVICE_MODEL_KEY = "model" DEVICE_TEST_MODULES_KEY = "test_modules" +DEVICE_TEST_PACK_KEY = "test_pack" +DEVICE_TYPE_KEY = "type" +DEVICE_TECH_KEY = "technology" +DEVICE_ADDITIONAL_INFO_KEY = "additional_info" + DEVICES_PATH = "local/devices" -DEFAULT_DEVICE_INTF = "enx123456789123" + +RESOURCES_PATH = "resources" +DEVICE_FOLDER_PATH = "devices" +DEVICE_QUESTIONS_FILE_NAME = "device_profile.json" LATEST_RELEASE_CHECK = ("https://api.github.com/repos/google/" + "testrun/releases/latest") @@ -45,32 +55,45 @@ class Api: """Provide REST endpoints to manage Testrun""" - def __init__(self, test_run): + def __init__(self, testrun): - self._test_run = test_run + self._testrun = testrun self._name = "Testrun API" self._router = APIRouter() - self._session = self._test_run.get_session() + # Load static JSON resources + device_resources = os.path.join(self._testrun.get_root_dir(), + RESOURCES_PATH, + DEVICE_FOLDER_PATH) + + # Load device profile questions + self._device_profile = self._load_json(device_resources, + DEVICE_QUESTIONS_FILE_NAME) + # Fetch Testrun session + self._session = self._testrun.get_session() + + # System endpoints self._router.add_api_route("/system/interfaces", self.get_sys_interfaces) self._router.add_api_route("/system/config", self.post_sys_config, methods=["POST"]) self._router.add_api_route("/system/config", self.get_sys_config) self._router.add_api_route("/system/start", - self.start_test_run, + self.start_testrun, methods=["POST"]) self._router.add_api_route("/system/stop", - self.stop_test_run, + self.stop_testrun, methods=["POST"]) self._router.add_api_route("/system/status", self.get_status) self._router.add_api_route("/system/shutdown", self.shutdown, methods=["POST"]) - self._router.add_api_route("/system/version", self.get_version) + self._router.add_api_route("/system/modules", self.get_test_modules) + self._router.add_api_route("/system/testpacks", self.get_test_packs) + # Report endpoints self._router.add_api_route("/reports", self.get_reports) self._router.add_api_route("/report", self.delete_report, @@ -81,6 +104,7 @@ def __init__(self, test_run): self.get_results, methods=["POST"]) + # Device endpoints self._router.add_api_route("/devices", self.get_devices) self._router.add_api_route("/device", self.delete_device, @@ -89,10 +113,9 @@ def __init__(self, test_run): self._router.add_api_route("/device/edit", self.edit_device, methods=["POST"]) + self._router.add_api_route("/devices/format", self.get_devices_profile) - # Load modules - self._router.add_api_route("/system/modules", self.get_test_modules) - + # Certificate endpoints self._router.add_api_route("/system/config/certs", self.get_certs) self._router.add_api_route("/system/config/certs", self.upload_cert, @@ -115,10 +138,15 @@ def __init__(self, test_run): origins = ["*"] # Scheduler for background periodic tasks - self._scheduler = tasks.PeriodicTasks(self._test_run) + self._scheduler = tasks.PeriodicTasks(self._testrun) + # Init FastAPI self._app = FastAPI(lifespan=self._scheduler.start) + + # Attach router to FastAPI self._app.include_router(self._router) + + # Attach CORS middleware self._app.add_middleware( CORSMiddleware, allow_origins=origins, @@ -127,10 +155,27 @@ def __init__(self, test_run): allow_headers=["*"], ) + # Use separate thread for API self._api_thread = threading.Thread(target=self._start, name="Testrun API", daemon=True) + def _load_json(self, directory, file_name): + """Utility method to load json files' """ + # Construct the base path relative to the main folder + root_dir = self._testrun.get_root_dir() + + # Construct the full file path + file_path = os.path.join(root_dir, directory, file_name) + + # Open the file in read mode + with open(file_path, "r", encoding="utf-8") as file: + # Return the file content + return json.load(file) + + def _get_testrun(self): + return self._testrun + def start(self): LOGGER.info("Starting API") self._api_thread.start() @@ -191,9 +236,12 @@ async def get_sys_config(self): return self._session.get_config() async def get_devices(self): - return self._session.get_device_repository() + devices = [] + for device in self._session.get_device_repository(): + devices.append(device.to_dict()) + return devices - async def start_test_run(self, request: Request, response: Response): + async def start_testrun(self, request: Request, response: Response): LOGGER.debug("Received start command") @@ -214,9 +262,22 @@ async def start_test_run(self, request: Request, response: Response): device = self._session.get_device(body_json["device"]["mac_addr"]) + # Check if requested device is known in the device repository + 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") + + # Check if device is fully configured + if device.status != "Valid": + response.status_code = status.HTTP_400_BAD_REQUEST + return self._generate_msg(False, "Device configuration is not complete") + # Check Testrun is not already running - if self._test_run.get_session().get_status() in [ - "In Progress", "Waiting for Device", "Monitoring" + if self._testrun.get_session().get_status() in [ + TestrunStatus.IN_PROGRESS, + TestrunStatus.WAITING_FOR_DEVICE, + TestrunStatus.MONITORING ]: LOGGER.debug("Testrun is already running. Cannot start another instance") response.status_code = status.HTTP_409_CONFLICT @@ -224,23 +285,17 @@ async def start_test_run(self, request: Request, response: Response): False, "Testrun cannot be started " + "whilst a test is running on another device") - # Check if requested device is known in the device repository - 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") - device.firmware = body_json["device"]["firmware"] # Check if config has been updated (device interface not default) - if (self._test_run.get_session().get_device_interface() == - DEFAULT_DEVICE_INTF): + if (self._testrun.get_session().get_device_interface() == + ""): response.status_code = status.HTTP_400_BAD_REQUEST return self._generate_msg( False, "Testrun configuration has not yet " + "been completed.") # Check Testrun is able to start - if self._test_run.get_net_orc().check_config() is False: + if self._testrun.get_net_orc().check_config() is False: response.status_code = status.HTTP_500_INTERNAL_SERVER_ERROR return self._generate_msg( False, "Configured interfaces are not " + @@ -260,12 +315,12 @@ async def start_test_run(self, request: Request, response: Response): f"{device.manufacturer} {device.model} with " + f"MAC address {device.mac_addr}") - thread = threading.Thread(target=self._start_test_run, name="Testrun") + thread = threading.Thread(target=self._start_testrun, name="Testrun") thread.start() - self._test_run.get_session().set_target_device(device) + self._testrun.get_session().set_target_device(device) - return self._test_run.get_session().to_json() + return self._testrun.get_session().to_json() def _generate_msg(self, success, message): msg_type = "success" @@ -273,24 +328,26 @@ def _generate_msg(self, success, message): msg_type = "error" return json.loads('{"' + msg_type + '": "' + message + '"}') - def _start_test_run(self): - self._test_run.start() + def _start_testrun(self): + self._testrun.start() - async def stop_test_run(self, response: Response): + async def stop_testrun(self, response: Response): LOGGER.debug("Received stop command") # Check if Testrun is running - if (self._test_run.get_session().get_status() - not in ["In Progress", "Waiting for Device", "Monitoring"]): + if (self._testrun.get_session().get_status() + not in [TestrunStatus.IN_PROGRESS, + TestrunStatus.WAITING_FOR_DEVICE, + TestrunStatus.MONITORING]): response.status_code = 404 return self._generate_msg(False, "Testrun is not currently running") - self._test_run.stop() + self._testrun.stop() return self._generate_msg(True, "Testrun stopped") async def get_status(self): - return self._test_run.get_session().to_json() + return self._testrun.get_session().to_json() def shutdown(self, response: Response): @@ -298,20 +355,24 @@ def shutdown(self, response: Response): # Check that Testrun is not currently running if (self._session.get_status() - not in ["Cancelled", "Compliant", "Non-Compliant", "Idle"]): + not in [TestrunStatus.CANCELLED, + TestrunStatus.COMPLIANT, + TestrunStatus.NON_COMPLIANT, + TestrunStatus.IDLE + ]): LOGGER.debug("Unable to shutdown Testrun as Testrun is in progress") response.status_code = 400 return self._generate_msg( False, "Unable to shutdown. A test is currently in progress.") - self._test_run.shutdown() + self._testrun.shutdown() os.kill(os.getpid(), signal.SIGTERM) async def get_version(self, response: Response): # Add defaults json_response = {} - json_response["installed_version"] = "v" + self._test_run.get_version() + json_response["installed_version"] = "v" + self._testrun.get_version() json_response["update_available"] = False json_response["latest_version"] = None json_response["latest_version_url"] = ( @@ -383,7 +444,7 @@ async def delete_report(self, request: Request, response: Response): if len(body_raw) == 0: response.status_code = 400 - return self._generate_msg(False, "Invalid request received") + return self._generate_msg(False, "Invalid request received, missing body") try: body_json = json.loads(body_raw) @@ -395,12 +456,18 @@ async def delete_report(self, request: Request, response: Response): if "mac_addr" not in body_json or "timestamp" not in body_json: response.status_code = 400 - return self._generate_msg(False, "Invalid request received") + return self._generate_msg(False, "Missing mac address or timestamp") mac_addr = body_json.get("mac_addr").lower() timestamp = body_json.get("timestamp") - parsed_timestamp = datetime.strptime(timestamp, "%Y-%m-%d %H:%M:%S") - timestamp_formatted = parsed_timestamp.strftime("%Y-%m-%dT%H:%M:%S") + + try: + parsed_timestamp = datetime.strptime(timestamp, "%Y-%m-%d %H:%M:%S") + timestamp_formatted = parsed_timestamp.strftime("%Y-%m-%dT%H:%M:%S") + + except ValueError: + response.status_code = 400 + return self._generate_msg(False, "Incorrect timestamp format") # Get device from MAC address device = self._session.get_device(mac_addr) @@ -409,7 +476,15 @@ async def delete_report(self, request: Request, response: Response): response.status_code = 404 return self._generate_msg(False, "Could not find device") - if self._test_run.delete_report(device, timestamp_formatted): + # Assign the reports folder path from testrun + reports_folder = self._testrun.get_reports_folder(device) + + # Check if reports folder exists + if not os.path.exists(reports_folder): + response.status_code = 404 + return self._generate_msg(False, "Report not found") + + if self._testrun.delete_report(device, timestamp_formatted): return self._generate_msg(True, "Deleted report") response.status_code = 500 @@ -433,7 +508,7 @@ async def delete_device(self, request: Request, response: Response): mac_addr = device_json.get("mac_addr").lower() # Check that device exists - device = self._test_run.get_session().get_device(mac_addr) + device = self._testrun.get_session().get_device(mac_addr) if device is None: response.status_code = 404 @@ -442,13 +517,16 @@ async def delete_device(self, request: Request, response: Response): # Check that Testrun is not currently running against this device if (self._session.get_target_device() == device and self._session.get_status() - not in ["Cancelled", "Compliant", "Non-Compliant"]): + not in [TestrunStatus.CANCELLED, + TestrunStatus.COMPLIANT, + TestrunStatus.NON_COMPLIANT + ]): response.status_code = 403 return self._generate_msg( False, "Cannot delete this device whilst " + "it is being tested") # Delete device - self._test_run.delete_device(device) + self._testrun.delete_device(device) # Return success response response.status_code = 200 @@ -488,7 +566,7 @@ async def save_device(self, request: Request, response: Response): ) # Check if device folder exists - device_folder = os.path.join(self._test_run.get_root_dir(), + device_folder = os.path.join(self._testrun.get_root_dir(), DEVICES_PATH, device_json.get(DEVICE_MANUFACTURER_KEY) + " " + @@ -507,10 +585,15 @@ async def save_device(self, request: Request, response: Response): 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_pack = device_json.get(DEVICE_TEST_PACK_KEY) + device.type = device_json.get(DEVICE_TYPE_KEY) + device.technology = device_json.get(DEVICE_TECH_KEY) + device.additional_info = device_json.get(DEVICE_ADDITIONAL_INFO_KEY) + device.device_folder = device.manufacturer + " " + device.model device.test_modules = device_json.get(DEVICE_TEST_MODULES_KEY) - self._test_run.create_device(device) + self._testrun.create_device(device) response.status_code = status.HTTP_201_CREATED else: @@ -553,14 +636,17 @@ async def edit_device(self, request: Request, response: Response): 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") + 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"]): + not in [TestrunStatus.CANCELLED, + TestrunStatus.COMPLIANT, + TestrunStatus.NON_COMPLIANT + ]): response.status_code = 403 return self._generate_msg( - False, "Cannot edit this device whilst " + "it is being tested") + 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( @@ -570,15 +656,22 @@ async def edit_device(self, request: Request, response: Response): != 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") + 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_pack = device_json.get(DEVICE_TEST_PACK_KEY) + device.type = device_json.get(DEVICE_TYPE_KEY) + device.technology = device_json.get(DEVICE_TECH_KEY) + device.additional_info = device_json.get(DEVICE_ADDITIONAL_INFO_KEY) device.test_modules = device_json.get(DEVICE_TEST_MODULES_KEY) - self._test_run.save_device(device, device_json) + # Update device status to valid now that configuration is complete + device.status = "Valid" + + self._testrun.save_device(device) response.status_code = status.HTTP_200_OK return device.to_config_json() @@ -591,6 +684,12 @@ async def edit_device(self, request: Request, response: Response): async def get_report(self, response: Response, device_name, timestamp): device = self._session.get_device_by_name(device_name) + # If the device not found + if device is None: + LOGGER.info("Device not found, returning 404") + response.status_code = 404 + return self._generate_msg(False, "Device not found") + # 1.3 file path file_path = os.path.join( DEVICES_PATH, @@ -644,47 +743,77 @@ async def get_results(self, request: Request, response: Response, device_name, return self._generate_msg(False, "A device with that name could not be found") - file_path = self._get_test_run().get_test_orc().zip_results( + # Check if report exists (1.3 file path) + report_file_path = os.path.join( + DEVICES_PATH, + device_name, + "reports", + timestamp,"test", + device.mac_addr.replace(":","")) + + if not os.path.isdir(report_file_path): + # pre 1.3 file path + report_file_path = os.path.join(DEVICES_PATH, device_name, "reports", + timestamp) + + if not os.path.isdir(report_file_path): + LOGGER.info("Report could not be found, returning 404") + response.status_code = 404 + return self._generate_msg(False, "Report could not be found") + + zip_file_path = self._get_testrun().get_test_orc().zip_results( device, timestamp, profile) - if file_path is None: + if zip_file_path is None: response.status_code = status.HTTP_500_INTERNAL_SERVER_ERROR return self._generate_msg( False, "An error occurred whilst archiving test results") - if os.path.isfile(file_path): - return FileResponse(file_path) + if os.path.isfile(zip_file_path): + return FileResponse(zip_file_path) else: LOGGER.info("Test results could not be found, returning 404") response.status_code = 404 return self._generate_msg(False, "Test results could not be found") + async def get_devices_profile(self): + """Device profile questions""" + return self._device_profile + def _validate_device_json(self, json_obj): # Check all required properties are present - if not (DEVICE_MAC_ADDR_KEY in json_obj and DEVICE_MANUFACTURER_KEY - in json_obj and DEVICE_MODEL_KEY in json_obj): - return False + for string in [ + DEVICE_MAC_ADDR_KEY, + DEVICE_MANUFACTURER_KEY, + DEVICE_MODEL_KEY, + DEVICE_TYPE_KEY, + DEVICE_TECH_KEY, + DEVICE_ADDITIONAL_INFO_KEY + ]: + if string not in json_obj: + LOGGER.error(f"Missing required key {string} in device configuration") + return False # Check length of strings if len(json_obj.get(DEVICE_MANUFACTURER_KEY)) > 28 or len( json_obj.get(DEVICE_MODEL_KEY)) > 28: + LOGGER.error("Device manufacturer or model are longer than 28 characters") return False disallowed_chars = ["/", "\\", "\'", "\"", ";"] for char in json_obj.get(DEVICE_MANUFACTURER_KEY): if char in disallowed_chars: + LOGGER.error("Disallowed character in device manufacturer") return False for char in json_obj.get(DEVICE_MODEL_KEY): if char in disallowed_chars: + LOGGER.error("Disallowed character in device model") return False return True - def _get_test_run(self): - return self._test_run - # Profiles def get_profiles_format(self, response: Response): @@ -706,6 +835,12 @@ async def update_profile(self, request: Request, response: Response): LOGGER.debug("Received profile update request") + # Check if the profiles format was loaded correctly + if self.get_session().get_profiles_format() is None: + response.status_code = status.HTTP_501_NOT_IMPLEMENTED + return self._generate_msg(False, + "Risk profiles are not available right now") + try: req_raw = (await request.body()).decode("UTF-8") req_json = json.loads(req_raw) @@ -726,6 +861,7 @@ async def update_profile(self, request: Request, response: Response): profile = self.get_session().get_profile(profile_name) if profile is None: + # Create new profile profile = self.get_session().update_profile(req_json) @@ -886,7 +1022,13 @@ async def delete_cert(self, request: Request, response: Response): def get_test_modules(self): modules = [] - for module in self._test_run.get_test_orc().get_test_modules(): + for module in self._testrun.get_test_orc().get_test_modules(): if module.enabled and module.enable_container: modules.append(module.display_name) return modules + + def get_test_packs(self): + test_packs: list[str] = [] + for test_pack in self._testrun.get_test_orc().get_test_packs(): + test_packs.append(test_pack.name) + return test_packs diff --git a/framework/python/src/common/device.py b/framework/python/src/common/device.py index c6a289d2c..d90720d90 100644 --- a/framework/python/src/common/device.py +++ b/framework/python/src/common/device.py @@ -14,7 +14,7 @@ """Track device object information.""" -from typing import Dict, List +from typing import List, Dict from dataclasses import dataclass, field from common.testreport import TestReport from datetime import datetime @@ -23,17 +23,21 @@ class Device(): """Represents a physical device and it's configuration.""" + status: str = 'Valid' folder_url: str = None mac_addr: str = None manufacturer: str = None model: str = None + type: str = None + technology: str = None + test_pack: str = 'Device Qualification' + additional_info: List[dict] = field(default_factory=list) test_modules: Dict = field(default_factory=dict) ip_addr: str = None firmware: str = None device_folder: str = None reports: List[TestReport] = field(default_factory=list) max_device_reports: int = None - reports: List[TestReport] = field(default_factory=list) def add_report(self, report): self.reports.append(report) @@ -54,11 +58,18 @@ def to_dict(self): """Returns the device as a python dictionary. This is used for the system status API endpoint and in the report.""" device_json = {} + device_json['status'] = self.status device_json['mac_addr'] = self.mac_addr device_json['manufacturer'] = self.manufacturer device_json['model'] = self.model + device_json['type'] = self.type + device_json['technology'] = self.technology + device_json['test_pack'] = self.test_pack + device_json['additional_info'] = self.additional_info + if self.firmware is not None: device_json['firmware'] = self.firmware + device_json['test_modules'] = self.test_modules return device_json @@ -69,5 +80,9 @@ def to_config_json(self): device_json['mac_addr'] = self.mac_addr device_json['manufacturer'] = self.manufacturer device_json['model'] = self.model + device_json['type'] = self.type + device_json['technology'] = self.technology + device_json['test_pack'] = self.test_pack device_json['test_modules'] = self.test_modules + device_json['additional_info'] = self.additional_info return device_json diff --git a/framework/python/src/common/docker_util.py b/framework/python/src/common/docker_util.py new file mode 100644 index 000000000..06b030419 --- /dev/null +++ b/framework/python/src/common/docker_util.py @@ -0,0 +1,35 @@ +# Copyright 2023 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +"""Utility for common docker methods""" +import docker + +def create_private_net(network_name): + client = docker.from_env() + try: + network = client.networks.get(network_name) + network.remove() + except docker.errors.NotFound: + pass + + # TODO: These should be made into variables + ipam_pool = docker.types.IPAMPool(subnet='100.100.0.0/16', + iprange='100.100.100.0/24') + + ipam_config = docker.types.IPAMConfig(pool_configs=[ipam_pool]) + + client.networks.create(network_name, + ipam=ipam_config, + internal=True, + check_duplicate=True, + driver='macvlan') diff --git a/framework/python/src/common/mqtt.py b/framework/python/src/common/mqtt.py index c58d24d3f..fc0458e7d 100644 --- a/framework/python/src/common/mqtt.py +++ b/framework/python/src/common/mqtt.py @@ -28,26 +28,25 @@ def __init__(self, message: str) -> None: class MQTT: - """ MQTT client class - """ + """ MQTT client class""" def __init__(self) -> None: self._host = WEBSOCKETS_HOST self._client = mqtt_client.Client(mqtt_client.CallbackAPIVersion.VERSION2) - LOGGER.setLevel(logger.logging.INFO) self._client.enable_logger(LOGGER) + LOGGER.setLevel(logger.logging.INFO) def _connect(self): - """Establish connection to Mosquitto server - - Raises: - MQTTException: Raises exception on connection error - """ + """Establish connection to MQTT broker""" if not self._client.is_connected(): try: self._client.connect(self._host, WEBSOCKETS_PORT, 60) - except (ValueError, ConnectionRefusedError) as e: - LOGGER.error("Can't connect to host") - raise MQTTException("Connection to the Mosquitto server failed") from e + except (ValueError, ConnectionRefusedError): + LOGGER.error("Cannot connect to MQTT broker") + + def disconnect(self): + """Disconnect the local client from the MQTT broker""" + if self._client.is_connected(): + self._client.disconnect() def send_message(self, topic: str, message: t.Union[str, dict]) -> None: """Send message to specific topic diff --git a/framework/python/src/common/risk_profile.py b/framework/python/src/common/risk_profile.py index f50dffdde..eeae44db7 100644 --- a/framework/python/src/common/risk_profile.py +++ b/framework/python/src/common/risk_profile.py @@ -20,10 +20,15 @@ from common import logger import json import os +from jinja2 import Template +from copy import deepcopy PROFILES_PATH = 'local/risk_profiles' LOGGER = logger.get_logger('risk_profile') RESOURCES_DIR = 'resources/report' +TEMPLATE_FILE = 'risk_report_template.html' +TEMPLATE_STYLES = 'risk_report_styles.css' +DEVICE_FORMAT_PATH = 'resources/devices/device_profile.json' # Locate parent directory current_dir = os.path.dirname(os.path.realpath(__file__)) @@ -43,6 +48,34 @@ class RiskProfile(): def __init__(self, profile_json=None, profile_format=None): + # Jinja template + with open(os.path.join(report_resource_dir, TEMPLATE_FILE), + 'r', + encoding='UTF-8' + ) as template_file: + self._template = Template(template_file.read()) + with open(os.path.join(report_resource_dir, + TEMPLATE_STYLES), + 'r', + encoding='UTF-8' + ) as style_file: + self._template_styles = style_file.read() + + # Device profile format + self._device_format = [] + try: + with open(os.path.join(root_dir, DEVICE_FORMAT_PATH), + 'r', + encoding='utf-8') as device_format_file: + device_format_json = json.load(device_format_file) + for step in device_format_json: + self._device_format.extend(step['questions']) + except (IOError, ValueError) as e: + LOGGER.error( + 'An error occurred whilst loading the device profile format') + LOGGER.debug(e) + + if profile_json is None or profile_format is None: return @@ -92,6 +125,7 @@ def update(self, profile_json, profile_format): self.risk = new_profile.risk def get_file_path(self): + """Returns the file path for the current risk profile json""" return os.path.join(PROFILES_PATH, self.name + '.json') @@ -108,6 +142,7 @@ def _validate(self, profile_json, profile_format): self.status = 'Draft' def update_risk(self, profile_format): + """Update the calculated risk for the risk profile""" if self.status == 'Valid': @@ -176,6 +211,15 @@ def update_risk(self, profile_format): self.risk = risk + def _update_risk_by_device(self): + risk = self.risk + if self._device and self.status == 'Valid': + for question in self._device.additional_info: + if 'risk' in question and question['risk'] == 'High': + risk = 'High' + break + return risk + def _get_format_question(self, question: str, profile_format: dict): for q in profile_format: @@ -281,6 +325,7 @@ def _expired(self): return today > expiry_date def to_json(self, pretty=False): + """Returns the current risk profile in JSON format""" json_dict = { 'name': self.name, 'version': self.version, @@ -293,386 +338,88 @@ def to_json(self, pretty=False): return json.dumps(json_dict, indent=indent) def to_html(self, device): - - self._device = device - - return f''' - - - {self._generate_head()} - -

- - - ''' - - def _generate_head(self): - - return f''' - - - - Risk Assessment - - - ''' - - def _generate_header(self): + """Returns the current risk profile in HTML format""" + + high_risk_message = '''The device has been assessed to be high + risk due to the nature of the answers provided + about the device functionality.''' + limited_risk_message = '''The device has been assessed to be limited risk + due to the nature of the answers provided about + the device functionality.''' with open(test_run_img_file, 'rb') as f: - tr_img_b64 = base64.b64encode(f.read()).decode('utf-8') - header = f''' -
-

Risk assessment

-

- {self._device.manufacturer} - {self._device.model} -

''' - header += f'''Testrun -
- ''' - return header - - def _generate_risk_banner(self): - return f''' -
-
-

{'high' if self.risk == 'High' else 'limited'} Risk

-
-
- { - 'The device has been assessed to be high risk due to the nature of the answers provided about the device functionality.' - if self.risk == 'High' else - 'The device has been assessed to be limited risk due to the nature of the answers provided about the device functionality.' - } -
-
- ''' - - def _generate_risk_questions(self): - + logo_img_b64 = base64.b64encode(f.read()).decode('utf-8') + + self._device = self._format_device_profile(device) + pages = self._generate_report_pages() + return self._template.render( + styles=self._template_styles, + manufacturer=self._device.manufacturer, + model=self._device.model, + logo=logo_img_b64, + risk=self._update_risk_by_device(), + high_risk_message=high_risk_message, + limited_risk_message=limited_risk_message, + pages=pages, + total_pages=len(pages), + version=self.version, + created_at=self.created.strftime('%d.%m.%Y') + ) + + def _generate_report_pages(self): max_page_height = 350 - content = '' - - content += self._generate_table_head() - - index = 1 height = 0 + pages = [] + current_page = [] + index = 1 + + questions = deepcopy(self._device.additional_info) + questions.extend(self.questions) - for question in self.questions: + for question in questions: if height > max_page_height: - content += self._generate_new_page() + pages.append(current_page) height = 0 + current_page = [] - content += f''' -
-
{index}.
-
{question['question']}
-
''' + page_item = deepcopy(question) - # String answers (one line) - if isinstance(question['answer'], str): - content += question['answer'] + if isinstance(page_item['answer'], str): - if len(question['answer']) > 400: + if len(page_item['answer']) > 400: height += 160 - elif len(question['answer']) > 300: + elif len(page_item['answer']) > 300: height += 140 - elif len(question['answer']) > 200: + elif len(page_item['answer']) > 200: height += 120 - elif len(question['answer']) > 100: + elif len(page_item['answer']) > 100: height += 70 else: height += 53 # Select multiple answers - elif isinstance(question['answer'], list): - content += '
    ' + elif isinstance(page_item['answer'], list): + text_answers = [] options = self._get_format_question( - question=question['question'], + question=page_item['question'], profile_format=self._profile_format)['options'] - for answer_index in question['answer']: - height += 40 - content += f''' -
  • - {self._get_option_from_index(options, answer_index)['text']}
  • ''' - - content += '
' - - # Question risk label - if 'risk' in question: - if question['risk'] == 'High': - content += '
HIGH RISK
' - elif question['risk'] == 'Limited': - content += '''
- LIMITED RISK
''' - - content += '''
''' + options_dict = dict(enumerate(options)) + for answer_index in page_item['answer']: + height += 40 + text_answers.append(options_dict[answer_index]['text']) + page_item['answer'] = text_answers + page_item['index'] = index index += 1 + current_page.append(page_item) + pages.append(current_page) - return content - - def _generate_table_head(self): - return ''' -
-
-
Question
-
Answer
-
''' - - def _generate_new_page(self): - - # End the current table - content = ''' -
''' - - # End the page - content += self._generate_footer() - content += '

J*V<(k-r@DT^pndo5E0+!^Fos-0~B+VA` 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 delta 142395 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

' - - # Start a new page - content += ''' -
- ''' - - content += self._generate_header() - - content += self._generate_table_head() - - return content - - def _generate_footer(self): - footer = f''' - - ''' - return footer - - def _generate_css(self): - return ''' - /* Set some global variables */ - :root { - --header-height: .75in; - --header-width: 8.5in; - --header-pos-x: 0in; - --header-pos-y: 0in; - --page-width: 8.5in; - } - - @font-face { - font-family: 'Google Sans'; - font-style: normal; - src: url(https://fonts.gstatic.com/s/googlesans/v58/4Ua_rENHsxJlGDuGo1OIlJfC6l_24rlCK1Yo_Iqcsih3SAyH6cAwhX9RFD48TE63OOYKtrwEIJllpyk.woff2) format('woff2'); - unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; - } - - @font-face { - font-family: 'Roboto Mono'; - font-style: normal; - src: url(https://fonts.googleapis.com/css2?family=Roboto+Mono:ital,wght@0,100..700;1,100..700&display=swap) format('woff2'); - unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; - } - - /* Define some common body formatting*/ - body { - font-family: 'Google Sans', sans-serif; - margin: 0px; - padding: 0px; - } - - /* Sets proper page size during print to pdf for weasyprint */ - @page { - size: Letter; - width: 8.5in; - height: 11in; - } - - .page { - position: relative; - margin: 0 20px; - width: 8.5in; - height: 11in; - } - - /* Define the header related css elements*/ - .header { - position: relative; - } - - h1 { - margin: 0 0 8px 0; - font-size: 20px; - font-weight: 400; - } - - h2 { - margin: 0px; - font-size: 48px; - font-weight: 700; - } - - h3 { - font-size: 24px; - margin-bottom: 10px; - margin-top: 15px; - } - - h4 { - font-size: 12px; - font-weight: 500; - color: #5F6368; - margin-bottom: 0; - margin-top: 0; - } - - /* CSS for the footer */ - .footer { - position: absolute; - height: 30px; - width: 8.5in; - bottom: 0in; - border-top: 1px solid #D3D3D3; - } - - .footer-label { - color: #3C4043; - position: absolute; - top: 5px; - font-size: 12px; - } - - @media print { - @page { - size: Letter; - width: 8.5in; - height: 11in; - } - } - - .risk-banner { - min-height: 120px; - padding: 5px 40px 0 40px; - margin-top: 30px; - } - - .risk-banner-limited { - background-color: #E4F7FB; - color: #007B83; - } - - .risk-banner-high { - background-color: #FCE8E6; - color: #C5221F; - } - - .risk-banner-title { - text-transform: uppercase; - font-weight: bold; - } - - .risk-table { - width: 100%; - margin-top: 40px; - text-align: left; - color: #3C4043; - font-size: 14px; - } - - .risk-table-head { - margin-bottom: 15px; - } - - .risk-table-head-question { - display: inline-block; - margin-left: 70px; - font-weight: bold; - } - - .risk-table-head-answer { - display: inline-block; - margin-left: 325px; - font-weight: bold; - } - - .risk-table-row { - margin-bottom: 8px; - background-color: #F8F9FA; - display: flex; - align-items: stretch; - overflow: hidden; - } - - .risk-question-no { - padding: 15px 20px; - width: 10px; - display: inline-block; - vertical-align: top; - position: relative; - } - - .risk-question { - padding: 15px 20px; - display: inline-block; - width: 350px; - vertical-align: top; - position: relative; - height: 100%; - } - - .risk-answer { - background-color: #E8F0FE; - padding: 15px 20px; - display: inline-block; - width: 340px; - position: relative; - height: 100%; - } - - ul { - margin-top: 0; - } - - .risk-label{ - position: absolute; - top: 0px; - right: 0px; - width: 52px; - height: 16px; - font-family: 'Google Sans', sans-serif; - font-size: 8px; - font-weight: 500; - line-height: 16px; - letter-spacing: 0.64px; - text-align: center; - font-weight: bold; - border-radius: 3px; - } - - .risk-label-high{ - background-color: #FCE8E6; - color: #C5221F; - } - - .risk-label-limited{ - width: 65px; - background-color:#E4F7FB; - color: #007B83; - } - ''' + return pages def to_pdf(self, device): + """Returns the current risk profile in PDF format""" # Resolve the data as html first html = self.to_html(device) @@ -681,3 +428,21 @@ def to_pdf(self, device): pdf_bytes = BytesIO() HTML(string=html).write_pdf(pdf_bytes) return pdf_bytes + + # Adding risks to device profile questions + def _format_device_profile(self, device): + device_copy = deepcopy(device) + risk_map = { + question['question']: { + option['text']: option.get('risk', None) + for option in question['options'] if 'risk' in option + } + for question in self._device_format + } + for question in device_copy.additional_info: + risk = risk_map.get( + question['question'], {} + ).get(question['answer'], None) + if risk: + question['risk'] = risk + return device_copy diff --git a/framework/python/src/common/statuses.py b/framework/python/src/common/statuses.py new file mode 100644 index 000000000..4817d7cf8 --- /dev/null +++ b/framework/python/src/common/statuses.py @@ -0,0 +1,36 @@ +# Copyright 2023 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +"""Enums for Testrun""" + + +class TestrunStatus: + IDLE = "Idle" + WAITING_FOR_DEVICE = "Waiting for Device" + MONITORING = "Monitoring" + IN_PROGRESS = "In Progress" + CANCELLED = "Cancelled" + COMPLIANT = "Compliant" + NON_COMPLIANT = "Non-Compliant" + STOPPING = "Stopping" + + +class TestResult: + IN_PROGRESS = "In Progress" + COMPLIANT = "Compliant" + NON_COMPLIANT = "Non-Compliant" + ERROR = "Error" + FEATURE_NOT_DETECTED = "Feature Not Detected" + INFORMATIONAL = "Informational" + NOT_STARTED = "Not Started" + DISABLED = "Disabled" diff --git a/framework/python/src/common/testreport.py b/framework/python/src/common/testreport.py index 88a25a2b1..f9401fe80 100644 --- a/framework/python/src/common/testreport.py +++ b/framework/python/src/common/testreport.py @@ -17,14 +17,19 @@ from weasyprint import HTML from io import BytesIO from common import util +from common.statuses import TestrunStatus import base64 import os from test_orc.test_case import TestCase +from jinja2 import Environment, FileSystemLoader +from collections import OrderedDict DATE_TIME_FORMAT = '%Y-%m-%d %H:%M:%S' RESOURCES_DIR = 'resources/report' TESTS_FIRST_PAGE = 11 TESTS_PER_PAGE = 20 +TEST_REPORT_STYLES = 'test_report_styles.css' +TEST_REPORT_TEMPLATE = 'test_report_template.html' # Locate parent directory current_dir = os.path.dirname(os.path.realpath(__file__)) @@ -37,13 +42,15 @@ report_resource_dir = os.path.join(root_dir, RESOURCES_DIR) test_run_img_file = os.path.join(report_resource_dir, 'testrun.png') +qualification_icon = os.path.join(report_resource_dir, 'qualification-icon.png') +pilot_icon = os.path.join(report_resource_dir, 'pilot-icon.png') class TestReport(): """Represents a previous Testrun report.""" def __init__(self, - status='Non-Compliant', + status=TestrunStatus.NON_COMPLIANT, started=None, finished=None, total_tests=0): @@ -115,6 +122,10 @@ def to_json(self): if test.recommendations is not None and len(test.recommendations) > 0: test_dict['recommendations'] = test.recommendations + if (test.optional_recommendations is not None + and len(test.optional_recommendations) > 0): + test_dict['optional_recommendations'] = test.optional_recommendations + test_results.append(test_dict) report_json['tests'] = {'total': self._total_tests, @@ -141,6 +152,12 @@ def from_json(self, json_file): if 'test_modules' in json_file['device']: self._device['test_modules'] = json_file['device']['test_modules'] + if 'test_pack' in json_file['device']: + self._device['test_pack'] = json_file['device']['test_pack'] + + if 'additional_info' in json_file['device']: + self._device['device_profile'] = json_file['device']['additional_info'] + self._status = json_file['status'] self._started = datetime.strptime(json_file['started'], DATE_TIME_FORMAT) self._finished = datetime.strptime(json_file['finished'], DATE_TIME_FORMAT) @@ -157,8 +174,16 @@ def from_json(self, json_file): expected_behavior=test_result['expected_behavior'], required_result=test_result['required_result'], result=test_result['result']) + + # Add test recommendations if 'recommendations' in test_result: test_case.recommendations = test_result['recommendations'] + + # Add optional test recommendations + if 'optional_recommendations' in test_result: + test_case.optional_recommendations = test_result[ + 'optional_recommendations'] + self.add_test(test_case) # Create a pdf file in memory and return the bytes @@ -172,35 +197,81 @@ def to_pdf(self): return pdf_bytes def to_html(self): - json_data = self.to_json() - return f''' - - - {self.generate_head()} - - {self.generate_body(json_data)} - - - ''' - - def generate_test_sections(self, json_data): - results = json_data['tests']['results'] - sections = '' - for result in results: - sections += self.generate_test_section(result) - return sections - - def generate_test_section(self, result): - section_content = '
\n' - for key, value in result.items(): - if value is not None: # Check if the value is not None - # Replace underscores and capitalize - formatted_key = key.replace('_', ' ').title() - section_content += f'

{formatted_key}: {value}

\n' - section_content += '
\n
\n' - return section_content - - def generate_pages(self, json_data): + + # Jinja template + template_env = Environment(loader=FileSystemLoader(report_resource_dir)) + template = template_env.get_template(TEST_REPORT_TEMPLATE) + with open(os.path.join(report_resource_dir, + TEST_REPORT_STYLES), + 'r', + encoding='UTF-8' + ) as style_file: + styles = style_file.read() + + # Load Testrun logo to base64 + with open(test_run_img_file, 'rb') as f: + logo = base64.b64encode(f.read()).decode('utf-8') + + json_data=self.to_json() + + # Icons + with open(qualification_icon, 'rb') as f: + icon_qualification = base64.b64encode(f.read()).decode('utf-8') + with open(pilot_icon, 'rb') as f: + icon_pilot = base64.b64encode(f.read()).decode('utf-8') + + # Convert the timestamp strings to datetime objects + start_time = datetime.strptime(json_data['started'], '%Y-%m-%d %H:%M:%S') + end_time = datetime.strptime(json_data['finished'], '%Y-%m-%d %H:%M:%S') + + # Calculate the duration + duration = end_time - start_time + + # Calculate number of successful tests + successful_tests = 0 + for test in json_data['tests']['results']: + if test['result'] != 'Error': + successful_tests += 1 + + # Obtain the steps to resolve + steps_to_resolve = self._get_steps_to_resolve(json_data) + + # Obtain optional recommendations + optional_steps_to_resolve = self._get_optional_steps_to_resolve(json_data) + + module_reports = self._get_module_pages() + pages_num = self._pages_num(json_data) + total_pages = pages_num + len(module_reports) + 1 + if len(steps_to_resolve) > 0: + total_pages += 1 + if (len(optional_steps_to_resolve) > 0 + and json_data['device']['test_pack'] == 'Pilot Assessment' + ): + total_pages += 1 + + return template.render(styles=styles, + logo=logo, + icon_qualification=icon_qualification, + icon_pilot=icon_pilot, + version=self._version, + json_data=json_data, + device=json_data['device'], + modules=self._device_modules(json_data['device']), + test_status=json_data['status'], + duration=duration, + successful_tests=successful_tests, + total_tests=self._total_tests, + test_results=json_data['tests']['results'], + steps_to_resolve=steps_to_resolve, + optional_steps_to_resolve=optional_steps_to_resolve, + module_reports=module_reports, + pages_num=pages_num, + total_pages=total_pages, + tests_first_page=TESTS_FIRST_PAGE, + tests_per_page=TESTS_PER_PAGE, + ) + + def _pages_num(self, json_data): # Calculate pages test_count = len(json_data['tests']['results']) @@ -208,136 +279,62 @@ def generate_pages(self, json_data): # Multiple pages required if test_count > TESTS_FIRST_PAGE: # First page - full_page = 1 + pages = 1 - # Remaining tests + # Remaining testsgenerate test_count -= TESTS_FIRST_PAGE - full_page += (int)(test_count / TESTS_PER_PAGE) - partial_page = 1 if test_count % TESTS_PER_PAGE > 0 else 0 + pages += (int)(test_count / TESTS_PER_PAGE) + pages = pages + 1 if test_count % TESTS_PER_PAGE > 0 else pages # 1 page required - elif test_count == TESTS_FIRST_PAGE: - full_page = 1 - partial_page = 0 - # Less than 1 page required else: - full_page = 0 - partial_page = 1 + pages = 1 - num_pages = full_page + partial_page - - pages = '' - for _ in range(num_pages): - self._cur_page += 1 - pages += self.generate_results_page(json_data=json_data, - page_num=self._cur_page) return pages - def generate_results_page(self, json_data, page_num): - page = '
' - page += self.generate_header(json_data, (page_num == 1)) - if page_num == 1: - page += self.generate_summary(json_data) - page += self.generate_results(json_data, page_num) - page += self.generate_footer(page_num) - page += '
' - page += '
' - return page - - def generate_module_page(self, json_data, module_report): - self._cur_page += 1 - page = '
' - page += self.generate_header(json_data, False) - page += f''' -
- {module_report} -
''' - page += self.generate_footer(self._cur_page) - page += '
' # Page end - page += '
' - return page - - def generate_steps_to_resolve(self, json_data): - - steps_so_far = 0 + def _device_modules(self, device): + sorted_modules = {} + + if 'test_modules' in device: + + for test_module in device['test_modules']: + if 'enabled' in device['test_modules'][test_module]: + sorted_modules[ + util.get_module_display_name(test_module)] = device['test_modules'][ + test_module]['enabled'] + + # Sort the modules by enabled first + sorted_modules = OrderedDict(sorted(sorted_modules.items(), + key=lambda x:x[1], + reverse=True) + ) + return sorted_modules + + def _get_steps_to_resolve(self, json_data): tests_with_recommendations = [] - index = 1 # Collect all tests with recommendations for test in json_data['tests']['results']: if 'recommendations' in test: tests_with_recommendations.append(test) - # Check if test has recommendations - if len(tests_with_recommendations) == 0: - return '' - - # Start new page - self._cur_page += 1 - page = '
' - page += self.generate_header(json_data, False) - - # Add title - page += '

Steps to Resolve

' - - for test in tests_with_recommendations: - - # Generate new page - if steps_so_far == 4 and ( - len(tests_with_recommendations) - (index-1) > 0): - - # Reset steps counter - steps_so_far = 0 - - # Render footer - page += self.generate_footer(self._cur_page) - page += '
' # Page end - page += '
' - - # Render new header - self._cur_page += 1 - page += '
' - page += self.generate_header(json_data, False) - - # Render test recommendations - page += f''' -
-
- {index}. -
- Name
{test["name"]} -
-
- Description
{test["description"]} -
-
-
- Steps to resolve - ''' - - step_number = 1 - for recommendation in test['recommendations']: - page += f''' -
{ - step_number}. {recommendation}''' - step_number += 1 - - page += '
' - - index += 1 - steps_so_far += 1 - - # Render final footer - page += self.generate_footer(self._cur_page) - page += '
' # Page end - page += '
' - - return page - - def generate_module_pages(self, json_data): - pages = '' + return tests_with_recommendations + + def _get_optional_steps_to_resolve(self, json_data): + tests_with_recommendations = [] + + # Collect all tests with recommendations + for test in json_data['tests']['results']: + if 'optional_recommendations' in test: + tests_with_recommendations.append(test) + + return tests_with_recommendations + + def _get_module_pages(self): content_max_size = 913 + reports = [] + for module_reports in self._module_reports: # ToDo: Figure out how to make this dynamic # Padding values from CSS @@ -391,8 +388,7 @@ def generate_module_pages(self, json_data): # If in the middle of a table, close the table if data_rows_active: page_content += '' - page = self.generate_module_page(json_data, page_content) - pages += page + '\n' + reports.append(page_content) content_size = 0 # If in the middle of a data table, restart # it for the rest of the rows @@ -400,727 +396,5 @@ def generate_module_pages(self, json_data): if data_rows_active else '') page_content += line + '\n' if len(page_content) > 0: - page = self.generate_module_page(json_data, page_content) - pages += page + '\n' - return pages - - def generate_body(self, json_data): - self._num_pages = 0 - self._cur_page = 0 - body = f''' - - {self.generate_pages(json_data)} - {self.generate_steps_to_resolve(json_data)} - {self.generate_module_pages(json_data)} - - ''' - # Set the max pages after all pages have been generated - return body.replace('MAX_PAGE', str(self._cur_page)) - - def generate_footer(self, page_num): - footer = f''' - - ''' - return footer - - def generate_results(self, json_data, page_num): - - successful_tests = 0 - for test in json_data['tests']['results']: - if test['result'] != 'Error': - successful_tests += 1 - - result_list = f''' -
-

Results List ({successful_tests}/{self._total_tests})

-
-
Name
-
Description
-
Result
-
''' - if page_num == 1: - start = 0 - elif page_num == 2: - start = TESTS_FIRST_PAGE - else: - start = (page_num - 2) * TESTS_PER_PAGE + TESTS_FIRST_PAGE - results_on_page = TESTS_FIRST_PAGE if page_num == 1 else TESTS_PER_PAGE - result_end = min(start + results_on_page, - len(json_data['tests']['results'])) - for ix in range(result_end - start): - result = json_data['tests']['results'][ix + start] - result_list += self.generate_result(result) - result_list += '
' - return result_list - - def generate_result(self, result): - if result['result'] == 'Non-Compliant': - result_class = 'result-test-result-non-compliant' - elif result['result'] == 'Compliant': - result_class = 'result-test-result-compliant' - elif result['result'] == 'Error': - result_class = 'result-test-result-error' - elif result['result'] == 'Feature Not Detected': - result_class = 'result-test-result-feature-not-detected' - elif result['result'] == 'Informational': - result_class = 'result-test-result-informational' - else: - result_class = 'result-test-result-skipped' - - result_html = f''' -
-
{result['name']}
-
{result['description']}
-
{result['result']}
-
- ''' - return result_html - - def generate_header(self, json_data, first_page): - with open(test_run_img_file, 'rb') as f: - tr_img_b64 = base64.b64encode(f.read()).decode('utf-8') - header = '' - - if first_page: - header += f''' -
-

Testrun report

-

- {json_data["device"]["manufacturer"]} - {json_data["device"]["model"]} -

''' - else: - header += f''' -
-

Testrun report

-

- {json_data["device"]["manufacturer"]} - {json_data["device"]["model"]} -

''' - header += f'''Testrun -
- ''' - return header - - def generate_summary(self, json_data): - # Generate the basic content section layout - summary = ''' -
- ''' - # Add the device information - manufacturer = (json_data['device']['manufacturer'] - if 'manufacturer' in json_data['device'] else 'Undefined') - model = (json_data['device']['model'] - if 'model' in json_data['device'] else 'Undefined') - fw = (json_data['device']['firmware'] - if 'firmware' in json_data['device'] else 'Undefined') - mac = (json_data['device']['mac_addr'] - if 'mac_addr' in json_data['device'] else 'Undefined') - - summary += '''
-
''' - - summary += self.generate_device_summary_label('Manufacturer', manufacturer) - summary += self.generate_device_summary_label('Model', model) - summary += self.generate_device_summary_label('Firmware', fw) - summary += self.generate_device_summary_label('MAC Address', - mac, - trailing_space=False) - - summary += '
' - - # Add device configuration - summary += ''' -
-
-

Device Configuration

-
- ''' - - if 'test_modules' in json_data['device']: - - sorted_modules = {} - - for test_module in json_data['device']['test_modules']: - if 'enabled' in json_data['device']['test_modules'][test_module]: - sorted_modules[test_module] = json_data['device']['test_modules'][ - test_module]['enabled'] - - # Sort the modules by enabled first - sorted_modules = sorted(sorted_modules.items(), - key=lambda x:x[1], - reverse=True) - - for module in sorted_modules: - summary += self.generate_device_module_label( - module[0], - module[1] - ) - - summary += '
' - - # Add device configuration - summary += ''' -
-
-

Device Configuration

-
- ''' - - if 'test_modules' in json_data['device']: - - sorted_modules = {} - - for test_module in json_data['device']['test_modules']: - if 'enabled' in json_data['device']['test_modules'][test_module]: - sorted_modules[test_module] = json_data['device']['test_modules'][ - test_module]['enabled'] - - # Sort the modules by enabled first - sorted_modules = sorted(sorted_modules.items(), - key=lambda x:x[1], - reverse=True) - - for module in sorted_modules: - summary += self.generate_device_module_label( - module[0], - module[1] - ) - - summary += '
' - - # Add the result summary - summary += self.generate_result_summary(json_data) - - summary += '\n
' - return summary - - def generate_device_module_label(self, module, enabled): - - # Do not render deleted modules - if module == 'nmap': - return '' - - label = '
' - if enabled: - label += '' - else: - label += '' - label += util.get_module_display_name(module) - label += '
' - return label - - def generate_result_summary(self, json_data): - if json_data['status'] == 'Compliant': - result_summary = '''
''' - else: - result_summary = '''
''' - result_summary += self.generate_result_summary_item('Test status', - 'Complete') - result_summary += self.generate_result_summary_item( - 'Test result', - json_data['status'], - style='color: white; font-size:24px; font-weight: 700;') - result_summary += self.generate_result_summary_item('Started', - json_data['started']) - - # Convert the timestamp strings to datetime objects - start_time = datetime.strptime(json_data['started'], '%Y-%m-%d %H:%M:%S') - end_time = datetime.strptime(json_data['finished'], '%Y-%m-%d %H:%M:%S') - # Calculate the duration - duration = end_time - start_time - result_summary += self.generate_result_summary_item('Duration', - str(duration)) - - result_summary += '\n
' - return result_summary - - def generate_result_summary_item(self, key, value, style=None): - summary_item = f'''
{key}
''' - if style is not None: - summary_item += f'''
{value}
''' - else: - summary_item += f'''
{value}
''' - return summary_item - - def generate_device_summary_label(self, key, value, trailing_space=True): - label = f''' -

{key}

-
{value}
- ''' - if trailing_space: - label += '''
''' - return label - - def generate_head(self): - return f''' - - - - Testrun Report - - - ''' - - def generate_css(self): - return ''' - /* Set some global variables */ - :root { - --header-height: .75in; - --header-width: 8.5in; - --header-pos-x: 0in; - --header-pos-y: 0in; - --page-width: 8.5in; - --summary-height: 2.8in; - --vertical-line-height: calc(var(--summary-height)-.2in); - --vertical-line-pos-x: 25%; - } - - @font-face { - font-family: 'Google Sans'; - font-style: normal; - src: url(https://fonts.gstatic.com/s/googlesans/v58/4Ua_rENHsxJlGDuGo1OIlJfC6l_24rlCK1Yo_Iqcsih3SAyH6cAwhX9RFD48TE63OOYKtrwEIJllpyk.woff2) format('woff2'); - unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; - } - - @font-face { - font-family: 'Roboto Mono'; - font-style: normal; - src: url(https://fonts.googleapis.com/css2?family=Roboto+Mono:ital,wght@0,100..700;1,100..700&display=swap) format('woff2'); - unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; - } - - /* Define some common body formatting*/ - body { - font-family: 'Google Sans', sans-serif; - margin: 0px; - padding: 0px; - } - - /* Use this for various section breaks*/ - .gradient-line { - position: relative; - background-image: linear-gradient(to right, red, blue, green, yellow, orange); - height: 1px; - /* Adjust the height as needed */ - width: 100%; - /* To span the entire width */ - display: block; - /* Ensures it's a block-level element */ - } - - /* Sets proper page size during print to pdf for weasyprint */ - @page { - size: Letter; - width: 8.5in; - height: 11in; - } - - .page { - position: relative; - margin: 0 20px; - width: 8.5in; - height: 11in; - } - - /* Define the header related css elements*/ - .header { - position: relative; - } - - h1 { - margin: 0 0 8px 0; - font-size: 20px; - font-weight: 400; - } - - h2 { - margin: 0px; - font-size: 48px; - font-weight: 700; - } - - h3 { - font-size: 24px; - } - - h4 { - font-size: 12px; - font-weight: 500; - color: #5F6368; - margin-bottom: 0; - margin-top: 0; - } - - .module-summary { - background-color: #F8F9FA; - width: 100%; - margin-bottom: 25px; - margin-top: 25px; - } - - .module-summary thead tr th { - text-align: left; - padding-top: 15px; - padding-left: 15px; - font-weight: 500; - color: #5F6368; - font-size: 14px; - } - - .module-summary tbody tr td { - padding-bottom: 15px; - padding-left: 15px; - font-size: 24px; - } - - .module-data { - border: 1px solid #DADCE0; - border-radius: 3px; - border-spacing: 0; - } - - .module-data thead tr th { - text-align: left; - padding: 12px 25px; - color: #3C4043; - font-size: 14px; - font-weight: 700; - } - - .module-data tbody tr td { - text-align: left; - padding: 12px 25px; - color: #3C4043; - font-size: 14px; - font-weight: 400; - border-top: 1px solid #DADCE0; - font-family: 'Roboto Mono', monospace; - } - - div.steps-to-resolve { - background-color: #F8F9FA; - margin-bottom: 30px; - width: 756px; - padding: 20px 30px; - vertical-align: top; - } - - .steps-to-resolve-row { - vertical-align: top; - } - - .steps-to-resolve-test-name { - display: inline-block; - margin-left: 70px; - margin-bottom: 20px; - width: 250px; - vertical-align: top; - } - - .steps-to-resolve-description { - display: inline-block; - } - - .steps-to-resolve.subtitle { - text-align: left; - padding-top: 15px; - font-weight: 500; - color: #5F6368; - font-size: 14px; - } - - .steps-to-resolve-index { - font-size: 40px; - position: absolute; - } - - .callout-container.info { - background-color: #e8f0fe; - } - - .callout-container.info .icon { - width: 22px; - height: 22px; - margin-right: 5px; - background-size: contain; - background-image: url('data:image/png;base64,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'); - } - - .callout-container { - display: flex; - box-sizing: border-box; - height: auto; - min-height: 48px; - padding: 6px 24px; - border-radius: 8px; - align-items: center; - gap: 10px; - color: #3c4043; - font-size: 14px; - } - - .device-information { - padding-top: 0.2in; - padding-left: 0.2in; - background-color: #F8F9FA; - width: 250px; - height: 100.4%; - } - - /* Define the summary related css elements*/ - .summary-content { - position: relative; - width: var(--page-width); - height: var(--summary-height); - margin-top: 19px; - margin-bottom: 19px; - background-color: #E8EAED; - padding-bottom: 20px; - } - - .summary-item-label { - position: relative; - } - - .summary-item-value { - position: relative; - font-size: 20px; - font-weight: 400; - color: #202124; - } - - .summary-item-space { - position: relative; - padding-bottom: 15px; - margin: 0; - } - - .summary-device-modules { - position: absolute; - left: 3.2in; - top: .3in; - } - - .summary-device-module-label { - font-size: 16px; - font-weight: 500; - color: #202124; - width: fit-content; - margin-bottom: 0.1in; - } - - .summary-vertical-line { - width: 1px; - height: var(--vertical-line-height); - background-color: #80868B; - position: absolute; - top: .3in; - bottom: .1in; - left: 3in; - } - - /* CSS for the color box */ - .summary-color-box { - position: absolute; - right: 0in; - top: 0in; - width: 2.6in; - height: 100%; - } - - .summary-box-compliant { - background-color: rgb(24, 128, 56); - } - - .summary-box-non-compliant { - background-color: #b31412; - } - - .summary-box-label { - font-size: 14px; - margin-top: 5px; - color: #DADCE0; - position: relative; - top: 10px; - left: 20px; - font-weight: 500; - } - - .summary-box-value { - font-size: 18px; - margin: 0 0 10px 0; - color: #ffffff; - position: relative; - top: 10px; - left: 20px; - } - - .result-list-title { - font-size: 24px; - } - - .result-list { - position: relative; - margin-top: .2in; - font-size: 18px; - } - - .result-line { - border: 1px solid #D3D3D3; - /* Light Gray border*/ - height: .4in; - width: 8.5in; - } - - .result-line-result { - border-top: 0px; - } - - .result-list-header-label { - font-weight: 500; - position: absolute; - font-size: 12px; - font-weight: bold; - height: 40px; - display: flex; - align-items: center; - } - - .result-test-label { - position: absolute; - font-size: 12px; - margin-top: 12px; - max-width: 300px; - font-weight: normal; - align-items: center; - text-overflow: ellipsis; - white-space: nowrap; - overflow: hidden; - } - - .result-test-description { - max-width: 380px; - } - - .result-test-result-error { - background-color: #FCE8E6; - color: #C5221F; - left: 7.3in; - } - - .result-test-result-feature-not-detected { - background-color: #e3e3e3; - left: 6.85in; - } - - .result-test-result-informational { - background-color: #d9f0ff; - color: #0b5c8d; - left: 7.08in; - } - - .result-test-result-non-compliant { - background-color: #FCE8E6; - color: #C5221F; - left: 7.01in; - } - - .result-test-result { - position: absolute; - font-size: 12px; - width: fit-content; - height: 12px; - margin-top: 8px; - padding: 4px 4px 7px 5px; - border-radius: 2px; - } - - .result-test-result-compliant { - background-color: #E6F4EA; - color: #137333; - left: 7.16in; - } - - .result-test-result-skipped { - background-color: #e3e3e3; - color: #393939; - left: 7.24in; - } - - /* CSS for the footer */ - .footer { - position: absolute; - height: 30px; - width: 8.5in; - bottom: 0in; - border-top: 1px solid #D3D3D3; - } - - .footer-label { - color: #3C4043; - position: absolute; - top: 5px; - font-size: 12px; - } - - /*CSS for the markdown tables */ - .markdown-table { - border-collapse: collapse; - margin-left: 20px; - background-color: #F8F9FA; - } - - .markdown-table th, .markdown-table td { - border: none; - text-align: left; - padding: 8px; - } - - .markdown-header-h1 { - margin-top:20px; - margin-bottom:20px; - margin-right:0px; - font-size: 2em; - } - - .markdown-header-h2 { - margin-top:20px; - margin-bottom:20px; - margin-right:0px; - font-size: 1.5em; - } - - .module-page-content { - /*Page height minus header(93px), footer(30px), - and a 20px bottom padding.*/ - height: calc(11in - 93px - 30px - 20px); - - /* In case we mess something up in our calculations - we'll cut off the content of the page so - the header, footer and line break work - as expected - */ - overflow: hidden; - } - - .module-page-content h1 { - font-size: 32px; - } - - @media print { - @page { - size: Letter; - width: 8.5in; - height: 11in; - } - }''' + reports.append(page_content) + return reports diff --git a/framework/python/src/common/util.py b/framework/python/src/common/util.py index 7c31631fb..ba1b23e81 100644 --- a/framework/python/src/common/util.py +++ b/framework/python/src/common/util.py @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -"""Provides basic utilities for the network orchestrator.""" +"""Provides basic utilities for Testrun.""" import getpass import os import subprocess @@ -52,12 +52,15 @@ def run_command(cmd, output=True, timeout=None): def interface_exists(interface): + """Checks whether an interface is available""" return interface in netifaces.interfaces() def prettify(mac_string): + """Formats a MAC address with colons""" return ':'.join([f'{ord(b):02x}' for b in mac_string]) def get_host_user(): + """Returns the username of the host user""" user = get_os_user() # If primary method failed, try secondary @@ -67,6 +70,7 @@ def get_host_user(): return user def get_os_user(): + """Attempts to get the username using os library""" user = None try: user = os.getlogin() @@ -79,6 +83,7 @@ def get_os_user(): return user def get_user(): + """Attempts to get the host user using the getpass library""" user = None try: user = getpass.getuser() @@ -97,9 +102,11 @@ def get_user(): return user def set_file_owner(path, owner): + """Change the owner of a file""" run_command(f'chown -R {owner} {path}') def get_module_display_name(search): + """Returns the display name of a test module""" modules = { 'ntp': 'NTP', 'dns': 'DNS', diff --git a/framework/python/src/core/docker/docker_module.py b/framework/python/src/core/docker/docker_module.py new file mode 100644 index 000000000..21dabdc16 --- /dev/null +++ b/framework/python/src/core/docker/docker_module.py @@ -0,0 +1,163 @@ +# Copyright 2023 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +"""Represents the base module.""" +import docker +from docker.models.containers import Container +import os +from common import logger +import json + +IMAGE_PREFIX = 'testrun/' +CONTAINER_PREFIX = 'tr-ct' +DEFAULT_NETWORK = 'bridge' + + +class Module: + """Represents the base module.""" + + def __init__(self, + module_config_file, + session, + docker_network=DEFAULT_NETWORK, + extra_hosts=None): + self._session = session + self.extra_hosts = extra_hosts + + # Read the config file into a json object + with open(module_config_file, encoding='UTF-8') as config_file: + module_json = json.load(config_file) + + self.docker_network = docker_network + # General module information + self.name = module_json['config']['meta']['name'] + self.display_name = module_json['config']['meta']['display_name'] + self.description = module_json['config']['meta']['description'] + self.enabled = module_json['config'].get('enabled', True) + self.depends_on = module_json['config']['docker'].get('depends_on', None) + + # Absolute path + # Store the root directory of Testrun based on the expected locatoin + # Testrun/modules///conf -> 5 levels + self.root_path = os.path.abspath( + os.path.join(module_config_file, '../../../../..')) + self.dir = os.path.dirname(os.path.dirname(module_config_file)) + self.dir_name = os.path.basename(self.dir) + + # Docker settings + self.build_file = self.dir_name + '.Dockerfile' + self.image_name = f'{IMAGE_PREFIX}{self.dir_name}' + self.container_name = f'{CONTAINER_PREFIX}-{self.dir_name}' + if 'tests' in module_json['config']: + # Append Test module + self.image_name += '-test' + self.container_name += '-test' + self.enable_container = module_json['config']['docker'].get( + 'enable_container', True) + self.container: Container = None + + self._add_logger(log_name=self.name, module_name=self.name) + self.setup_module(module_json) + + def _add_logger(self, log_name, module_name, log_dir=None): + self.logger = logger.get_logger( + name=f'{log_name}_module', # pylint: disable=E1123 + log_file=f'{module_name}_module', + log_dir=log_dir) + + def build(self): + self.logger.debug('Building module ' + self.dir_name) + client = docker.from_env() + client.images.build( + dockerfile=os.path.join(self.dir, self.build_file), + path=self._path, + forcerm=True, # Cleans up intermediate containers during build + tag=self.image_name) + + def get_container(self): + container = None + try: + client = docker.from_env() + container = client.containers.get(self.container_name) + except docker.errors.NotFound: + self.logger.debug('Container ' + self.container_name + ' not found') + except docker.errors.APIError as error: + self.logger.error('Failed to resolve container') + self.logger.error(error) + return container + + def get_session(self): + return self._session + + def get_status(self): + self.container = self.get_container() + if self.container is not None: + return self.container.status + return None + + def get_network(self): + return self.docker_network + + def get_mounts(self): + return [] + + def get_environment(self, device=None): # pylint: disable=W0613 + return {} + + def setup_module(self, module_json): + pass + + def _setup_runtime(self, device=None): + pass + + def start(self, device=None): + self._setup_runtime(device) + + self.logger.debug('Starting module ' + self.display_name) + network = self.get_network() + self.logger.debug(f"""Network: {network}, image name: {self.image_name}, + container name: {self.container_name}""") + + try: + client = docker.from_env() + self.container = client.containers.run( + self.image_name, + auto_remove=True, + cap_add=['NET_ADMIN'], + name=self.container_name, + hostname=self.container_name, + network_mode=network, + privileged=True, + detach=True, + mounts=self.get_mounts(), + environment=self.get_environment(device), + extra_hosts=self.extra_hosts if self.extra_hosts is not None else {}) + except docker.errors.ContainerError as error: + self.logger.error('Container run error') + self.logger.error(error) + + def stop(self, kill=False): + self.logger.debug('Stopping module ' + self.container_name) + try: + container = self.get_container() + if container is not None: + if kill: + self.logger.debug('Killing container: ' + self.container_name) + container.kill() + else: + self.logger.debug('Stopping container: ' + self.container_name) + container.stop() + self.logger.debug('Container stopped: ' + self.container_name) + except Exception as error: # pylint: disable=W0703 + self.logger.error('Container stop error') + self.logger.error(error) diff --git a/framework/python/src/core/docker/network_docker_module.py b/framework/python/src/core/docker/network_docker_module.py new file mode 100644 index 000000000..6c892092a --- /dev/null +++ b/framework/python/src/core/docker/network_docker_module.py @@ -0,0 +1,98 @@ +# Copyright 2023 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +"""Represents a test module.""" +from core.docker.docker_module import Module +import os +from docker.types import Mount + +RUNTIME_DIR = 'runtime' +RUNTIME_TEST_DIR = os.path.join(RUNTIME_DIR, 'test') +DEFAULT_TIMEOUT = 60 # time in seconds +DEFAULT_DOCKER_NETWORK = 'none' + + +class NetworkModule(Module): + """Represents a test module.""" + + def __init__(self, module_config_file, session): + super().__init__(module_config_file=module_config_file, + docker_network=DEFAULT_DOCKER_NETWORK, + session=session) + + def setup_module(self, module_json): + self.template = module_json['config']['docker'].get('template', False) + self.net_config = NetworkModuleNetConfig() + if self.enable_container: + self.net_config.enable_wan = module_json['config']['network'].get( + 'enable_wan', False) + self.net_config.host = module_json['config']['network'].get('host', False) + # Override default network if host is requested + if self.net_config.host: + self.docker_network = 'host' + + if not self.net_config.host: + self.net_config.ip_index = module_json['config']['network'].get( + 'ip_index') + + self.net_config.ipv4_address = self.get_session().get_ipv4_subnet()[ + self.net_config.ip_index] + self.net_config.ipv4_network = self.get_session().get_ipv4_subnet() + + self.net_config.ipv6_address = self.get_session().get_ipv6_subnet()[ + self.net_config.ip_index] + + self.net_config.ipv6_network = self.get_session().get_ipv6_subnet() + + self._mounts = [] + if 'mounts' in module_json['config']['docker']: + for mount_point in module_json['config']['docker']['mounts']: + self._mounts.append( + Mount(target=mount_point['target'], + source=os.path.join(os.getcwd(), mount_point['source']), + type='bind')) + + def _setup_runtime(self, device): + pass + + def get_environment(self, device=None): # pylint: disable=W0613 + environment = { + 'TZ': self.get_session().get_timezone(), + 'HOST_USER': self.get_session().get_host_user() + } + return environment + + def get_mounts(self): + return self._mounts + + +class NetworkModuleNetConfig: + """Define all the properties of the network config for a network module""" + + def __init__(self): + + self.enable_wan = False + + self.ip_index = 0 + self.ipv4_address = None + self.ipv4_network = None + self.ipv6_address = None + self.ipv6_network = None + + self.host = False + + def get_ipv4_addr_with_prefix(self): + return format(self.ipv4_address) + '/' + str(self.ipv4_network.prefixlen) + + def get_ipv6_addr_with_prefix(self): + return format(self.ipv6_address) + '/' + str(self.ipv6_network.prefixlen) diff --git a/framework/python/src/core/docker/test_docker_module.py b/framework/python/src/core/docker/test_docker_module.py new file mode 100644 index 000000000..4bbf72594 --- /dev/null +++ b/framework/python/src/core/docker/test_docker_module.py @@ -0,0 +1,135 @@ +# Copyright 2023 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +"""Represents a test module.""" +from core.docker.docker_module import Module +from test_orc.test_case import TestCase +import os +import json +from common import util +from docker.types import Mount + +RUNTIME_DIR = 'runtime' +RUNTIME_TEST_DIR = os.path.join(RUNTIME_DIR, 'test') +DEFAULT_TIMEOUT = 60 # time in seconds + + +class TestModule(Module): + """Represents a test module.""" + + def __init__(self, module_config_file, session, extra_hosts): + super().__init__(module_config_file=module_config_file, + session=session, + extra_hosts=extra_hosts) + + # Set IP Index for all test modules + self.ip_index = 9 + + def setup_module(self, module_json): + # Set the defaults + self.network = True + self.total_tests = 0 + self.time = DEFAULT_TIMEOUT + self.tests: list = [] + + if 'timeout' in module_json['config']['docker']: + self.timeout = module_json['config']['docker']['timeout'] + + # Determine if this module needs network access + if 'network' in module_json['config']: + self.network = module_json['config']['network'] + + # Load test cases + if 'tests' in module_json['config']: + self.total_tests = len(module_json['config']['tests']) + for test_case_json in module_json['config']['tests']: + try: + test_case = TestCase( + name=test_case_json['name'], + description=test_case_json['test_description'], + expected_behavior=test_case_json['expected_behavior']) + + # Check if steps to resolve have been specified + if 'recommendations' in test_case_json: + test_case.recommendations = test_case_json['recommendations'] + + self.tests.append(test_case) + except Exception as error: # pylint: disable=W0718 + self.logger.error('Failed to load test case. See error for details') + self.logger.error(error) + + def _setup_runtime(self, device): + self.device_test_dir = os.path.join(self.root_path, RUNTIME_TEST_DIR, + device.mac_addr.replace(':', '')) + + self.container_runtime_dir = os.path.join(self.device_test_dir, self.name) + os.makedirs(self.container_runtime_dir, exist_ok=True) + + self.container_log_file = os.path.join(self.container_runtime_dir, + 'module.log') + + self.config_file = os.path.join(self.root_path, 'local/system.json') + self.root_certs_dir = os.path.join(self.root_path, 'local/root_certs') + + self.network_runtime_dir = os.path.join(self.root_path, 'runtime/network') + + self.device_startup_capture = os.path.join(self.device_test_dir, + 'startup.pcap') + host_user = self.get_session().get_host_user() + util.run_command(f'chown -R {host_user} {self.device_startup_capture}') + + self.device_monitor_capture = os.path.join(self.device_test_dir, + 'monitor.pcap') + util.run_command(f'chown -R {host_user} {self.device_monitor_capture}') + + def get_environment(self, device): + environment = { + 'TZ': self.get_session().get_timezone(), + 'HOST_USER': self.get_session().get_host_user(), + 'DEVICE_MAC': device.mac_addr, + 'IPV4_ADDR': device.ip_addr, + 'DEVICE_TEST_MODULES': json.dumps(device.test_modules), + 'IPV4_SUBNET': self.get_session().get_ipv4_subnet(), + 'IPV6_SUBNET': self.get_session().get_ipv6_subnet(), + 'DEV_IFACE': self.get_session().get_device_interface(), + 'DEV_IFACE_MAC': self.get_session().get_device_interface_mac_addr() + } + return environment + + def get_mounts(self): + mounts = [ + Mount(target='/testrun/system.json', + source=self.config_file, + type='bind', + read_only=True), + Mount(target='/testrun/root_certs', + source=self.root_certs_dir, + type='bind', + read_only=True), + Mount(target='/runtime/output', + source=self.container_runtime_dir, + type='bind'), + Mount(target='/runtime/network', + source=self.network_runtime_dir, + type='bind', + read_only=True), + Mount(target='/runtime/device/startup.pcap', + source=self.device_startup_capture, + type='bind', + read_only=True), + Mount(target='/runtime/device/monitor.pcap', + source=self.device_monitor_capture, + type='bind', + read_only=True) + ] + return mounts diff --git a/framework/python/src/common/session.py b/framework/python/src/core/session.py similarity index 76% rename from framework/python/src/common/session.py rename to framework/python/src/core/session.py index 940fbe8f0..f65da0bb5 100644 --- a/framework/python/src/common/session.py +++ b/framework/python/src/core/session.py @@ -20,6 +20,7 @@ from fastapi.encoders import jsonable_encoder from common import util, logger, mqtt from common.risk_profile import RiskProfile +from common.statuses import TestrunStatus, TestResult from net_orc.ip_control import IPControl # Certificate dependencies @@ -36,10 +37,13 @@ API_URL_KEY = 'api_url' API_PORT_KEY = 'api_port' MAX_DEVICE_REPORTS_KEY = 'max_device_reports' +ORG_NAME_KEY = 'org_name' CERTS_PATH = 'local/root_certs' CONFIG_FILE_PATH = 'local/system.json' STATUS_TOPIC = 'status' +MAKE_CONTROL_DIR = 'make/DEBIAN/control' + PROFILE_FORMAT_PATH = 'resources/risk_assessment.json' PROFILES_DIR = 'local/risk_profiles' @@ -52,7 +56,7 @@ def wrapper(self, *args, **kwargs): result = method(self, *args, **kwargs) - if self.get_status() != 'Idle': + if self.get_status() != TestrunStatus.IDLE: self.get_mqtt_client().send_message( STATUS_TOPIC, jsonable_encoder(self.to_json()) @@ -72,7 +76,6 @@ def apply_session_tracker(cls): setattr(cls, attr, session_tracker(getattr(cls, attr))) return cls - @apply_session_tracker class TestrunSession(): """Represents the current session of Testrun.""" @@ -80,7 +83,8 @@ class TestrunSession(): def __init__(self, root_dir): self._root_dir = root_dir - self._status = 'Idle' + self._status = TestrunStatus.IDLE + self._description = None # Target test device self._device = None @@ -130,6 +134,13 @@ def __init__(self, root_dir): self._load_config() self._load_profiles() + # Network information + self._ipv4_subnet = None + self._ipv6_subnet = None + + # Store host user for permissions use + self._host_user = util.get_host_user() + self._certs = [] self.load_certs() @@ -144,7 +155,7 @@ def __init__(self, root_dir): def start(self): self.reset() - self._status = 'Waiting for Device' + self._status = TestrunStatus.WAITING_FOR_DEVICE self._started = datetime.datetime.now() def get_started(self): @@ -154,14 +165,14 @@ def get_finished(self): return self._finished def stop(self): - self.set_status('Stopping') + self.set_status(TestrunStatus.STOPPING) self.finish() def finish(self): # Set any in progress test results to Error for test_result in self._results: - if test_result.result == 'In Progress': - test_result.result = 'Error' + if test_result.result == TestResult.IN_PROGRESS: + test_result.result = TestResult.ERROR self._finished = datetime.datetime.now() @@ -176,7 +187,9 @@ def _get_default_config(self): 'monitor_period': 30, 'max_device_reports': 0, 'api_url': 'http://localhost', - 'api_port': 8000 + 'api_port': 8000, + 'org_name': '', + 'single_intf': False, } def get_config(self): @@ -196,7 +209,7 @@ def _load_config(self): # Network interfaces if (NETWORK_KEY in config_file_json and DEVICE_INTF_KEY in config_file_json.get(NETWORK_KEY) - and INTERNET_INTF_KEY in config_file_json.get(NETWORK_KEY)): + and INTERNET_INTF_KEY in config_file_json.get(NETWORK_KEY)): self._config[NETWORK_KEY][DEVICE_INTF_KEY] = config_file_json.get( NETWORK_KEY, {}).get(DEVICE_INTF_KEY) self._config[NETWORK_KEY][INTERNET_INTF_KEY] = config_file_json.get( @@ -223,13 +236,16 @@ def _load_config(self): self._config[MAX_DEVICE_REPORTS_KEY] = config_file_json.get( MAX_DEVICE_REPORTS_KEY) - LOGGER.debug(self._config) + if ORG_NAME_KEY in config_file_json: + self._config[ORG_NAME_KEY] = config_file_json.get( + ORG_NAME_KEY + ) def _load_version(self): version_cmd = util.run_command( 'dpkg-query --showformat=\'${Version}\' --show testrun') # index 1 of response is the stderr byte stream so if - # it has any data in it, there was an error and we + # it has any data in it, there was an error and wen # did not resolve the version and we'll use the fallback if len(version_cmd[1]) == 0: version = version_cmd[0] @@ -237,14 +253,37 @@ def _load_version(self): else: LOGGER.debug('Failed getting the version from dpkg-query') # Try getting the version from the make control file + + # Check if MAKE_CONTROL_DIR exists + if not os.path.exists(MAKE_CONTROL_DIR): + LOGGER.error('make/DEBIAN/control file path not found') + self._version = 'Unknown' + return + try: - version = util.run_command( - '$(grep -R "Version: " $MAKE_CONTROL_DIR | awk "{print $2}"') - except Exception as e: + # Run the grep command to find the version line + grep_cmd = util.run_command(f'grep -R "Version: " {MAKE_CONTROL_DIR}') + + if grep_cmd[0] and len(grep_cmd[1]) == 0: + # Extract the version number from grep + version = grep_cmd[0].split()[1] + self._version = version + LOGGER.debug(f'Testrun version is: {self._version}') + + else: + # Error handling if grep can't find the version line + self._version = 'Unknown' + LOGGER.debug(f'Testrun version is {self._version}') + raise Exception('Version line not found in make control file') + + except Exception as e: # pylint: disable=W0703 LOGGER.debug('Failed getting the version from make control file') LOGGER.error(e) self._version = 'Unknown' + def get_host_user(self): + return self._host_user + def get_version(self): return self._version @@ -260,11 +299,17 @@ def get_runtime_params(self): return self._runtime_params def add_runtime_param(self, param): + if param == 'single_intf': + self._config['single_intf'] = True self._runtime_params.append(param) def get_device_interface(self): return self._config.get(NETWORK_KEY, {}).get(DEVICE_INTF_KEY) + def get_device_interface_mac_addr(self): + iface = self.get_device_interface() + return IPControl.get_iface_mac_address(iface=iface) + def get_internet_interface(self): return self._config.get(NETWORK_KEY, {}).get(INTERNET_INTF_KEY) @@ -288,7 +333,7 @@ def set_config(self, config_json): self._save_config() # Update log level - LOGGER.debug(f'Setting log level to {config_json["log_level"]}') + LOGGER.debug(f'Setting log level to {config_json["log_level"]}') # pylint: disable=W1405 logger.set_log_level(config_json['log_level']) def set_target_device(self, device): @@ -326,12 +371,21 @@ def get_device(self, mac_addr): def remove_device(self, device): self._device_repository.remove(device) + def get_ipv4_subnet(self): + return self._ipv4_subnet + + def get_ipv6_subnet(self): + return self._ipv6_subnet + def get_status(self): return self._status def set_status(self, status): self._status = status + def set_description(self, desc: str): + self._description = desc + def get_test_results(self): return self._results @@ -357,19 +411,48 @@ def add_test_result(self, result): # result type is TestCase object if test_result.name == result.name: - # Just update the result and description - test_result.result = result.result - test_result.description = result.description - test_result.recommendations = result.recommendations + # Just update the result, description and recommendations + if len(result.description) != 0: + test_result.description = result.description + + # Add recommendations if provided + if result.recommendations is not None: + test_result.recommendations = result.recommendations + + if len(result.recommendations) == 0: + test_result.recommendations = None + + if result.result is not None: + + # Any informational test should always report informational + if test_result.required_result == 'Informational': + + # Set test result to informational + if result.result in [ + TestResult.NON_COMPLIANT, + TestResult.COMPLIANT, + TestResult.INFORMATIONAL + ]: + test_result.result = TestResult.INFORMATIONAL + else: + test_result.result = result.result + + # Copy any test recommendations to optional + test_result.optional_recommendations = result.recommendations + + # Remove recommendations from informational tests + test_result.recommendations = None + else: + test_result.result = result.result + updated = True if not updated: - result.result = 'In Progress' self._results.append(result) def set_test_result_error(self, result): """Set test result error""" - result.result = 'Error' + result.result = TestResult.ERROR result.recommendations = None self._results.append(result) @@ -398,15 +481,18 @@ def get_report_url(self): def set_report_url(self, url): self._report_url = url + def set_subnets(self, ipv4_subnet, ipv6_subnet): + self._ipv4_subnet = ipv4_subnet + self._ipv6_subnet = ipv6_subnet + def _load_profiles(self): # Load format of questionnaire LOGGER.debug('Loading risk assessment format') try: - with open(os.path.join( - self._root_dir, PROFILE_FORMAT_PATH - ), encoding='utf-8') as profile_format_file: + with open(os.path.join(self._root_dir, PROFILE_FORMAT_PATH), + encoding='utf-8') as profile_format_file: format_json = json.load(profile_format_file) # Save original profile format for internal validation self._profile_format = format_json @@ -415,6 +501,10 @@ def _load_profiles(self): 'An error occurred whilst loading the risk assessment format') LOGGER.debug(e) + # If the format JSON fails to load, skip loading profiles + LOGGER.error('Profiles will not be loaded') + return + profile_format_array = [] # Remove internal properties @@ -439,7 +529,7 @@ def _load_profiles(self): try: for risk_profile_file in os.listdir( - os.path.join(self._root_dir, PROFILES_DIR)): + os.path.join(self._root_dir, PROFILES_DIR)): LOGGER.debug(f'Discovered profile {risk_profile_file}') @@ -448,7 +538,7 @@ def _load_profiles(self): encoding='utf-8') as f: # Parse risk profile json - json_data = json.load(f) + json_data: dict = json.load(f) # Validate profile JSON if not self.validate_profile_json(json_data): @@ -456,18 +546,22 @@ def _load_profiles(self): continue # Instantiate a new risk profile - risk_profile = RiskProfile() + risk_profile: RiskProfile = RiskProfile() + + # Assign the profile questions + questions: list[dict] = json_data.get('questions') + + # Pass only the valid questions to the risk profile + json_data['questions'] = self._remove_invalid_questions(questions) # Pass JSON to populate risk profile - risk_profile.load( - profile_json=json_data, - profile_format=self._profile_format - ) + risk_profile.load(profile_json=json_data, + profile_format=self._profile_format) # Add risk profile to session self._profiles.append(risk_profile) - except Exception as e: + except Exception as e: # pylint: disable=W0703 LOGGER.error('An error occurred whilst loading risk profiles') LOGGER.debug(e) @@ -506,14 +600,19 @@ def update_profile(self, profile_json): profile_json['version'] = self.get_version() profile_json['created'] = datetime.datetime.now().strftime('%Y-%m-%d') + # Assign the profile questions + questions: list[dict] = profile_json.get('questions') + + # Pass only the valid questions to the risk profile + profile_json['questions'] = self._remove_invalid_questions(questions) + # Check if profile already exists risk_profile = self.get_profile(profile_name) if risk_profile is None: # Create a new risk profile - risk_profile = RiskProfile( - profile_json=profile_json, - profile_format=self._profile_format) + risk_profile = RiskProfile(profile_json=profile_json, + profile_format=self._profile_format) self._profiles.append(risk_profile) else: @@ -536,6 +635,28 @@ def update_profile(self, profile_json): return risk_profile + def _remove_invalid_questions(self, questions: list[dict]) -> list[dict]: + """Remove unrecognised questions from the profile""" + + # Store valid questions + valid_questions = [] + + # Remove any additional (outdated questions from the profile) + for question in questions: + + # Check if question exists in the profile format + if self.get_profile_format_question( + question=question['question']) is not None: + + # Add the question to the valid_questions + valid_questions.append(question) + + else: + LOGGER.debug(f'Removed unrecognised question: {question["question"]}') + + # Return the list of valid questions + return valid_questions + def validate_profile_json(self, profile_json): """Validate properties in profile update requests""" @@ -572,13 +693,14 @@ def validate_profile_json(self, profile_json): LOGGER.error('A question is missing from "question" field') return False - # Check if question is a recognized question + # Check if question is a recognised question format_q = self.get_profile_format_question( question.get('question')) if format_q is None: - LOGGER.error(f'Unrecognized question: {question.get("question")}') - return False + LOGGER.error(f'Unrecognised question: {question.get("question")}') + # Just ignore additional questions + continue # Error handling if 'answer' is missing if 'answer' not in question and valid: @@ -649,13 +771,14 @@ def delete_profile(self, profile): return True - except Exception as e: + except Exception as e: # pylint: disable=W0703 LOGGER.error('An error occurred whilst deleting a profile') LOGGER.debug(e) return False def reset(self): - self.set_status('Idle') + self.set_status(TestrunStatus.IDLE) + self.set_description(None) self.set_target_device(None) self._report_url = None self._total_tests = 0 @@ -688,6 +811,9 @@ def to_json(self): if self._report_url is not None: session_json['report'] = self.get_report_url() + if self._description is not None: + session_json['description'] = self._description + return session_json def get_timezone(self): @@ -789,7 +915,7 @@ def load_certs(self): self._certs.append(cert_obj) LOGGER.debug(f'Successfully loaded {cert_file}') - except Exception as e: + except Exception as e: # pylint: disable=W0703 LOGGER.error(f'An error occurred whilst loading {cert_file}') LOGGER.debug(e) @@ -809,7 +935,7 @@ def delete_cert(self, filename): self._certs.remove(cert) return True - except Exception as e: + except Exception as e: # pylint: disable=W0703 LOGGER.error('An error occurred whilst deleting the certificate') LOGGER.debug(e) return False diff --git a/framework/python/src/core/tasks.py b/framework/python/src/core/tasks.py new file mode 100644 index 000000000..5da0b40c9 --- /dev/null +++ b/framework/python/src/core/tasks.py @@ -0,0 +1,78 @@ +# Copyright 2024 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +"""Periodic background tasks""" + +from contextlib import asynccontextmanager +import datetime +import logging + +from apscheduler.schedulers.asyncio import AsyncIOScheduler +from fastapi import FastAPI + +from common import logger + +# Check adapters period seconds +# Check adapters period seconds +CHECK_NETWORK_ADAPTERS_PERIOD = 5 +CHECK_INTERNET_PERIOD = 2 +INTERNET_CONNECTION_TOPIC = 'events/internet' +NETWORK_ADAPTERS_TOPIC = 'events/adapter' + +LOGGER = logger.get_logger('tasks') + + +class PeriodicTasks: + """Background periodic tasks + """ + def __init__( + self, testrun_obj, + ) -> None: + self._testrun = testrun_obj + self._mqtt_client = self._testrun.get_mqtt_client() + local_tz = datetime.datetime.now().astimezone().tzinfo + self._scheduler = AsyncIOScheduler(timezone=local_tz) + # Prevent scheduler warnings + self._scheduler._logger.setLevel(logging.ERROR) + + self.adapters_checker_job = self._scheduler.add_job( + func=self._testrun.get_net_orc().network_adapters_checker, + kwargs={ + 'mqtt_client': self._mqtt_client, + 'topic': NETWORK_ADAPTERS_TOPIC + }, + trigger='interval', + seconds=CHECK_NETWORK_ADAPTERS_PERIOD, + ) + # add internet connection cheking job only in single-intf mode + if 'single_intf' not in self._testrun.get_session().get_runtime_params(): + self.internet_shecker = self._scheduler.add_job( + func=self._testrun.get_net_orc().internet_conn_checker, + kwargs={ + 'mqtt_client': self._mqtt_client, + 'topic': INTERNET_CONNECTION_TOPIC + }, + trigger='interval', + seconds=CHECK_INTERNET_PERIOD, + ) + + @asynccontextmanager + async def start(self, app: FastAPI): # pylint: disable=unused-argument + """Start background tasks + + Args: + app (FastAPI): app instance + """ + # Job that checks for changes in network adapters + self._scheduler.start() + yield diff --git a/framework/python/src/core/test_runner.py b/framework/python/src/core/test_runner.py index 870e97752..a295f47e1 100644 --- a/framework/python/src/core/test_runner.py +++ b/framework/python/src/core/test_runner.py @@ -39,7 +39,7 @@ def __init__(self, single_intf=False, no_ui=False): self._register_exits() - self.test_run = Testrun(config_file=config_file, + self._testrun = Testrun(config_file=config_file, validate=validate, net_only=net_only, single_intf=single_intf, @@ -62,7 +62,7 @@ def _exit_handler(self, signum, arg): # pylint: disable=unused-argument sys.exit(1) def stop(self): - self.test_run.stop() + self._testrun.stop() def parse_args(): diff --git a/framework/python/src/core/testrun.py b/framework/python/src/core/testrun.py index dccde6a35..1855b71b5 100644 --- a/framework/python/src/core/testrun.py +++ b/framework/python/src/core/testrun.py @@ -12,13 +12,9 @@ # See the License for the specific language governing permissions and # limitations under the License. -"""The overall control of the Test Run application. - +"""The overall control of the Testrun application. This file provides the integration between all of the Testrun components, such as net_orc, test_orc and test_ui. - -Run using the provided command scripts in the cmd folder. -E.g sudo cmd/start """ import docker import json @@ -29,8 +25,9 @@ import time from common import logger, util, mqtt from common.device import Device -from common.session import TestrunSession from common.testreport import TestReport +from common.statuses import TestrunStatus +from session import TestrunSession from api.api import Api from net_orc.listener import NetworkEvent from net_orc import network_orchestrator as net_orc @@ -38,14 +35,7 @@ from docker.errors import ImageNotFound -# Locate parent directory -current_dir = os.path.dirname(os.path.realpath(__file__)) - -# Locate the test-run root directory, 4 levels, src->python->framework->test-run -root_dir = os.path.dirname(os.path.dirname( - os.path.dirname(os.path.dirname(current_dir)))) - -LOGGER = logger.get_logger('test_run') +LOGGER = logger.get_logger('testrun') DEFAULT_CONFIG_FILE = 'local/system.json' EXAMPLE_CONFIG_FILE = 'local/system.json.example' @@ -58,10 +48,15 @@ DEVICE_MODEL = 'model' DEVICE_MAC_ADDR = 'mac_addr' DEVICE_TEST_MODULES = 'test_modules' +DEVICE_TYPE_KEY = 'type' +DEVICE_TECHNOLOGY_KEY = 'technology' +DEVICE_TEST_PACK_KEY = 'test_pack' +DEVICE_ADDITIONAL_INFO_KEY = 'additional_info' + MAX_DEVICE_REPORTS_KEY = 'max_device_reports' class Testrun: # pylint: disable=too-few-public-methods - """Test Run controller. + """Testrun controller. Creates an instance of the network orchestrator, test orchestrator and user interface. @@ -74,6 +69,15 @@ def __init__(self, single_intf=False, no_ui=False): + # Locate parent directory + current_dir = os.path.dirname(os.path.realpath(__file__)) + + # Locate the test-run root directory, 4 levels, + # src->python->framework->test-run + self._root_dir = os.path.dirname(os.path.dirname( + os.path.dirname(os.path.dirname(current_dir)))) + + # Determine config file if config_file is None: self._config_file = self._get_config_abs(DEFAULT_CONFIG_FILE) else: @@ -89,7 +93,7 @@ def __init__(self, self._register_exits() # Create session - self._session = TestrunSession(root_dir=root_dir) + self._session = TestrunSession(root_dir=self._root_dir) # Register runtime parameters if single_intf: @@ -139,6 +143,9 @@ def __init__(self, while True: time.sleep(1) + def get_root_dir(self): + return self._root_dir + def get_version(self): return self.get_session().get_version() @@ -165,7 +172,7 @@ def _load_devices(self, device_dir): # Check if device config file exists before loading if not os.path.exists(device_config_file_path): LOGGER.error('Device configuration file missing ' + - f'from device {device_folder}') + f'for device {device_folder}') continue # Open device config file @@ -177,6 +184,8 @@ def _load_devices(self, device_dir): device_model = device_config_json.get(DEVICE_MODEL) mac_addr = device_config_json.get(DEVICE_MAC_ADDR) test_modules = device_config_json.get(DEVICE_TEST_MODULES) + + # Load max device reports max_device_reports = None if 'max_device_reports' in device_config_json: max_device_reports = device_config_json.get(MAX_DEVICE_REPORTS_KEY) @@ -191,6 +200,25 @@ def _load_devices(self, device_dir): max_device_reports=max_device_reports, device_folder=device_folder) + # Load in the additional fields + if DEVICE_TYPE_KEY in device_config_json: + device.type = device_config_json.get(DEVICE_TYPE_KEY) + + if DEVICE_TECHNOLOGY_KEY in device_config_json: + device.technology = device_config_json.get(DEVICE_TECHNOLOGY_KEY) + + if DEVICE_TEST_PACK_KEY in device_config_json: + device.test_pack = device_config_json.get(DEVICE_TEST_PACK_KEY) + + if DEVICE_ADDITIONAL_INFO_KEY in device_config_json: + device.additional_info = device_config_json.get( + DEVICE_ADDITIONAL_INFO_KEY) + + if None in [device.type, device.technology, device.test_pack]: + LOGGER.warning( + 'Device is outdated and requires further configuration') + device.status = 'Invalid' + self._load_test_reports(device) # Add device to device repository @@ -200,22 +228,19 @@ def _load_devices(self, device_dir): def _load_test_reports(self, device): - LOGGER.debug(f'Loading test reports for device {device.model}') + LOGGER.debug('Loading test reports for device ' + + f'{device.manufacturer} {device.model}') # Remove the existing reports in memory device.clear_reports() # Locate reports folder - reports_folder = os.path.join(root_dir, - LOCAL_DEVICES_DIR, - device.device_folder, 'reports') + reports_folder = self.get_reports_folder(device) # Check if reports folder exists (device may have no reports) if not os.path.exists(reports_folder): return - LOGGER.info(f'Loading reports from {reports_folder}') - for report_folder in os.listdir(reports_folder): # 1.3 file path report_json_file_path = os.path.join( @@ -251,18 +276,18 @@ def _load_test_reports(self, device): test_report.set_mac_addr(device.mac_addr) device.add_report(test_report) + def get_reports_folder(self, device): + """Return the reports folder path for the device""" + return os.path.join(self._root_dir, + LOCAL_DEVICES_DIR, + device.device_folder, 'reports') + def delete_report(self, device: Device, timestamp): LOGGER.debug(f'Deleting test report for device {device.model} ' + f'at {timestamp}') # Locate reports folder - reports_folder = os.path.join(root_dir, - LOCAL_DEVICES_DIR, - device.device_folder, 'reports') - - # Check if reports folder exists (device may have no reports) - if not os.path.exists(reports_folder): - return False + reports_folder = self.get_reports_folder(device) for report_folder in os.listdir(reports_folder): if report_folder == timestamp: @@ -276,7 +301,7 @@ def delete_report(self, device: Device, timestamp): def create_device(self, device: Device): # Define the device folder location - device_folder_path = os.path.join(root_dir, + device_folder_path = os.path.join(self._root_dir, LOCAL_DEVICES_DIR, device.device_folder) @@ -297,20 +322,11 @@ def create_device(self, device: Device): return device.to_config_json() - def save_device(self, device: Device, device_json): + def save_device(self, device: Device): """Edit and save an existing device config.""" - # Update device properties - device.manufacturer = device_json['manufacturer'] - device.model = device_json['model'] - - if 'test_modules' in device_json: - device.test_modules = device_json['test_modules'] - else: - device.test_modules = {} - # Obtain the config file path - config_file_path = os.path.join(root_dir, + config_file_path = os.path.join(self._root_dir, LOCAL_DEVICES_DIR, device.device_folder, DEVICE_CONFIG) @@ -326,7 +342,7 @@ def save_device(self, device: Device, device_json): def delete_device(self, device: Device): # Obtain the config file path - device_folder = os.path.join(root_dir, + device_folder = os.path.join(self._root_dir, LOCAL_DEVICES_DIR, device.device_folder) @@ -364,15 +380,21 @@ def start(self): def stop(self): + # First, change the status to stopping + self.get_session().stop() + # Prevent discovering new devices whilst stopping if self.get_net_orc().get_listener() is not None: self.get_net_orc().get_listener().stop_listener() - self.get_session().stop() - self._stop_tests() + + self.get_session().set_status(TestrunStatus.CANCELLED) + + # Disconnect before WS server stops to prevent error + self._mqtt_client.disconnect() + self._stop_network(kill=True) - self.get_session().set_status('Cancelled') def _register_exits(self): signal.signal(signal.SIGINT, self._exit_handler) @@ -396,14 +418,11 @@ def _exit_handler(self, signum, arg): # pylint: disable=unused-argument def _get_config_abs(self, config_file=None): if config_file is None: # If not defined, use relative pathing to local file - config_file = os.path.join(root_dir, self._config_file) + config_file = os.path.join(self._root_dir, self._config_file) # Expand the config file to absolute pathing return os.path.abspath(config_file) - def get_root_dir(self): - return root_dir - def get_config_file(self): return self._get_config_abs() @@ -457,16 +476,17 @@ def _device_discovered(self, mac_addr): def _device_stable(self, mac_addr): # Do not continue testing if Testrun has cancelled during monitor phase - if self.get_session().get_status() == 'Cancelled': + if self.get_session().get_status() == TestrunStatus.CANCELLED: self._stop_network() return LOGGER.info(f'Device with mac address {mac_addr} is ready for testing.') - self._set_status('In Progress') + self._set_status(TestrunStatus.IN_PROGRESS) result = self._test_orc.run_test_modules() if result is not None: self._set_status(result) + self._stop_network() def get_session(self): @@ -513,7 +533,6 @@ def _stop_ui(self): except docker.errors.NotFound: pass - def start_ws(self): self._stop_ws() diff --git a/framework/python/src/net_orc/ip_control.py b/framework/python/src/net_orc/ip_control.py index 04686f0cd..aa07283af 100644 --- a/framework/python/src/net_orc/ip_control.py +++ b/framework/python/src/net_orc/ip_control.py @@ -17,6 +17,7 @@ from common import logger from common import util import re +import socket LOGGER = logger.get_logger('ip_ctrl') @@ -88,6 +89,16 @@ def get_iface_connection_stats(self, iface): else: return None + @staticmethod + def get_iface_mac_address(iface): + net_if_addrs = psutil.net_if_addrs() + if iface in net_if_addrs: + for addr_info in net_if_addrs[iface]: + # AF_LINK corresponds to the MAC address + if addr_info.family == psutil.AF_LINK: + return addr_info.address + return None + def get_iface_port_stats(self, iface): """Extract information about packets connection""" response = util.run_command(f'ethtool -S {iface}') @@ -96,6 +107,14 @@ def get_iface_port_stats(self, iface): else: return None + def get_ip_address(self, iface): + addrs = psutil.net_if_addrs() + if iface in addrs: + for addr in addrs[iface]: + if addr.family == socket.AF_INET: + return addr.address + return None + def get_namespaces(self): result = util.run_command('ip netns list') # Strip ID's from the namespace results diff --git a/framework/python/src/net_orc/network_orchestrator.py b/framework/python/src/net_orc/network_orchestrator.py index a94bca89b..b8e7befd2 100644 --- a/framework/python/src/net_orc/network_orchestrator.py +++ b/framework/python/src/net_orc/network_orchestrator.py @@ -17,19 +17,20 @@ import json import os from scapy.all import sniff, wrpcap, BOOTP, AsyncSniffer +from scapy.error import Scapy_Exception import shutil import subprocess import sys -import docker import time import traceback -from docker.types import Mount from common import logger, util, mqtt +from common.statuses import TestrunStatus from net_orc.listener import Listener from net_orc.network_event import NetworkEvent from net_orc.network_validator import NetworkValidator from net_orc.ovs_control import OVSControl from net_orc.ip_control import IPControl +from core.docker.network_docker_module import NetworkModule LOGGER = logger.get_logger('net_orc') RUNTIME_DIR = 'runtime' @@ -67,6 +68,10 @@ def __init__(self, session): self._ovs = OVSControl(self._session) self._ip_ctrl = IPControl() + # Load subnet information into the session + self._session.set_subnets(self.network_config.ipv4_network, + self.network_config.ipv6_network) + def start(self): """Start the network orchestrator.""" @@ -138,6 +143,9 @@ def start_network(self): # Get network ready (via Network orchestrator) LOGGER.debug('Network is ready') + def get_ip_address(self, iface): + return self._ip_ctrl.get_ip_address(iface) + def get_listener(self): return self._listener @@ -191,7 +199,7 @@ def _device_discovered(self, mac_addr): test_dir = os.path.join(RUNTIME_DIR, TEST_DIR) device_tests = os.listdir(test_dir) for device_test in device_tests: - device_test_path = os.path.join(RUNTIME_DIR,TEST_DIR,device_test) + device_test_path = os.path.join(RUNTIME_DIR, TEST_DIR, device_test) if os.path.isdir(device_test_path): shutil.rmtree(device_test_path, ignore_errors=True) @@ -216,7 +224,7 @@ def _device_discovered(self, mac_addr): if device.ip_addr is None: LOGGER.info( f'Timed out whilst waiting for {mac_addr} to obtain an IP address') - self._session.set_status('Cancelled') + self._session.set_status(TestrunStatus.CANCELLED) return LOGGER.info( f'Device with mac addr {device.mac_addr} has obtained IP address ' @@ -276,7 +284,7 @@ def _dhcp_lease_ack(self, packet): def _start_device_monitor(self, device): """Start a timer until the steady state has been reached and callback the steady state method for this device.""" - self.get_session().set_status('Monitoring') + self.get_session().set_status(TestrunStatus.MONITORING) self._monitor_packets = [] LOGGER.info(f'Monitoring device with mac addr {device.mac_addr} ' f'for {str(self._session.get_monitor_period())} seconds') @@ -293,15 +301,22 @@ def _start_device_monitor(self, device): time.sleep(1) # Check Testrun hasn't been cancelled - if self._session.get_status() == 'Cancelled': + if self._session.get_status() in ( + TestrunStatus.STOPPING, + TestrunStatus.CANCELLED + ): sniffer.stop() return if not self._ip_ctrl.check_interface_status( self._session.get_device_interface()): - sniffer.stop() - self._session.set_status('Cancelled') - LOGGER.error('Device interface disconnected, cancelling Testrun') + try: + sniffer.stop() + except Scapy_Exception: + LOGGER.error('Device adapter disconnected whilst monitoring.') + finally: + self._session.set_status(TestrunStatus.CANCELLED) + LOGGER.error('Device interface disconnected, cancelling Testrun') LOGGER.debug('Writing packets to monitor.pcap') wrpcap(os.path.join(device_runtime_dir, 'monitor.pcap'), @@ -333,26 +348,6 @@ def _ping(self, net_module): success = util.run_command(cmd, output=False) return success - def _create_private_net(self): - client = docker.from_env() - try: - network = client.networks.get(PRIVATE_DOCKER_NET) - network.remove() - except docker.errors.NotFound: - pass - - # TODO: These should be made into variables - ipam_pool = docker.types.IPAMPool(subnet='100.100.0.0/16', - iprange='100.100.100.0/24') - - ipam_config = docker.types.IPAMConfig(pool_configs=[ipam_pool]) - - client.networks.create(PRIVATE_DOCKER_NET, - ipam=ipam_config, - internal=True, - check_duplicate=True, - driver='macvlan') - def _ci_pre_network_create(self): """ Stores network properties to restore network after network creation and flushes internet interface @@ -439,79 +434,33 @@ def load_network_modules(self): for module_dir in os.listdir(net_modules_dir): - if self._get_network_module(module_dir) is None: + if (self._get_network_module(module_dir) is None and + module_dir != 'template'): loaded_module = self._load_network_module(module_dir) loaded_modules += loaded_module.dir_name + ' ' LOGGER.info(loaded_modules) def _load_network_module(self, module_dir): + """Import module configuration from module_config.json.""" - net_modules_dir = os.path.join(self._path, NETWORK_MODULES_DIR) + # Make sure we only load each module once since some modules will + # depend on the same module + if not any(m.dir_name == module_dir for m in self._net_modules): + + LOGGER.debug(f'Loading network module {module_dir}') - net_module = NetworkModule() - - # Load module information - with open(os.path.join(self._path, net_modules_dir, module_dir, - NETWORK_MODULE_METADATA), - 'r', - encoding='UTF-8') as module_file_open: - net_module_json = json.load(module_file_open) - - net_module.name = net_module_json['config']['meta']['name'] - net_module.display_name = net_module_json['config']['meta']['display_name'] - net_module.description = net_module_json['config']['meta']['description'] - net_module.dir = os.path.join(self._path, net_modules_dir, module_dir) - net_module.dir_name = module_dir - net_module.build_file = module_dir + '.Dockerfile' - net_module.container_name = 'tr-ct-' + net_module.dir_name - net_module.image_name = 'test-run/' + net_module.dir_name - - # Attach folder mounts to network module - if 'docker' in net_module_json['config']: - - if 'mounts' in net_module_json['config']['docker']: - for mount_point in net_module_json['config']['docker']['mounts']: - net_module.mounts.append( - Mount(target=mount_point['target'], - source=os.path.join(os.getcwd(), mount_point['source']), - type='bind')) - - if 'depends_on' in net_module_json['config']['docker']: - depends_on_module = net_module_json['config']['docker']['depends_on'] - if self._get_network_module(depends_on_module) is None: - self._load_network_module(depends_on_module) - - # Determine if this is a container or just an image/template - if 'enable_container' in net_module_json['config']['docker']: - net_module.enable_container = net_module_json['config']['docker'][ - 'enable_container'] - - # Determine if this is a template - if 'template' in net_module_json['config']['docker']: - net_module.template = net_module_json['config']['docker']['template'] - - # Load network service networking configuration - if net_module.enable_container: - - net_module.net_config.enable_wan = net_module_json['config']['network'][ - 'enable_wan'] - net_module.net_config.ip_index = net_module_json['config']['network'][ - 'ip_index'] - - net_module.net_config.host = False if not 'host' in net_module_json[ - 'config']['network'] else net_module_json['config']['network']['host'] - - net_module.net_config.ipv4_address = self.network_config.ipv4_network[ - net_module.net_config.ip_index] - net_module.net_config.ipv4_network = self.network_config.ipv4_network - - net_module.net_config.ipv6_address = self.network_config.ipv6_network[ - net_module.net_config.ip_index] - net_module.net_config.ipv6_network = self.network_config.ipv6_network - - self._net_modules.append(net_module) - return net_module + modules_dir = os.path.join(self._path, NETWORK_MODULES_DIR) + + module_conf_file = os.path.join(self._path, modules_dir, module_dir, + NETWORK_MODULE_METADATA) + + module = NetworkModule(module_conf_file, self._session) + if module.depends_on is not None: + self._load_network_module(module.depends_on) + self._net_modules.append(module) + + return module def build_network_modules(self): LOGGER.info('Building network modules...') @@ -521,12 +470,7 @@ def build_network_modules(self): def _build_module(self, net_module): LOGGER.debug('Building network module ' + net_module.dir_name) - client = docker.from_env() - client.images.build(dockerfile=os.path.join(net_module.dir, - net_module.build_file), - path=self._path, - forcerm=True, - tag='test-run/' + net_module.dir_name) + net_module.build() def _get_network_module(self, name): for net_module in self._net_modules: @@ -538,61 +482,17 @@ def _get_network_module(self, name): def _start_network_service(self, net_module): LOGGER.debug('Starting network service ' + net_module.display_name) - network = 'host' if net_module.net_config.host else PRIVATE_DOCKER_NET + network = 'host' if net_module.net_config.host else 'bridge' LOGGER.debug(f"""Network: {network}, image name: {net_module.image_name}, container name: {net_module.container_name}""") - try: - client = docker.from_env() - net_module.container = client.containers.run( - net_module.image_name, - auto_remove=True, - cap_add=['NET_ADMIN'], - name=net_module.container_name, - hostname=net_module.container_name, - network_mode='none', - privileged=True, - detach=True, - mounts=net_module.mounts, - environment={ - 'TZ': self.get_session().get_timezone(), - 'HOST_USER': util.get_host_user() - }) - except docker.errors.ContainerError as error: - LOGGER.error('Container run error') - LOGGER.error(error) - - if network != 'host': + net_module.start() + if net_module.get_network() != 'host': self._attach_service_to_network(net_module) def _stop_service_module(self, net_module, kill=False): LOGGER.debug('Stopping network container ' + net_module.container_name) - try: - container = self._get_service_container(net_module) - if container is not None: - if kill: - LOGGER.debug('Killing container: ' + net_module.container_name) - container.kill() - else: - LOGGER.debug('Stopping container: ' + net_module.container_name) - container.stop() - LOGGER.debug('Container stopped: ' + net_module.container_name) - except Exception as error: # pylint: disable=W0703 - LOGGER.error('Container stop error') - LOGGER.error(error) - - def _get_service_container(self, net_module): - LOGGER.debug('Resolving service container: ' + net_module.container_name) - container = None - try: - client = docker.from_env() - container = client.containers.get(net_module.container_name) - except docker.errors.NotFound: - LOGGER.debug('Container ' + net_module.container_name + ' not found') - except Exception as e: # pylint: disable=W0703 - LOGGER.error('Failed to resolve container') - LOGGER.error(e) - return container + net_module.stop(kill=kill) def stop_networking_services(self, kill=False): LOGGER.info('Stopping network services') @@ -757,13 +657,10 @@ def restore_net(self): if self.get_listener() is not None and self.get_listener().is_running(): self.get_listener().stop_listener() - client = docker.from_env() - # Stop all network containers if still running for net_module in self._net_modules: try: - container = client.containers.get('tr-ct-' + net_module.dir_name) - container.kill() + net_module.stop(kill=True) except Exception: # pylint: disable=W0703 continue @@ -793,7 +690,7 @@ def network_adapters_checker(self, mqtt_client: mqtt.MQTT, topic: str): adapters = self._session.detect_network_adapters_change() if adapters: mqtt_client.send_message(topic, adapters) - except Exception: + except Exception: # pylint: disable=W0703 LOGGER.error(traceback.format_exc()) def is_device_connected(self): @@ -805,22 +702,26 @@ def is_device_connected(self): def internet_conn_checker(self, mqtt_client: mqtt.MQTT, topic: str): """Checks internet connection and sends a status to frontend""" - # Only check if Testrun is running not in single-intf mode - if (self.get_session().get_status() in [ - 'Waiting for Device', - 'Monitoring', - 'In Progress' - ]): - # Default message - message = {'connection': False} + # Default message + message = {'connection': False} + + # Only check if Testrun is running + if self.get_session().get_status() not in [ + TestrunStatus.WAITING_FOR_DEVICE, + TestrunStatus.MONITORING, + TestrunStatus.IN_PROGRESS + ]: + message['connection'] = None + + # Only run if single intf mode not used + elif 'single_intf' not in self._session.get_runtime_params(): iface = self._session.get_internet_interface() # Check that an internet intf has been selected if iface and iface in self._ip_ctrl.get_sys_interfaces(): # Ping google.com from gateway container - internet_connection = self._ip_ctrl.ping_via_gateway( - 'google.com') + internet_connection = self._ip_ctrl.ping_via_gateway('google.com') if internet_connection: message['connection'] = True @@ -828,53 +729,6 @@ def internet_conn_checker(self, mqtt_client: mqtt.MQTT, topic: str): # Broadcast via MQTT client mqtt_client.send_message(topic, message) -class NetworkModule: - """Define all the properties of a Network Module""" - - def __init__(self): - self.name = None - self.display_name = None - self.description = None - - self.container = None - self.container_name = None - self.image_name = None - self.template = False - - # Absolute path - self.dir = None - self.dir_name = None - self.build_file = None - self.mounts = [] - - self.enable_container = True - - self.net_config = NetworkModuleNetConfig() - - -class NetworkModuleNetConfig: - """Define all the properties of the network config - for a network module""" - - def __init__(self): - - self.enable_wan = False - - self.ip_index = 0 - self.ipv4_address = None - self.ipv4_network = None - self.ipv6_address = None - self.ipv6_network = None - - self.host = False - - def get_ipv4_addr_with_prefix(self): - return format(self.ipv4_address) + '/' + str(self.ipv4_network.prefixlen) - - def get_ipv6_addr_with_prefix(self): - return format(self.ipv6_address) + '/' + str(self.ipv6_network.prefixlen) - - class NetworkConfig: """Define all the properties of the network configuration""" diff --git a/framework/python/src/net_orc/network_validator.py b/framework/python/src/net_orc/network_validator.py index df9b96b1d..d760970a3 100644 --- a/framework/python/src/net_orc/network_validator.py +++ b/framework/python/src/net_orc/network_validator.py @@ -106,7 +106,7 @@ def _load_devices(self): device.dir_name = module_dir device.build_file = module_dir + '.Dockerfile' device.container_name = 'tr-ct-' + device.dir_name - device.image_name = 'test-run/' + device.dir_name + device.image_name = 'testrun/' + device.dir_name runtime_source = os.path.join(os.getcwd(), OUTPUT_DIR, device.name) conf_source = os.path.join(os.getcwd(), CONF_DIR) diff --git a/framework/python/src/test_orc/test_case.py b/framework/python/src/test_orc/test_case.py index cf0d6593a..6f4e3434b 100644 --- a/framework/python/src/test_orc/test_case.py +++ b/framework/python/src/test_orc/test_case.py @@ -14,6 +14,7 @@ """Represents an individual test case.""" from dataclasses import dataclass, field +from common.statuses import TestResult @dataclass @@ -24,25 +25,25 @@ class TestCase: # pylint: disable=too-few-public-methods,too-many-instance-attr description: str = "" expected_behavior: str = "" required_result: str = "Recommended" - result: str = "Non-Compliant" + result: str = TestResult.NON_COMPLIANT recommendations: list = field(default_factory=lambda: []) + optional_recommendations: list = field(default_factory=lambda: []) def to_dict(self): + test_dict = { + "name": self.name, + "description": self.description, + "expected_behavior": self.expected_behavior, + "required_result": self.required_result, + "result": self.result + } + if self.recommendations is not None and len(self.recommendations) > 0: - return { - "name": self.name, - "description": self.description, - "expected_behavior": self.expected_behavior, - "required_result": self.required_result, - "result": self.result, - "recommendations": self.recommendations - } - - return { - "name": self.name, - "description": self.description, - "expected_behavior": self.expected_behavior, - "required_result": self.required_result, - "result": self.result - } + test_dict["recommendations"] = self.recommendations + + if (self.optional_recommendations is not None + and len(self.optional_recommendations) > 0): + test_dict["optional_recommendations"] = self.optional_recommendations + + return test_dict diff --git a/framework/python/src/test_orc/test_orchestrator.py b/framework/python/src/test_orc/test_orchestrator.py index a38371d07..4085e91ad 100644 --- a/framework/python/src/test_orc/test_orchestrator.py +++ b/framework/python/src/test_orc/test_orchestrator.py @@ -20,23 +20,32 @@ import shutil import docker from datetime import datetime -from docker.types import Mount from common import logger, util from common.testreport import TestReport -from test_orc.module import TestModule +from common.statuses import TestrunStatus, TestResult +from core.docker.test_docker_module import TestModule from test_orc.test_case import TestCase +from test_orc.test_pack import TestPack import threading +from typing import List LOG_NAME = "test_orc" LOGGER = logger.get_logger("test_orc") + RUNTIME_DIR = "runtime" -RUNTIME_TEST_DIR = os.path.join(RUNTIME_DIR,"test") +RESOURCES_DIR = "resources" + +RUNTIME_TEST_DIR = os.path.join(RUNTIME_DIR, "test") +TEST_PACKS_DIR = os.path.join(RESOURCES_DIR, "test_packs") + TEST_MODULES_DIR = "modules/test" MODULE_CONFIG = "conf/module_config.json" -LOG_REGEX = r"^[A-Z][a-z]{2} [0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2} test_" + SAVED_DEVICE_REPORTS = "report/{device_folder}/" LOCAL_DEVICE_REPORTS = "local/devices/{device_folder}/reports" DEVICE_ROOT_CERTS = "local/root_certs" + +LOG_REGEX = r"^[A-Z][a-z]{2} [0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2} test_" API_URL = "http://localhost:8000" @@ -44,17 +53,18 @@ class TestOrchestrator: """Manages and controls the test modules.""" def __init__(self, session, net_orc): - self._test_modules = [] + + self._test_modules: List[TestModule] = [] + self._test_packs: List[TestPack] = [] + self._container_logs = [] self._session = session - self._api_url = (self._session.get_api_url() + ":" + - str(self._session.get_api_port())) + + self._api_url = (self.get_session().get_api_url() + ":" + + str(self.get_session().get_api_port())) + self._net_orc = net_orc self._test_in_progress = False - self._path = os.path.dirname( - os.path.dirname( - os.path.dirname( - os.path.dirname(os.path.dirname(os.path.realpath(__file__)))))) self._root_path = os.path.dirname( os.path.dirname( @@ -75,6 +85,7 @@ def start(self): os.makedirs(DEVICE_ROOT_CERTS, exist_ok=True) self._load_test_modules() + self._load_test_packs() def stop(self): """Stop any running tests""" @@ -84,24 +95,55 @@ def run_test_modules(self): """Iterates through each test module and starts the container.""" # Do not start test modules if status is not in progress, e.g. Stopping - if self.get_session().get_status() != "In Progress": + if self.get_session().get_status() != TestrunStatus.IN_PROGRESS: return - device = self._session.get_target_device() + device = self.get_session().get_target_device() + test_pack_name = device.test_pack + test_pack = self.get_test_pack(test_pack_name) + LOGGER.debug("Using test pack " + test_pack.name) + self._test_in_progress = True + LOGGER.info( f"Running test modules on device with mac addr {device.mac_addr}") test_modules = [] + for module in self._test_modules: + # Ignore test modules that are just base images etc if module is None or not module.enable_container: continue + # Ignore test modules that are disabled for this device if not self._is_module_enabled(module, device): continue + # Add module to list of modules to run test_modules.append(module) + + for test in module.tests: + + # Duplicate test obj so we don't alter the source + test_copy = copy.deepcopy(test) + + # Set result to Not Started + test_copy.result = TestResult.NOT_STARTED + + # We don't want steps to resolve for not started tests + if hasattr(test_copy, "recommendations"): + test_copy.recommendations = None + + # Set the required result from the correct test pack + required_result = test_pack.get_required_result(test.name) + + test_copy.required_result = required_result + + # Add test result to the session + self.get_session().add_test_result(test_copy) + + # Increment number of tests that will be run self.get_session().add_total_tests(len(module.tests)) # Store enabled test modules in the TestsOrchectrator object @@ -115,14 +157,16 @@ def run_test_modules(self): LOGGER.info("All tests complete") - self._session.finish() + self.get_session().finish() # Do not carry on (generating a report) if Testrun has been stopped - if self.get_session().get_status() != "In Progress": - return "Cancelled" + if self.get_session().get_status() != TestrunStatus.IN_PROGRESS: + return TestrunStatus.CANCELLED report = TestReport() - report.from_json(self._generate_report()) + + generated_report_json = self._generate_report() + report.from_json(generated_report_json) report.add_module_reports(self.get_session().get_module_reports()) device.add_report(report) @@ -130,6 +174,19 @@ def run_test_modules(self): self._test_in_progress = False self.get_session().set_report_url(report.get_report_url()) + # Set testing description + test_pack: TestPack = self.get_test_pack(device.test_pack) + + # Default message is empty (better than an error message). + # This should never be shown + message: str = "" + if report.get_status() == TestrunStatus.COMPLIANT: + message = test_pack.get_message("compliant_description") + elif report.get_status() == TestrunStatus.NON_COMPLIANT: + message = test_pack.get_message("non_compliant_description") + + self.get_session().set_description(message) + # Move testing output from runtime to local device folder self._timestamp_results(device) @@ -144,7 +201,7 @@ def _write_reports(self, test_report): out_dir = os.path.join( self._root_path, RUNTIME_TEST_DIR, - self._session.get_target_device().mac_addr.replace(":", "")) + self.get_session().get_target_device().mac_addr.replace(":", "")) LOGGER.debug(f"Writing reports to {out_dir}") @@ -165,9 +222,7 @@ def _write_reports(self, test_report): def _generate_report(self): report = {} - report["testrun"] = { - "version": self.get_session().get_version() - } + report["testrun"] = {"version": self.get_session().get_version()} report["mac_addr"] = self.get_session().get_target_device().mac_addr report["device"] = self.get_session().get_target_device().to_dict() @@ -186,16 +241,22 @@ def _generate_report(self): return report def _calculate_result(self): - result = "Compliant" - for test_result in self._session.get_test_results(): + result = TestResult.COMPLIANT + for test_result in self.get_session().get_test_results(): + # Check Required tests if (test_result.required_result.lower() == "required" - and test_result.result.lower() != "compliant"): - result = "Non-Compliant" + and test_result.result not in [ + TestResult.COMPLIANT, + TestResult.ERROR + ]): + result = TestResult.NON_COMPLIANT + # Check Required if Applicable tests elif (test_result.required_result.lower() == "required if applicable" - and test_result.result.lower() == "non-compliant"): - result = "Non-Compliant" + and test_result.result == TestResult.NON_COMPLIANT): + result = TestResult.NON_COMPLIANT + return result def _cleanup_old_test_results(self, device): @@ -203,7 +264,7 @@ def _cleanup_old_test_results(self, device): if device.max_device_reports is not None: max_device_reports = device.max_device_reports else: - max_device_reports = self._session.get_max_device_reports() + max_device_reports = self.get_session().get_max_device_reports() if max_device_reports > 0: completed_results_dir = os.path.join( @@ -278,25 +339,20 @@ def _timestamp_results(self, device): return completed_results_dir - def zip_results(self, - device, - timestamp, - profile): + def zip_results(self, device, timestamp, profile): try: LOGGER.debug("Archiving test results") - src_path = os.path.join(LOCAL_DEVICE_REPORTS.replace( - "{device_folder}", - device.device_folder), - timestamp) + src_path = os.path.join( + LOCAL_DEVICE_REPORTS.replace("{device_folder}", device.device_folder), + timestamp) # Define temp directory to store files before zipping results_dir = os.path.join(f"/tmp/testrun/{time.time()}") # Define where to save the zip file - zip_location = os.path.join("/tmp/testrun", - timestamp) + zip_location = os.path.join("/tmp/testrun", timestamp) # Delete zip_temp if it already exists if os.path.exists(results_dir): @@ -306,16 +362,13 @@ def zip_results(self, if os.path.exists(zip_location + ".zip"): os.remove(zip_location + ".zip") - shutil.copytree(src_path,results_dir) + shutil.copytree(src_path, results_dir) # Include profile if specified if profile is not None: - LOGGER.debug( - f"Copying profile {profile.name} to results directory") + LOGGER.debug(f"Copying profile {profile.name} to results directory") shutil.copy(profile.get_file_path(), - os.path.join( - results_dir, - "profile.json")) + os.path.join(results_dir, "profile.json")) with open(os.path.join(results_dir, "profile.pdf"), "wb") as f: f.write(profile.to_pdf(device).getvalue()) @@ -328,14 +381,13 @@ def zip_results(self, # Check that the ZIP was successfully created zip_file = zip_location + ".zip" - LOGGER.info(f'''Archive {'created at ' + zip_file + LOGGER.info(f"""Archive {"created at " + zip_file if os.path.exists(zip_file) - else'creation failed'}''') - + else "creation failed"}""") return zip_file - except Exception as error: # pylint: disable=W0703 + except Exception as error: # pylint: disable=W0703 LOGGER.error("Failed to create zip file") LOGGER.debug(error) return None @@ -362,25 +414,28 @@ def _run_test_module(self, module): """Start the test container and extract the results.""" # Check that Testrun is not stopping - if self.get_session().get_status() != "In Progress": + if self.get_session().get_status() != TestrunStatus.IN_PROGRESS: return - device = self._session.get_target_device() + device = self.get_session().get_target_device() LOGGER.info(f"Running test module {module.name}") # Get all tests to be executed and set to in progress - for current_test,test in enumerate(module.tests): + for current_test, test in enumerate(module.tests): # Check that device is connected if not self._net_orc.is_device_connected(): LOGGER.error("Device was disconnected") self._set_test_modules_error(current_test) - self._session.set_status("Cancelled") + self.get_session().set_status(TestrunStatus.CANCELLED) return + # Copy the test so we don't alter the source test_copy = copy.deepcopy(test) - test_copy.result = "In Progress" + + # Update test status to in progress + test_copy.result = TestResult.IN_PROGRESS # We don't want steps to resolve for in progress tests if hasattr(test_copy, "recommendations"): @@ -388,76 +443,8 @@ def _run_test_module(self, module): self.get_session().add_test_result(test_copy) - try: - - device_test_dir = os.path.join(self._root_path, RUNTIME_TEST_DIR, - device.mac_addr.replace(":", "")) - - container_runtime_dir = os.path.join(device_test_dir, module.name) - os.makedirs(container_runtime_dir, exist_ok=True) - - config_file = os.path.join(self._root_path, "local/system.json") - root_certs_dir = os.path.join(self._root_path, "local/root_certs") - - container_log_file = os.path.join(container_runtime_dir, "module.log") - - network_runtime_dir = os.path.join(self._root_path, "runtime/network") - - device_startup_capture = os.path.join(device_test_dir, "startup.pcap") - util.run_command(f"chown -R {self._host_user} {device_startup_capture}") - - device_monitor_capture = os.path.join(device_test_dir, "monitor.pcap") - util.run_command(f"chown -R {self._host_user} {device_monitor_capture}") - - client = docker.from_env() - - module.container = client.containers.run( - module.image_name, - auto_remove=True, - cap_add=["NET_ADMIN"], - name=module.container_name, - hostname=module.container_name, - privileged=True, - detach=True, - mounts=[ - Mount(target="/testrun/system.json", - source=config_file, - type="bind", - read_only=True), - Mount(target="/testrun/root_certs", - source=root_certs_dir, - type="bind", - read_only=True), - Mount(target="/runtime/output", - source=container_runtime_dir, - type="bind"), - Mount(target="/runtime/network", - source=network_runtime_dir, - type="bind", - read_only=True), - Mount(target="/runtime/device/startup.pcap", - source=device_startup_capture, - type="bind", - read_only=True), - Mount(target="/runtime/device/monitor.pcap", - source=device_monitor_capture, - type="bind", - read_only=True) - ], - environment={ - "TZ": self.get_session().get_timezone(), - "HOST_USER": self._host_user, - "DEVICE_MAC": device.mac_addr, - "IPV4_ADDR": device.ip_addr, - "DEVICE_TEST_MODULES": json.dumps(device.test_modules), - "IPV4_SUBNET": self._net_orc.network_config.ipv4_network, - "IPV6_SUBNET": self._net_orc.network_config.ipv6_network - }) - except (docker.errors.APIError, - docker.errors.ContainerError) as container_error: - LOGGER.error("Test module " + module.name + " has failed to start") - LOGGER.debug(container_error) - return + # Start the test module + module.start(device) # Mount the test container to the virtual network if requried if module.network: @@ -466,7 +453,6 @@ def _run_test_module(self, module): # Determine the module timeout time test_module_timeout = time.time() + module.timeout - status = self._get_module_status(module) # Resolving container logs is blocking so we need to spawn a new thread log_stream = module.container.logs(stream=True, stdout=True, stderr=True) @@ -475,29 +461,27 @@ def _run_test_module(self, module): log_thread.daemon = True log_thread.start() - while (status == "running" and self._session.get_status() == "In Progress"): + while (module.get_status() == "running" + and self.get_session().get_status() == TestrunStatus.IN_PROGRESS): + + # Check that timeout has not exceeded if time.time() > test_module_timeout: LOGGER.error("Module timeout exceeded, killing module: " + module.name) - self._stop_module(module=module, kill=True) + module.stop(kill=True) break - status = self._get_module_status(module) # Save all container logs to file - with open(container_log_file, "w", encoding="utf-8") as f: + with open(module.container_log_file, "w", encoding="utf-8") as f: for line in self._container_logs: f.write(line + "\n") # Check that Testrun has not been stopped whilst this module was running - if self.get_session().get_status() == "Stopping": + if self.get_session().get_status() == TestrunStatus.STOPPING: # Discard results for this module LOGGER.info(f"Test module {module.name} has forcefully quit") return - # Get test results from module - container_runtime_dir = os.path.join( - self._root_path, - "runtime/test/" + device.mac_addr.replace(":", "") + "/" + module.name) - results_file = f"{container_runtime_dir}/{module.name}-result.json" + results_file = f"{module.container_runtime_dir}/{module.name}-result.json" try: with open(results_file, "r", encoding="utf-8-sig") as f: @@ -509,59 +493,54 @@ def _run_test_module(self, module): # Convert dict from json into TestCase object test_case = TestCase( - name=test_result["name"], - description=test_result["description"], - expected_behavior=test_result["expected_behavior"], - required_result=test_result["required_result"], - result=test_result["result"]) - - # Any informational test should always report informational - if test_case.required_result == "Informational": - test_case.result = "Informational" + name=test_result["name"], + result=test_result["result"], + description=test_result["description"]) # Add steps to resolve if test is non-compliant - if (test_case.result == "Non-Compliant" and + if (test_case.result == TestResult.NON_COMPLIANT and "recommendations" in test_result): test_case.recommendations = test_result["recommendations"] else: - test_case.recommendations = None + test_case.recommendations = [] - self._session.add_test_result(test_case) + self.get_session().add_test_result(test_case) except (FileNotFoundError, PermissionError, json.JSONDecodeError) as results_error: LOGGER.error( - f"Error occurred whilst obtaining results for module {module.name}") + f"Error occurred whilst obtaining results for module {module.name}") LOGGER.error(results_error) # Get the markdown report from the module if generated - markdown_file = f"{container_runtime_dir}/{module.name}_report.md" + markdown_file = f"{module.container_runtime_dir}/{module.name}_report.md" try: with open(markdown_file, "r", encoding="utf-8") as f: module_report = f.read() - self._session.add_module_report(module_report) + self.get_session().add_module_report(module_report) except (FileNotFoundError, PermissionError): LOGGER.debug("Test module did not produce a markdown module report") # Get the HTML report from the module if generated - html_file = f"{container_runtime_dir}/{module.name}_report.html" + html_file = f"{module.container_runtime_dir}/{module.name}_report.html" try: with open(html_file, "r", encoding="utf-8") as f: module_report = f.read() LOGGER.debug(f"Adding module report for module {module.name}") - self._session.add_module_report(module_report) + self.get_session().add_module_report(module_report) except (FileNotFoundError, PermissionError): LOGGER.debug("Test module did not produce a html module report") LOGGER.info(f"Test module {module.name} has finished") - # Resolve all current log data in the containers log_stream - # this method is blocking so should be called in - # a thread or within a proper blocking context def _get_container_logs(self, log_stream): + """Resolve all current log data in the containers log_stream + this method is blocking so should be called in + a thread or within a proper blocking context""" self._container_logs = [] for log_chunk in log_stream: lines = log_chunk.decode("utf-8").splitlines() + # Process each line and strip blank space processed_lines = [line.strip() for line in lines if line.strip()] self._container_logs.extend(processed_lines) @@ -595,12 +574,32 @@ def _get_module_container(self, module): LOGGER.error(error) return container + def _load_test_packs(self): + + for test_pack_file in os.listdir(TEST_PACKS_DIR): + + LOGGER.debug(f"Loading test pack {test_pack_file}") + + with open(os.path.join( + self._root_path, + TEST_PACKS_DIR, + test_pack_file), encoding="utf-8") as f: + test_pack_json = json.load(f) + + test_pack: TestPack = TestPack( + name = test_pack_json["name"], + tests = test_pack_json["tests"], + language = test_pack_json["language"] + ) + + self._test_packs.append(test_pack) + def _load_test_modules(self): """Load network modules from module_config.json.""" LOGGER.debug("Loading test modules from /" + TEST_MODULES_DIR) loaded_modules = "Loaded the following test modules: " - test_modules_dir = os.path.join(self._path, TEST_MODULES_DIR) + test_modules_dir = os.path.join(self._root_path, TEST_MODULES_DIR) module_dirs = os.listdir(test_modules_dir) # Check if the directory protocol exists and move it to the beginning @@ -620,87 +619,39 @@ def _load_test_modules(self): def _load_test_module(self, module_dir): """Import module configuration from module_config.json.""" - LOGGER.debug(f"Loading test module {module_dir}") + # Resolve the main docker interface (docker0) for host interaction + # Can't use device or internet iface since these are not in a stable + # state for this type of communication during testing but docker0 has + # to exist and should always be available + external_ip = self._net_orc.get_ip_address("docker0") + extra_hosts = { + "external.localhost": external_ip + } if external_ip is not None else {} - modules_dir = os.path.join(self._path, TEST_MODULES_DIR) + # Make sure we only load each module once since some modules will + # depend on the same module + if not any(m.dir_name == module_dir for m in self._test_modules): - # Load basic module information - module = TestModule() - with open(os.path.join(self._path, modules_dir, module_dir, MODULE_CONFIG), - encoding="UTF-8") as module_config_file: - module_json = json.load(module_config_file) + modules_dir = os.path.join(self._root_path, TEST_MODULES_DIR) - module.name = module_json["config"]["meta"]["name"] - module.display_name = module_json["config"]["meta"]["display_name"] - module.description = module_json["config"]["meta"]["description"] + module_conf_file = os.path.join(self._root_path, modules_dir, module_dir, + MODULE_CONFIG) - if "enabled" in module_json["config"]: - module.enabled = module_json["config"]["enabled"] + module = TestModule(module_conf_file, self.get_session(), extra_hosts) + if module.depends_on is not None: + self._load_test_module(module.depends_on) + self._test_modules.append(module) - module.dir = os.path.join(self._path, modules_dir, module_dir) - module.dir_name = module_dir - module.build_file = module_dir + ".Dockerfile" - module.container_name = "tr-ct-" + module.dir_name + "-test" - module.image_name = "test-run/" + module.dir_name + "-test" + return module - # Load test cases - if "tests" in module_json["config"]: - module.total_tests = len(module_json["config"]["tests"]) - for test_case_json in module_json["config"]["tests"]: - try: - test_case = TestCase( - name=test_case_json["name"], - description=test_case_json["test_description"], - expected_behavior=test_case_json["expected_behavior"], - required_result=test_case_json["required_result"] - ) + def get_test_packs(self) -> List[TestPack]: + return self._test_packs - if "recommendations" in test_case_json: - test_case.recommendations = test_case_json["recommendations"] - module.tests.append(test_case) - except Exception as error: # pylint: disable=W0718 - LOGGER.error("Failed to load test case. See error for details") - LOGGER.error(error) - - if "timeout" in module_json["config"]["docker"]: - module.timeout = module_json["config"]["docker"]["timeout"] - - # Determine if this is a container or just an image/template - if "enable_container" in module_json["config"]["docker"]: - module.enable_container = module_json["config"]["docker"][ - "enable_container"] - - # Determine if this module needs network access - if "network" in module_json["config"]: - module.network = module_json["config"]["network"] - - # Ensure container is built after any dependencies - if "depends_on" in module_json["config"]["docker"]: - depends_on_module = module_json["config"]["docker"]["depends_on"] - if self._get_test_module(depends_on_module) is None: - self._load_test_module(depends_on_module) - - self._test_modules.append(module) - return module - - def build_test_modules(self): - """Build all test modules.""" - LOGGER.info("Building test modules...") - for module in self._test_modules: - self._build_test_module(module) - - def _build_test_module(self, module): - LOGGER.debug("Building docker image for module " + module.dir_name) - - client = docker.from_env() - try: - client.images.build( - dockerfile=os.path.join(module.dir, module.build_file), - path=self._path, - forcerm=True, # Cleans up intermediate containers during build - tag=module.image_name) - except docker.errors.BuildError as error: - LOGGER.error(error) + def get_test_pack(self, name: str) -> TestPack: + for test_pack in self._test_packs: + if test_pack.name.lower() == name.lower(): + return test_pack + return None def _stop_modules(self, kill=False): LOGGER.info("Stopping test modules") @@ -713,18 +664,7 @@ def _stop_modules(self, kill=False): def _stop_module(self, module, kill=False): LOGGER.debug("Stopping test module " + module.container_name) - try: - container = module.container - if container is not None: - if kill: - LOGGER.debug("Killing container:" + module.container_name) - container.kill() - else: - LOGGER.debug("Stopping container:" + module.container_name) - container.stop() - LOGGER.debug("Container stopped:" + module.container_name) - except docker.errors.NotFound: - pass + module.stop(kill=kill) def get_test_modules(self): return self._test_modules @@ -759,4 +699,3 @@ def _set_test_modules_error(self, current_test): self.get_session().set_test_result_error( self._test_modules_running[i].tests[j] ) - diff --git a/framework/python/src/test_orc/test_pack.py b/framework/python/src/test_orc/test_pack.py new file mode 100644 index 000000000..b1e3d5d3c --- /dev/null +++ b/framework/python/src/test_orc/test_pack.py @@ -0,0 +1,42 @@ +# Copyright 2023 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Represents a testing pack.""" +from typing import List, Dict +from dataclasses import dataclass, field +from collections import defaultdict + + +@dataclass +class TestPack: # pylint: disable=too-few-public-methods,too-many-instance-attributes + """Represents a test pack.""" + + name: str = "undefined" + description: str = "" + tests: List[dict] = field(default_factory=lambda: []) + language: Dict = field(default_factory=lambda: defaultdict(dict)) + + def get_required_result(self, test_name: str) -> str: + + for test in self.tests: + if "name" in test and test["name"].lower() == test_name.lower(): + if "required_result" in test: + return test["required_result"] + + return "Informational" + + def get_message(self, name: str) -> str: + if name in self.language: + return self.language[name] + return "Message not found" diff --git a/framework/requirements.txt b/framework/requirements.txt index 0484905ee..fab17b071 100644 --- a/framework/requirements.txt +++ b/framework/requirements.txt @@ -39,3 +39,6 @@ paho-mqtt==2.1.0 # Requirements for background tasks APScheduler==3.10.4 + +# Requirements for reports generation +Jinja2==3.1.4 diff --git a/local/system.json.example b/local/system.json.example index 23023bead..df89b502f 100644 --- a/local/system.json.example +++ b/local/system.json.example @@ -1,10 +1,11 @@ { "network": { - "device_intf": "enx123456789123", - "internet_intf": "enx123456789124" + "device_intf": "", + "internet_intf": "" }, "log_level": "INFO", "startup_timeout": 60, "monitor_period": 300, - "max_device_reports": 0 + "max_device_reports": 0, + "org_name": "" } diff --git a/make/DEBIAN/control b/make/DEBIAN/control index cecad9d17..31922abde 100644 --- a/make/DEBIAN/control +++ b/make/DEBIAN/control @@ -1,5 +1,5 @@ Package: Testrun -Version: 1.4-a +Version: 2.0 Architecture: amd64 Maintainer: Google Homepage: https://github.com/google/testrun diff --git a/modules/devices/faux-dev/bin/start_network_service b/modules/devices/faux-dev/bin/start_network_service index d4bb8a92d..7d689f9dd 100644 --- a/modules/devices/faux-dev/bin/start_network_service +++ b/modules/devices/faux-dev/bin/start_network_service @@ -35,7 +35,7 @@ else INTF=$DEFINED_IFACE fi -#Create and set permissions on the output files +# Create and set permissions on the output files OUTPUT_DIR=/runtime/validation/ LOG_FILE=$OUTPUT_DIR/$MODULE_NAME.log RESULT_FILE=$OUTPUT_DIR/result.json diff --git a/modules/devices/faux-dev/faux-dev.Dockerfile b/modules/devices/faux-dev/faux-dev.Dockerfile index ecfdfc5c2..18901a2a1 100644 --- a/modules/devices/faux-dev/faux-dev.Dockerfile +++ b/modules/devices/faux-dev/faux-dev.Dockerfile @@ -12,13 +12,14 @@ # See the License for the specific language governing permissions and # limitations under the License. -# Image name: test-run/faux-dev -FROM test-run/base:latest +# Image name: testrun/faux-dev +FROM testrun/base:latest ARG MODULE_NAME=faux-dev ARG MODULE_DIR=modules/devices/$MODULE_NAME +ARG COMMON_DIR=framework/python/src/common -#Update and get all additional requirements not contained in the base image +# Update and get all additional requirements not contained in the base image RUN apt-get update --fix-missing # NTP requireds interactive installation so we're going to turn that off @@ -34,4 +35,4 @@ COPY $MODULE_DIR/conf /testrun/conf COPY $MODULE_DIR/bin /testrun/bin # Copy over all python files -COPY $MODULE_DIR/python /testrun/python \ No newline at end of file +COPY $MODULE_DIR/python /testrun/python diff --git a/modules/devices/faux-dev/python/src/logger.py b/modules/devices/faux-dev/python/src/logger.py deleted file mode 100644 index a727ad7bb..000000000 --- a/modules/devices/faux-dev/python/src/logger.py +++ /dev/null @@ -1,61 +0,0 @@ -# Copyright 2023 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -"""Sets up the logger to be used for the faux-device.""" - -import json -import logging -import os - -LOGGERS = {} -_LOG_FORMAT = '%(asctime)s %(name)-8s %(levelname)-7s %(message)s' -_DATE_FORMAT = '%b %02d %H:%M:%S' -_CONF_DIR = 'conf' -_CONF_FILE_NAME = 'system.json' -_LOG_DIR = '/runtime/validation' - -# Set log level -with open(os.path.join(_CONF_DIR, _CONF_FILE_NAME), - encoding='utf-8') as conf_file: - system_conf_json = json.load(conf_file) - -log_level_str = system_conf_json['log_level'] -log_level = logging.getLevelName(log_level_str) - -log_format = logging.Formatter(fmt=_LOG_FORMAT, datefmt=_DATE_FORMAT) - - -def add_file_handler(log, log_file): - """Add file handler to existing log.""" - handler = logging.FileHandler(os.path.join(_LOG_DIR, log_file + '.log')) - handler.setFormatter(log_format) - log.addHandler(handler) - - -def add_stream_handler(log): - """Add stream handler to existing log.""" - handler = logging.StreamHandler() - handler.setFormatter(log_format) - log.addHandler(handler) - - -def get_logger(name, log_file=None): - """Return logger for requesting class.""" - if name not in LOGGERS: - LOGGERS[name] = logging.getLogger(name) - LOGGERS[name].setLevel(log_level) - add_stream_handler(LOGGERS[name]) - if log_file is not None: - add_file_handler(LOGGERS[name], log_file) - return LOGGERS[name] diff --git a/modules/devices/faux-dev/python/src/util.py b/modules/devices/faux-dev/python/src/util.py deleted file mode 100644 index 81f9d2ced..000000000 --- a/modules/devices/faux-dev/python/src/util.py +++ /dev/null @@ -1,48 +0,0 @@ -# Copyright 2023 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -"""Provides basic utilities for the faux-device.""" -import subprocess -import shlex - - -def run_command(cmd, logger, output=True): - """Runs a process at the os level - By default, returns the standard output and error output - If the caller sets optional output parameter to False, - will only return a boolean result indicating if it was - successful in running the command. Failure is indicated - by any return code from the process other than zero.""" - - success = False - with subprocess.Popen( - shlex.split(cmd), - stdout=subprocess.PIPE, - stderr=subprocess.PIPE) as process: - - stdout, stderr = process.communicate() - - if process.returncode != 0: - err_msg = f'{stderr.strip()}. Code: {process.returncode}' - logger.error('Command Failed: ' + cmd) - logger.error('Error: ' + err_msg) - else: - success = True - logger.debug('Command succeeded: ' + cmd) - if output: - out = stdout.strip().decode('utf-8') - logger.debug('Command output: ' + out) - return success, out - else: - return success, None diff --git a/modules/network/base/base.Dockerfile b/modules/network/base/base.Dockerfile index b30f6a7d9..7f6edb409 100644 --- a/modules/network/base/base.Dockerfile +++ b/modules/network/base/base.Dockerfile @@ -12,8 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. -# Image name: test-run/base -FROM ubuntu@sha256:e6173d4dc55e76b87c4af8db8821b1feae4146dd47341e4d431118c7dd060a74 +# Image name: testrun/base +FROM ubuntu@sha256:77d57fd89366f7d16615794a5b53e124d742404e20f035c22032233f1826bd6a RUN apt-get update @@ -30,8 +30,9 @@ COPY $COMMON_DIR/ /testrun/python/src/common # Setup the base python requirements COPY $MODULE_DIR/python /testrun/python -# Install all python requirements for the module -RUN pip3 install -r /testrun/python/requirements.txt +# Install all python requirements for the module +# --break-system-packages flag used to bypass PEP668 +RUN pip3 install --break-system-packages -r /testrun/python/requirements.txt # Add the bin files COPY $MODULE_DIR/bin /testrun/bin @@ -42,5 +43,5 @@ RUN dos2unix /testrun/bin/* # Make sure all the bin files are executable RUN chmod u+x /testrun/bin/* -#Start the network module +# Start the network module ENTRYPOINT [ "/testrun/bin/start_module" ] \ No newline at end of file diff --git a/modules/network/base/bin/start_module b/modules/network/base/bin/start_module index 8e8cb5e4b..3b1c092cd 100644 --- a/modules/network/base/bin/start_module +++ b/modules/network/base/bin/start_module @@ -42,6 +42,7 @@ fi # Extract the necessary config parameters MODULE_NAME=$(echo "$CONF" | jq -r '.config.meta.name') DEFINED_IFACE=$(echo "$CONF" | jq -r '.config.network.interface') +HOST=$(echo "$CONF" | jq -r '.config.network.host') GRPC=$(echo "$CONF" | jq -r '.config.grpc') # Validate the module name is present @@ -70,14 +71,19 @@ $BIN_DIR/setup_binaries $BIN_DIR echo "Starting module $MODULE_NAME on local interface $INTF..." -# Wait for interface to become ready -$BIN_DIR/wait_for_interface $INTF +# Only non-host containers will have a specific +# interface for capturing +if [[ "$HOST" != "true" ]]; then -# Small pause to let the interface stabalize before starting the capture -#sleep 1 + # Wait for interface to become ready + $BIN_DIR/wait_for_interface $INTF -# Start network capture -$BIN_DIR/capture $MODULE_NAME $INTF + # Small pause to let the interface stabalize before starting the capture + #sleep 1 + + # Start network capture + $BIN_DIR/capture $MODULE_NAME $INTF +fi # Start the grpc server if [[ ! -z $GRPC && ! $GRPC == "null" ]] @@ -96,4 +102,4 @@ fi sleep 3 # Start the networking service -$BIN_DIR/start_network_service $MODULE_NAME $INTF \ No newline at end of file +$BIN_DIR/start_network_service $MODULE_NAME $INTF diff --git a/modules/network/base/python/src/grpc_server/start_server.py b/modules/network/base/python/src/grpc_server/start_server.py index d372949e5..9c34ec736 100644 --- a/modules/network/base/python/src/grpc_server/start_server.py +++ b/modules/network/base/python/src/grpc_server/start_server.py @@ -46,6 +46,5 @@ def run(): print('gRPC server starting on port ' + port) serve(port) - if __name__ == '__main__': run() diff --git a/modules/network/dhcp-1/dhcp-1.Dockerfile b/modules/network/dhcp-1/dhcp-1.Dockerfile index e50ed9a95..7df94b4fd 100644 --- a/modules/network/dhcp-1/dhcp-1.Dockerfile +++ b/modules/network/dhcp-1/dhcp-1.Dockerfile @@ -12,8 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. -# Image name: test-run/dhcp-primary -FROM test-run/base:latest +# Image name: testrun/dhcp-primary +FROM testrun/base:latest ARG MODULE_NAME=dhcp-1 ARG MODULE_DIR=modules/network/$MODULE_NAME diff --git a/modules/network/dhcp-2/dhcp-2.Dockerfile b/modules/network/dhcp-2/dhcp-2.Dockerfile index 66ea857c3..4dcd7a819 100644 --- a/modules/network/dhcp-2/dhcp-2.Dockerfile +++ b/modules/network/dhcp-2/dhcp-2.Dockerfile @@ -12,8 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. -# Image name: test-run/dhcp-primary -FROM test-run/base:latest +# Image name: testrun/dhcp-primary +FROM testrun/base:latest ARG MODULE_NAME=dhcp-2 ARG MODULE_DIR=modules/network/$MODULE_NAME diff --git a/modules/network/dns/dns.Dockerfile b/modules/network/dns/dns.Dockerfile index d59b8a391..2b46dfb4a 100644 --- a/modules/network/dns/dns.Dockerfile +++ b/modules/network/dns/dns.Dockerfile @@ -12,8 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. -# Image name: test-run/dns -FROM test-run/base:latest +# Image name: testrun/dns +FROM testrun/base:latest ARG MODULE_NAME=dns ARG MODULE_DIR=modules/network/$MODULE_NAME diff --git a/modules/network/gateway/gateway.Dockerfile b/modules/network/gateway/gateway.Dockerfile index 885e4a9f0..2b72174ab 100644 --- a/modules/network/gateway/gateway.Dockerfile +++ b/modules/network/gateway/gateway.Dockerfile @@ -12,8 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. -# Image name: test-run/gateway -FROM test-run/base:latest +# Image name: testrun/gateway +FROM testrun/base:latest ARG MODULE_NAME=gateway ARG MODULE_DIR=modules/network/$MODULE_NAME diff --git a/modules/network/host/bin/start_network_service b/modules/network/host/bin/start_network_service new file mode 100644 index 000000000..b94b6ff7c --- /dev/null +++ b/modules/network/host/bin/start_network_service @@ -0,0 +1,22 @@ +#!/bin/bash + +# Copyright 2023 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +echo "Starting host service..." + +# Keep host container running until stopped +while true; do + sleep 3 +done diff --git a/modules/network/host/conf/module_config.json b/modules/network/host/conf/module_config.json new file mode 100644 index 000000000..87ec39a35 --- /dev/null +++ b/modules/network/host/conf/module_config.json @@ -0,0 +1,24 @@ +{ + "config": { + "meta": { + "name": "host", + "display_name": "Host", + "description": "Used to access host level networking operations" + }, + "network": { + "host": true + }, + "grpc":{ + "port": 5001 + }, + "docker": { + "depends_on": "base", + "mounts": [ + { + "source": "runtime/network", + "target": "/runtime/network" + } + ] + } + } +} \ No newline at end of file diff --git a/modules/network/host/host.Dockerfile b/modules/network/host/host.Dockerfile new file mode 100644 index 000000000..60c8bf59a --- /dev/null +++ b/modules/network/host/host.Dockerfile @@ -0,0 +1,34 @@ +# Copyright 2023 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Image name: testrun/host +FROM testrun/base:latest + +ARG MODULE_NAME=host +ARG MODULE_DIR=modules/network/$MODULE_NAME + +#Update and get all additional requirements not contained in the base image +RUN apt-get update --fix-missing + +# Install all necessary packages +RUN apt-get install -y net-tools ethtool + +# Copy over all configuration files +COPY $MODULE_DIR/conf /testrun/conf + +# Copy over all binary files +COPY $MODULE_DIR/bin /testrun/bin + +# Copy over all python files +COPY $MODULE_DIR/python /testrun/python diff --git a/modules/network/host/python/src/grpc_server/network_service.py b/modules/network/host/python/src/grpc_server/network_service.py new file mode 100644 index 000000000..cbb3a1b7a --- /dev/null +++ b/modules/network/host/python/src/grpc_server/network_service.py @@ -0,0 +1,120 @@ +# Copyright 2023 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +"""gRPC Network Service for the Host network module""" +import proto.grpc_pb2_grpc as pb2_grpc +import proto.grpc_pb2 as pb2 + +import traceback +from common import logger +from common import util + +LOG_NAME = 'network_service' +LOGGER = None + + +class NetworkService(pb2_grpc.HostNetworkModule): + """gRPC endpoints for the Host container""" + + def __init__(self): + global LOGGER + LOGGER = logger.get_logger(LOG_NAME, 'host') + + def CheckInterfaceStatus(self, request, context): # pylint: disable=W0613 + try: + status = self.check_interface_status(request.iface_name) + return pb2.CheckInterfaceStatusResponse(code=200, status=status) + except Exception as e: # pylint: disable=W0718 + fail_message = 'Failed to read iface status: ' + str(e) + LOGGER.error(fail_message) + LOGGER.error(traceback.format_exc()) + return pb2.CheckInterfaceStatusResponse(code=500, status=False) + + def GetIfaceConnectionStats(self, request, context): # pylint: disable=W0613 + try: + stats = self.get_iface_connection_stats(request.iface_name) + return pb2.GetIfaceStatsResponse(code=200, stats=stats) + except Exception as e: # pylint: disable=W0718 + fail_message = 'Failed to read connection stats: ' + str(e) + LOGGER.error(fail_message) + LOGGER.error(traceback.format_exc()) + return pb2.GetIfaceStatsResponse(code=500, stats=False) + + def GetIfacePortStats(self, request, context): # pylint: disable=W0613 + try: + stats = self.get_iface_port_stats(request.iface_name) + return pb2.GetIfaceStatsResponse(code=200, stats=stats) + except Exception as e: # pylint: disable=W0718 + fail_message = 'Failed to read port stats: ' + str(e) + LOGGER.error(fail_message) + LOGGER.error(traceback.format_exc()) + return pb2.GetIfaceStatsResponse(code=500, stats=False) + + def SetIfaceDown(self, request, context): # pylint: disable=W0613 + try: + success = self.set_interface_down(request.iface_name) + return pb2.SetIfaceResponse(code=200, success=success) + except Exception as e: # pylint: disable=W0718 + fail_message = 'Failed to set interface down: ' + str(e) + LOGGER.error(fail_message) + LOGGER.error(traceback.format_exc()) + return pb2.SetIfaceResponse(code=500, success=False) + + def SetIfaceUp(self, request, context): # pylint: disable=W0613 + try: + success = self.set_interface_up(request.iface_name) + return pb2.SetIfaceResponse(code=200, success=success) + except Exception as e: # pylint: disable=W0718 + fail_message = 'Failed to set interface up: ' + str(e) + LOGGER.error(fail_message) + LOGGER.error(traceback.format_exc()) + return pb2.SetIfaceResponse(code=500, success=False) + + def check_interface_status(self, interface_name): + output = util.run_command(cmd=f'ip link show {interface_name}', output=True) + if 'state DOWN ' in output[0]: + return False + else: + return True + + def get_iface_connection_stats(self, iface): + """Extract information about the physical connection""" + response = util.run_command(f'ethtool {iface}') + if len(response[1]) == 0: + return response[0] + else: + return None + + def get_iface_port_stats(self, iface): + """Extract information about packets connection""" + response = util.run_command(f'ethtool -S {iface}') + if len(response[1]) == 0: + return response[0] + else: + return None + + def set_interface_up(self, interface_name): + """Set the interface to the up state""" + response = util.run_command('ip link set dev ' + interface_name + ' up') + if len(response[1]) == 0: + return response[0] + else: + return None + + def set_interface_down(self, interface_name): + """Set the interface to the up state""" + response = util.run_command('ip link set dev ' + interface_name + ' down') + if len(response[1]) == 0: + return response[0] + else: + return None diff --git a/modules/network/host/python/src/grpc_server/proto/grpc.proto b/modules/network/host/python/src/grpc_server/proto/grpc.proto new file mode 100644 index 000000000..c881b13f7 --- /dev/null +++ b/modules/network/host/python/src/grpc_server/proto/grpc.proto @@ -0,0 +1,37 @@ +syntax = "proto3"; + +service HostNetworkModule { + + rpc CheckInterfaceStatus(CheckInterfaceStatusRequest) returns (CheckInterfaceStatusResponse) {}; + rpc GetIfaceConnectionStats(GetIfaceStatsRequest) returns (GetIfaceStatsResponse) {}; + rpc SetIfaceDown(SetIfaceRequest) returns (SetIfaceResponse) {}; + rpc SetIfaceUp(SetIfaceRequest) returns (SetIfaceResponse) {}; +} + +message CheckInterfaceStatusRequest { + string iface_name = 1; +} + +message CheckInterfaceStatusResponse { + int32 code = 1; + bool status = 2; +} + +message GetIfaceStatsRequest { + string iface_name = 1; +} + +message GetIfaceStatsResponse { + int32 code = 1; + string stats = 2; +} + +message SetIfaceRequest { + string iface_name = 1; +} + +message SetIfaceResponse { + int32 code = 1; + bool success = 2; +} + diff --git a/modules/network/host/python/src/grpc_server/start_server.py b/modules/network/host/python/src/grpc_server/start_server.py new file mode 100644 index 000000000..962277188 --- /dev/null +++ b/modules/network/host/python/src/grpc_server/start_server.py @@ -0,0 +1,50 @@ +# Copyright 2023 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Base class for starting the gRPC server for a network module.""" +from concurrent import futures +import grpc +import proto.grpc_pb2_grpc as pb2_grpc +from network_service import NetworkService +import argparse + +DEFAULT_PORT = '5001' + + +def serve(port): + server = grpc.server(futures.ThreadPoolExecutor(max_workers=10)) + pb2_grpc.add_HostNetworkModuleServicer_to_server(NetworkService(), server) + server.add_insecure_port('[::]:' + port) + server.start() + server.wait_for_termination() + + +def run(): + parser = argparse.ArgumentParser( + description='GRPC Server for Network Module', + formatter_class=argparse.ArgumentDefaultsHelpFormatter) + parser.add_argument('-p', + '--port', + default=DEFAULT_PORT, + help='Define the default port to run the server on.') + + args = parser.parse_args() + + port = args.port + + print('gRPC server starting on port ' + port) + serve(port) + +if __name__ == '__main__': + run() diff --git a/modules/network/ntp/ntp.Dockerfile b/modules/network/ntp/ntp.Dockerfile index aa6f63e3f..1d2a52494 100644 --- a/modules/network/ntp/ntp.Dockerfile +++ b/modules/network/ntp/ntp.Dockerfile @@ -12,8 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. -# Image name: test-run/ntp -FROM test-run/base:latest +# Image name: testrun/ntp +FROM testrun/base:latest ARG MODULE_NAME=ntp ARG MODULE_DIR=modules/network/$MODULE_NAME diff --git a/modules/network/ntp/python/src/ntp_server.py b/modules/network/ntp/python/src/ntp_server.py index 42fe21e77..fbe3ac17e 100644 --- a/modules/network/ntp/python/src/ntp_server.py +++ b/modules/network/ntp/python/src/ntp_server.py @@ -38,7 +38,7 @@ def is_running(self): if __name__ == '__main__': ntp = NTPServer() ntp.start() - # give some time for the server to start + # Give some time for the server to start running = False for _ in range(10): running = ntp.is_running() diff --git a/modules/network/radius/radius.Dockerfile b/modules/network/radius/radius.Dockerfile index 4c8f8fac5..802480ce9 100644 --- a/modules/network/radius/radius.Dockerfile +++ b/modules/network/radius/radius.Dockerfile @@ -12,8 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. -# Image name: test-run/radius -FROM test-run/base:latest +# Image name: testrun/radius +FROM testrun/base:latest ARG MODULE_NAME=radius ARG MODULE_DIR=modules/network/$MODULE_NAME @@ -25,7 +25,8 @@ RUN apt-get update && apt-get install -y openssl freeradius git RUN git clone --branch 0.0.25 https://github.com/faucetsdn/chewie # Install chewie as Python module -RUN pip3 install chewie/ +# --break-system-packages flag used to bypass PEP668 +RUN pip3 install --break-system-packages chewie/ EXPOSE 1812/udp EXPOSE 1813/udp @@ -40,4 +41,5 @@ COPY $MODULE_DIR/bin /testrun/bin COPY $MODULE_DIR/python /testrun/python # Install all python requirements for the module -RUN pip3 install -r /testrun/python/requirements.txt \ No newline at end of file +# --break-system-packages flag used to bypass PEP668 +RUN pip3 install --break-system-packages -r /testrun/python/requirements.txt \ No newline at end of file diff --git a/modules/network/template/template.Dockerfile b/modules/network/template/template.Dockerfile index 1c3060496..265725258 100644 --- a/modules/network/template/template.Dockerfile +++ b/modules/network/template/template.Dockerfile @@ -12,8 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. -# Image name: test-run/template -FROM test-run/base:latest +# Image name: testrun/template +FROM testrun/base:latest ARG MODULE_NAME=template ARG MODULE_DIR=modules/network/$MODULE_NAME diff --git a/modules/test/base/base.Dockerfile b/modules/test/base/base.Dockerfile index 4d8c0399a..cc78a934c 100644 --- a/modules/test/base/base.Dockerfile +++ b/modules/test/base/base.Dockerfile @@ -12,17 +12,23 @@ # See the License for the specific language governing permissions and # limitations under the License. -# Image name: test-run/base-test -FROM ubuntu@sha256:e6173d4dc55e76b87c4af8db8821b1feae4146dd47341e4d431118c7dd060a74 +# Builder stage +# Image name: testrun/base-test +FROM python:3.10-slim AS builder ARG MODULE_NAME=base ARG MODULE_DIR=modules/test/$MODULE_NAME ARG COMMON_DIR=framework/python/src/common -RUN apt-get update +# Install additional requirements needed to build python packages +RUN apt-get update && \ + apt-get install -y gcc dos2unix -# Install common software -RUN DEBIAN_FRONTEND=noninteractive apt-get install -yq net-tools iputils-ping tzdata tcpdump iproute2 jq python3 python3-pip dos2unix nmap wget --fix-missing +# Create the virtual environment +RUN python -m venv /opt/venv + +# Activate the virtual environment +ENV PATH="/opt/venv/bin:$PATH" # Install common python modules COPY $COMMON_DIR/ /testrun/python/src/common @@ -31,7 +37,7 @@ COPY $COMMON_DIR/ /testrun/python/src/common COPY $MODULE_DIR/python /testrun/python # Install all python requirements for the module -RUN pip3 install -r /testrun/python/requirements.txt +RUN pip install -r /testrun/python/requirements.txt # Copy over all binary files COPY $MODULE_DIR/bin /testrun/bin @@ -49,6 +55,7 @@ ARG CONTAINER_PROTO_DIR=testrun/python/src/grpc_server/proto COPY $NET_MODULE_DIR/dhcp-1/$NET_MODULE_PROTO_DIR $CONTAINER_PROTO_DIR/dhcp1/ COPY $NET_MODULE_DIR/dhcp-2/$NET_MODULE_PROTO_DIR $CONTAINER_PROTO_DIR/dhcp2/ +COPY $NET_MODULE_DIR/host/$NET_MODULE_PROTO_DIR $CONTAINER_PROTO_DIR/host/ # Copy the cached version of oui.txt incase the download fails RUN mkdir -p /usr/local/etc @@ -57,5 +64,21 @@ COPY $MODULE_DIR/usr/local/etc/oui.txt /usr/local/etc/oui.txt # Update the oui.txt file from ieee RUN wget https://standards-oui.ieee.org/oui.txt -O /usr/local/etc/oui.txt || echo "Unable to update the MAC OUI database" +# Operational stage +FROM python:3.10-slim + +# Install common software +RUN DEBIAN_FRONTEND=noninteractive apt-get update && apt-get install -yq net-tools iputils-ping tzdata tcpdump iproute2 jq dos2unix nmap wget --fix-missing + +# Get the virtual environment from builder stage +COPY --from=builder /opt/venv /opt/venv + +# Copy over all testrun files from the builder stage +COPY --from=builder /testrun /testrun +COPY --from=builder /usr/local/etc/oui.txt /usr/local/etc/oui.txt + +# Activate the virtual environment by setting the PATH +ENV PATH="/opt/venv/bin:$PATH" + # Start the test module ENTRYPOINT [ "/testrun/bin/start" ] \ No newline at end of file diff --git a/modules/test/base/bin/setup b/modules/test/base/bin/setup index 23c96c513..5b74790e5 100644 --- a/modules/test/base/bin/setup +++ b/modules/test/base/bin/setup @@ -41,7 +41,7 @@ CONF=`cat $CONF_FILE` if [[ -z $CONF ]] then - echo "No config file present at $CONF_FILE. Exiting startup." + echo "No config file present at $CONF_FILE. Exiting startup." exit 1 fi diff --git a/modules/test/base/python/src/grpc/proto/host/client.py b/modules/test/base/python/src/grpc/proto/host/client.py new file mode 100644 index 000000000..e08d3376a --- /dev/null +++ b/modules/test/base/python/src/grpc/proto/host/client.py @@ -0,0 +1,63 @@ +# Copyright 2023 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License +"""gRPC client module for the secondary DHCP Server""" +import grpc +import host.grpc_pb2_grpc as pb2_grpc +import host.grpc_pb2 as pb2 + +DEFAULT_PORT = '5001' +DEFAULT_HOST = 'external.localhost' # Default DHCP2 server + + +class Client(): + """gRPC Client for the secondary DHCP server""" + def __init__(self, port=DEFAULT_PORT, host=DEFAULT_HOST): + self._port = port + self._host = host + + # Create a gRPC channel to connect to the server + self._channel = grpc.insecure_channel(self._host + ':' + self._port) + + # Create a gRPC stub + self._stub = pb2_grpc.HostNetworkModuleStub(self._channel) + + def check_interface_status(self, iface_name): + # Create a request message + request = pb2.CheckInterfaceStatusRequest() + request.iface_name = iface_name + + # Make the RPC call + response = self._stub.CheckInterfaceStatus(request) + + return response + + def set_iface_down(self, iface_name): + # Create a request message + request = pb2.SetIfaceRequest() + request.iface_name = iface_name + + # Make the RPC call + response = self._stub.SetIfaceDown(request) + + return response + + def set_iface_up(self, iface_name): + # Create a request message + request = pb2.SetIfaceRequest() + request.iface_name = iface_name + + # Make the RPC call + response = self._stub.SetIfaceUp(request) + + return response diff --git a/modules/test/base/python/src/logger.py b/modules/test/base/python/src/logger.py deleted file mode 100644 index e6a2b004c..000000000 --- a/modules/test/base/python/src/logger.py +++ /dev/null @@ -1,61 +0,0 @@ -# Copyright 2023 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -"""Sets up the logger to be used for the test modules.""" -import json -import logging - -LOGGERS = {} -_LOG_FORMAT = '%(asctime)s %(name)-8s %(levelname)-7s %(message)s' -_DATE_FORMAT = '%b %02d %H:%M:%S' -_DEFAULT_LEVEL = logging.INFO -_CONF_FILE_NAME = 'testrun/system.json' -_LOG_DIR = '/runtime/output/' - -# Set log level -try: - with open(_CONF_FILE_NAME, - encoding='UTF-8') as config_json_file: - system_conf_json = json.load(config_json_file) - - log_level_str = system_conf_json['log_level'] - log_level = logging.getLevelName(log_level_str) -except OSError: - # TODO: Print out warning that log level is incorrect or missing - log_level = _DEFAULT_LEVEL - -log_format = logging.Formatter(fmt=_LOG_FORMAT, datefmt=_DATE_FORMAT) - - -def add_file_handler(log, log_file, log_dir=_LOG_DIR): - handler = logging.FileHandler(log_dir + log_file + '.log') - handler.setFormatter(log_format) - log.addHandler(handler) - - -def add_stream_handler(log): - handler = logging.StreamHandler() - handler.setFormatter(log_format) - log.addHandler(handler) - - -def get_logger(name, log_file=None, log_dir=_LOG_DIR): - if name not in LOGGERS: - LOGGERS[name] = logging.getLogger(name) - LOGGERS[name].setLevel(log_level) - add_stream_handler(LOGGERS[name]) - if log_file is not None: - log_dir = log_dir if log_dir is not None else _LOG_DIR - add_file_handler(LOGGERS[name], log_file, log_dir=log_dir) - return LOGGERS[name] diff --git a/modules/test/base/python/src/test_module.py b/modules/test/base/python/src/test_module.py index deed0d978..1487fb786 100644 --- a/modules/test/base/python/src/test_module.py +++ b/modules/test/base/python/src/test_module.py @@ -19,6 +19,8 @@ from datetime import datetime import traceback +from common.statuses import TestResult + LOGGER = None RESULTS_DIR = '/runtime/output/' CONF_FILE = '/testrun/conf/module_config.json' @@ -39,6 +41,7 @@ def __init__(self, self._ipv4_addr = os.environ.get('IPV4_ADDR', '') self._ipv4_subnet = os.environ.get('IPV4_SUBNET', '') self._ipv6_subnet = os.environ.get('IPV6_SUBNET', '') + self._dev_iface_mac = os.environ.get('DEV_IFACE_MAC', '') self._add_logger(log_name=log_name, module_name=module_name, log_dir=log_dir) @@ -116,25 +119,25 @@ def run_tests(self): LOGGER.error(e) traceback.print_exc() else: - LOGGER.info(f'Test {test["name"]} not implemented. Skipping') - test['result'] = 'Error' - test['description'] = 'This test could not be found' + LOGGER.error(f'Test {test["name"]} has not been implemented') + result = TestResult.ERROR, 'This test could not be found' else: LOGGER.debug(f'Test {test["name"]} is disabled') + result = (TestResult.DISABLED, + 'This test did not run because it is disabled') - # To be added in v1.3.2 - # result = 'Disabled', 'This test is disabled and did not run' - + # Check if the test module has returned a result if result is not None: + # Compliant or non-compliant as a boolean only if isinstance(result, bool): - test['result'] = 'Compliant' if result else 'Non-Compliant' + test['result'] = (TestResult.COMPLIANT + if result else TestResult.NON_COMPLIANT) test['description'] = 'No description was provided for this test' else: - # TODO: This is assuming that result is an array but haven't checked # Error result if result[0] is None: - test['result'] = 'Error' + test['result'] = TestResult.ERROR if len(result) > 1: test['description'] = result[1] else: @@ -142,13 +145,14 @@ def run_tests(self): # Compliant / Non-Compliant result elif isinstance(result[0], bool): - test['result'] = 'Compliant' if result[0] else 'Non-Compliant' + test['result'] = (TestResult.COMPLIANT + if result[0] else TestResult.NON_COMPLIANT) # Result may be a string, e.g Error, Feature Not Detected elif isinstance(result[0], str): test['result'] = result[0] else: LOGGER.error(f'Unknown result detected: {result[0]}') - test['result'] = 'Error' + test['result'] = TestResult.ERROR # Check that description is a string if isinstance(result[1], str): @@ -159,12 +163,17 @@ def run_tests(self): # Check if details were provided if len(result)>2: test['details'] = result[2] + + # Check if tags were provided + if len(result)>3: + test['tags'] = result[3] else: - test['result'] = 'Error' + LOGGER.debug('No result was returned from the test module') + test['result'] = TestResult.ERROR test['description'] = 'An error occured whilst running this test' # Remove the steps to resolve if compliant already - if (test['result'] == 'Compliant' and 'recommendations' in test): + if (test['result'] == TestResult.COMPLIANT and 'recommendations' in test): test.pop('recommendations') test['end'] = datetime.now().isoformat() diff --git a/modules/test/base/python/src/util.py b/modules/test/base/python/src/util.py deleted file mode 100644 index 006648037..000000000 --- a/modules/test/base/python/src/util.py +++ /dev/null @@ -1,47 +0,0 @@ -# Copyright 2023 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -"""Provides basic utilities for a test module.""" -import subprocess -import shlex -import logger - -LOGGER = logger.get_logger('util') - -def run_command(cmd, output=True): - """Runs a process at the os level - By default, returns the standard output and error output - If the caller sets optional output parameter to False, - will only return a boolean result indicating if it was - successful in running the command. Failure is indicated - by any return code from the process other than zero.""" - - success = False - with subprocess.Popen(shlex.split(cmd), - stdout=subprocess.PIPE, - stderr=subprocess.PIPE) as process: - stdout, stderr = process.communicate() - if process.returncode != 0 and output: - err_msg = f'{stderr.strip()}. Code: {process.returncode}' - LOGGER.error('Command Failed: ' + cmd) - LOGGER.error('Error: ' + err_msg) - else: - success = True - LOGGER.debug('Command succeeded: ' + cmd) - if output: - out = stdout.strip().decode('utf-8') - LOGGER.debug('Command output: ' + out) - return out, stderr - else: - return success diff --git a/modules/test/baseline/baseline.Dockerfile b/modules/test/baseline/baseline.Dockerfile index f7d21f8c8..7a83c8de2 100644 --- a/modules/test/baseline/baseline.Dockerfile +++ b/modules/test/baseline/baseline.Dockerfile @@ -12,8 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. -# Image name: test-run/baseline-test -FROM test-run/base-test:latest +# Image name: testrun/baseline-test +FROM testrun/base-test:latest ARG MODULE_NAME=baseline ARG MODULE_DIR=modules/test/$MODULE_NAME diff --git a/modules/test/baseline/conf/module_config.json b/modules/test/baseline/conf/module_config.json index f28f74a06..d120f1c71 100644 --- a/modules/test/baseline/conf/module_config.json +++ b/modules/test/baseline/conf/module_config.json @@ -16,20 +16,17 @@ { "name": "baseline.compliant", "test_description": "Simulate a compliant test", - "expected_behavior": "A compliant test result is generated", - "required_result": "Required" + "expected_behavior": "A compliant test result is generated" }, { "name": "baseline.non_compliant", "test_description": "Simulate a non-compliant test", - "expected_behavior": "A non-compliant test result is generated", - "required_result": "Recommended" + "expected_behavior": "A non-compliant test result is generated" }, { "name": "baseline.skipped", "test_description": "Simulate a skipped test", - "expected_behavior": "A skipped test result is generated", - "required_result": "Skipped" + "expected_behavior": "A skipped test result is generated" } ] } diff --git a/modules/test/conn/conf/module_config.json b/modules/test/conn/conf/module_config.json index 5289e7eb0..ca386a5e4 100644 --- a/modules/test/conn/conf/module_config.json +++ b/modules/test/conn/conf/module_config.json @@ -16,38 +16,32 @@ { "name": "connection.port_link", "test_description": "The network switch port connected to the device has an active link without errors", - "expected_behavior": "When the etherent cable is connected to the port, the device triggers the port to its enabled \"Link UP\" (LEDs illuminate on device and switch ports if present) state, and the switch shows no errors with the LEDs and when interrogated with a \"show interface\" command on most network switches.", - "required_result": "Required" + "expected_behavior": "When the etherent cable is connected to the port, the device triggers the port to its enabled \"Link UP\" (LEDs illuminate on device and switch ports if present) state, and the switch shows no errors with the LEDs and when interrogated with a \"show interface\" command on most network switches." }, { "name": "connection.port_speed", "test_description": "The network switch port connected to the device has auto-negotiated a speed that is 10 Mbps or higher", - "expected_behavior": "When the ethernet cable is connected to the port, the device autonegotiates a speed that can be checked with the \"show interface\" command on most network switches. The output of this command must also show that the \"configured speed\" is set to \"auto\".", - "required_result": "Required" + "expected_behavior": "When the ethernet cable is connected to the port, the device autonegotiates a speed that can be checked with the \"show interface\" command on most network switches. The output of this command must also show that the \"configured speed\" is set to \"auto\"." }, { "name": "connection.port_duplex", "test_description": "The network switch port connected to the device has auto-negotiated full-duplex", - "expected_behavior": "When the ethernet cable is connected to the port, the device autonegotiates a full-duplex connection.", - "required_result": "Required" + "expected_behavior": "When the ethernet cable is connected to the port, the device autonegotiates a full-duplex connection." }, { "name": "connection.switch.arp_inspection", "test_description": "The device implements ARP correctly as per RFC826", - "expected_behavior": "Device continues to operate correctly when ARP inspection is enabled on the switch. No functionality is lost with ARP inspection enabled.", - "required_result": "Required" + "expected_behavior": "Device continues to operate correctly when ARP inspection is enabled on the switch. No functionality is lost with ARP inspection enabled." }, { "name": "connection.switch.dhcp_snooping", "test_description": "The device operates as a DHCP client and operates correctly when DHCP snooping is enabled on a switch.", - "expected_behavior": "Device continues to operate correctly when DHCP snooping is enabled on the switch.", - "required_result": "Required" + "expected_behavior": "Device continues to operate correctly when DHCP snooping is enabled on the switch." }, { "name": "connection.dhcp_address", "test_description": "The device under test has received an IP address from the DHCP server and responds to an ICMP echo (ping) request", "expected_behavior": "The device is not setup with a static IP address. The device accepts an IP address from a DHCP server (RFC 2131) and responds succesfully to an ICMP echo (ping) request.", - "required_result": "Required", "recommendations": [ "Enable DHCP", "Install a DHCP client" @@ -57,7 +51,6 @@ "name": "connection.mac_address", "test_description": "Check and note device physical address.", "expected_behavior": "N/A", - "required_result": "Required", "recommendations": [ "Ensure that the MAC address is set by hardware only" ] @@ -66,7 +59,6 @@ "name": "connection.mac_oui", "test_description": "The device under test has a MAC address prefix that is registered against a known manufacturer.", "expected_behavior": "The MAC address prefix is registered in the IEEE Organizationally Unique Identifier database.", - "required_result": "Required", "recommendations": [ "Register the device MAC address with IEEE" ] @@ -75,7 +67,6 @@ "name": "connection.private_address", "test_description": "The device under test accepts an IP address that is compliant with RFC 1918 Address Allocation for Private Internets.", "expected_behavior": "The device under test accepts IP addresses within all ranges specified in RFC 1918 and communicates using these addresses. The Internet Assigned Numbers Authority (IANA) has reserved the following three blocks of the IP address space for private internets. 10.0.0.0 - 10.255.255.255.255 (10/8 prefix). 172.16.0.0 - 172.31.255.255 (172.16/12 prefix). 192.168.0.0 - 192.168.255.255 (192.168/16 prefix)", - "required_result": "Required", "config": { "lease_wait_time_sec": 60, "ranges": [ @@ -101,7 +92,6 @@ "name": "connection.shared_address", "test_description": "Ensure the device supports RFC 6598 IANA-Reserved IPv4 Prefix for Shared Address Space", "expected_behavior": "The device under test accepts IP addresses within the ranges specified in RFC 6598 and communicates using these addresses", - "required_result": "Required", "config": { "lease_wait_time_sec": 60, "ranges": [ @@ -116,11 +106,21 @@ "Enable shared address space support in the DHCP client" ] }, + { + "name": "connection.dhcp_disconnect", + "test_description": "The device under test issues a new DHCPREQUEST packet after a port ph ysical disconnection and reconnection", + "expected_behavior": "A client SHOULD use DHCP to reacquire or verify its IP address and network parameters whenever the local network parameters may have changed; e.g., at system boot time or after a disconnection from the local network, as the local network configuration may change without the client's or user's knowledge. If a client has knowledge ofa previous network address and is unable to contact a local DHCP server, the client may continue to use the previous network addres until the lease for that address expires. If the lease expires before the client can contact a DHCP server, the client must immediately discontinue use of the previous network address and may inform local users of the problem." + }, + { + "name": "connection.dhcp_disconnect_ip_change", + "test_description": "When device is disconnected, update device IP on the DHCP server and reconnect the device. Ensure device received new IP address", + "expected_behavior": "If IP address for a device was changed on the DHCP server while the device was disconnected then the device shoiuld request and update the new IP upon reconnecting to the network", + "required_result": "Required" + }, { "name": "connection.single_ip", "test_description": "The network switch port connected to the device reports only one IP address for the device under test.", "expected_behavior": "The device under test does not behave as a network switch and only requets one IP address. This test is to avoid that devices implement network switches that allow connecting strings of daisy chained devices to one single network port, as this would not make 802.1x port based authentication possible.", - "required_result": "Required", "recommendations": [ "Ensure that all ports on the device are isolated", "Ensure only one DHCP client is running" @@ -130,7 +130,6 @@ "name": "connection.target_ping", "test_description": "The device under test responds to an ICMP echo (ping) request.", "expected_behavior": "The device under test responds to an ICMP echo (ping) request.", - "required_result": "Required", "recommendations": [ "Configure device to allow ICMP requests (ping)", "Create a firewall exception to allow ICMP requests from LAN" @@ -140,7 +139,6 @@ "name": "connection.ipaddr.ip_change", "test_description": "The device responds to a ping (ICMP echo request) to the new IP address it has received after the initial DHCP lease has expired.", "expected_behavior": "If the lease expires before the client receiveds a DHCPACK, the client moves to INIT state, MUST immediately stop any other network processing and requires network initialization parameters as if the client were uninitialized. If the client then receives a DHCPACK allocating the client its previous network addres, the client SHOULD continue network processing. If the client is given a new network address, it MUST NOT continue using the previous network address and SHOULD notify the local users of the problem.", - "required_result": "Required", "config":{ "lease_wait_time_sec": 60 }, @@ -152,7 +150,6 @@ "name": "connection.ipaddr.dhcp_failover", "test_description": "The device has requested a DHCPREQUEST/REBIND to the DHCP failover server after the primary DHCP server has been brought down.", "expected_behavior": "", - "required_result": "Required", "config":{ "lease_wait_time_sec": 60 }, @@ -164,7 +161,6 @@ "name": "connection.ipv6_slaac", "test_description": "The device forms a valid IPv6 address as a combination of the IPv6 router prefix and the device interface identifier", "expected_behavior": "The device under test complies with RFC4862 and forms a valid IPv6 SLAAC address", - "required_result": "Required", "recommendations": [ "Install a network manager that supports IPv6", "Disable DHCPv6" @@ -174,7 +170,6 @@ "name": "connection.ipv6_ping", "test_description": "The device responds to an IPv6 ping (ICMPv6 Echo) request to the SLAAC address", "expected_behavior": "The device responds to the ping as per RFC4443", - "required_result": "Required", "recommendations": [ "Enable ping response to IPv6 ICMP requests in network manager settings", "Create a firewall exception to allow ICMPv6 via LAN" diff --git a/modules/test/conn/conn.Dockerfile b/modules/test/conn/conn.Dockerfile index a9f523e44..58aec8048 100644 --- a/modules/test/conn/conn.Dockerfile +++ b/modules/test/conn/conn.Dockerfile @@ -12,8 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. -# Image name: test-run/conn-test -FROM test-run/base-test:latest +# Image name: testrun/conn-test +FROM testrun/base-test:latest ARG MODULE_NAME=conn ARG MODULE_DIR=modules/test/$MODULE_NAME @@ -27,7 +27,7 @@ RUN apt-get install -y wget COPY $MODULE_DIR/python/requirements.txt /testrun/python # Install all python requirements for the module -RUN pip3 install -r /testrun/python/requirements.txt +RUN pip install -r /testrun/python/requirements.txt # Copy over all configuration files COPY $MODULE_DIR/conf /testrun/conf diff --git a/modules/test/conn/python/src/connection_module.py b/modules/test/conn/python/src/connection_module.py index 88dd40393..867d8a3ff 100644 --- a/modules/test/conn/python/src/connection_module.py +++ b/modules/test/conn/python/src/connection_module.py @@ -15,10 +15,12 @@ import util import time import traceback +import os from scapy.all import rdpcap, DHCP, ARP, Ether, ICMP, IPv6, ICMPv6ND_NS from test_module import TestModule from dhcp1.client import Client as DHCPClient1 from dhcp2.client import Client as DHCPClient2 +from host.client import Client as HostClient from dhcp_util import DHCPUtil from port_stats_util import PortStatsUtil @@ -59,6 +61,7 @@ def __init__(self, self._port_stats = PortStatsUtil(logger=LOGGER) self.dhcp1_client = DHCPClient1() self.dhcp2_client = DHCPClient2() + self.host_client = HostClient() self._dhcp_util = DHCPUtil(self.dhcp1_client, self.dhcp2_client, LOGGER) self._lease_wait_time_sec = LEASE_WAIT_TIME_DEFAULT @@ -243,7 +246,8 @@ def _connection_single_ip(self): if self._get_dhcp_type(packet) == 3: mac_address = packet[Ether].src LOGGER.info('DHCPREQUEST detected MAC address: ' + mac_address) - if not mac_address.startswith(TR_CONTAINER_MAC_PREFIX): + if (not mac_address.startswith(TR_CONTAINER_MAC_PREFIX) + and mac_address != self._dev_iface_mac): mac_addresses.add(mac_address.upper()) # Check if the device mac address is in the list of DHCPREQUESTs @@ -379,6 +383,171 @@ def _connection_ipaddr_dhcp_failover(self, config): result = None, 'Network is not ready for this test' return result + def _connection_dhcp_disconnect(self): + LOGGER.info('Running connection.dhcp.disconnect') + result = None + description = '' + dev_iface = os.getenv('DEV_IFACE') + iface_status = self.host_client.check_interface_status(dev_iface) + if iface_status.code == 200: + LOGGER.info('Successfully resolved iface status') + if iface_status.status: + lease = self._dhcp_util.get_cur_lease(mac_address=self._device_mac, + timeout=self._lease_wait_time_sec) + if lease is not None: + LOGGER.info('Current device lease resolved') + if self._dhcp_util.is_lease_active(lease): + + # Disable the device interface + iface_down = self.host_client.set_iface_down(dev_iface) + if iface_down: + LOGGER.info('Device interface set to down state') + + # Wait for the lease to expire + self._dhcp_util.wait_for_lease_expire(lease, + self._lease_wait_time_sec) + + # Wait an additonal 10 seconds to better test a true disconnect + # state + LOGGER.info('Waiting 10 seconds before bringing iface back up') + time.sleep(10) + + # Enable the device interface + iface_up = self.host_client.set_iface_up(dev_iface) + if iface_up: + LOGGER.info('Device interface set to up state') + + # Confirm device receives a new lease + if self._dhcp_util.get_cur_lease( + mac_address=self._device_mac, + timeout=self._lease_wait_time_sec): + if self._dhcp_util.is_lease_active(lease): + result = True + description = ( + 'Device received a DHCP lease after disconnect') + else: + result = False + description = ( + 'Could not confirm DHCP lease active after disconnect') + else: + result = False + description = ( + 'Device did not recieve a DHCP lease after disconnect') + else: + result = 'Error' + description = 'Failed to set device interface to up state' + else: + result = 'Error' + description = 'Failed to set device interface to down state' + else: + result = 'Error' + description = 'No active lease available for device' + else: + result = 'Error' + description = 'Device interface is down' + else: + result = 'Error' + description = 'Device interface could not be resolved' + return result, description + + def _connection_dhcp_disconnect_ip_change(self): + LOGGER.info('Running connection.dhcp.disconnect_ip_change') + result = None + description = '' + reserved_lease = None + dev_iface = os.getenv('DEV_IFACE') + if self._dhcp_util.setup_single_dhcp_server(): + iface_status = self.host_client.check_interface_status(dev_iface) + if iface_status.code == 200: + LOGGER.info('Successfully resolved iface status') + if iface_status.status: + lease = self._dhcp_util.get_cur_lease( + mac_address=self._device_mac, timeout=self._lease_wait_time_sec) + if lease is not None: + LOGGER.info('Current device lease resolved') + if self._dhcp_util.is_lease_active(lease): + + # Add a reserved lease with a different IP + ip_address = '10.10.10.30' + reserved_lease = self._dhcp_util.add_reserved_lease( + lease['hostname'], self._device_mac, ip_address) + + # Disable the device interface + iface_down = self.host_client.set_iface_down(dev_iface) + if iface_down: + LOGGER.info('Device interface set to down state') + + # Wait for the lease to expire + self._dhcp_util.wait_for_lease_expire(lease, + self._lease_wait_time_sec) + + if reserved_lease: + # Wait an additonal 10 seconds to better test a true + # disconnect state + LOGGER.info( + 'Waiting 10 seconds before bringing iface back up') + time.sleep(10) + + # Enable the device interface + iface_up = self.host_client.set_iface_up(dev_iface) + if iface_up: + LOGGER.info('Device interface set to up state') + # Confirm device receives a new lease + reserved_lease_accepted = False + LOGGER.info('Checking device accepted new ip') + for _ in range(5): + LOGGER.info('Pinging device at IP: ' + ip_address) + if self._ping(ip_address): + LOGGER.debug('Ping success') + LOGGER.debug( + 'Reserved lease confirmed active in device') + reserved_lease_accepted = True + break + else: + LOGGER.info('Device did not respond to ping') + time.sleep(5) # Wait 5 seconds before trying again + + if reserved_lease_accepted: + result = True + description = ('Device received expected IP address ' + 'after disconnect') + else: + result = False + description = ( + 'Could not confirm DHCP lease active after disconnect' + ) + else: + result = 'Error' + description = 'Failed to set device interface to up state' + else: + result = 'Error' + description = 'Failed to set reserved address in DHCP server' + else: + result = 'Error' + description = 'Failed to set device interface to down state' + else: + result = 'Error' + description = 'No active lease available for device' + else: + result = 'Error' + description = 'Device interface is down' + else: + result = 'Error' + description = 'Device interface could not be resolved' + else: + result = 'Error' + description = 'Failed to configure network for test' + if reserved_lease: + self._dhcp_util.delete_reserved_lease(self._device_mac) + + # Restore the network + self._dhcp_util.restore_failover_dhcp_server() + LOGGER.info('Waiting 30 seconds for reserved lease to expire') + time.sleep(30) + self._dhcp_util.get_cur_lease(mac_address=self._device_mac, + timeout=self._lease_wait_time_sec) + return result, description + def _get_oui_manufacturer(self, mac_address): # Do some quick fixes on the format of the mac_address # to match the oui file pattern diff --git a/modules/test/dns/conf/module_config.json b/modules/test/dns/conf/module_config.json index f048d5deb..d3d0a1134 100644 --- a/modules/test/dns/conf/module_config.json +++ b/modules/test/dns/conf/module_config.json @@ -16,7 +16,6 @@ "name": "dns.network.hostname_resolution", "test_description": "Verify the device sends DNS requests", "expected_behavior": "The device sends DNS requests.", - "required_result": "Required", "recommendations": [ "Install a supported DNS client", "Ensure DNS servers have been set correctly", @@ -27,7 +26,6 @@ "name": "dns.network.from_dhcp", "test_description": "Verify the device allows for a DNS server to be entered automatically", "expected_behavior": "The device sends DNS requests to the DNS server provided by the DHCP server", - "required_result": "Informational", "recommendations": [ "Install a DNS client that supports fetching DNS servers from DHCP options" ] @@ -35,8 +33,7 @@ { "name": "dns.mdns", "test_description": "Does the device has MDNS (or any kind of IP multicast)", - "expected_behavior": "Device may send MDNS requests", - "required_result": "Informational" + "expected_behavior": "Device may send MDNS requests" } ] } diff --git a/modules/test/dns/dns.Dockerfile b/modules/test/dns/dns.Dockerfile index 0197fd72e..461e87899 100644 --- a/modules/test/dns/dns.Dockerfile +++ b/modules/test/dns/dns.Dockerfile @@ -12,8 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. -# Image name: test-run/conn-test -FROM test-run/base-test:latest +# Image name: testrun/conn-test +FROM testrun/base-test:latest ARG MODULE_NAME=dns ARG MODULE_DIR=modules/test/$MODULE_NAME @@ -22,7 +22,7 @@ ARG MODULE_DIR=modules/test/$MODULE_NAME COPY $MODULE_DIR/python/requirements.txt /testrun/python # Install all python requirements for the module -RUN pip3 install -r /testrun/python/requirements.txt +RUN pip install -r /testrun/python/requirements.txt # Copy over all configuration files COPY $MODULE_DIR/conf /testrun/conf diff --git a/modules/test/dns/python/src/dns_module.py b/modules/test/dns/python/src/dns_module.py index c04e289d3..2d98c36dc 100644 --- a/modules/test/dns/python/src/dns_module.py +++ b/modules/test/dns/python/src/dns_module.py @@ -53,7 +53,7 @@ def generate_module_report(self): # Extract DNS data from the pcap file dns_table_data = self.extract_dns_data() - html_content = '

DNS Module

' + html_content = '

DNS Module

' # Set the summary variables local_requests = sum( @@ -97,6 +97,7 @@ def generate_module_report(self): Source Destination + Resolved IP Type URL Count @@ -105,16 +106,16 @@ def generate_module_report(self): ''' # Count unique combinations - counter = Counter( - (row['Source'], row['Destination'], row['Type'], row['Data']) - for row in dns_table_data) + counter = Counter((row['Source'], row['Destination'], row['ResolvedIP'], + row['Type'], row['Data']) for row in dns_table_data) # Generate the HTML table with the count column - for (src, dst, typ, dat), count in counter.items(): + for (src, dst, res_ip, typ, dat), count in counter.items(): table_content += f''' {src} {dst} + {res_ip} {typ} {dat} {count} @@ -122,8 +123,7 @@ def generate_module_report(self): table_content += ''' - - ''' + ''' html_content += table_content @@ -166,23 +166,38 @@ def extract_dns_data(self): # 'qr' field indicates query (0) or response (1) dns_type = 'Query' if dns_layer.qr == 0 else 'Response' - # Check for the presence of DNS query name - if hasattr(dns_layer, 'qd') and dns_layer.qd is not None: + # Check if 'qd' (query data) exists and has at least one entry + if hasattr(dns_layer, 'qd') and dns_layer.qdcount > 0: qname = dns_layer.qd.qname.decode() if dns_layer.qd.qname else 'N/A' else: qname = 'N/A' + resolved_ip = 'N/A' + # If it's a response packet, extract the resolved IP address + # from the answer section + if dns_layer.qr == 1 and hasattr(dns_layer, + 'an') and dns_layer.ancount > 0: + # Loop through all answers in the DNS response + for i in range(dns_layer.ancount): + answer = dns_layer.an[i] + # Check for IPv4 (A record) or IPv6 (AAAA record) + if answer.type == 1: # Indicates an A record (IPv4 address) + resolved_ip = answer.rdata # Extract IPv4 address + break # Stop after finding the first valid resolved IP + elif answer.type == 28: # Indicates an AAAA record (IPv6 address) + resolved_ip = answer.rdata # Extract IPv6 address + break # Stop after finding the first valid resolved IP + dns_data.append({ 'Timestamp': float(packet.time), # Timestamp of the DNS packet 'Source': source_ip, 'Destination': destination_ip, + 'ResolvedIP': resolved_ip, # Adding the resolved IP address 'Type': dns_type, 'Data': qname[:-1] }) # Filter unique entries based on 'Timestamp' - # DNS Server will duplicate messages caught by - # startup and monitor filtered_unique_dns_data = [] seen_timestamps = set() diff --git a/modules/test/ntp/conf/module_config.json b/modules/test/ntp/conf/module_config.json index 55eb3df76..6634b127d 100644 --- a/modules/test/ntp/conf/module_config.json +++ b/modules/test/ntp/conf/module_config.json @@ -9,14 +9,13 @@ "docker": { "depends_on": "base", "enable_container": true, - "timeout": 30 + "timeout": 60 }, "tests":[ { "name": "ntp.network.ntp_support", "test_description": "Does the device request network time sync as client as per RFC 5905 - Network Time Protocol Version 4: Protocol and Algorithms Specification", "expected_behavior": "The device sends an NTPv4 request to the configured NTP server.", - "required_result": "Required", "recommendations": [ "Set the NTP version to v4 in the NTP client", "Install an NTP client that supports NTPv4" @@ -26,7 +25,6 @@ "name": "ntp.network.ntp_dhcp", "test_description": "Accept NTP address over DHCP", "expected_behavior": "Device can accept NTP server address, provided by the DHCP server (DHCP OFFER PACKET)", - "required_result": "Roadmap", "recommendations": [ "Install an NTP client that supports fetching the NTP servers from DHCP options" ] diff --git a/modules/test/ntp/ntp.Dockerfile b/modules/test/ntp/ntp.Dockerfile index 33b06287e..4d9701464 100644 --- a/modules/test/ntp/ntp.Dockerfile +++ b/modules/test/ntp/ntp.Dockerfile @@ -1,5 +1,19 @@ -# Image name: test-run/ntp-test -FROM test-run/base-test:latest +# Copyright 2023 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Image name: testrun/ntp-test +FROM testrun/base-test:latest ARG MODULE_NAME=ntp ARG MODULE_DIR=modules/test/$MODULE_NAME @@ -8,7 +22,7 @@ ARG MODULE_DIR=modules/test/$MODULE_NAME COPY $MODULE_DIR/python/requirements.txt /testrun/python # Install all python requirements for the module -RUN pip3 install -r /testrun/python/requirements.txt +RUN pip install -r /testrun/python/requirements.txt # Copy over all configuration files COPY $MODULE_DIR/conf /testrun/conf diff --git a/modules/test/ntp/python/src/ntp_module.py b/modules/test/ntp/python/src/ntp_module.py index be27abbad..f82240ff1 100644 --- a/modules/test/ntp/python/src/ntp_module.py +++ b/modules/test/ntp/python/src/ntp_module.py @@ -54,7 +54,7 @@ def generate_module_report(self): # Extract NTP data from the pcap file ntp_table_data = self.extract_ntp_data() - html_content = '

NTP Module

' + html_content = '

NTP Module

' # Set the summary variables local_requests = sum( @@ -267,11 +267,9 @@ def _ntp_network_ntp_support(self): result = False, 'Device has not sent any NTP requests' if device_sends_ntp3 and device_sends_ntp4: - result = False, ('Device sent NTPv3 and NTPv4 packets. ' + - 'NTPv3 is not allowed') + result = False, ('Device sent NTPv3 and NTPv4 packets') elif device_sends_ntp3: - result = False, ('Device sent NTPv3 packets. ' - 'NTPv3 is not allowed') + result = False, ('Device sent NTPv3 packets') elif device_sends_ntp4: result = True, 'Device sent NTPv4 packets' diff --git a/modules/test/protocol/conf/module_config.json b/modules/test/protocol/conf/module_config.json index 365bd346b..554f43cc7 100644 --- a/modules/test/protocol/conf/module_config.json +++ b/modules/test/protocol/conf/module_config.json @@ -16,20 +16,17 @@ { "name": "protocol.valid_bacnet", "test_description": "Can valid BACnet traffic be seen", - "expected_behavior": "BACnet traffic can be seen on the network and packets are valid and not malformed", - "required_result": "Recommended" + "expected_behavior": "BACnet traffic can be seen on the network and packets are valid and not malformed" }, { "name": "protocol.bacnet.version", "test_description": "Obtain the version of BACnet client used", - "expected_behavior": "The BACnet client implements an up to date version of BACnet", - "required_result": "Recommended" + "expected_behavior": "The BACnet client implements an up to date version of BACnet" }, { "name": "protocol.valid_modbus", "test_description": "Can valid Modbus traffic be seen", "expected_behavior": "Any Modbus functionality works as expected and valid Modbus traffic can be observed", - "required_result": "Recommended", "config":{ "port": 502, "device_id": 1, diff --git a/modules/test/protocol/protocol.Dockerfile b/modules/test/protocol/protocol.Dockerfile index 6f55520e1..4494ae94e 100644 --- a/modules/test/protocol/protocol.Dockerfile +++ b/modules/test/protocol/protocol.Dockerfile @@ -12,8 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. -# Image name: test-run/protocol-test -FROM test-run/base-test:latest +# Image name: testrun/protocol-test +FROM testrun/base-test:latest # Set DEBIAN_FRONTEND to noninteractive mode ENV DEBIAN_FRONTEND=noninteractive @@ -28,7 +28,7 @@ ARG MODULE_DIR=modules/test/$MODULE_NAME COPY $MODULE_DIR/python/requirements.txt /testrun/python #Install all python requirements for the module -RUN pip3 install -r /testrun/python/requirements.txt +RUN pip install -r /testrun/python/requirements.txt # Copy over all configuration files COPY $MODULE_DIR/conf /testrun/conf diff --git a/modules/test/services/conf/module_config.json b/modules/test/services/conf/module_config.json index 5c20b4beb..fecb41862 100644 --- a/modules/test/services/conf/module_config.json +++ b/modules/test/services/conf/module_config.json @@ -16,7 +16,6 @@ "name": "security.services.ftp", "test_description": "Check FTP port 20/21 is disabled and FTP is not running on any port", "expected_behavior": "There is no FTP service running on any port", - "required_result": "Required", "config": { "services": [ "ftp", @@ -50,7 +49,6 @@ "name": "security.ssh.version", "test_description": "If the device is running a SSH server ensure it is SSHv2", "expected_behavior": "SSH server is not running or server is SSHv2", - "required_result": "Required", "config": { "services": ["ssh"], "ports": [ @@ -70,7 +68,6 @@ "name": "security.services.telnet", "test_description": "Check TELNET port 23 is disabled and TELNET is not running on any port", "expected_behavior": "There is no Telnet service running on any port", - "required_result": "Required", "config": { "services": [ "telnet" @@ -95,7 +92,6 @@ "name": "security.services.smtp", "test_description": "Check SMTP ports 25, 465 and 587 are not enabled and SMTP is not running on any port.", "expected_behavior": "There is no SMTP service running on any port", - "required_result": "Required", "config": { "services": [ "smtp" @@ -123,7 +119,6 @@ "name": "security.services.http", "test_description": "Check that there is no HTTP server running on any port", "expected_behavior": "Device is unreachable on port 80 (or any other port) and only responds to HTTPS requests on port 443 (or any other port if HTTP is used at all)", - "required_result": "Required", "config": { "services": [ "http" @@ -158,7 +153,6 @@ "name": "security.services.pop", "test_description": "Check POP ports 109 and 110 are disabled and POP is not running on any port", "expected_behavior": "There is no POP service running on any port", - "required_result": "Required", "config": { "services": [ "pop2", @@ -200,7 +194,6 @@ "name": "security.services.imap", "test_description": "Check IMAP port 143 is disabled and IMAP is not running on any port", "expected_behavior": "There is no IMAP service running on any port", - "required_result": "Required", "config": { "services": [ "imap", @@ -250,7 +243,6 @@ "name": "security.services.snmpv3", "test_description": "Check SNMP port 161/162 is disabled. If SNMP is an essential service, check it supports version 3", "expected_behavior": "Device is unreachable on port 161 (or any other port) and device is unreachable on port 162 (or any other port) unless SNMP is essential in which case it is SNMPv3 is used.", - "required_result": "Required", "config": { "services": [ "snmp" @@ -275,7 +267,6 @@ "name": "security.services.vnc", "test_description": "Check VNC is disabled on any port", "expected_behavior": "Device cannot be accessed / connected to via VNC on any port", - "required_result": "Required", "config": { "services": [ "vnc", @@ -330,7 +321,6 @@ "name": "security.services.tftp", "test_description": "Check TFTP port 69 is disabled (UDP)", "expected_behavior": "There is no TFTP service running on any port", - "required_result": "Required", "config": { "services": [ "tftp", @@ -363,7 +353,6 @@ "name": "ntp.network.ntp_server", "test_description": "Check NTP port 123 is disabled and the device is not operating as an NTP server", "expected_behavior": "The device does not respond to NTP requests when it's IP is set as the NTP server on another device", - "required_result": "Required", "config": { "services": [ "ntp" diff --git a/modules/test/services/python/src/services_module.py b/modules/test/services/python/src/services_module.py index b14c74234..a96d47bc0 100644 --- a/modules/test/services/python/src/services_module.py +++ b/modules/test/services/python/src/services_module.py @@ -83,7 +83,7 @@ def generate_module_report(self): else: udp_open += 1 - html_content = '

Services Module

' + html_content = '

Services Module

' # Add summary table html_content += (f''' diff --git a/modules/test/services/services.Dockerfile b/modules/test/services/services.Dockerfile index 3a89fc33c..8dcaafcc1 100644 --- a/modules/test/services/services.Dockerfile +++ b/modules/test/services/services.Dockerfile @@ -12,8 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. -# Image name: test-run/services-test -FROM test-run/base-test:latest +# Image name: testrun/services-test +FROM testrun/base-test:latest ARG MODULE_NAME=services ARG MODULE_DIR=modules/test/$MODULE_NAME @@ -22,7 +22,7 @@ ARG MODULE_DIR=modules/test/$MODULE_NAME COPY $MODULE_DIR/python/requirements.txt /testrun/python # Install all python requirements for the module -RUN pip3 install -r /testrun/python/requirements.txt +RUN pip install -r /testrun/python/requirements.txt # Copy over all configuration files COPY $MODULE_DIR/conf /testrun/conf diff --git a/modules/test/tls/bin/get_client_hello_packets.sh b/modules/test/tls/bin/get_client_hello_packets.sh index d563d11f2..317657187 100755 --- a/modules/test/tls/bin/get_client_hello_packets.sh +++ b/modules/test/tls/bin/get_client_hello_packets.sh @@ -21,13 +21,21 @@ TLS_VERSION="$3" TSHARK_OUTPUT="-T json -e ip.src -e tcp.dstport -e ip.dst" TSHARK_FILTER="ssl.handshake.type==1 and ip.src==$SRC_IP" -if [[ $TLS_VERSION == '1.2' || -z $TLS_VERSION ]];then +if [[ $TLS_VERSION == '1.0' ]]; then + TSHARK_FILTER="$TSHARK_FILTER and ssl.handshake.version==0x0301" +elif [[ $TLS_VERSION == '1.1' ]]; then + TSHARK_FILTER="$TSHARK_FILTER and ssl.handshake.version==0x0302" +elif [[ $TLS_VERSION == '1.2' || -z $TLS_VERSION ]]; then TSHARK_FILTER="$TSHARK_FILTER and ssl.handshake.version==0x0303" -elif [ $TLS_VERSION == '1.3' ];then +elif [[ $TLS_VERSION == '1.3' ]]; then TSHARK_FILTER="$TSHARK_FILTER and (ssl.handshake.version==0x0304 or tls.handshake.extensions.supported_version==0x0304)" +else + echo "Unsupported TLS version: $TLS_VERSION" + exit 1 fi response=$(tshark -r "$CAPTURE_FILE" $TSHARK_OUTPUT $TSHARK_FILTER) echo "$response" + \ No newline at end of file diff --git a/modules/test/tls/bin/get_handshake_complete.sh b/modules/test/tls/bin/get_handshake_complete.sh index 9bf9c525d..b36997f6d 100755 --- a/modules/test/tls/bin/get_handshake_complete.sh +++ b/modules/test/tls/bin/get_handshake_complete.sh @@ -1,33 +1,39 @@ -#!/bin/bash - -# Copyright 2023 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -CAPTURE_FILE="$1" -SRC_IP="$2" -DST_IP="$3" -TLS_VERSION="$4" - -TSHARK_FILTER="ip.src==$SRC_IP and ip.dst==$DST_IP " - -if [[ $TLS_VERSION == '1.2' || -z $TLS_VERSION ]];then - TSHARK_FILTER=$TSHARK_FILTER " and ssl.handshake.type==2 and tls.handshake.type==14 " -elif [ $TLS_VERSION == '1.2' ];then - TSHARK_FILTER=$TSHARK_FILTER "and ssl.handshake.type==2 and tls.handshake.extensions.supported_version==0x0304" -fi - -response=$(tshark -r "$CAPTURE_FILE" $TSHARK_FILTER) - -echo "$response" - \ No newline at end of file +#!/bin/bash + +# Copyright 2023 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +CAPTURE_FILE="$1" +SRC_IP="$2" +DST_IP="$3" +TLS_VERSION="$4" + +TSHARK_FILTER="ip.src==$SRC_IP and ip.dst==$DST_IP" + +if [[ $TLS_VERSION == '1.0' ]]; then + TSHARK_FILTER=$TSHARK_FILTER "and ssl.handshake.type==2 and tls.handshake.type==14" +elif [[ $TLS_VERSION == '1.1' ]]; then + TSHARK_FILTER=$TSHARK_FILTER "and ssl.handshake.type==2 and tls.handshake.type==14" +elif [[ $TLS_VERSION == '1.2' || -z $TLS_VERSION ]]; then + TSHARK_FILTER=$TSHARK_FILTER "and ssl.handshake.type==2 and tls.handshake.type==14" +elif [[ $TLS_VERSION == '1.3' ]]; then + TSHARK_FILTER=$TSHARK_FILTER "and ssl.handshake.type==2 and tls.handshake.extensions.supported_version==0x0304" +else + echo "Unsupported TLS version: $TLS_VERSION" + exit 1 +fi + +response=$(tshark -r "$CAPTURE_FILE" $TSHARK_FILTER) + +echo "$response" diff --git a/modules/test/tls/conf/module_config.json b/modules/test/tls/conf/module_config.json index c74bfd667..7058129f2 100644 --- a/modules/test/tls/conf/module_config.json +++ b/modules/test/tls/conf/module_config.json @@ -12,11 +12,19 @@ "timeout": 300 }, "tests":[ + { + "name": "security.tls.v1_0_client", + "test_description": "Device uses TLS with connection to an external service on port 443 (or any other port which could be running the webserver-HTTPS)", + "expected_behavior": "The packet indicates a TLS connection with at least TLS 1.0 and support", + "recommendations": [ + "Disable connections to unsecure services", + "Ensure any URLs connected to are secure (https)" + ] + }, { "name": "security.tls.v1_2_server", "test_description": "Check the device web server TLS 1.2 & certificate is valid", "expected_behavior": "TLS 1.2 certificate is issued to the web browser client when accessed", - "required_result": "Required if Applicable", "recommendations": [ "Enable TLS 1.2 support in the web server configuration", "Disable TLS 1.0 and 1.1", @@ -27,7 +35,6 @@ "name": "security.tls.v1_2_client", "test_description": "Device uses TLS with connection to an external service on port 443 (or any other port which could be running the webserver-HTTPS)", "expected_behavior": "The packet indicates a TLS connection with at least TLS 1.2 and support for ECDH and ECDSA ciphers", - "required_result": "Required if Applicable", "recommendations": [ "Disable connections to unsecure services", "Ensure any URLs connected to are secure (https)" @@ -37,7 +44,6 @@ "name": "security.tls.v1_3_server", "test_description": "Check the device web server TLS 1.3 & certificate is valid", "expected_behavior": "TLS 1.3 certificate is issued to the web browser client when accessed", - "required_result": "Informational", "recommendations": [ "Enable TLS 1.3 support in the web server configuration", "Disable TLS 1.0 and 1.1", @@ -48,7 +54,25 @@ "name": "security.tls.v1_3_client", "test_description": "Device uses TLS with connection to an external service on port 443 (or any other port which could be running the webserver-HTTPS)", "expected_behavior": "The packet indicates a TLS connection with at least TLS 1.3", - "required_result": "Informational", + "recommendations": [ + "Disable connections to unsecure services", + "Ensure any URLs connected to are secure (https)" + ] + }, + { + "name": "security.tls.v1_3_server", + "test_description": "Check the device web server TLS 1.3 & certificate is valid", + "expected_behavior": "TLS 1.3 certificate is issued to the web browser client when accessed", + "recommendations": [ + "Enable TLS 1.3 support in the web server configuration", + "Disable TLS 1.0 and 1.1", + "Sign the certificate used by the web server" + ] + }, + { + "name": "security.tls.v1_3_client", + "test_description": "Device uses TLS with connection to an external service on port 443 (or any other port which could be running the webserver-HTTPS)", + "expected_behavior": "The packet indicates a TLS connection with at least TLS 1.3", "recommendations": [ "Disable connections to unsecure services", "Ensure any URLs connected to are secure (https)" diff --git a/modules/test/tls/python/requirements.txt b/modules/test/tls/python/requirements.txt index 846a224f3..e03b1f45a 100644 --- a/modules/test/tls/python/requirements.txt +++ b/modules/test/tls/python/requirements.txt @@ -1,5 +1,5 @@ -cryptography==38.0.0 # Do not upgrade until TLS module can be fixed to account for removed x509 property in version 39 -pyOpenSSL==23.0.0 +cryptography==43.0.1 +pyOpenSSL==24.2.1 lxml==5.1.0 # Requirement of pyshark but if upgraded automatically above 5.1 will cause a python crash pyshark==0.6 requests==2.32.3 diff --git a/modules/test/tls/python/src/tls_module.py b/modules/test/tls/python/src/tls_module.py index 9aab1b782..186766b17 100644 --- a/modules/test/tls/python/src/tls_module.py +++ b/modules/test/tls/python/src/tls_module.py @@ -26,6 +26,7 @@ GATEWAY_CAPTURE_FILE = '/runtime/network/gateway.pcap' LOGGER = None + class TLSModule(TestModule): """The TLS testing module.""" @@ -234,8 +235,8 @@ def _security_tls_v1_2_server(self): self._device_ipv4_addr, tls_version='1.2') tls_1_3_results = self._tls_util.validate_tls_server( self._device_ipv4_addr, tls_version='1.3') - results = self._tls_util.process_tls_server_results(tls_1_2_results, - tls_1_3_results) + results = self._tls_util.process_tls_server_results( + tls_1_2_results, tls_1_3_results) # Determine results and return proper messaging and details description = '' if results[0] is None: @@ -244,7 +245,7 @@ def _security_tls_v1_2_server(self): description = 'TLS 1.2 certificate is valid' else: description = 'TLS 1.2 certificate is invalid' - return results[0], description,results[1] + return results[0], description, results[1] else: LOGGER.error('Could not resolve device IP address. Skipping') @@ -256,7 +257,7 @@ def _security_tls_v1_3_server(self): # If the ipv4 address wasn't resolved yet, try again if self._device_ipv4_addr is not None: results = self._tls_util.validate_tls_server(self._device_ipv4_addr, - tls_version='1.3') + tls_version='1.3') # Determine results and return proper messaging and details description = '' if results[0] is None: @@ -265,31 +266,57 @@ def _security_tls_v1_3_server(self): description = 'TLS 1.3 certificate is valid' else: description = 'TLS 1.3 certificate is invalid' - return results[0], description,results[1] + return results[0], description, results[1] else: LOGGER.error('Could not resolve device IP address') return 'Error', 'Could not resolve device IP address' + def _security_tls_v1_0_client(self): + LOGGER.info('Running security.tls.v1_0_client') + self._resolve_device_ip() + # If the ipv4 address wasn't resolved yet, try again + if self._device_ipv4_addr is not None: + tls_1_0_valid = self._validate_tls_client(self._device_ipv4_addr, '1.0') + tls_1_1_valid = self._validate_tls_client(self._device_ipv4_addr, '1.1') + tls_1_2_valid = self._validate_tls_client(self._device_ipv4_addr, '1.2') + tls_1_3_valid = self._validate_tls_client(self._device_ipv4_addr, '1.3') + states = [ + tls_1_0_valid[0], tls_1_1_valid[0], tls_1_2_valid[0], tls_1_3_valid[0] + ] + if any(state is True for state in states): + # If any state is True, return True + result_state = True + result_message = 'TLS 1.0 or higher detected' + elif all(state == 'Feature Not Detected' for state in states): + # If all states are "Feature not Detected" + result_state = 'Feature Not Detected' + result_message = tls_1_0_valid[1] + elif all(state == 'Error' for state in states): + # If all states are "Error" + result_state = 'Error' + result_message = '' + else: + result_state = False + result_message = 'TLS 1.0 or higher was not detected' + result_details = tls_1_0_valid[2] + tls_1_1_valid[2] + tls_1_2_valid[ + 2] + tls_1_3_valid[2] + result_tags = list( + set(tls_1_0_valid[3] + tls_1_1_valid[3] + tls_1_2_valid[3] + + tls_1_3_valid[3])) + return result_state, result_message, result_details, result_tags + else: + LOGGER.error('Could not resolve device IP address. Skipping') + return 'Error', 'Could not resolve device IP address' + def _security_tls_v1_2_client(self): LOGGER.info('Running security.tls.v1_2_client') self._resolve_device_ip() # If the ipv4 address wasn't resolved yet, try again if self._device_ipv4_addr is not None: - results = self._validate_tls_client(self._device_ipv4_addr, '1.2') - # Determine results and return proper messaging and details - description = '' - result = None - if results[0] is None: - description = 'No outbound connections were found' - result = 'Feature Not Detected' - elif results[0]: - description = 'TLS 1.2 client connections valid' - result = True - else: - description = 'TLS 1.2 client connections invalid' - result = False - return result, description, results[1] + return self._validate_tls_client(self._device_ipv4_addr, + '1.2', + unsupported_versions=['1.0', '1.1']) else: LOGGER.error('Could not resolve device IP address. Skipping') return 'Error', 'Could not resolve device IP address' @@ -299,41 +326,43 @@ def _security_tls_v1_3_client(self): self._resolve_device_ip() # If the ipv4 address wasn't resolved yet, try again if self._device_ipv4_addr is not None: - results = self._validate_tls_client(self._device_ipv4_addr, '1.3') - # Determine results and return proper messaging and details - description = '' - if results[0] is None: - description = 'No outbound connections were found' - elif results[0]: - description = 'TLS 1.3 client connections valid' - else: - description = 'TLS 1.3 client connections invalid' - return results[0], description, results[1] + return self._validate_tls_client(self._device_ipv4_addr, + '1.3', + unsupported_versions=['1.0', '1.1']) else: - LOGGER.error('Could not resolve device IP address') + LOGGER.error('Could not resolve device IP address. Skipping') return 'Error', 'Could not resolve device IP address' - def _validate_tls_client(self, client_ip, tls_version): + def _validate_tls_client(self, + client_ip, + tls_version, + unsupported_versions=None): client_results = self._tls_util.validate_tls_client( client_ip=client_ip, tls_version=tls_version, capture_files=[ MONITOR_CAPTURE_FILE, STARTUP_CAPTURE_FILE, TLS_CAPTURE_FILE - ]) + ], + unsupported_versions=unsupported_versions) # Generate results based on the state - result_message = 'No outbound connections were found.' - result_state = 'Feature Not Detected' + result_state = None + result_message = '' + result_details = '' + result_tags = [] - # If any of the packets detect failed client comms, fail the test - if not client_results[0] and client_results[0] is not None: - result_state = False - result_message = client_results[1] - else: + if client_results[0] is not None: + result_details = client_results[1] if client_results[0]: result_state = True - result_message = client_results[1] - return result_state, result_message + result_message = f'TLS {tls_version} client connections valid' + else: + result_state = False + result_message = f'TLS {tls_version} client connections invalid' + else: + result_state = 'Feature Not Detected' + result_message = 'No outbound connections were found' + return result_state, result_message, result_details, result_tags def _resolve_device_ip(self): # If the ipv4 address wasn't resolved yet, try again diff --git a/modules/test/tls/python/src/tls_util.py b/modules/test/tls/python/src/tls_util.py index 0364479c6..9f00b96ef 100644 --- a/modules/test/tls/python/src/tls_util.py +++ b/modules/test/tls/python/src/tls_util.py @@ -236,8 +236,8 @@ def validate_cert_chain(self, device_cert_path): ca_issuer_cert, cert_file_path = self.get_ca_issuer(certificate) if ca_issuer_cert is not None and cert_file_path is not None: LOGGER.info('CA Issuer resolved') - cert_text = crypto.dump_certificate(crypto.FILETYPE_TEXT, - ca_issuer_cert).decode() + cert_text = ca_issuer_cert.public_bytes( + encoding=serialization.Encoding.PEM).decode() LOGGER.info(cert_text) return self.validate_trusted_ca_signature_chain( device_cert_path=device_cert_path, @@ -551,7 +551,7 @@ def process_hello_packets(self, f'\nAllowing {protocol_name} traffic to {packet["dst_ip"]}') client_hello_results['valid'].append(packet) else: - # No cipher check for TLS 1.3 + # No cipher check for TLS 1.0, 1.1 or TLS 1.3 client_hello_results['valid'] = hello_packets return client_hello_results @@ -589,36 +589,31 @@ def get_non_tls_client_connection_ips(self, client_ip, capture_files): # we will assume any local connections using the same IP subnet as our # local network are approved and only connections to IP addresses outside # our network will be flagged. - def get_unsupported_tls_ips(self, client_ip, capture_files): + def get_unsupported_tls_ips(self, + client_ip, + capture_files, + unsupported_versions=None): LOGGER.info('Checking client for unsupported TLS client connections') - tls_1_0_packets = self.get_tls_packets(capture_files, client_ip, '1.0') - tls_1_1_packets = self.get_tls_packets(capture_files, client_ip, '1.1') - unsupported_tls_dst_ips = {} - if len(tls_1_0_packets) > 0: - for packet in tls_1_0_packets: - dst_ip = packet['dst_ip'] - tls_version = '1.0' - if dst_ip not in unsupported_tls_dst_ips: - LOGGER.info(f'''Unsupported TLS {tls_version} - connections detected to {dst_ip}''') - unsupported_tls_dst_ips[dst_ip] = [tls_version] - - if len(tls_1_1_packets) > 0: - for packet in tls_1_1_packets: - dst_ip = packet['dst_ip'] - tls_version = '1.1' - # Check if the IP is already in the dictionary - if dst_ip in unsupported_tls_dst_ips: - # If the IP is already present, append the new TLS version to the - # list - unsupported_tls_dst_ips[dst_ip].append(tls_version) - else: - # If the IP is not present, create a new list with the current - # TLS version - LOGGER.info(f'''Unsupported TLS {tls_version} connections detected - to {dst_ip}''') - unsupported_tls_dst_ips[dst_ip] = [tls_version] + if unsupported_versions is not None: + for unsupported_version in unsupported_versions: + tls_packets = self.get_tls_packets(capture_files, client_ip, '1.0') + if len(tls_packets) > 0: + for packet in tls_packets: + dst_ip = packet['dst_ip'] + tls_version = unsupported_version + if dst_ip not in unsupported_tls_dst_ips: + # If the IP is already present, append the new TLS version to the + # list + LOGGER.info(f'''Unsupported TLS {tls_version} + connections detected to {dst_ip}''') + unsupported_tls_dst_ips[dst_ip] = [tls_version] + else: + # If the IP is not present, create a new list with the current + # TLS version + LOGGER.info(f'''Unsupported TLS {tls_version} connections detected + to {dst_ip}''') + unsupported_tls_dst_ips[dst_ip] = [tls_version] return unsupported_tls_dst_ips # Check if the device has made any outbound connections that use any @@ -657,20 +652,26 @@ def is_private_ip(self, ip): return True return False - def validate_tls_client(self, client_ip, tls_version, capture_files): + def validate_tls_client(self, + client_ip, + tls_version, + capture_files, + unsupported_versions=None): LOGGER.info('Validating client for TLS: ' + tls_version) hello_packets = self.get_hello_packets(capture_files, client_ip, tls_version) # Resolve allowed protocol connections that require # additional consideration beyond packet inspection - allowed_protocol_client_ips = ( - self.get_allowed_protocol_client_connection_ips(client_ip, - capture_files)) + protocol_client_ips = (self.get_allowed_protocol_client_connection_ips( + client_ip, capture_files)) - LOGGER.info(f'Protocol IPS: {allowed_protocol_client_ips}') - client_hello_results = self.process_hello_packets( - hello_packets, allowed_protocol_client_ips, tls_version) + if len(protocol_client_ips) > 0: + LOGGER.info( + f'Allowed Protocol IP connections detected: {protocol_client_ips}') + client_hello_results = self.process_hello_packets(hello_packets, + protocol_client_ips, + tls_version) handshakes = {'complete': [], 'incomplete': []} for packet in client_hello_results['valid']: @@ -758,7 +759,8 @@ def validate_tls_client(self, client_ip, tls_version, capture_files): LOGGER.info(f'''TLS connection detected to {ip}. Ignoring non-TLS traffic detected to this IP''') - unsupported_tls_ips = self.get_unsupported_tls_ips(client_ip, capture_files) + unsupported_tls_ips = self.get_unsupported_tls_ips(client_ip, capture_files, + unsupported_versions) if len(unsupported_tls_ips) > 0: tls_client_valid = False for ip, tls_versions in unsupported_tls_ips.items(): diff --git a/modules/test/tls/tls.Dockerfile b/modules/test/tls/tls.Dockerfile index 987ede591..c448c8478 100644 --- a/modules/test/tls/tls.Dockerfile +++ b/modules/test/tls/tls.Dockerfile @@ -12,8 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. -# Image name: test-run/tls-test -FROM test-run/base-test:latest +# Image name: testrun/tls-test +FROM testrun/base-test:latest # Set DEBIAN_FRONTEND to noninteractive mode ENV DEBIAN_FRONTEND=noninteractive @@ -41,7 +41,10 @@ RUN chmod u+x /testrun/bin/* COPY $MODULE_DIR/python /testrun/python # Install all python requirements for the module -RUN pip3 install -r /testrun/python/requirements.txt +RUN pip install -r /testrun/python/requirements.txt + +# Install all python requirements for the modules unit test +RUN pip install -r /testrun/python/requirements-test.txt # Install all python requirements for the modules unit test RUN pip3 install -r /testrun/python/requirements-test.txt diff --git a/modules/ui/angular.json b/modules/ui/angular.json index 0bf42377f..8109c8691 100644 --- a/modules/ui/angular.json +++ b/modules/ui/angular.json @@ -38,8 +38,8 @@ }, { "type": "anyComponentStyle", - "maximumWarning": "3kb", - "maximumError": "4kb" + "maximumWarning": "5kb", + "maximumError": "6kb" } ], "outputHashing": "all" diff --git a/modules/ui/build.Dockerfile b/modules/ui/build.Dockerfile index 180ad9747..082efbbcc 100644 --- a/modules/ui/build.Dockerfile +++ b/modules/ui/build.Dockerfile @@ -16,5 +16,4 @@ FROM node@sha256:ffebb4405810c92d267a764b21975fb2d96772e41877248a37bf3abaa0d3b590 as build # Set the working directory -WORKDIR /modules/ui - +WORKDIR /modules/ui \ No newline at end of file diff --git a/modules/ui/package-lock.json b/modules/ui/package-lock.json index 637dc47e4..7d3fb5c5c 100644 --- a/modules/ui/package-lock.json +++ b/modules/ui/package-lock.json @@ -8,37 +8,37 @@ "name": "test-run-ui", "version": "0.0.0", "dependencies": { - "@angular/animations": "^17.0.8", - "@angular/cdk": "^17.0.4", - "@angular/common": "^17.0.8", - "@angular/compiler": "^17.0.8", - "@angular/core": "^17.0.8", - "@angular/forms": "^17.3.1", - "@angular/material": "^17.3.1", - "@angular/platform-browser": "^17.0.8", - "@angular/platform-browser-dynamic": "^17.3.1", - "@angular/router": "^17.3.1", - "@ngrx/component-store": "^17.1.1", - "@ngrx/effects": "^17.1.1", - "@ngrx/store": "^17.0.1", + "@angular/animations": "^18.2.4", + "@angular/cdk": "^18.2.0", + "@angular/common": "^18.2.4", + "@angular/compiler": "^18.2.4", + "@angular/core": "^18.2.4", + "@angular/forms": "^18.2.4", + "@angular/material": "^18.2.0", + "@angular/platform-browser": "^18.2.4", + "@angular/platform-browser-dynamic": "^18.2.4", + "@angular/router": "^18.2.4", + "@ngrx/component-store": "^18.0.2", + "@ngrx/effects": "^18.0.2", + "@ngrx/store": "^18.0.2", "ngx-mask": "^16.4.2", "ngx-mqtt": "^17.0.0", "rxjs": "~7.8.0", "tslib": "^2.6.2", - "zone.js": "^0.14.4" + "zone.js": "^0.14.10" }, "devDependencies": { - "@angular-devkit/build-angular": "^17.3.2", - "@angular-eslint/builder": "17.2.0", - "@angular-eslint/eslint-plugin": "17.2.0", - "@angular-eslint/eslint-plugin-template": "17.2.0", - "@angular-eslint/schematics": "17.2.0", - "@angular-eslint/template-parser": "17.2.0", - "@angular/cli": "~17.0.9", - "@angular/compiler-cli": "^17.3.1", + "@angular-devkit/build-angular": "^18.1.4", + "@angular-eslint/builder": "18.3.0", + "@angular-eslint/eslint-plugin": "^18.3.0", + "@angular-eslint/eslint-plugin-template": "^18.3.0", + "@angular-eslint/schematics": "^18.3.0", + "@angular-eslint/template-parser": "18.3.0", + "@angular/cli": "~18.2.4", + "@angular/compiler-cli": "^18.2.4", "@types/jasmine": "~4.3.6", - "@typescript-eslint/eslint-plugin": "^6.21.0", - "@typescript-eslint/parser": "^6.21.0", + "@typescript-eslint/eslint-plugin": "^8.2.0", + "@typescript-eslint/parser": "^8.2.0", "eslint": "^8.57.0", "eslint-config-prettier": "^9.1.0", "eslint-plugin-prettier": "^5.1.3", @@ -49,8 +49,8 @@ "karma-jasmine": "~5.1.0", "karma-jasmine-html-reporter": "~2.1.0", "prettier": "^3.2.5", - "prettier-eslint": "^16.3.0", - "typescript": "~5.2.2" + "prettier-eslint": "^13.0.0", + "typescript": "~5.5.4" } }, "node_modules/@ampproject/remapping": { @@ -67,112 +67,111 @@ } }, "node_modules/@angular-devkit/architect": { - "version": "0.1703.6", - "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1703.6.tgz", - "integrity": "sha512-Ck501FD/QuOjeKVFs7hU92w8+Ffetv0d5Sq09XY2/uygo5c/thMzp9nkevaIWBxUSeU5RqYZizDrhFVgYzbbOw==", + "version": "0.1802.6", + "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1802.6.tgz", + "integrity": "sha512-oF7cPFdTLxeuvXkK/opSdIxZ1E4LrBbmuytQ/nCoAGOaKBWdqvwagRZ6jVhaI0Gwu48rkcV7Zhesg/ESNnROdw==", "dev": true, "dependencies": { - "@angular-devkit/core": "17.3.6", + "@angular-devkit/core": "18.2.6", "rxjs": "7.8.1" }, "engines": { - "node": "^18.13.0 || >=20.9.0", + "node": "^18.19.1 || ^20.11.1 || >=22.0.0", "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", "yarn": ">= 1.13.0" } }, "node_modules/@angular-devkit/build-angular": { - "version": "17.3.6", - "resolved": "https://registry.npmjs.org/@angular-devkit/build-angular/-/build-angular-17.3.6.tgz", - "integrity": "sha512-K4CEZvhQZUUOpmXPVoI1YBM8BARbIlqE6FZRxakmnr+YOtVTYE5s+Dr1wgja8hZIohNz6L7j167G9Aut7oPU/w==", + "version": "18.2.6", + "resolved": "https://registry.npmjs.org/@angular-devkit/build-angular/-/build-angular-18.2.6.tgz", + "integrity": "sha512-u12cJZttgs5j7gICHWSmcaTCu0EFXEzKqI8nkYCwq2MtuJlAXiMQSXYuEP9OU3Go4vMAPtQh2kShyOWCX5b4EQ==", "dev": true, "dependencies": { "@ampproject/remapping": "2.3.0", - "@angular-devkit/architect": "0.1703.6", - "@angular-devkit/build-webpack": "0.1703.6", - "@angular-devkit/core": "17.3.6", - "@babel/core": "7.24.0", - "@babel/generator": "7.23.6", - "@babel/helper-annotate-as-pure": "7.22.5", - "@babel/helper-split-export-declaration": "7.22.6", - "@babel/plugin-transform-async-generator-functions": "7.23.9", - "@babel/plugin-transform-async-to-generator": "7.23.3", - "@babel/plugin-transform-runtime": "7.24.0", - "@babel/preset-env": "7.24.0", - "@babel/runtime": "7.24.0", - "@discoveryjs/json-ext": "0.5.7", - "@ngtools/webpack": "17.3.6", + "@angular-devkit/architect": "0.1802.6", + "@angular-devkit/build-webpack": "0.1802.6", + "@angular-devkit/core": "18.2.6", + "@angular/build": "18.2.6", + "@babel/core": "7.25.2", + "@babel/generator": "7.25.0", + "@babel/helper-annotate-as-pure": "7.24.7", + "@babel/helper-split-export-declaration": "7.24.7", + "@babel/plugin-transform-async-generator-functions": "7.25.0", + "@babel/plugin-transform-async-to-generator": "7.24.7", + "@babel/plugin-transform-runtime": "7.24.7", + "@babel/preset-env": "7.25.3", + "@babel/runtime": "7.25.0", + "@discoveryjs/json-ext": "0.6.1", + "@ngtools/webpack": "18.2.6", "@vitejs/plugin-basic-ssl": "1.1.0", "ansi-colors": "4.1.3", - "autoprefixer": "10.4.18", + "autoprefixer": "10.4.20", "babel-loader": "9.1.3", - "babel-plugin-istanbul": "6.1.1", "browserslist": "^4.21.5", - "copy-webpack-plugin": "11.0.0", - "critters": "0.0.22", - "css-loader": "6.10.0", - "esbuild-wasm": "0.20.1", + "copy-webpack-plugin": "12.0.2", + "critters": "0.0.24", + "css-loader": "7.1.2", + "esbuild-wasm": "0.23.0", "fast-glob": "3.3.2", - "http-proxy-middleware": "2.0.6", - "https-proxy-agent": "7.0.4", - "inquirer": "9.2.15", - "jsonc-parser": "3.2.1", + "http-proxy-middleware": "3.0.0", + "https-proxy-agent": "7.0.5", + "istanbul-lib-instrument": "6.0.3", + "jsonc-parser": "3.3.1", "karma-source-map-support": "1.4.0", "less": "4.2.0", - "less-loader": "11.1.0", + "less-loader": "12.2.0", "license-webpack-plugin": "4.0.2", - "loader-utils": "3.2.1", - "magic-string": "0.30.8", - "mini-css-extract-plugin": "2.8.1", + "loader-utils": "3.3.1", + "magic-string": "0.30.11", + "mini-css-extract-plugin": "2.9.0", "mrmime": "2.0.0", - "open": "8.4.2", + "open": "10.1.0", "ora": "5.4.1", "parse5-html-rewriting-stream": "7.0.0", - "picomatch": "4.0.1", - "piscina": "4.4.0", - "postcss": "8.4.35", + "picomatch": "4.0.2", + "piscina": "4.6.1", + "postcss": "8.4.41", "postcss-loader": "8.1.1", "resolve-url-loader": "5.0.0", "rxjs": "7.8.1", - "sass": "1.71.1", - "sass-loader": "14.1.1", - "semver": "7.6.0", + "sass": "1.77.6", + "sass-loader": "16.0.0", + "semver": "7.6.3", "source-map-loader": "5.0.0", "source-map-support": "0.5.21", - "terser": "5.29.1", + "terser": "5.31.6", "tree-kill": "1.2.2", - "tslib": "2.6.2", - "undici": "6.11.1", - "vite": "5.1.7", - "watchpack": "2.4.0", - "webpack": "5.90.3", - "webpack-dev-middleware": "6.1.2", - "webpack-dev-server": "4.15.1", - "webpack-merge": "5.10.0", + "tslib": "2.6.3", + "vite": "5.4.6", + "watchpack": "2.4.1", + "webpack": "5.94.0", + "webpack-dev-middleware": "7.4.2", + "webpack-dev-server": "5.0.4", + "webpack-merge": "6.0.1", "webpack-subresource-integrity": "5.1.0" }, "engines": { - "node": "^18.13.0 || >=20.9.0", + "node": "^18.19.1 || ^20.11.1 || >=22.0.0", "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", "yarn": ">= 1.13.0" }, "optionalDependencies": { - "esbuild": "0.20.1" + "esbuild": "0.23.0" }, "peerDependencies": { - "@angular/compiler-cli": "^17.0.0", - "@angular/localize": "^17.0.0", - "@angular/platform-server": "^17.0.0", - "@angular/service-worker": "^17.0.0", + "@angular/compiler-cli": "^18.0.0", + "@angular/localize": "^18.0.0", + "@angular/platform-server": "^18.0.0", + "@angular/service-worker": "^18.0.0", "@web/test-runner": "^0.18.0", "browser-sync": "^3.0.2", "jest": "^29.5.0", "jest-environment-jsdom": "^29.5.0", "karma": "^6.3.0", - "ng-packagr": "^17.0.0", + "ng-packagr": "^18.0.0", "protractor": "^7.0.0", "tailwindcss": "^2.0.0 || ^3.0.0", - "typescript": ">=5.2 <5.5" + "typescript": ">=5.4 <5.6" }, "peerDependenciesMeta": { "@angular/localize": { @@ -210,40 +209,46 @@ } } }, + "node_modules/@angular-devkit/build-angular/node_modules/tslib": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz", + "integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==", + "dev": true + }, "node_modules/@angular-devkit/build-webpack": { - "version": "0.1703.6", - "resolved": "https://registry.npmjs.org/@angular-devkit/build-webpack/-/build-webpack-0.1703.6.tgz", - "integrity": "sha512-pJu0et2SiF0kfXenHSTtAART0omzbWpLgBfeUo4hBh4uwX5IaT+mRpYpr8gCXMq+qsjoQp3HobSU3lPDeBn+bg==", + "version": "0.1802.6", + "resolved": "https://registry.npmjs.org/@angular-devkit/build-webpack/-/build-webpack-0.1802.6.tgz", + "integrity": "sha512-JMLcXFaitJplwZMKkqhbYirINCRD6eOPZuIGaIOVynXYGWgvJkLT9t5C2wm9HqSLtp1K7NcYG2Y7PtTVR4krnQ==", "dev": true, "dependencies": { - "@angular-devkit/architect": "0.1703.6", + "@angular-devkit/architect": "0.1802.6", "rxjs": "7.8.1" }, "engines": { - "node": "^18.13.0 || >=20.9.0", + "node": "^18.19.1 || ^20.11.1 || >=22.0.0", "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", "yarn": ">= 1.13.0" }, "peerDependencies": { "webpack": "^5.30.0", - "webpack-dev-server": "^4.0.0" + "webpack-dev-server": "^5.0.2" } }, "node_modules/@angular-devkit/core": { - "version": "17.3.6", - "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-17.3.6.tgz", - "integrity": "sha512-FVbkT9dEwHEvjnxr4mvMNSMg2bCFoGoP4X68xXU9dhLEUpC05opLvfbaR3Qh543eCJ5AstosBFVzB/krfIkOvA==", + "version": "18.2.6", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-18.2.6.tgz", + "integrity": "sha512-la4CFvs5PcRWSkQ/H7TB5cPZirFVA9GoWk5LzIk8si6VjWBJRm8b3keKJoC9LlNeABRUIR5z0ocYkyQQUhdMfg==", "dev": true, "dependencies": { - "ajv": "8.12.0", - "ajv-formats": "2.1.1", - "jsonc-parser": "3.2.1", - "picomatch": "4.0.1", + "ajv": "8.17.1", + "ajv-formats": "3.0.1", + "jsonc-parser": "3.3.1", + "picomatch": "4.0.2", "rxjs": "7.8.1", "source-map": "0.7.4" }, "engines": { - "node": "^18.13.0 || >=20.9.0", + "node": "^18.19.1 || ^20.11.1 || >=22.0.0", "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", "yarn": ">= 1.13.0" }, @@ -257,440 +262,280 @@ } }, "node_modules/@angular-devkit/schematics": { - "version": "17.0.10", - "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-17.0.10.tgz", - "integrity": "sha512-hjf4gaMx2uB6ZhBstBSH0Q2hzfp6kxI4IiJ5i1QrxPNE1MdGnb2h+LgPTRCdO72a7PGeWcSxFRE7cxrXeQy19g==", + "version": "18.2.6", + "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-18.2.6.tgz", + "integrity": "sha512-uIttrQ2cQ2PWAFFVPeCoNR8xvs7tPJ2i8gzqsIwYdge107xDC6u9CqfgmBqPDSFpWj+IiC2Jwcm8Z4HYKU4+7A==", "dev": true, "dependencies": { - "@angular-devkit/core": "17.0.10", - "jsonc-parser": "3.2.0", - "magic-string": "0.30.5", + "@angular-devkit/core": "18.2.6", + "jsonc-parser": "3.3.1", + "magic-string": "0.30.11", "ora": "5.4.1", "rxjs": "7.8.1" }, "engines": { - "node": "^18.13.0 || >=20.9.0", - "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", - "yarn": ">= 1.13.0" - } - }, - "node_modules/@angular-devkit/schematics/node_modules/@angular-devkit/core": { - "version": "17.0.10", - "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-17.0.10.tgz", - "integrity": "sha512-93N6oHnmtRt0hL3AXxvnk47sN1rHndfj+pqI5haEY41AGWzIWv9cSBsqlM0PWltNpo6VivcExZESvbLJ71wqbQ==", - "dev": true, - "dependencies": { - "ajv": "8.12.0", - "ajv-formats": "2.1.1", - "jsonc-parser": "3.2.0", - "picomatch": "3.0.1", - "rxjs": "7.8.1", - "source-map": "0.7.4" - }, - "engines": { - "node": "^18.13.0 || >=20.9.0", + "node": "^18.19.1 || ^20.11.1 || >=22.0.0", "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", "yarn": ">= 1.13.0" - }, - "peerDependencies": { - "chokidar": "^3.5.2" - }, - "peerDependenciesMeta": { - "chokidar": { - "optional": true - } - } - }, - "node_modules/@angular-devkit/schematics/node_modules/jsonc-parser": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.0.tgz", - "integrity": "sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==", - "dev": true - }, - "node_modules/@angular-devkit/schematics/node_modules/magic-string": { - "version": "0.30.5", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.5.tgz", - "integrity": "sha512-7xlpfBaQaP/T6Vh8MO/EqXSW5En6INHEvEXQiuff7Gku0PWjU3uf6w/j9o7O+SpB5fOAkrI5HeoNgwjEO0pFsA==", - "dev": true, - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.4.15" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/@angular-devkit/schematics/node_modules/picomatch": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-3.0.1.tgz", - "integrity": "sha512-I3EurrIQMlRc9IaAZnqRR044Phh2DXY+55o7uJ0V+hYZAcQYSuFWsc9q5PvyDHUSCe1Qxn/iBz+78s86zWnGag==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" } }, "node_modules/@angular-eslint/builder": { - "version": "17.2.0", - "resolved": "https://registry.npmjs.org/@angular-eslint/builder/-/builder-17.2.0.tgz", - "integrity": "sha512-xPxgCTPcnFRT8OYs9R5UZVAtzVouIIfdMOqTcB847Cev4H8kqRz0gO5aqkQiL+0erwnLf8D4nRzMTJjSBpQQNw==", + "version": "18.3.0", + "resolved": "https://registry.npmjs.org/@angular-eslint/builder/-/builder-18.3.0.tgz", + "integrity": "sha512-httEQyqyBw3+0CRtAa7muFxHrauRfkEfk/jmrh5fn2Eiu+I53hAqFPgrwVi1V6AP/kj2zbAiWhd5xM3pMJdoRQ==", "dev": true, - "dependencies": { - "@nx/devkit": "17.2.8", - "nx": "17.2.8" - }, "peerDependencies": { - "eslint": "^7.20.0 || ^8.0.0", + "eslint": "^8.57.0 || ^9.0.0", "typescript": "*" } }, "node_modules/@angular-eslint/bundled-angular-compiler": { - "version": "17.2.0", - "resolved": "https://registry.npmjs.org/@angular-eslint/bundled-angular-compiler/-/bundled-angular-compiler-17.2.0.tgz", - "integrity": "sha512-uBvPbPE2JxqpdLs//Nd5+TRLgjxDxvTYgmGFTKI9Eo98krqps+rhSQCRSHWACukzc25X3Q4ITHfvjODQL8qQkg==", + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/@angular-eslint/bundled-angular-compiler/-/bundled-angular-compiler-18.3.1.tgz", + "integrity": "sha512-sikmkjfsXPpPTku1aQkQ1MNNEKGBgGGRvUN/WeNS9dhCJ4dxU3O7dZctt1aQWj+W3nbuUtDiimAWF5fZHGFE2Q==", "dev": true }, "node_modules/@angular-eslint/eslint-plugin": { - "version": "17.2.0", - "resolved": "https://registry.npmjs.org/@angular-eslint/eslint-plugin/-/eslint-plugin-17.2.0.tgz", - "integrity": "sha512-8A3hD/11N6QEchsAGggTPmNsa0GS5p44t930slMsxrTvdSlKAo56FzVdxwSkOcejKIJs57oWxoKvtK4UyLYkeA==", + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/@angular-eslint/eslint-plugin/-/eslint-plugin-18.3.1.tgz", + "integrity": "sha512-MP4Nm+SHboF8KdnN0KpPEGAaTTzDLPm3+S/4W3Mg8onqWCyadyd4mActh9mK/pvCj8TVlb/SW1zeTtdMYhwonw==", "dev": true, "dependencies": { - "@angular-eslint/utils": "17.2.0", - "@typescript-eslint/utils": "6.18.0" + "@angular-eslint/bundled-angular-compiler": "18.3.1", + "@angular-eslint/utils": "18.3.1" }, "peerDependencies": { - "eslint": "^7.20.0 || ^8.0.0", + "@typescript-eslint/utils": "^7.11.0 || ^8.0.0", + "eslint": "^8.57.0 || ^9.0.0", "typescript": "*" } }, "node_modules/@angular-eslint/eslint-plugin-template": { - "version": "17.2.0", - "resolved": "https://registry.npmjs.org/@angular-eslint/eslint-plugin-template/-/eslint-plugin-template-17.2.0.tgz", - "integrity": "sha512-CkcAOWWqNwX0FXeLwJu0Vctso8q/NPHJ95R2Cy8hjwuMyFF83/vDouyeIjYC+SRv6hbevmNa+BbdYXhQZinIHw==", + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/@angular-eslint/eslint-plugin-template/-/eslint-plugin-template-18.3.1.tgz", + "integrity": "sha512-hBJ3+f7VSidvrtYaXH7Vp0sWvblA9jLK2c6uQzhYGWdEDUcTg7g7VI9ThW39WvMbHqkyzNE4PPOynK69cBEDGg==", "dev": true, "dependencies": { - "@angular-eslint/bundled-angular-compiler": "17.2.0", - "@angular-eslint/utils": "17.2.0", - "@typescript-eslint/type-utils": "6.18.0", - "@typescript-eslint/utils": "6.18.0", + "@angular-eslint/bundled-angular-compiler": "18.3.1", + "@angular-eslint/utils": "18.3.1", "aria-query": "5.3.0", - "axobject-query": "4.0.0" + "axobject-query": "4.1.0" }, "peerDependencies": { - "eslint": "^7.20.0 || ^8.0.0", + "@typescript-eslint/utils": "^7.11.0 || ^8.0.0", + "eslint": "^8.57.0 || ^9.0.0", "typescript": "*" } }, "node_modules/@angular-eslint/schematics": { - "version": "17.2.0", - "resolved": "https://registry.npmjs.org/@angular-eslint/schematics/-/schematics-17.2.0.tgz", - "integrity": "sha512-lV2+2H3Hf6FCJfM+cddJ/7ss3qc99OO2wuvTjGNH512mP75tvfLakV+e6TFFdzK0km+ceXvB2VqNXMSShB4PVQ==", + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/@angular-eslint/schematics/-/schematics-18.3.1.tgz", + "integrity": "sha512-BTsQHDu7LjvXannJTb5BqMPCFIHRNN94eRyb60VfjJxB/ZFtsbAQDFFOi5lEZsRsd4mBeUMuL9mW4IMcPtUQ9Q==", "dev": true, "dependencies": { - "@angular-eslint/eslint-plugin": "17.2.0", - "@angular-eslint/eslint-plugin-template": "17.2.0", - "@nx/devkit": "17.2.8", - "ignore": "5.3.0", - "nx": "17.2.8", - "strip-json-comments": "3.1.1", - "tmp": "0.2.1" + "@angular-eslint/eslint-plugin": "18.3.1", + "@angular-eslint/eslint-plugin-template": "18.3.1", + "ignore": "5.3.2", + "semver": "7.6.3", + "strip-json-comments": "3.1.1" }, "peerDependencies": { - "@angular/cli": ">= 17.0.0 < 18.0.0" + "@angular-devkit/core": ">= 18.0.0 < 19.0.0", + "@angular-devkit/schematics": ">= 18.0.0 < 19.0.0" } }, "node_modules/@angular-eslint/template-parser": { - "version": "17.2.0", - "resolved": "https://registry.npmjs.org/@angular-eslint/template-parser/-/template-parser-17.2.0.tgz", - "integrity": "sha512-Js9w1IXWPvXEjd05bWkZzRaLw0g0mJPztAWOj3DiU7H9LKkautQq0zZu02cAAnXZim2CsAagEh2GmGjhaYvoKg==", + "version": "18.3.0", + "resolved": "https://registry.npmjs.org/@angular-eslint/template-parser/-/template-parser-18.3.0.tgz", + "integrity": "sha512-1mUquqcnugI4qsoxcYZKZ6WMi6RPelDcJZg2YqGyuaIuhWmi3ZqJZLErSSpjP60+TbYZu7wM8Kchqa1bwJtEaQ==", "dev": true, "dependencies": { - "@angular-eslint/bundled-angular-compiler": "17.2.0", - "eslint-scope": "^8.0.0" + "@angular-eslint/bundled-angular-compiler": "18.3.0", + "eslint-scope": "^8.0.2" }, "peerDependencies": { - "eslint": "^7.20.0 || ^8.0.0", + "eslint": "^8.57.0 || ^9.0.0", "typescript": "*" } }, + "node_modules/@angular-eslint/template-parser/node_modules/@angular-eslint/bundled-angular-compiler": { + "version": "18.3.0", + "resolved": "https://registry.npmjs.org/@angular-eslint/bundled-angular-compiler/-/bundled-angular-compiler-18.3.0.tgz", + "integrity": "sha512-v/59FxUKnMzymVce99gV43huxoqXWMb85aKvzlNvLN+ScDu6ZE4YMiTQNpfapVL2lkxhs0uwB3jH17EYd5TcsA==", + "dev": true + }, "node_modules/@angular-eslint/utils": { - "version": "17.2.0", - "resolved": "https://registry.npmjs.org/@angular-eslint/utils/-/utils-17.2.0.tgz", - "integrity": "sha512-J7DsFKb5yxv8te8LQvChNn6MBvKulcBx+jtHX1uen+uuuv8XhZuVMZXS0rolUkdl1Q0mBeHpkuO2q6Vh17pqbQ==", + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/@angular-eslint/utils/-/utils-18.3.1.tgz", + "integrity": "sha512-sd9niZI7h9H2FQ7OLiQsLFBhjhRQTASh+Q0+4+hyjv9idbSHBJli8Gsi2fqj9zhtMKpAZFTrWzuLUpubJ9UYbA==", "dev": true, "dependencies": { - "@angular-eslint/bundled-angular-compiler": "17.2.0", - "@typescript-eslint/utils": "6.18.0" + "@angular-eslint/bundled-angular-compiler": "18.3.1" }, "peerDependencies": { - "eslint": "^7.20.0 || ^8.0.0", + "@typescript-eslint/utils": "^7.11.0 || ^8.0.0", + "eslint": "^8.57.0 || ^9.0.0", "typescript": "*" } }, "node_modules/@angular/animations": { - "version": "17.3.7", - "resolved": "https://registry.npmjs.org/@angular/animations/-/animations-17.3.7.tgz", - "integrity": "sha512-ahenGALPPweeHgqtl9BMkGIAV4fUNI5kOWUrLNbKBfwIJN+aOBOYV1Jz6NKUQq6eYn/1ZYtm0f3lIkHIdtLKEw==", + "version": "18.2.6", + "resolved": "https://registry.npmjs.org/@angular/animations/-/animations-18.2.6.tgz", + "integrity": "sha512-vy9wy+Q9beiRxkEO8wNxFQ63AqAujGvk8AUHepxxIT7QNNc512TNKz8uH+feWDPO38Dm2obwYQHMGzs3WO7pUA==", "dependencies": { "tslib": "^2.3.0" }, "engines": { - "node": "^18.13.0 || >=20.9.0" + "node": "^18.19.1 || ^20.11.1 || >=22.0.0" }, "peerDependencies": { - "@angular/core": "17.3.7" - } - }, - "node_modules/@angular/cdk": { - "version": "17.3.7", - "resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-17.3.7.tgz", - "integrity": "sha512-aFEh8tzKFOwini6aNEp57S54Ocp9T7YIJfBVMESptu2TCPdMTlJ1HJTg5XS8NcQO+vwi9cFPGVwGF1frOx4LXA==", - "dependencies": { - "tslib": "^2.3.0" - }, - "optionalDependencies": { - "parse5": "^7.1.2" - }, - "peerDependencies": { - "@angular/common": "^17.0.0 || ^18.0.0", - "@angular/core": "^17.0.0 || ^18.0.0", - "rxjs": "^6.5.3 || ^7.4.0" - } - }, - "node_modules/@angular/cli": { - "version": "17.0.10", - "resolved": "https://registry.npmjs.org/@angular/cli/-/cli-17.0.10.tgz", - "integrity": "sha512-52rd8KmOMe3NJDp/wA+Mwj21qd4HR8fuLtfrErgVnZaJZKX2Bzi/z7FHQD3gdgMAdzUiG0OJWGM0h75Ls9X6Gw==", - "dev": true, - "dependencies": { - "@angular-devkit/architect": "0.1700.10", - "@angular-devkit/core": "17.0.10", - "@angular-devkit/schematics": "17.0.10", - "@schematics/angular": "17.0.10", - "@yarnpkg/lockfile": "1.1.0", - "ansi-colors": "4.1.3", - "ini": "4.1.1", - "inquirer": "9.2.11", - "jsonc-parser": "3.2.0", - "npm-package-arg": "11.0.1", - "npm-pick-manifest": "9.0.0", - "open": "8.4.2", - "ora": "5.4.1", - "pacote": "17.0.4", - "resolve": "1.22.8", - "semver": "7.5.4", - "symbol-observable": "4.0.0", - "yargs": "17.7.2" - }, - "bin": { - "ng": "bin/ng.js" - }, - "engines": { - "node": "^18.13.0 || >=20.9.0", - "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", - "yarn": ">= 1.13.0" - } - }, - "node_modules/@angular/cli/node_modules/@angular-devkit/architect": { - "version": "0.1700.10", - "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1700.10.tgz", - "integrity": "sha512-JD/3jkdN1jrFMIDEk9grKdbjutIoxUDMRazq1LZooWjTkzlYk09i/s6HwvIPao7zvxJfelD6asTPspgkjOMP5A==", - "dev": true, - "dependencies": { - "@angular-devkit/core": "17.0.10", - "rxjs": "7.8.1" - }, - "engines": { - "node": "^18.13.0 || >=20.9.0", - "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", - "yarn": ">= 1.13.0" + "@angular/core": "18.2.6" } }, - "node_modules/@angular/cli/node_modules/@angular-devkit/core": { - "version": "17.0.10", - "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-17.0.10.tgz", - "integrity": "sha512-93N6oHnmtRt0hL3AXxvnk47sN1rHndfj+pqI5haEY41AGWzIWv9cSBsqlM0PWltNpo6VivcExZESvbLJ71wqbQ==", + "node_modules/@angular/build": { + "version": "18.2.6", + "resolved": "https://registry.npmjs.org/@angular/build/-/build-18.2.6.tgz", + "integrity": "sha512-TQzX6Mi7uXFvmz7+OVl4Za7WawYPcx+B5Ewm6IY/DdMyB9P/Z4tbKb1LO+ynWUXYwm7avXo6XQQ4m5ArDY5F/A==", "dev": true, "dependencies": { - "ajv": "8.12.0", - "ajv-formats": "2.1.1", - "jsonc-parser": "3.2.0", - "picomatch": "3.0.1", - "rxjs": "7.8.1", - "source-map": "0.7.4" + "@ampproject/remapping": "2.3.0", + "@angular-devkit/architect": "0.1802.6", + "@babel/core": "7.25.2", + "@babel/helper-annotate-as-pure": "7.24.7", + "@babel/helper-split-export-declaration": "7.24.7", + "@babel/plugin-syntax-import-attributes": "7.24.7", + "@inquirer/confirm": "3.1.22", + "@vitejs/plugin-basic-ssl": "1.1.0", + "browserslist": "^4.23.0", + "critters": "0.0.24", + "esbuild": "0.23.0", + "fast-glob": "3.3.2", + "https-proxy-agent": "7.0.5", + "listr2": "8.2.4", + "lmdb": "3.0.13", + "magic-string": "0.30.11", + "mrmime": "2.0.0", + "parse5-html-rewriting-stream": "7.0.0", + "picomatch": "4.0.2", + "piscina": "4.6.1", + "rollup": "4.22.4", + "sass": "1.77.6", + "semver": "7.6.3", + "vite": "5.4.6", + "watchpack": "2.4.1" }, "engines": { - "node": "^18.13.0 || >=20.9.0", + "node": "^18.19.1 || ^20.11.1 || >=22.0.0", "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", "yarn": ">= 1.13.0" }, "peerDependencies": { - "chokidar": "^3.5.2" + "@angular/compiler-cli": "^18.0.0", + "@angular/localize": "^18.0.0", + "@angular/platform-server": "^18.0.0", + "@angular/service-worker": "^18.0.0", + "less": "^4.2.0", + "postcss": "^8.4.0", + "tailwindcss": "^2.0.0 || ^3.0.0", + "typescript": ">=5.4 <5.6" }, "peerDependenciesMeta": { - "chokidar": { + "@angular/localize": { + "optional": true + }, + "@angular/platform-server": { + "optional": true + }, + "@angular/service-worker": { + "optional": true + }, + "less": { + "optional": true + }, + "postcss": { + "optional": true + }, + "tailwindcss": { "optional": true } } }, - "node_modules/@angular/cli/node_modules/chalk": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", - "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", - "dev": true, - "engines": { - "node": "^12.17.0 || ^14.13 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/@angular/cli/node_modules/escape-string-regexp": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", - "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", - "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@angular/cli/node_modules/figures": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/figures/-/figures-5.0.0.tgz", - "integrity": "sha512-ej8ksPF4x6e5wvK9yevct0UCXh8TTFlWGVLlgjZuoBH1HwjIfKE/IdL5mq89sFA7zELi1VhKpmtDnrs7zWyeyg==", - "dev": true, - "dependencies": { - "escape-string-regexp": "^5.0.0", - "is-unicode-supported": "^1.2.0" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@angular/cli/node_modules/inquirer": { - "version": "9.2.11", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-9.2.11.tgz", - "integrity": "sha512-B2LafrnnhbRzCWfAdOXisUzL89Kg8cVJlYmhqoi3flSiV/TveO+nsXwgKr9h9PIo+J1hz7nBSk6gegRIMBBf7g==", - "dev": true, - "dependencies": { - "@ljharb/through": "^2.3.9", - "ansi-escapes": "^4.3.2", - "chalk": "^5.3.0", - "cli-cursor": "^3.1.0", - "cli-width": "^4.1.0", - "external-editor": "^3.1.0", - "figures": "^5.0.0", - "lodash": "^4.17.21", - "mute-stream": "1.0.0", - "ora": "^5.4.1", - "run-async": "^3.0.0", - "rxjs": "^7.8.1", - "string-width": "^4.2.3", - "strip-ansi": "^6.0.1", - "wrap-ansi": "^6.2.0" - }, - "engines": { - "node": ">=14.18.0" - } - }, - "node_modules/@angular/cli/node_modules/is-unicode-supported": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-1.3.0.tgz", - "integrity": "sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ==", - "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@angular/cli/node_modules/jsonc-parser": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.0.tgz", - "integrity": "sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==", - "dev": true - }, - "node_modules/@angular/cli/node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, + "node_modules/@angular/cdk": { + "version": "18.2.6", + "resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-18.2.6.tgz", + "integrity": "sha512-Gfq/iv4zhlKYpdQkDaBRwxI71NHNUHM1Cs1XhnZ0/oFct5HXvSv1RHRGTKqBJLLACaAPzZKXJ/UglLoyO5CNiQ==", "dependencies": { - "yallist": "^4.0.0" + "tslib": "^2.3.0" }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@angular/cli/node_modules/picomatch": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-3.0.1.tgz", - "integrity": "sha512-I3EurrIQMlRc9IaAZnqRR044Phh2DXY+55o7uJ0V+hYZAcQYSuFWsc9q5PvyDHUSCe1Qxn/iBz+78s86zWnGag==", - "dev": true, - "engines": { - "node": ">=10" + "optionalDependencies": { + "parse5": "^7.1.2" }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" + "peerDependencies": { + "@angular/common": "^18.0.0 || ^19.0.0", + "@angular/core": "^18.0.0 || ^19.0.0", + "rxjs": "^6.5.3 || ^7.4.0" } }, - "node_modules/@angular/cli/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "node_modules/@angular/cli": { + "version": "18.2.6", + "resolved": "https://registry.npmjs.org/@angular/cli/-/cli-18.2.6.tgz", + "integrity": "sha512-tdXsnV/w+Rgu8q0zFsLU5L9ImTVqrTol1vppHaQkJ/vuoHy+s8ZEbBqhVrO/ffosNb2xseUybGYvqMS4zkNQjg==", "dev": true, "dependencies": { - "lru-cache": "^6.0.0" + "@angular-devkit/architect": "0.1802.6", + "@angular-devkit/core": "18.2.6", + "@angular-devkit/schematics": "18.2.6", + "@inquirer/prompts": "5.3.8", + "@listr2/prompt-adapter-inquirer": "2.0.15", + "@schematics/angular": "18.2.6", + "@yarnpkg/lockfile": "1.1.0", + "ini": "4.1.3", + "jsonc-parser": "3.3.1", + "listr2": "8.2.4", + "npm-package-arg": "11.0.3", + "npm-pick-manifest": "9.1.0", + "pacote": "18.0.6", + "resolve": "1.22.8", + "semver": "7.6.3", + "symbol-observable": "4.0.0", + "yargs": "17.7.2" }, "bin": { - "semver": "bin/semver.js" + "ng": "bin/ng.js" }, "engines": { - "node": ">=10" + "node": "^18.19.1 || ^20.11.1 || >=22.0.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" } }, - "node_modules/@angular/cli/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/@angular/common": { - "version": "17.3.7", - "resolved": "https://registry.npmjs.org/@angular/common/-/common-17.3.7.tgz", - "integrity": "sha512-A7LRJu1vVCGGgrfZXjU+njz50SiU4weheKCar5PIUprcdIofS1IrHAJDqYh+kwXxkjXbZMOr/ijQY0+AESLEsw==", + "version": "18.2.6", + "resolved": "https://registry.npmjs.org/@angular/common/-/common-18.2.6.tgz", + "integrity": "sha512-89793ow+wrI1c7C6kyMbnweLNIZHzXthosxAEjipRZGBrqBYjvTtkE45Fl+5yBa3JO7bAhyGkUnEoyvWtZIAEA==", "dependencies": { "tslib": "^2.3.0" }, "engines": { - "node": "^18.13.0 || >=20.9.0" + "node": "^18.19.1 || ^20.11.1 || >=22.0.0" }, "peerDependencies": { - "@angular/core": "17.3.7", + "@angular/core": "18.2.6", "rxjs": "^6.5.3 || ^7.4.0" } }, "node_modules/@angular/compiler": { - "version": "17.3.7", - "resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-17.3.7.tgz", - "integrity": "sha512-AlKiqPoxnrpQ0hn13fIaQPSVodaVAIjBW4vpFyuKFqs2LBKg6iolwZ21s8rEI0KR2gXl+8ugj0/UZ6YADiVM5w==", + "version": "18.2.6", + "resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-18.2.6.tgz", + "integrity": "sha512-3tX2/Qw+bZ8XzKitviH8jzNGyY0uohhehhBB57OJOCc+yr4ojy/7SYFnun1lSsRnDztdCE461641X4iQLCQ94w==", "dependencies": { "tslib": "^2.3.0" }, "engines": { - "node": "^18.13.0 || >=20.9.0" + "node": "^18.19.1 || ^20.11.1 || >=22.0.0" }, "peerDependencies": { - "@angular/core": "17.3.7" + "@angular/core": "18.2.6" }, "peerDependenciesMeta": { "@angular/core": { @@ -699,12 +544,12 @@ } }, "node_modules/@angular/compiler-cli": { - "version": "17.3.7", - "resolved": "https://registry.npmjs.org/@angular/compiler-cli/-/compiler-cli-17.3.7.tgz", - "integrity": "sha512-vSg5IQZ9jGmvYjpbfH8KbH4Sl1IVeE+Mr1ogcxkGEsURSRvKo7EWc0K7LSEI9+gg0VLamMiP9EyCJdPxiJeLJQ==", + "version": "18.2.6", + "resolved": "https://registry.npmjs.org/@angular/compiler-cli/-/compiler-cli-18.2.6.tgz", + "integrity": "sha512-b5x9STfjNiNM/S0D+CnqRP9UOxPtSz1+RlCH5WdOMiW/p8j5p6dBix8YYgTe6Wg3OD7eItD2pnFQKgF/dWiopA==", "dev": true, "dependencies": { - "@babel/core": "7.23.9", + "@babel/core": "7.25.2", "@jridgewell/sourcemap-codec": "^1.4.14", "chokidar": "^3.0.0", "convert-source-map": "^1.5.1", @@ -719,168 +564,76 @@ "ngcc": "bundles/ngcc/index.js" }, "engines": { - "node": "^18.13.0 || >=20.9.0" + "node": "^18.19.1 || ^20.11.1 || >=22.0.0" }, "peerDependencies": { - "@angular/compiler": "17.3.7", - "typescript": ">=5.2 <5.5" - } - }, - "node_modules/@angular/compiler-cli/node_modules/@babel/core": { - "version": "7.23.9", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.23.9.tgz", - "integrity": "sha512-5q0175NOjddqpvvzU+kDiSOAk4PfdO6FvwCWoQ6RO7rTzEe8vlo+4HVfcnAREhD4npMs0e9uZypjTwzZPCf/cw==", - "dev": true, - "dependencies": { - "@ampproject/remapping": "^2.2.0", - "@babel/code-frame": "^7.23.5", - "@babel/generator": "^7.23.6", - "@babel/helper-compilation-targets": "^7.23.6", - "@babel/helper-module-transforms": "^7.23.3", - "@babel/helpers": "^7.23.9", - "@babel/parser": "^7.23.9", - "@babel/template": "^7.23.9", - "@babel/traverse": "^7.23.9", - "@babel/types": "^7.23.9", - "convert-source-map": "^2.0.0", - "debug": "^4.1.0", - "gensync": "^1.0.0-beta.2", - "json5": "^2.2.3", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/babel" - } - }, - "node_modules/@angular/compiler-cli/node_modules/@babel/core/node_modules/convert-source-map": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", - "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", - "dev": true - }, - "node_modules/@angular/compiler-cli/node_modules/@babel/core/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "bin": { - "semver": "bin/semver.js" + "@angular/compiler": "18.2.6", + "typescript": ">=5.4 <5.6" } }, "node_modules/@angular/core": { - "version": "17.3.7", - "resolved": "https://registry.npmjs.org/@angular/core/-/core-17.3.7.tgz", - "integrity": "sha512-HWcrbxqnvIMSxFuQdN0KPt08bc87hqr0LKm89yuRTUwx/2sNJlNQUobk6aJj4trswGBttcRDT+GOS4DQP2Nr4g==", + "version": "18.2.6", + "resolved": "https://registry.npmjs.org/@angular/core/-/core-18.2.6.tgz", + "integrity": "sha512-PjFad2j4YBwLVTw+0Te8CJCa/tV0W8caTHG8aOjj3ObdL6ihGI+FKnwerLc9RVzDFd14BOO4C6/+LbOQAh3Ltw==", "dependencies": { "tslib": "^2.3.0" }, "engines": { - "node": "^18.13.0 || >=20.9.0" + "node": "^18.19.1 || ^20.11.1 || >=22.0.0" }, "peerDependencies": { "rxjs": "^6.5.3 || ^7.4.0", - "zone.js": "~0.14.0" + "zone.js": "~0.14.10" } }, "node_modules/@angular/forms": { - "version": "17.3.7", - "resolved": "https://registry.npmjs.org/@angular/forms/-/forms-17.3.7.tgz", - "integrity": "sha512-FEhXh/VmT++XCoO8i7bBtzxG7Am/cE1zrr9aF+fWW+4jpWvJvVN1IaSiJxgBB+iPsOJ9lTBRwfRW3onlcDkhrw==", + "version": "18.2.6", + "resolved": "https://registry.npmjs.org/@angular/forms/-/forms-18.2.6.tgz", + "integrity": "sha512-quGkUqTxlBaLB8C/RnpfFG57fdmNF5RQ+368N89Ma++2lpIsVAHaGZZn4yOyo3wNYaM2jBxNqaYxOzZNUl5Tig==", "dependencies": { "tslib": "^2.3.0" }, "engines": { - "node": "^18.13.0 || >=20.9.0" + "node": "^18.19.1 || ^20.11.1 || >=22.0.0" }, "peerDependencies": { - "@angular/common": "17.3.7", - "@angular/core": "17.3.7", - "@angular/platform-browser": "17.3.7", + "@angular/common": "18.2.6", + "@angular/core": "18.2.6", + "@angular/platform-browser": "18.2.6", "rxjs": "^6.5.3 || ^7.4.0" } }, "node_modules/@angular/material": { - "version": "17.3.7", - "resolved": "https://registry.npmjs.org/@angular/material/-/material-17.3.7.tgz", - "integrity": "sha512-wjSKkk9KZE8QiBPkMd5axh5u/3pUSxoLKNO7OasFhEagMmSv5oYTLm40cErhtb4UdkSmbC19WuuluS6P3leoPA==", - "dependencies": { - "@material/animation": "15.0.0-canary.7f224ddd4.0", - "@material/auto-init": "15.0.0-canary.7f224ddd4.0", - "@material/banner": "15.0.0-canary.7f224ddd4.0", - "@material/base": "15.0.0-canary.7f224ddd4.0", - "@material/button": "15.0.0-canary.7f224ddd4.0", - "@material/card": "15.0.0-canary.7f224ddd4.0", - "@material/checkbox": "15.0.0-canary.7f224ddd4.0", - "@material/chips": "15.0.0-canary.7f224ddd4.0", - "@material/circular-progress": "15.0.0-canary.7f224ddd4.0", - "@material/data-table": "15.0.0-canary.7f224ddd4.0", - "@material/density": "15.0.0-canary.7f224ddd4.0", - "@material/dialog": "15.0.0-canary.7f224ddd4.0", - "@material/dom": "15.0.0-canary.7f224ddd4.0", - "@material/drawer": "15.0.0-canary.7f224ddd4.0", - "@material/elevation": "15.0.0-canary.7f224ddd4.0", - "@material/fab": "15.0.0-canary.7f224ddd4.0", - "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", - "@material/floating-label": "15.0.0-canary.7f224ddd4.0", - "@material/form-field": "15.0.0-canary.7f224ddd4.0", - "@material/icon-button": "15.0.0-canary.7f224ddd4.0", - "@material/image-list": "15.0.0-canary.7f224ddd4.0", - "@material/layout-grid": "15.0.0-canary.7f224ddd4.0", - "@material/line-ripple": "15.0.0-canary.7f224ddd4.0", - "@material/linear-progress": "15.0.0-canary.7f224ddd4.0", - "@material/list": "15.0.0-canary.7f224ddd4.0", - "@material/menu": "15.0.0-canary.7f224ddd4.0", - "@material/menu-surface": "15.0.0-canary.7f224ddd4.0", - "@material/notched-outline": "15.0.0-canary.7f224ddd4.0", - "@material/radio": "15.0.0-canary.7f224ddd4.0", - "@material/ripple": "15.0.0-canary.7f224ddd4.0", - "@material/rtl": "15.0.0-canary.7f224ddd4.0", - "@material/segmented-button": "15.0.0-canary.7f224ddd4.0", - "@material/select": "15.0.0-canary.7f224ddd4.0", - "@material/shape": "15.0.0-canary.7f224ddd4.0", - "@material/slider": "15.0.0-canary.7f224ddd4.0", - "@material/snackbar": "15.0.0-canary.7f224ddd4.0", - "@material/switch": "15.0.0-canary.7f224ddd4.0", - "@material/tab": "15.0.0-canary.7f224ddd4.0", - "@material/tab-bar": "15.0.0-canary.7f224ddd4.0", - "@material/tab-indicator": "15.0.0-canary.7f224ddd4.0", - "@material/tab-scroller": "15.0.0-canary.7f224ddd4.0", - "@material/textfield": "15.0.0-canary.7f224ddd4.0", - "@material/theme": "15.0.0-canary.7f224ddd4.0", - "@material/tooltip": "15.0.0-canary.7f224ddd4.0", - "@material/top-app-bar": "15.0.0-canary.7f224ddd4.0", - "@material/touch-target": "15.0.0-canary.7f224ddd4.0", - "@material/typography": "15.0.0-canary.7f224ddd4.0", + "version": "18.2.6", + "resolved": "https://registry.npmjs.org/@angular/material/-/material-18.2.6.tgz", + "integrity": "sha512-ObxC/vomSb9QF3vIztuiInQzws+D6u09Dhfx6uNFjtyICqxEFpF7+Qx7QVDWrsuXOgxZTKgacK8f46iV8hWUfg==", + "dependencies": { "tslib": "^2.3.0" }, "peerDependencies": { - "@angular/animations": "^17.0.0 || ^18.0.0", - "@angular/cdk": "17.3.7", - "@angular/common": "^17.0.0 || ^18.0.0", - "@angular/core": "^17.0.0 || ^18.0.0", - "@angular/forms": "^17.0.0 || ^18.0.0", - "@angular/platform-browser": "^17.0.0 || ^18.0.0", + "@angular/animations": "^18.0.0 || ^19.0.0", + "@angular/cdk": "18.2.6", + "@angular/common": "^18.0.0 || ^19.0.0", + "@angular/core": "^18.0.0 || ^19.0.0", + "@angular/forms": "^18.0.0 || ^19.0.0", + "@angular/platform-browser": "^18.0.0 || ^19.0.0", "rxjs": "^6.5.3 || ^7.4.0" } }, "node_modules/@angular/platform-browser": { - "version": "17.3.7", - "resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-17.3.7.tgz", - "integrity": "sha512-Nn8ZMaftAvO9dEwribWdNv+QBHhYIBrRkv85G6et80AXfXoYAr/xcfnQECRFtZgPmANqHC5auv/xrmExQG+Yeg==", + "version": "18.2.6", + "resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-18.2.6.tgz", + "integrity": "sha512-RA8UMiYNLga+QMwpKcDw1357gYPfPyY/rmLeezMak//BbsENFYQOJ4Z6DBOBNiPlHxmBsUJMGaKdlpQhfCROyQ==", "dependencies": { "tslib": "^2.3.0" }, "engines": { - "node": "^18.13.0 || >=20.9.0" + "node": "^18.19.1 || ^20.11.1 || >=22.0.0" }, "peerDependencies": { - "@angular/animations": "17.3.7", - "@angular/common": "17.3.7", - "@angular/core": "17.3.7" + "@angular/animations": "18.2.6", + "@angular/common": "18.2.6", + "@angular/core": "18.2.6" }, "peerDependenciesMeta": { "@angular/animations": { @@ -889,46 +642,46 @@ } }, "node_modules/@angular/platform-browser-dynamic": { - "version": "17.3.7", - "resolved": "https://registry.npmjs.org/@angular/platform-browser-dynamic/-/platform-browser-dynamic-17.3.7.tgz", - "integrity": "sha512-9c2I4u0L1p2v1/lW8qy+WaNHisUWbyy6wqsv2v9FfCaSM49Lxymgo9LPFPC4qEG5ei5nE+eIQ2ocRiXXsf5QkQ==", + "version": "18.2.6", + "resolved": "https://registry.npmjs.org/@angular/platform-browser-dynamic/-/platform-browser-dynamic-18.2.6.tgz", + "integrity": "sha512-kGBU3FNc+DF9r33hwHZqiWoZgQbCDdEIucU0NCLCIg0Hw6/Q9Hr2ndjxQI+WynCPg0JeBn34jpouvpeJer3YDQ==", "dependencies": { "tslib": "^2.3.0" }, "engines": { - "node": "^18.13.0 || >=20.9.0" + "node": "^18.19.1 || ^20.11.1 || >=22.0.0" }, "peerDependencies": { - "@angular/common": "17.3.7", - "@angular/compiler": "17.3.7", - "@angular/core": "17.3.7", - "@angular/platform-browser": "17.3.7" + "@angular/common": "18.2.6", + "@angular/compiler": "18.2.6", + "@angular/core": "18.2.6", + "@angular/platform-browser": "18.2.6" } }, "node_modules/@angular/router": { - "version": "17.3.7", - "resolved": "https://registry.npmjs.org/@angular/router/-/router-17.3.7.tgz", - "integrity": "sha512-lMkuRrc1ZjP5JPDxNHqoAhB0uAnfPQ/q6mJrw1s8IZoVV6VyM+FxR5r13ajNcXWC38xy/YhBjpXPF1vBdxuLXg==", + "version": "18.2.6", + "resolved": "https://registry.npmjs.org/@angular/router/-/router-18.2.6.tgz", + "integrity": "sha512-t57Sqja8unHhZlPr+4CWnQacuox2M4p2pMHps+31wt337qH6mKf4jqDmK0dE/MFdRyKjT2a2E/2NwtxXxcWNuw==", "dependencies": { "tslib": "^2.3.0" }, "engines": { - "node": "^18.13.0 || >=20.9.0" + "node": "^18.19.1 || ^20.11.1 || >=22.0.0" }, "peerDependencies": { - "@angular/common": "17.3.7", - "@angular/core": "17.3.7", - "@angular/platform-browser": "17.3.7", + "@angular/common": "18.2.6", + "@angular/core": "18.2.6", + "@angular/platform-browser": "18.2.6", "rxjs": "^6.5.3 || ^7.4.0" } }, "node_modules/@babel/code-frame": { - "version": "7.24.2", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.2.tgz", - "integrity": "sha512-y5+tLQyV8pg3fsiln67BVLD1P13Eg4lh5RW9mF0zUuvLrv9uIQ4MCL+CRT+FTsBlBjcIan6PGsLcBN0m3ClUyQ==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.7.tgz", + "integrity": "sha512-BcYH1CVJBO9tvyIZ2jVeXgSIMvGZ2FDRvDdOIVQyuklNKSsx+eppDEBq/g47Ayw+RqNFE+URvOShmf+f/qwAlA==", "dev": true, "dependencies": { - "@babel/highlight": "^7.24.2", + "@babel/highlight": "^7.24.7", "picocolors": "^1.0.0" }, "engines": { @@ -936,30 +689,30 @@ } }, "node_modules/@babel/compat-data": { - "version": "7.24.4", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.24.4.tgz", - "integrity": "sha512-vg8Gih2MLK+kOkHJp4gBEIkyaIi00jgWot2D9QOmmfLC8jINSOzmCLta6Bvz/JSBCqnegV0L80jhxkol5GWNfQ==", + "version": "7.25.4", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.25.4.tgz", + "integrity": "sha512-+LGRog6RAsCJrrrg/IO6LGmpphNe5DiK30dGjCoxxeGv49B10/3XYGxPsAwrDlMFcFEvdAUavDT8r9k/hSyQqQ==", "dev": true, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/core": { - "version": "7.24.0", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.24.0.tgz", - "integrity": "sha512-fQfkg0Gjkza3nf0c7/w6Xf34BW4YvzNfACRLmmb7XRLa6XHdR+K9AlJlxneFfWYf6uhOzuzZVTjF/8KfndZANw==", + "version": "7.25.2", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.25.2.tgz", + "integrity": "sha512-BBt3opiCOxUr9euZ5/ro/Xv8/V7yJ5bjYMqG/C1YAo8MIKAnumZalCN+msbci3Pigy4lIQfPUpfMM27HMGaYEA==", "dev": true, "dependencies": { "@ampproject/remapping": "^2.2.0", - "@babel/code-frame": "^7.23.5", - "@babel/generator": "^7.23.6", - "@babel/helper-compilation-targets": "^7.23.6", - "@babel/helper-module-transforms": "^7.23.3", - "@babel/helpers": "^7.24.0", - "@babel/parser": "^7.24.0", - "@babel/template": "^7.24.0", - "@babel/traverse": "^7.24.0", - "@babel/types": "^7.24.0", + "@babel/code-frame": "^7.24.7", + "@babel/generator": "^7.25.0", + "@babel/helper-compilation-targets": "^7.25.2", + "@babel/helper-module-transforms": "^7.25.2", + "@babel/helpers": "^7.25.0", + "@babel/parser": "^7.25.0", + "@babel/template": "^7.25.0", + "@babel/traverse": "^7.25.2", + "@babel/types": "^7.25.2", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", @@ -990,14 +743,14 @@ } }, "node_modules/@babel/generator": { - "version": "7.23.6", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.6.tgz", - "integrity": "sha512-qrSfCYxYQB5owCmGLbl8XRpX1ytXlpueOb0N0UmQwA073KZxejgQTzAmJezxvpwQD9uGtK2shHdi55QT+MbjIw==", + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.25.0.tgz", + "integrity": "sha512-3LEEcj3PVW8pW2R1SR1M89g/qrYk/m/mB/tLqn7dn4sbBUQyTqnlod+II2U4dqiGtUmkcnAmkMDralTFZttRiw==", "dev": true, "dependencies": { - "@babel/types": "^7.23.6", - "@jridgewell/gen-mapping": "^0.3.2", - "@jridgewell/trace-mapping": "^0.3.17", + "@babel/types": "^7.25.0", + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25", "jsesc": "^2.5.1" }, "engines": { @@ -1005,38 +758,39 @@ } }, "node_modules/@babel/helper-annotate-as-pure": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.22.5.tgz", - "integrity": "sha512-LvBTxu8bQSQkcyKOU+a1btnNFQ1dMAd0R6PyW3arXes06F6QLWLIrd681bxRPIXlrMGR3XYnW9JyML7dP3qgxg==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.24.7.tgz", + "integrity": "sha512-BaDeOonYvhdKw+JoMVkAixAAJzG2jVPIwWoKBPdYuY9b452e2rPuI9QPYh3KpofZ3pW2akOmwZLOiOsHMiqRAg==", "dev": true, "dependencies": { - "@babel/types": "^7.22.5" + "@babel/types": "^7.24.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-builder-binary-assignment-operator-visitor": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.22.15.tgz", - "integrity": "sha512-QkBXwGgaoC2GtGZRoma6kv7Szfv06khvhFav67ZExau2RaXzy8MpHSMO2PNoP2XtmQphJQRHFfg77Bq731Yizw==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.24.7.tgz", + "integrity": "sha512-xZeCVVdwb4MsDBkkyZ64tReWYrLRHlMN72vP7Bdm3OUOuyFZExhsHUUnuWnm2/XOlAJzR0LfPpB56WXZn0X/lA==", "dev": true, "dependencies": { - "@babel/types": "^7.22.15" + "@babel/traverse": "^7.24.7", + "@babel/types": "^7.24.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-compilation-targets": { - "version": "7.23.6", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.23.6.tgz", - "integrity": "sha512-9JB548GZoQVmzrFgp8o7KxdgkTGm6xs9DW0o/Pim72UDjzr5ObUQ6ZzYPqA+g9OTS2bBQoctLJrky0RDCAWRgQ==", + "version": "7.25.2", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.25.2.tgz", + "integrity": "sha512-U2U5LsSaZ7TAt3cfaymQ8WHh0pxvdHoEk6HVpaexxixjyEquMh0L0YNJNM6CTGKMXV1iksi0iZkGw4AcFkPaaw==", "dev": true, "dependencies": { - "@babel/compat-data": "^7.23.5", - "@babel/helper-validator-option": "^7.23.5", - "browserslist": "^4.22.2", + "@babel/compat-data": "^7.25.2", + "@babel/helper-validator-option": "^7.24.8", + "browserslist": "^4.23.1", "lru-cache": "^5.1.1", "semver": "^6.3.1" }, @@ -1054,19 +808,17 @@ } }, "node_modules/@babel/helper-create-class-features-plugin": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.24.5.tgz", - "integrity": "sha512-uRc4Cv8UQWnE4NXlYTIIdM7wfFkOqlFztcC/gVXDKohKoVB3OyonfelUBaJzSwpBntZ2KYGF/9S7asCHsXwW6g==", - "dev": true, - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.22.5", - "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-function-name": "^7.23.0", - "@babel/helper-member-expression-to-functions": "^7.24.5", - "@babel/helper-optimise-call-expression": "^7.22.5", - "@babel/helper-replace-supers": "^7.24.1", - "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5", - "@babel/helper-split-export-declaration": "^7.24.5", + "version": "7.25.4", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.25.4.tgz", + "integrity": "sha512-ro/bFs3/84MDgDmMwbcHgDa8/E6J3QKNTk4xJJnVeFtGE+tL0K26E3pNxhYz2b67fJpt7Aphw5XcploKXuCvCQ==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.24.7", + "@babel/helper-member-expression-to-functions": "^7.24.8", + "@babel/helper-optimise-call-expression": "^7.24.7", + "@babel/helper-replace-supers": "^7.25.0", + "@babel/helper-skip-transparent-expression-wrappers": "^7.24.7", + "@babel/traverse": "^7.25.4", "semver": "^6.3.1" }, "engines": { @@ -1076,18 +828,6 @@ "@babel/core": "^7.0.0" } }, - "node_modules/@babel/helper-create-class-features-plugin/node_modules/@babel/helper-split-export-declaration": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.24.5.tgz", - "integrity": "sha512-5CHncttXohrHk8GWOFCcCl4oRD9fKosWlIRgWm4ql9VYioKm52Mk2xsmoohvm7f3JoiLSM5ZgJuRaf5QZZYd3Q==", - "dev": true, - "dependencies": { - "@babel/types": "^7.24.5" - }, - "engines": { - "node": ">=6.9.0" - } - }, "node_modules/@babel/helper-create-class-features-plugin/node_modules/semver": { "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", @@ -1098,12 +838,12 @@ } }, "node_modules/@babel/helper-create-regexp-features-plugin": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.22.15.tgz", - "integrity": "sha512-29FkPLFjn4TPEa3RE7GpW+qbE8tlsu3jntNYNfcGsc49LphF1PQIiD+vMZ1z1xVOKt+93khA9tc2JBs3kBjA7w==", + "version": "7.25.2", + "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.25.2.tgz", + "integrity": "sha512-+wqVGP+DFmqwFD3EH6TMTfUNeqDehV3E/dl+Sd54eaXqm17tEUNbEIn4sVivVowbvUpOtIGxdo3GoXyDH9N/9g==", "dev": true, "dependencies": { - "@babel/helper-annotate-as-pure": "^7.22.5", + "@babel/helper-annotate-as-pure": "^7.24.7", "regexpu-core": "^5.3.1", "semver": "^6.3.1" }, @@ -1139,125 +879,80 @@ "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, - "node_modules/@babel/helper-environment-visitor": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz", - "integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==", + "node_modules/@babel/helper-member-expression-to-functions": { + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.24.8.tgz", + "integrity": "sha512-LABppdt+Lp/RlBxqrh4qgf1oEH/WxdzQNDJIu5gC/W1GyvPVrOBiItmmM8wan2fm4oYqFuFfkXmlGpLQhPY8CA==", "dev": true, + "dependencies": { + "@babel/traverse": "^7.24.8", + "@babel/types": "^7.24.8" + }, "engines": { "node": ">=6.9.0" } }, - "node_modules/@babel/helper-function-name": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz", - "integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==", + "node_modules/@babel/helper-module-imports": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.24.7.tgz", + "integrity": "sha512-8AyH3C+74cgCVVXow/myrynrAGv+nTVg5vKu2nZph9x7RcRwzmh0VFallJuFTZ9mx6u4eSdXZfcOzSqTUm0HCA==", "dev": true, "dependencies": { - "@babel/template": "^7.22.15", - "@babel/types": "^7.23.0" + "@babel/traverse": "^7.24.7", + "@babel/types": "^7.24.7" }, "engines": { "node": ">=6.9.0" } }, - "node_modules/@babel/helper-hoist-variables": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz", - "integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==", + "node_modules/@babel/helper-module-transforms": { + "version": "7.25.2", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.25.2.tgz", + "integrity": "sha512-BjyRAbix6j/wv83ftcVJmBt72QtHI56C7JXZoG2xATiLpmoC7dpd8WnkikExHDVPpi/3qCmO6WY1EaXOluiecQ==", "dev": true, "dependencies": { - "@babel/types": "^7.22.5" + "@babel/helper-module-imports": "^7.24.7", + "@babel/helper-simple-access": "^7.24.7", + "@babel/helper-validator-identifier": "^7.24.7", + "@babel/traverse": "^7.25.2" }, "engines": { "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" } }, - "node_modules/@babel/helper-member-expression-to-functions": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.24.5.tgz", - "integrity": "sha512-4owRteeihKWKamtqg4JmWSsEZU445xpFRXPEwp44HbgbxdWlUV1b4Agg4lkA806Lil5XM/e+FJyS0vj5T6vmcA==", + "node_modules/@babel/helper-optimise-call-expression": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.24.7.tgz", + "integrity": "sha512-jKiTsW2xmWwxT1ixIdfXUZp+P5yURx2suzLZr5Hi64rURpDYdMW0pv+Uf17EYk2Rd428Lx4tLsnjGJzYKDM/6A==", "dev": true, "dependencies": { - "@babel/types": "^7.24.5" + "@babel/types": "^7.24.7" }, "engines": { "node": ">=6.9.0" } }, - "node_modules/@babel/helper-module-imports": { - "version": "7.24.3", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.24.3.tgz", - "integrity": "sha512-viKb0F9f2s0BCS22QSF308z/+1YWKV/76mwt61NBzS5izMzDPwdq1pTrzf+Li3npBWX9KdQbkeCt1jSAM7lZqg==", - "dev": true, - "dependencies": { - "@babel/types": "^7.24.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-module-transforms": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.24.5.tgz", - "integrity": "sha512-9GxeY8c2d2mdQUP1Dye0ks3VDyIMS98kt/llQ2nUId8IsWqTF0l1LkSX0/uP7l7MCDrzXS009Hyhe2gzTiGW8A==", - "dev": true, - "dependencies": { - "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-module-imports": "^7.24.3", - "@babel/helper-simple-access": "^7.24.5", - "@babel/helper-split-export-declaration": "^7.24.5", - "@babel/helper-validator-identifier": "^7.24.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-module-transforms/node_modules/@babel/helper-split-export-declaration": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.24.5.tgz", - "integrity": "sha512-5CHncttXohrHk8GWOFCcCl4oRD9fKosWlIRgWm4ql9VYioKm52Mk2xsmoohvm7f3JoiLSM5ZgJuRaf5QZZYd3Q==", - "dev": true, - "dependencies": { - "@babel/types": "^7.24.5" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-optimise-call-expression": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.22.5.tgz", - "integrity": "sha512-HBwaojN0xFRx4yIvpwGqxiV2tUfl7401jlok564NgB9EHS1y6QT17FmKWm4ztqjeVdXLuC4fSvHc5ePpQjoTbw==", - "dev": true, - "dependencies": { - "@babel/types": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-plugin-utils": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.5.tgz", - "integrity": "sha512-xjNLDopRzW2o6ba0gKbkZq5YWEBaK3PCyTOY1K2P/O07LGMhMqlMXPxwN4S5/RhWuCobT8z0jrlKGlYmeR1OhQ==", + "node_modules/@babel/helper-plugin-utils": { + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.8.tgz", + "integrity": "sha512-FFWx5142D8h2Mgr/iPVGH5G7w6jDn4jUSpZTyDnQO0Yn7Ks2Kuz6Pci8H6MPCoUJegd/UZQ3tAvfLCxQSnWWwg==", "dev": true, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-remap-async-to-generator": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.22.20.tgz", - "integrity": "sha512-pBGyV4uBqOns+0UvhsTO8qgl8hO89PmiDYv+/COyp1aeMcmfrfruz+/nCMFiYyFF/Knn0yfrC85ZzNFjembFTw==", + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.25.0.tgz", + "integrity": "sha512-NhavI2eWEIz/H9dbrG0TuOicDhNexze43i5z7lEqwYm0WEZVTwnPpA0EafUTP7+6/W79HWIP2cTe3Z5NiSTVpw==", "dev": true, "dependencies": { - "@babel/helper-annotate-as-pure": "^7.22.5", - "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-wrap-function": "^7.22.20" + "@babel/helper-annotate-as-pure": "^7.24.7", + "@babel/helper-wrap-function": "^7.25.0", + "@babel/traverse": "^7.25.0" }, "engines": { "node": ">=6.9.0" @@ -1267,14 +962,14 @@ } }, "node_modules/@babel/helper-replace-supers": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.24.1.tgz", - "integrity": "sha512-QCR1UqC9BzG5vZl8BMicmZ28RuUBnHhAMddD8yHFHDRH9lLTZ9uUPehX8ctVPT8l0TKblJidqcgUUKGVrePleQ==", + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.25.0.tgz", + "integrity": "sha512-q688zIvQVYtZu+i2PsdIu/uWGRpfxzr5WESsfpShfZECkO+d2o+WROWezCi/Q6kJ0tfPa5+pUGUlfx2HhrA3Bg==", "dev": true, "dependencies": { - "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-member-expression-to-functions": "^7.23.0", - "@babel/helper-optimise-call-expression": "^7.22.5" + "@babel/helper-member-expression-to-functions": "^7.24.8", + "@babel/helper-optimise-call-expression": "^7.24.7", + "@babel/traverse": "^7.25.0" }, "engines": { "node": ">=6.9.0" @@ -1284,103 +979,104 @@ } }, "node_modules/@babel/helper-simple-access": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.24.5.tgz", - "integrity": "sha512-uH3Hmf5q5n7n8mz7arjUlDOCbttY/DW4DYhE6FUsjKJ/oYC1kQQUvwEQWxRwUpX9qQKRXeqLwWxrqilMrf32sQ==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.24.7.tgz", + "integrity": "sha512-zBAIvbCMh5Ts+b86r/CjU+4XGYIs+R1j951gxI3KmmxBMhCg4oQMsv6ZXQ64XOm/cvzfU1FmoCyt6+owc5QMYg==", "dev": true, "dependencies": { - "@babel/types": "^7.24.5" + "@babel/traverse": "^7.24.7", + "@babel/types": "^7.24.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-skip-transparent-expression-wrappers": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.22.5.tgz", - "integrity": "sha512-tK14r66JZKiC43p8Ki33yLBVJKlQDFoA8GYN67lWCDCqoL6EMMSuM9b+Iff2jHaM/RRFYl7K+iiru7hbRqNx8Q==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.24.7.tgz", + "integrity": "sha512-IO+DLT3LQUElMbpzlatRASEyQtfhSE0+m465v++3jyyXeBTBUjtVZg28/gHeV5mrTJqvEKhKroBGAvhW+qPHiQ==", "dev": true, "dependencies": { - "@babel/types": "^7.22.5" + "@babel/traverse": "^7.24.7", + "@babel/types": "^7.24.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-split-export-declaration": { - "version": "7.22.6", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz", - "integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.24.7.tgz", + "integrity": "sha512-oy5V7pD+UvfkEATUKvIjvIAH/xCzfsFVw7ygW2SI6NClZzquT+mwdTfgfdbUiceh6iQO0CHtCPsyze/MZ2YbAA==", "dev": true, "dependencies": { - "@babel/types": "^7.22.5" + "@babel/types": "^7.24.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-string-parser": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.1.tgz", - "integrity": "sha512-2ofRCjnnA9y+wk8b9IAREroeUP02KHp431N2mhKniy2yKIDKpbrHv9eXwm8cBeWQYcJmzv5qKCu65P47eCF7CQ==", + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.8.tgz", + "integrity": "sha512-pO9KhhRcuUyGnJWwyEgnRJTSIZHiT+vMD0kPeD+so0l7mxkMT19g3pjY9GTnHySck/hDzq+dtW/4VgnMkippsQ==", "dev": true, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.5.tgz", - "integrity": "sha512-3q93SSKX2TWCG30M2G2kwaKeTYgEUp5Snjuj8qm729SObL6nbtUldAi37qbxkD5gg3xnBio+f9nqpSepGZMvxA==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.7.tgz", + "integrity": "sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w==", "dev": true, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-option": { - "version": "7.23.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.23.5.tgz", - "integrity": "sha512-85ttAOMLsr53VgXkTbkx8oA6YTfT4q7/HzXSLEYmjcSTJPMPQtvq1BD79Byep5xMUYbGRzEpDsjUf3dyp54IKw==", + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.24.8.tgz", + "integrity": "sha512-xb8t9tD1MHLungh/AIoWYN+gVHaB9kwlu8gffXGSt3FFEIT7RjS+xWbc2vUD1UTZdIpKj/ab3rdqJ7ufngyi2Q==", "dev": true, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-wrap-function": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.24.5.tgz", - "integrity": "sha512-/xxzuNvgRl4/HLNKvnFwdhdgN3cpLxgLROeLDl83Yx0AJ1SGvq1ak0OszTOjDfiB8Vx03eJbeDWh9r+jCCWttw==", + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.25.0.tgz", + "integrity": "sha512-s6Q1ebqutSiZnEjaofc/UKDyC4SbzV5n5SrA2Gq8UawLycr3i04f1dX4OzoQVnexm6aOCh37SQNYlJ/8Ku+PMQ==", "dev": true, "dependencies": { - "@babel/helper-function-name": "^7.23.0", - "@babel/template": "^7.24.0", - "@babel/types": "^7.24.5" + "@babel/template": "^7.25.0", + "@babel/traverse": "^7.25.0", + "@babel/types": "^7.25.0" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helpers": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.24.5.tgz", - "integrity": "sha512-CiQmBMMpMQHwM5m01YnrM6imUG1ebgYJ+fAIW4FZe6m4qHTPaRHti+R8cggAwkdz4oXhtO4/K9JWlh+8hIfR2Q==", + "version": "7.25.6", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.25.6.tgz", + "integrity": "sha512-Xg0tn4HcfTijTwfDwYlvVCl43V6h4KyVVX2aEm4qdO/PC6L2YvzLHFdmxhoeSA3eslcE6+ZVXHgWwopXYLNq4Q==", "dev": true, "dependencies": { - "@babel/template": "^7.24.0", - "@babel/traverse": "^7.24.5", - "@babel/types": "^7.24.5" + "@babel/template": "^7.25.0", + "@babel/types": "^7.25.6" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/highlight": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.5.tgz", - "integrity": "sha512-8lLmua6AVh/8SLJRRVD6V8p73Hir9w5mJrhE+IPpILG31KKlI9iz5zmBYKcWPS59qSfgP9RaSBQSHHE81WKuEw==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.7.tgz", + "integrity": "sha512-EStJpq4OuY8xYfhGVXngigBJRWxftKX9ksiGDnmlY3o7B/V7KIAc9X4oiK87uPJSc/vs5L869bem5fhZa8caZw==", "dev": true, "dependencies": { - "@babel/helper-validator-identifier": "^7.24.5", + "@babel/helper-validator-identifier": "^7.24.7", "chalk": "^2.4.2", "js-tokens": "^4.0.0", "picocolors": "^1.0.0" @@ -1390,10 +1086,13 @@ } }, "node_modules/@babel/parser": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.5.tgz", - "integrity": "sha512-EOv5IK8arwh3LI47dz1b0tKUb/1uhHAnHJOrjgtQMIpu1uXd9mlFrJg9IUgGUgZ41Ch0K8REPTYpO7B76b4vJg==", + "version": "7.25.6", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.25.6.tgz", + "integrity": "sha512-trGdfBdbD0l1ZPmcJ83eNxB9rbEax4ALFTF7fN386TMYbeCQbyme5cOEXQhbGXKebwGaB/J52w1mrklMcbgy6Q==", "dev": true, + "dependencies": { + "@babel/types": "^7.25.6" + }, "bin": { "parser": "bin/babel-parser.js" }, @@ -1401,13 +1100,44 @@ "node": ">=6.0.0" } }, + "node_modules/@babel/plugin-bugfix-firefox-class-in-computed-class-key": { + "version": "7.25.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.25.3.tgz", + "integrity": "sha512-wUrcsxZg6rqBXG05HG1FPYgsP6EvwF4WpBbxIpWIIYnH8wG0gzx3yZY3dtEHas4sTAOGkbTsc9EGPxwff8lRoA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.8", + "@babel/traverse": "^7.25.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-safari-class-field-initializer-scope": { + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-class-field-initializer-scope/-/plugin-bugfix-safari-class-field-initializer-scope-7.25.0.tgz", + "integrity": "sha512-Bm4bH2qsX880b/3ziJ8KD711LT7z4u8CFudmjqle65AZj/HNUFhEf90dqYv6O86buWvSBmeQDjv0Tn2aF/bIBA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.8" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, "node_modules/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.24.1.tgz", - "integrity": "sha512-y4HqEnkelJIOQGd+3g1bTeKsA5c6qM7eOn7VggGVbBc0y8MLSKHacwcIE2PplNlQSj0PqS9rrXL/nkPVK+kUNg==", + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.25.0.tgz", + "integrity": "sha512-lXwdNZtTmeVOOFtwM/WDe7yg1PL8sYhRk/XH0FzbR2HDQ0xC+EnQ/JHeoMYSavtU115tnUk0q9CDyq8si+LMAA==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-plugin-utils": "^7.24.8" }, "engines": { "node": ">=6.9.0" @@ -1417,14 +1147,14 @@ } }, "node_modules/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.24.1.tgz", - "integrity": "sha512-Hj791Ii4ci8HqnaKHAlLNs+zaLXb0EzSDhiAWp5VNlyvCNymYfacs64pxTxbH1znW/NcArSmwpmG9IKE/TUVVQ==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.24.7.tgz", + "integrity": "sha512-+izXIbke1T33mY4MSNnrqhPXDz01WYhEf3yF5NbnUtkiNnm+XBZJl3kNfoK6NKmYlz/D07+l2GWVK/QfDkNCuQ==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0", - "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5", - "@babel/plugin-transform-optional-chaining": "^7.24.1" + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/helper-skip-transparent-expression-wrappers": "^7.24.7", + "@babel/plugin-transform-optional-chaining": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -1434,13 +1164,13 @@ } }, "node_modules/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.24.1.tgz", - "integrity": "sha512-m9m/fXsXLiHfwdgydIFnpk+7jlVbnvlK5B2EKiPdLUb6WX654ZaaEWJUjk8TftRbZpK0XibovlLWX4KIZhV6jw==", + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.25.0.tgz", + "integrity": "sha512-tggFrk1AIShG/RUQbEwt2Tr/E+ObkfwrPjR6BjbRvsx24+PSjK8zrq0GWPNCjo8qpRx4DuJzlcvWJqlm+0h3kw==", "dev": true, "dependencies": { - "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-plugin-utils": "^7.24.8", + "@babel/traverse": "^7.25.0" }, "engines": { "node": ">=6.9.0" @@ -1525,12 +1255,12 @@ } }, "node_modules/@babel/plugin-syntax-import-assertions": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.24.1.tgz", - "integrity": "sha512-IuwnI5XnuF189t91XbxmXeCDz3qs6iDRO7GJ++wcfgeXNs/8FmIlKcpDSXNVyuLQxlwvskmI3Ct73wUODkJBlQ==", + "version": "7.25.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.25.6.tgz", + "integrity": "sha512-aABl0jHw9bZ2karQ/uUD6XP4u0SG22SJrOHFoL6XB1R7dTovOP4TzTlsxOYC5yQ1pdscVK2JTUnF6QL3ARoAiQ==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-plugin-utils": "^7.24.8" }, "engines": { "node": ">=6.9.0" @@ -1540,12 +1270,12 @@ } }, "node_modules/@babel/plugin-syntax-import-attributes": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.24.1.tgz", - "integrity": "sha512-zhQTMH0X2nVLnb04tz+s7AMuasX8U0FnpE+nHTOhSOINjWMnopoZTxtIKsd45n4GQ/HIZLyfIpoul8e2m0DnRA==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.24.7.tgz", + "integrity": "sha512-hbX+lKKeUMGihnK8nvKqmXBInriT3GVjzXKFriV3YC6APGxMbP8RZNFwy91+hocLXq90Mta+HshoB31802bb8A==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-plugin-utils": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -1697,12 +1427,12 @@ } }, "node_modules/@babel/plugin-transform-arrow-functions": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.24.1.tgz", - "integrity": "sha512-ngT/3NkRhsaep9ck9uj2Xhv9+xB1zShY3tM3g6om4xxCELwCDN4g4Aq5dRn48+0hasAql7s2hdBOysCfNpr4fw==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.24.7.tgz", + "integrity": "sha512-Dt9LQs6iEY++gXUwY03DNFat5C2NbO48jj+j/bSAz6b3HgPs39qcPiYt77fDObIcFwj3/C2ICX9YMwGflUoSHQ==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-plugin-utils": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -1712,15 +1442,15 @@ } }, "node_modules/@babel/plugin-transform-async-generator-functions": { - "version": "7.23.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.23.9.tgz", - "integrity": "sha512-8Q3veQEDGe14dTYuwagbRtwxQDnytyg1JFu4/HwEMETeofocrB0U0ejBJIXoeG/t2oXZ8kzCyI0ZZfbT80VFNQ==", + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.25.0.tgz", + "integrity": "sha512-uaIi2FdqzjpAMvVqvB51S42oC2JEVgh0LDsGfZVDysWE8LrJtQC2jvKmOqEYThKyB7bDEb7BP1GYWDm7tABA0Q==", "dev": true, "dependencies": { - "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/helper-remap-async-to-generator": "^7.22.20", - "@babel/plugin-syntax-async-generators": "^7.8.4" + "@babel/helper-plugin-utils": "^7.24.8", + "@babel/helper-remap-async-to-generator": "^7.25.0", + "@babel/plugin-syntax-async-generators": "^7.8.4", + "@babel/traverse": "^7.25.0" }, "engines": { "node": ">=6.9.0" @@ -1730,14 +1460,14 @@ } }, "node_modules/@babel/plugin-transform-async-to-generator": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.23.3.tgz", - "integrity": "sha512-A7LFsKi4U4fomjqXJlZg/u0ft/n8/7n7lpffUP/ZULx/DtV9SGlNKZolHH6PE8Xl1ngCc0M11OaeZptXVkfKSw==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.24.7.tgz", + "integrity": "sha512-SQY01PcJfmQ+4Ash7NE+rpbLFbmqA2GPIgqzxfFTL4t1FKRq4zTms/7htKpoCUI9OcFYgzqfmCdH53s6/jn5fA==", "dev": true, "dependencies": { - "@babel/helper-module-imports": "^7.22.15", - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/helper-remap-async-to-generator": "^7.22.20" + "@babel/helper-module-imports": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/helper-remap-async-to-generator": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -1747,12 +1477,12 @@ } }, "node_modules/@babel/plugin-transform-block-scoped-functions": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.24.1.tgz", - "integrity": "sha512-TWWC18OShZutrv9C6mye1xwtam+uNi2bnTOCBUd5sZxyHOiWbU6ztSROofIMrK84uweEZC219POICK/sTYwfgg==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.24.7.tgz", + "integrity": "sha512-yO7RAz6EsVQDaBH18IDJcMB1HnrUn2FJ/Jslc/WtPPWcjhpUJXU/rjbwmluzp7v/ZzWcEhTMXELnnsz8djWDwQ==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-plugin-utils": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -1762,12 +1492,12 @@ } }, "node_modules/@babel/plugin-transform-block-scoping": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.24.5.tgz", - "integrity": "sha512-sMfBc3OxghjC95BkYrYocHL3NaOplrcaunblzwXhGmlPwpmfsxr4vK+mBBt49r+S240vahmv+kUxkeKgs+haCw==", + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.25.0.tgz", + "integrity": "sha512-yBQjYoOjXlFv9nlXb3f1casSHOZkWr29NX+zChVanLg5Nc157CrbEX9D7hxxtTpuFy7Q0YzmmWfJxzvps4kXrQ==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.24.5" + "@babel/helper-plugin-utils": "^7.24.8" }, "engines": { "node": ">=6.9.0" @@ -1777,13 +1507,13 @@ } }, "node_modules/@babel/plugin-transform-class-properties": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.24.1.tgz", - "integrity": "sha512-OMLCXi0NqvJfORTaPQBwqLXHhb93wkBKZ4aNwMl6WtehO7ar+cmp+89iPEQPqxAnxsOKTaMcs3POz3rKayJ72g==", + "version": "7.25.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.25.4.tgz", + "integrity": "sha512-nZeZHyCWPfjkdU5pA/uHiTaDAFUEqkpzf1YoQT2NeSynCGYq9rxfyI3XpQbfx/a0hSnFH6TGlEXvae5Vi7GD8g==", "dev": true, "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.24.1", - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-create-class-features-plugin": "^7.25.4", + "@babel/helper-plugin-utils": "^7.24.8" }, "engines": { "node": ">=6.9.0" @@ -1793,13 +1523,13 @@ } }, "node_modules/@babel/plugin-transform-class-static-block": { - "version": "7.24.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.24.4.tgz", - "integrity": "sha512-B8q7Pz870Hz/q9UgP8InNpY01CSLDSCyqX7zcRuv3FcPl87A2G17lASroHWaCtbdIcbYzOZ7kWmXFKbijMSmFg==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.24.7.tgz", + "integrity": "sha512-HMXK3WbBPpZQufbMG4B46A90PkuuhN9vBCb5T8+VAHqvAqvcLi+2cKoukcpmUYkszLhScU3l1iudhrks3DggRQ==", "dev": true, "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.24.4", - "@babel/helper-plugin-utils": "^7.24.0", + "@babel/helper-create-class-features-plugin": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7", "@babel/plugin-syntax-class-static-block": "^7.14.5" }, "engines": { @@ -1810,18 +1540,16 @@ } }, "node_modules/@babel/plugin-transform-classes": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.24.5.tgz", - "integrity": "sha512-gWkLP25DFj2dwe9Ck8uwMOpko4YsqyfZJrOmqqcegeDYEbp7rmn4U6UQZNj08UF6MaX39XenSpKRCvpDRBtZ7Q==", - "dev": true, - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.22.5", - "@babel/helper-compilation-targets": "^7.23.6", - "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-function-name": "^7.23.0", - "@babel/helper-plugin-utils": "^7.24.5", - "@babel/helper-replace-supers": "^7.24.1", - "@babel/helper-split-export-declaration": "^7.24.5", + "version": "7.25.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.25.4.tgz", + "integrity": "sha512-oexUfaQle2pF/b6E0dwsxQtAol9TLSO88kQvym6HHBWFliV2lGdrPieX+WgMRLSJDVzdYywk7jXbLPuO2KLTLg==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.24.7", + "@babel/helper-compilation-targets": "^7.25.2", + "@babel/helper-plugin-utils": "^7.24.8", + "@babel/helper-replace-supers": "^7.25.0", + "@babel/traverse": "^7.25.4", "globals": "^11.1.0" }, "engines": { @@ -1831,26 +1559,29 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-classes/node_modules/@babel/helper-split-export-declaration": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.24.5.tgz", - "integrity": "sha512-5CHncttXohrHk8GWOFCcCl4oRD9fKosWlIRgWm4ql9VYioKm52Mk2xsmoohvm7f3JoiLSM5ZgJuRaf5QZZYd3Q==", + "node_modules/@babel/plugin-transform-computed-properties": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.24.7.tgz", + "integrity": "sha512-25cS7v+707Gu6Ds2oY6tCkUwsJ9YIDbggd9+cu9jzzDgiNq7hR/8dkzxWfKWnTic26vsI3EsCXNd4iEB6e8esQ==", "dev": true, "dependencies": { - "@babel/types": "^7.24.5" + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/template": "^7.24.7" }, "engines": { "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-computed-properties": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.24.1.tgz", - "integrity": "sha512-5pJGVIUfJpOS+pAqBQd+QMaTD2vCL/HcePooON6pDpHgRp4gNRmzyHTPIkXntwKsq3ayUFVfJaIKPw2pOkOcTw==", + "node_modules/@babel/plugin-transform-destructuring": { + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.24.8.tgz", + "integrity": "sha512-36e87mfY8TnRxc7yc6M9g9gOB7rKgSahqkIKwLpz4Ppk2+zC2Cy1is0uwtuSG6AE4zlTOUa+7JGz9jCJGLqQFQ==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0", - "@babel/template": "^7.24.0" + "@babel/helper-plugin-utils": "^7.24.8" }, "engines": { "node": ">=6.9.0" @@ -1859,13 +1590,14 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-destructuring": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.24.5.tgz", - "integrity": "sha512-SZuuLyfxvsm+Ah57I/i1HVjveBENYK9ue8MJ7qkc7ndoNjqquJiElzA7f5yaAXjyW2hKojosOTAQQRX50bPSVg==", + "node_modules/@babel/plugin-transform-dotall-regex": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.24.7.tgz", + "integrity": "sha512-ZOA3W+1RRTSWvyqcMJDLqbchh7U4NRGqwRfFSVbOLS/ePIP4vHB5e8T8eXcuqyN1QkgKyj5wuW0lcS85v4CrSw==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.24.5" + "@babel/helper-create-regexp-features-plugin": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -1874,14 +1606,13 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-dotall-regex": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.24.1.tgz", - "integrity": "sha512-p7uUxgSoZwZ2lPNMzUkqCts3xlp8n+o05ikjy7gbtFJSt9gdU88jAmtfmOxHM14noQXBxfgzf2yRWECiNVhTCw==", + "node_modules/@babel/plugin-transform-duplicate-keys": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.24.7.tgz", + "integrity": "sha512-JdYfXyCRihAe46jUIliuL2/s0x0wObgwwiGxw/UbgJBr20gQBThrokO4nYKgWkD7uBaqM7+9x5TU7NkExZJyzw==", "dev": true, "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.22.15", - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-plugin-utils": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -1890,28 +1621,29 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-duplicate-keys": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.24.1.tgz", - "integrity": "sha512-msyzuUnvsjsaSaocV6L7ErfNsa5nDWL1XKNnDePLgmz+WdU4w/J8+AxBMrWfi9m4IxfL5sZQKUPQKDQeeAT6lA==", + "node_modules/@babel/plugin-transform-duplicate-named-capturing-groups-regex": { + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-named-capturing-groups-regex/-/plugin-transform-duplicate-named-capturing-groups-regex-7.25.0.tgz", + "integrity": "sha512-YLpb4LlYSc3sCUa35un84poXoraOiQucUTTu8X1j18JV+gNa8E0nyUf/CjZ171IRGr4jEguF+vzJU66QZhn29g==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-create-regexp-features-plugin": "^7.25.0", + "@babel/helper-plugin-utils": "^7.24.8" }, "engines": { "node": ">=6.9.0" }, "peerDependencies": { - "@babel/core": "^7.0.0-0" + "@babel/core": "^7.0.0" } }, "node_modules/@babel/plugin-transform-dynamic-import": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.24.1.tgz", - "integrity": "sha512-av2gdSTyXcJVdI+8aFZsCAtR29xJt0S5tas+Ef8NvBNmD1a+N/3ecMLeMBgfcK+xzsjdLDT6oHt+DFPyeqUbDA==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.24.7.tgz", + "integrity": "sha512-sc3X26PhZQDb3JhORmakcbvkeInvxz+A8oda99lj7J60QRuPZvNAk9wQlTBS1ZynelDrDmTU4pw1tyc5d5ZMUg==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0", + "@babel/helper-plugin-utils": "^7.24.7", "@babel/plugin-syntax-dynamic-import": "^7.8.3" }, "engines": { @@ -1922,13 +1654,13 @@ } }, "node_modules/@babel/plugin-transform-exponentiation-operator": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.24.1.tgz", - "integrity": "sha512-U1yX13dVBSwS23DEAqU+Z/PkwE9/m7QQy8Y9/+Tdb8UWYaGNDYwTLi19wqIAiROr8sXVum9A/rtiH5H0boUcTw==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.24.7.tgz", + "integrity": "sha512-Rqe/vSc9OYgDajNIK35u7ot+KeCoetqQYFXM4Epf7M7ez3lWlOjrDjrwMei6caCVhfdw+mIKD4cgdGNy5JQotQ==", "dev": true, "dependencies": { - "@babel/helper-builder-binary-assignment-operator-visitor": "^7.22.15", - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-builder-binary-assignment-operator-visitor": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -1938,12 +1670,12 @@ } }, "node_modules/@babel/plugin-transform-export-namespace-from": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.24.1.tgz", - "integrity": "sha512-Ft38m/KFOyzKw2UaJFkWG9QnHPG/Q/2SkOrRk4pNBPg5IPZ+dOxcmkK5IyuBcxiNPyyYowPGUReyBvrvZs7IlQ==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.24.7.tgz", + "integrity": "sha512-v0K9uNYsPL3oXZ/7F9NNIbAj2jv1whUEtyA6aujhekLs56R++JDQuzRcP2/z4WX5Vg/c5lE9uWZA0/iUoFhLTA==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0", + "@babel/helper-plugin-utils": "^7.24.7", "@babel/plugin-syntax-export-namespace-from": "^7.8.3" }, "engines": { @@ -1954,13 +1686,13 @@ } }, "node_modules/@babel/plugin-transform-for-of": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.24.1.tgz", - "integrity": "sha512-OxBdcnF04bpdQdR3i4giHZNZQn7cm8RQKcSwA17wAAqEELo1ZOwp5FFgeptWUQXFyT9kwHo10aqqauYkRZPCAg==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.24.7.tgz", + "integrity": "sha512-wo9ogrDG1ITTTBsy46oGiN1dS9A7MROBTcYsfS8DtsImMkHk9JXJ3EWQM6X2SUw4x80uGPlwj0o00Uoc6nEE3g==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0", - "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5" + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/helper-skip-transparent-expression-wrappers": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -1970,14 +1702,14 @@ } }, "node_modules/@babel/plugin-transform-function-name": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.24.1.tgz", - "integrity": "sha512-BXmDZpPlh7jwicKArQASrj8n22/w6iymRnvHYYd2zO30DbE277JO20/7yXJT3QxDPtiQiOxQBbZH4TpivNXIxA==", + "version": "7.25.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.25.1.tgz", + "integrity": "sha512-TVVJVdW9RKMNgJJlLtHsKDTydjZAbwIsn6ySBPQaEAUU5+gVvlJt/9nRmqVbsV/IBanRjzWoaAQKLoamWVOUuA==", "dev": true, "dependencies": { - "@babel/helper-compilation-targets": "^7.23.6", - "@babel/helper-function-name": "^7.23.0", - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-compilation-targets": "^7.24.8", + "@babel/helper-plugin-utils": "^7.24.8", + "@babel/traverse": "^7.25.1" }, "engines": { "node": ">=6.9.0" @@ -1987,12 +1719,12 @@ } }, "node_modules/@babel/plugin-transform-json-strings": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.24.1.tgz", - "integrity": "sha512-U7RMFmRvoasscrIFy5xA4gIp8iWnWubnKkKuUGJjsuOH7GfbMkB+XZzeslx2kLdEGdOJDamEmCqOks6e8nv8DQ==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.24.7.tgz", + "integrity": "sha512-2yFnBGDvRuxAaE/f0vfBKvtnvvqU8tGpMHqMNpTN2oWMKIR3NqFkjaAgGwawhqK/pIN2T3XdjGPdaG0vDhOBGw==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0", + "@babel/helper-plugin-utils": "^7.24.7", "@babel/plugin-syntax-json-strings": "^7.8.3" }, "engines": { @@ -2003,12 +1735,12 @@ } }, "node_modules/@babel/plugin-transform-literals": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.24.1.tgz", - "integrity": "sha512-zn9pwz8U7nCqOYIiBaOxoQOtYmMODXTJnkxG4AtX8fPmnCRYWBOHD0qcpwS9e2VDSp1zNJYpdnFMIKb8jmwu6g==", + "version": "7.25.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.25.2.tgz", + "integrity": "sha512-HQI+HcTbm9ur3Z2DkO+jgESMAMcYLuN/A7NRw9juzxAezN9AvqvUTnpKP/9kkYANz6u7dFlAyOu44ejuGySlfw==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-plugin-utils": "^7.24.8" }, "engines": { "node": ">=6.9.0" @@ -2018,12 +1750,12 @@ } }, "node_modules/@babel/plugin-transform-logical-assignment-operators": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.24.1.tgz", - "integrity": "sha512-OhN6J4Bpz+hIBqItTeWJujDOfNP+unqv/NJgyhlpSqgBTPm37KkMmZV6SYcOj+pnDbdcl1qRGV/ZiIjX9Iy34w==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.24.7.tgz", + "integrity": "sha512-4D2tpwlQ1odXmTEIFWy9ELJcZHqrStlzK/dAOWYyxX3zT0iXQB6banjgeOJQXzEc4S0E0a5A+hahxPaEFYftsw==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0", + "@babel/helper-plugin-utils": "^7.24.7", "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4" }, "engines": { @@ -2034,12 +1766,12 @@ } }, "node_modules/@babel/plugin-transform-member-expression-literals": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.24.1.tgz", - "integrity": "sha512-4ojai0KysTWXzHseJKa1XPNXKRbuUrhkOPY4rEGeR+7ChlJVKxFa3H3Bz+7tWaGKgJAXUWKOGmltN+u9B3+CVg==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.24.7.tgz", + "integrity": "sha512-T/hRC1uqrzXMKLQ6UCwMT85S3EvqaBXDGf0FaMf4446Qx9vKwlghvee0+uuZcDUCZU5RuNi4781UQ7R308zzBw==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-plugin-utils": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -2049,13 +1781,13 @@ } }, "node_modules/@babel/plugin-transform-modules-amd": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.24.1.tgz", - "integrity": "sha512-lAxNHi4HVtjnHd5Rxg3D5t99Xm6H7b04hUS7EHIXcUl2EV4yl1gWdqZrNzXnSrHveL9qMdbODlLF55mvgjAfaQ==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.24.7.tgz", + "integrity": "sha512-9+pB1qxV3vs/8Hdmz/CulFB8w2tuu6EB94JZFsjdqxQokwGa9Unap7Bo2gGBGIvPmDIVvQrom7r5m/TCDMURhg==", "dev": true, "dependencies": { - "@babel/helper-module-transforms": "^7.23.3", - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-module-transforms": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -2065,14 +1797,14 @@ } }, "node_modules/@babel/plugin-transform-modules-commonjs": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.24.1.tgz", - "integrity": "sha512-szog8fFTUxBfw0b98gEWPaEqF42ZUD/T3bkynW/wtgx2p/XCP55WEsb+VosKceRSd6njipdZvNogqdtI4Q0chw==", + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.24.8.tgz", + "integrity": "sha512-WHsk9H8XxRs3JXKWFiqtQebdh9b/pTk4EgueygFzYlTKAg0Ud985mSevdNjdXdFBATSKVJGQXP1tv6aGbssLKA==", "dev": true, "dependencies": { - "@babel/helper-module-transforms": "^7.23.3", - "@babel/helper-plugin-utils": "^7.24.0", - "@babel/helper-simple-access": "^7.22.5" + "@babel/helper-module-transforms": "^7.24.8", + "@babel/helper-plugin-utils": "^7.24.8", + "@babel/helper-simple-access": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -2082,15 +1814,15 @@ } }, "node_modules/@babel/plugin-transform-modules-systemjs": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.24.1.tgz", - "integrity": "sha512-mqQ3Zh9vFO1Tpmlt8QPnbwGHzNz3lpNEMxQb1kAemn/erstyqw1r9KeOlOfo3y6xAnFEcOv2tSyrXfmMk+/YZA==", + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.25.0.tgz", + "integrity": "sha512-YPJfjQPDXxyQWg/0+jHKj1llnY5f/R6a0p/vP4lPymxLu7Lvl4k2WMitqi08yxwQcCVUUdG9LCUj4TNEgAp3Jw==", "dev": true, "dependencies": { - "@babel/helper-hoist-variables": "^7.22.5", - "@babel/helper-module-transforms": "^7.23.3", - "@babel/helper-plugin-utils": "^7.24.0", - "@babel/helper-validator-identifier": "^7.22.20" + "@babel/helper-module-transforms": "^7.25.0", + "@babel/helper-plugin-utils": "^7.24.8", + "@babel/helper-validator-identifier": "^7.24.7", + "@babel/traverse": "^7.25.0" }, "engines": { "node": ">=6.9.0" @@ -2100,13 +1832,13 @@ } }, "node_modules/@babel/plugin-transform-modules-umd": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.24.1.tgz", - "integrity": "sha512-tuA3lpPj+5ITfcCluy6nWonSL7RvaG0AOTeAuvXqEKS34lnLzXpDb0dcP6K8jD0zWZFNDVly90AGFJPnm4fOYg==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.24.7.tgz", + "integrity": "sha512-3aytQvqJ/h9z4g8AsKPLvD4Zqi2qT+L3j7XoFFu1XBlZWEl2/1kWnhmAbxpLgPrHSY0M6UA02jyTiwUVtiKR6A==", "dev": true, "dependencies": { - "@babel/helper-module-transforms": "^7.23.3", - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-module-transforms": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -2116,13 +1848,13 @@ } }, "node_modules/@babel/plugin-transform-named-capturing-groups-regex": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.22.5.tgz", - "integrity": "sha512-YgLLKmS3aUBhHaxp5hi1WJTgOUb/NCuDHzGT9z9WTt3YG+CPRhJs6nprbStx6DnWM4dh6gt7SU3sZodbZ08adQ==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.24.7.tgz", + "integrity": "sha512-/jr7h/EWeJtk1U/uz2jlsCioHkZk1JJZVcc8oQsJ1dUlaJD83f4/6Zeh2aHt9BIFokHIsSeDfhUmju0+1GPd6g==", "dev": true, "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.22.5", - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-create-regexp-features-plugin": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -2132,12 +1864,12 @@ } }, "node_modules/@babel/plugin-transform-new-target": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.24.1.tgz", - "integrity": "sha512-/rurytBM34hYy0HKZQyA0nHbQgQNFm4Q/BOc9Hflxi2X3twRof7NaE5W46j4kQitm7SvACVRXsa6N/tSZxvPug==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.24.7.tgz", + "integrity": "sha512-RNKwfRIXg4Ls/8mMTza5oPF5RkOW8Wy/WgMAp1/F1yZ8mMbtwXW+HDoJiOsagWrAhI5f57Vncrmr9XeT4CVapA==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-plugin-utils": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -2147,12 +1879,12 @@ } }, "node_modules/@babel/plugin-transform-nullish-coalescing-operator": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.24.1.tgz", - "integrity": "sha512-iQ+caew8wRrhCikO5DrUYx0mrmdhkaELgFa+7baMcVuhxIkN7oxt06CZ51D65ugIb1UWRQ8oQe+HXAVM6qHFjw==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.24.7.tgz", + "integrity": "sha512-Ts7xQVk1OEocqzm8rHMXHlxvsfZ0cEF2yomUqpKENHWMF4zKk175Y4q8H5knJes6PgYad50uuRmt3UJuhBw8pQ==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0", + "@babel/helper-plugin-utils": "^7.24.7", "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3" }, "engines": { @@ -2163,12 +1895,12 @@ } }, "node_modules/@babel/plugin-transform-numeric-separator": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.24.1.tgz", - "integrity": "sha512-7GAsGlK4cNL2OExJH1DzmDeKnRv/LXq0eLUSvudrehVA5Rgg4bIrqEUW29FbKMBRT0ztSqisv7kjP+XIC4ZMNw==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.24.7.tgz", + "integrity": "sha512-e6q1TiVUzvH9KRvicuxdBTUj4AdKSRwzIyFFnfnezpCfP2/7Qmbb8qbU2j7GODbl4JMkblitCQjKYUaX/qkkwA==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0", + "@babel/helper-plugin-utils": "^7.24.7", "@babel/plugin-syntax-numeric-separator": "^7.10.4" }, "engines": { @@ -2179,15 +1911,15 @@ } }, "node_modules/@babel/plugin-transform-object-rest-spread": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.24.5.tgz", - "integrity": "sha512-7EauQHszLGM3ay7a161tTQH7fj+3vVM/gThlz5HpFtnygTxjrlvoeq7MPVA1Vy9Q555OB8SnAOsMkLShNkkrHA==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.24.7.tgz", + "integrity": "sha512-4QrHAr0aXQCEFni2q4DqKLD31n2DL+RxcwnNjDFkSG0eNQ/xCavnRkfCUjsyqGC2OviNJvZOF/mQqZBw7i2C5Q==", "dev": true, "dependencies": { - "@babel/helper-compilation-targets": "^7.23.6", - "@babel/helper-plugin-utils": "^7.24.5", + "@babel/helper-compilation-targets": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7", "@babel/plugin-syntax-object-rest-spread": "^7.8.3", - "@babel/plugin-transform-parameters": "^7.24.5" + "@babel/plugin-transform-parameters": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -2197,13 +1929,13 @@ } }, "node_modules/@babel/plugin-transform-object-super": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.24.1.tgz", - "integrity": "sha512-oKJqR3TeI5hSLRxudMjFQ9re9fBVUU0GICqM3J1mi8MqlhVr6hC/ZN4ttAyMuQR6EZZIY6h/exe5swqGNNIkWQ==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.24.7.tgz", + "integrity": "sha512-A/vVLwN6lBrMFmMDmPPz0jnE6ZGx7Jq7d6sT/Ev4H65RER6pZ+kczlf1DthF5N0qaPHBsI7UXiE8Zy66nmAovg==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0", - "@babel/helper-replace-supers": "^7.24.1" + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/helper-replace-supers": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -2213,12 +1945,12 @@ } }, "node_modules/@babel/plugin-transform-optional-catch-binding": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.24.1.tgz", - "integrity": "sha512-oBTH7oURV4Y+3EUrf6cWn1OHio3qG/PVwO5J03iSJmBg6m2EhKjkAu/xuaXaYwWW9miYtvbWv4LNf0AmR43LUA==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.24.7.tgz", + "integrity": "sha512-uLEndKqP5BfBbC/5jTwPxLh9kqPWWgzN/f8w6UwAIirAEqiIVJWWY312X72Eub09g5KF9+Zn7+hT7sDxmhRuKA==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0", + "@babel/helper-plugin-utils": "^7.24.7", "@babel/plugin-syntax-optional-catch-binding": "^7.8.3" }, "engines": { @@ -2229,13 +1961,13 @@ } }, "node_modules/@babel/plugin-transform-optional-chaining": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.24.5.tgz", - "integrity": "sha512-xWCkmwKT+ihmA6l7SSTpk8e4qQl/274iNbSKRRS8mpqFR32ksy36+a+LWY8OXCCEefF8WFlnOHVsaDI2231wBg==", + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.24.8.tgz", + "integrity": "sha512-5cTOLSMs9eypEy8JUVvIKOu6NgvbJMnpG62VpIHrTmROdQ+L5mDAaI40g25k5vXti55JWNX5jCkq3HZxXBQANw==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.24.5", - "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5", + "@babel/helper-plugin-utils": "^7.24.8", + "@babel/helper-skip-transparent-expression-wrappers": "^7.24.7", "@babel/plugin-syntax-optional-chaining": "^7.8.3" }, "engines": { @@ -2246,12 +1978,12 @@ } }, "node_modules/@babel/plugin-transform-parameters": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.24.5.tgz", - "integrity": "sha512-9Co00MqZ2aoky+4j2jhofErthm6QVLKbpQrvz20c3CH9KQCLHyNB+t2ya4/UrRpQGR+Wrwjg9foopoeSdnHOkA==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.24.7.tgz", + "integrity": "sha512-yGWW5Rr+sQOhK0Ot8hjDJuxU3XLRQGflvT4lhlSY0DFvdb3TwKaY26CJzHtYllU0vT9j58hc37ndFPsqT1SrzA==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.24.5" + "@babel/helper-plugin-utils": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -2261,13 +1993,13 @@ } }, "node_modules/@babel/plugin-transform-private-methods": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.24.1.tgz", - "integrity": "sha512-tGvisebwBO5em4PaYNqt4fkw56K2VALsAbAakY0FjTYqJp7gfdrgr7YX76Or8/cpik0W6+tj3rZ0uHU9Oil4tw==", + "version": "7.25.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.25.4.tgz", + "integrity": "sha512-ao8BG7E2b/URaUQGqN3Tlsg+M3KlHY6rJ1O1gXAEUnZoyNQnvKyH87Kfg+FoxSeyWUB8ISZZsC91C44ZuBFytw==", "dev": true, "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.24.1", - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-create-class-features-plugin": "^7.25.4", + "@babel/helper-plugin-utils": "^7.24.8" }, "engines": { "node": ">=6.9.0" @@ -2277,14 +2009,14 @@ } }, "node_modules/@babel/plugin-transform-private-property-in-object": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.24.5.tgz", - "integrity": "sha512-JM4MHZqnWR04jPMujQDTBVRnqxpLLpx2tkn7iPn+Hmsc0Gnb79yvRWOkvqFOx3Z7P7VxiRIR22c4eGSNj87OBQ==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.24.7.tgz", + "integrity": "sha512-9z76mxwnwFxMyxZWEgdgECQglF2Q7cFLm0kMf8pGwt+GSJsY0cONKj/UuO4bOH0w/uAel3ekS4ra5CEAyJRmDA==", "dev": true, "dependencies": { - "@babel/helper-annotate-as-pure": "^7.22.5", - "@babel/helper-create-class-features-plugin": "^7.24.5", - "@babel/helper-plugin-utils": "^7.24.5", + "@babel/helper-annotate-as-pure": "^7.24.7", + "@babel/helper-create-class-features-plugin": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7", "@babel/plugin-syntax-private-property-in-object": "^7.14.5" }, "engines": { @@ -2295,12 +2027,12 @@ } }, "node_modules/@babel/plugin-transform-property-literals": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.24.1.tgz", - "integrity": "sha512-LetvD7CrHmEx0G442gOomRr66d7q8HzzGGr4PMHGr+5YIm6++Yke+jxj246rpvsbyhJwCLxcTn6zW1P1BSenqA==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.24.7.tgz", + "integrity": "sha512-EMi4MLQSHfd2nrCqQEWxFdha2gBCqU4ZcCng4WBGZ5CJL4bBRW0ptdqqDdeirGZcpALazVVNJqRmsO8/+oNCBA==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-plugin-utils": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -2310,12 +2042,12 @@ } }, "node_modules/@babel/plugin-transform-regenerator": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.24.1.tgz", - "integrity": "sha512-sJwZBCzIBE4t+5Q4IGLaaun5ExVMRY0lYwos/jNecjMrVCygCdph3IKv0tkP5Fc87e/1+bebAmEAGBfnRD+cnw==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.24.7.tgz", + "integrity": "sha512-lq3fvXPdimDrlg6LWBoqj+r/DEWgONuwjuOuQCSYgRroXDH/IdM1C0IZf59fL5cHLpjEH/O6opIRBbqv7ELnuA==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0", + "@babel/helper-plugin-utils": "^7.24.7", "regenerator-transform": "^0.15.2" }, "engines": { @@ -2326,12 +2058,12 @@ } }, "node_modules/@babel/plugin-transform-reserved-words": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.24.1.tgz", - "integrity": "sha512-JAclqStUfIwKN15HrsQADFgeZt+wexNQ0uLhuqvqAUFoqPMjEcFCYZBhq0LUdz6dZK/mD+rErhW71fbx8RYElg==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.24.7.tgz", + "integrity": "sha512-0DUq0pHcPKbjFZCfTss/pGkYMfy3vFWydkUBd9r0GHpIyfs2eCDENvqadMycRS9wZCXR41wucAfJHJmwA0UmoQ==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-plugin-utils": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -2341,16 +2073,16 @@ } }, "node_modules/@babel/plugin-transform-runtime": { - "version": "7.24.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.24.0.tgz", - "integrity": "sha512-zc0GA5IitLKJrSfXlXmp8KDqLrnGECK7YRfQBmEKg1NmBOQ7e+KuclBEKJgzifQeUYLdNiAw4B4bjyvzWVLiSA==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.24.7.tgz", + "integrity": "sha512-YqXjrk4C+a1kZjewqt+Mmu2UuV1s07y8kqcUf4qYLnoqemhR4gRQikhdAhSVJioMjVTu6Mo6pAbaypEA3jY6fw==", "dev": true, "dependencies": { - "@babel/helper-module-imports": "^7.22.15", - "@babel/helper-plugin-utils": "^7.24.0", - "babel-plugin-polyfill-corejs2": "^0.4.8", - "babel-plugin-polyfill-corejs3": "^0.9.0", - "babel-plugin-polyfill-regenerator": "^0.5.5", + "@babel/helper-module-imports": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7", + "babel-plugin-polyfill-corejs2": "^0.4.10", + "babel-plugin-polyfill-corejs3": "^0.10.1", + "babel-plugin-polyfill-regenerator": "^0.6.1", "semver": "^6.3.1" }, "engines": { @@ -2370,12 +2102,12 @@ } }, "node_modules/@babel/plugin-transform-shorthand-properties": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.24.1.tgz", - "integrity": "sha512-LyjVB1nsJ6gTTUKRjRWx9C1s9hE7dLfP/knKdrfeH9UPtAGjYGgxIbFfx7xyLIEWs7Xe1Gnf8EWiUqfjLhInZA==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.24.7.tgz", + "integrity": "sha512-KsDsevZMDsigzbA09+vacnLpmPH4aWjcZjXdyFKGzpplxhbeB4wYtury3vglQkg6KM/xEPKt73eCjPPf1PgXBA==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-plugin-utils": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -2385,13 +2117,13 @@ } }, "node_modules/@babel/plugin-transform-spread": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.24.1.tgz", - "integrity": "sha512-KjmcIM+fxgY+KxPVbjelJC6hrH1CgtPmTvdXAfn3/a9CnWGSTY7nH4zm5+cjmWJybdcPSsD0++QssDsjcpe47g==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.24.7.tgz", + "integrity": "sha512-x96oO0I09dgMDxJaANcRyD4ellXFLLiWhuwDxKZX5g2rWP1bTPkBSwCYv96VDXVT1bD9aPj8tppr5ITIh8hBng==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0", - "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5" + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/helper-skip-transparent-expression-wrappers": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -2401,12 +2133,12 @@ } }, "node_modules/@babel/plugin-transform-sticky-regex": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.24.1.tgz", - "integrity": "sha512-9v0f1bRXgPVcPrngOQvLXeGNNVLc8UjMVfebo9ka0WF3/7+aVUHmaJVT3sa0XCzEFioPfPHZiOcYG9qOsH63cw==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.24.7.tgz", + "integrity": "sha512-kHPSIJc9v24zEml5geKg9Mjx5ULpfncj0wRpYtxbvKyTtHCYDkVE3aHQ03FrpEo4gEe2vrJJS1Y9CJTaThA52g==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-plugin-utils": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -2416,12 +2148,12 @@ } }, "node_modules/@babel/plugin-transform-template-literals": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.24.1.tgz", - "integrity": "sha512-WRkhROsNzriarqECASCNu/nojeXCDTE/F2HmRgOzi7NGvyfYGq1NEjKBK3ckLfRgGc6/lPAqP0vDOSw3YtG34g==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.24.7.tgz", + "integrity": "sha512-AfDTQmClklHCOLxtGoP7HkeMw56k1/bTQjwsfhL6pppo/M4TOBSq+jjBUBLmV/4oeFg4GWMavIl44ZeCtmmZTw==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-plugin-utils": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -2431,12 +2163,12 @@ } }, "node_modules/@babel/plugin-transform-typeof-symbol": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.24.5.tgz", - "integrity": "sha512-UTGnhYVZtTAjdwOTzT+sCyXmTn8AhaxOS/MjG9REclZ6ULHWF9KoCZur0HSGU7hk8PdBFKKbYe6+gqdXWz84Jg==", + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.24.8.tgz", + "integrity": "sha512-adNTUpDCVnmAE58VEqKlAA6ZBlNkMnWD0ZcW76lyNFN3MJniyGFZfNwERVk8Ap56MCnXztmDr19T4mPTztcuaw==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.24.5" + "@babel/helper-plugin-utils": "^7.24.8" }, "engines": { "node": ">=6.9.0" @@ -2446,12 +2178,12 @@ } }, "node_modules/@babel/plugin-transform-unicode-escapes": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.24.1.tgz", - "integrity": "sha512-RlkVIcWT4TLI96zM660S877E7beKlQw7Ig+wqkKBiWfj0zH5Q4h50q6er4wzZKRNSYpfo6ILJ+hrJAGSX2qcNw==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.24.7.tgz", + "integrity": "sha512-U3ap1gm5+4edc2Q/P+9VrBNhGkfnf+8ZqppY71Bo/pzZmXhhLdqgaUl6cuB07O1+AQJtCLfaOmswiNbSQ9ivhw==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-plugin-utils": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -2461,13 +2193,13 @@ } }, "node_modules/@babel/plugin-transform-unicode-property-regex": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.24.1.tgz", - "integrity": "sha512-Ss4VvlfYV5huWApFsF8/Sq0oXnGO+jB+rijFEFugTd3cwSObUSnUi88djgR5528Csl0uKlrI331kRqe56Ov2Ng==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.24.7.tgz", + "integrity": "sha512-uH2O4OV5M9FZYQrwc7NdVmMxQJOCCzFeYudlZSzUAHRFeOujQefa92E74TQDVskNHCzOXoigEuoyzHDhaEaK5w==", "dev": true, "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.22.15", - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-create-regexp-features-plugin": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -2477,13 +2209,13 @@ } }, "node_modules/@babel/plugin-transform-unicode-regex": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.24.1.tgz", - "integrity": "sha512-2A/94wgZgxfTsiLaQ2E36XAOdcZmGAaEEgVmxQWwZXWkGhvoHbaqXcKnU8zny4ycpu3vNqg0L/PcCiYtHtA13g==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.24.7.tgz", + "integrity": "sha512-hlQ96MBZSAXUq7ltkjtu3FJCCSMx/j629ns3hA3pXnBXjanNP0LHi+JpPeA81zaWgVK1VGH95Xuy7u0RyQ8kMg==", "dev": true, "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.22.15", - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-create-regexp-features-plugin": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -2493,13 +2225,13 @@ } }, "node_modules/@babel/plugin-transform-unicode-sets-regex": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.24.1.tgz", - "integrity": "sha512-fqj4WuzzS+ukpgerpAoOnMfQXwUHFxXUZUE84oL2Kao2N8uSlvcpnAidKASgsNgzZHBsHWvcm8s9FPWUhAb8fA==", + "version": "7.25.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.25.4.tgz", + "integrity": "sha512-qesBxiWkgN1Q+31xUE9RcMk79eOXXDCv6tfyGMRSs4RGlioSg2WVyQAm07k726cSE56pa+Kb0y9epX2qaXzTvA==", "dev": true, "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.22.15", - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-create-regexp-features-plugin": "^7.25.2", + "@babel/helper-plugin-utils": "^7.24.8" }, "engines": { "node": ">=6.9.0" @@ -2509,26 +2241,28 @@ } }, "node_modules/@babel/preset-env": { - "version": "7.24.0", - "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.24.0.tgz", - "integrity": "sha512-ZxPEzV9IgvGn73iK0E6VB9/95Nd7aMFpbE0l8KQFDG70cOV9IxRP7Y2FUPmlK0v6ImlLqYX50iuZ3ZTVhOF2lA==", - "dev": true, - "dependencies": { - "@babel/compat-data": "^7.23.5", - "@babel/helper-compilation-targets": "^7.23.6", - "@babel/helper-plugin-utils": "^7.24.0", - "@babel/helper-validator-option": "^7.23.5", - "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.23.3", - "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.23.3", - "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.23.7", + "version": "7.25.3", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.25.3.tgz", + "integrity": "sha512-QsYW7UeAaXvLPX9tdVliMJE7MD7M6MLYVTovRTIwhoYQVFHR1rM4wO8wqAezYi3/BpSD+NzVCZ69R6smWiIi8g==", + "dev": true, + "dependencies": { + "@babel/compat-data": "^7.25.2", + "@babel/helper-compilation-targets": "^7.25.2", + "@babel/helper-plugin-utils": "^7.24.8", + "@babel/helper-validator-option": "^7.24.8", + "@babel/plugin-bugfix-firefox-class-in-computed-class-key": "^7.25.3", + "@babel/plugin-bugfix-safari-class-field-initializer-scope": "^7.25.0", + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.25.0", + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.24.7", + "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.25.0", "@babel/plugin-proposal-private-property-in-object": "7.21.0-placeholder-for-preset-env.2", "@babel/plugin-syntax-async-generators": "^7.8.4", "@babel/plugin-syntax-class-properties": "^7.12.13", "@babel/plugin-syntax-class-static-block": "^7.14.5", "@babel/plugin-syntax-dynamic-import": "^7.8.3", "@babel/plugin-syntax-export-namespace-from": "^7.8.3", - "@babel/plugin-syntax-import-assertions": "^7.23.3", - "@babel/plugin-syntax-import-attributes": "^7.23.3", + "@babel/plugin-syntax-import-assertions": "^7.24.7", + "@babel/plugin-syntax-import-attributes": "^7.24.7", "@babel/plugin-syntax-import-meta": "^7.10.4", "@babel/plugin-syntax-json-strings": "^7.8.3", "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", @@ -2540,59 +2274,60 @@ "@babel/plugin-syntax-private-property-in-object": "^7.14.5", "@babel/plugin-syntax-top-level-await": "^7.14.5", "@babel/plugin-syntax-unicode-sets-regex": "^7.18.6", - "@babel/plugin-transform-arrow-functions": "^7.23.3", - "@babel/plugin-transform-async-generator-functions": "^7.23.9", - "@babel/plugin-transform-async-to-generator": "^7.23.3", - "@babel/plugin-transform-block-scoped-functions": "^7.23.3", - "@babel/plugin-transform-block-scoping": "^7.23.4", - "@babel/plugin-transform-class-properties": "^7.23.3", - "@babel/plugin-transform-class-static-block": "^7.23.4", - "@babel/plugin-transform-classes": "^7.23.8", - "@babel/plugin-transform-computed-properties": "^7.23.3", - "@babel/plugin-transform-destructuring": "^7.23.3", - "@babel/plugin-transform-dotall-regex": "^7.23.3", - "@babel/plugin-transform-duplicate-keys": "^7.23.3", - "@babel/plugin-transform-dynamic-import": "^7.23.4", - "@babel/plugin-transform-exponentiation-operator": "^7.23.3", - "@babel/plugin-transform-export-namespace-from": "^7.23.4", - "@babel/plugin-transform-for-of": "^7.23.6", - "@babel/plugin-transform-function-name": "^7.23.3", - "@babel/plugin-transform-json-strings": "^7.23.4", - "@babel/plugin-transform-literals": "^7.23.3", - "@babel/plugin-transform-logical-assignment-operators": "^7.23.4", - "@babel/plugin-transform-member-expression-literals": "^7.23.3", - "@babel/plugin-transform-modules-amd": "^7.23.3", - "@babel/plugin-transform-modules-commonjs": "^7.23.3", - "@babel/plugin-transform-modules-systemjs": "^7.23.9", - "@babel/plugin-transform-modules-umd": "^7.23.3", - "@babel/plugin-transform-named-capturing-groups-regex": "^7.22.5", - "@babel/plugin-transform-new-target": "^7.23.3", - "@babel/plugin-transform-nullish-coalescing-operator": "^7.23.4", - "@babel/plugin-transform-numeric-separator": "^7.23.4", - "@babel/plugin-transform-object-rest-spread": "^7.24.0", - "@babel/plugin-transform-object-super": "^7.23.3", - "@babel/plugin-transform-optional-catch-binding": "^7.23.4", - "@babel/plugin-transform-optional-chaining": "^7.23.4", - "@babel/plugin-transform-parameters": "^7.23.3", - "@babel/plugin-transform-private-methods": "^7.23.3", - "@babel/plugin-transform-private-property-in-object": "^7.23.4", - "@babel/plugin-transform-property-literals": "^7.23.3", - "@babel/plugin-transform-regenerator": "^7.23.3", - "@babel/plugin-transform-reserved-words": "^7.23.3", - "@babel/plugin-transform-shorthand-properties": "^7.23.3", - "@babel/plugin-transform-spread": "^7.23.3", - "@babel/plugin-transform-sticky-regex": "^7.23.3", - "@babel/plugin-transform-template-literals": "^7.23.3", - "@babel/plugin-transform-typeof-symbol": "^7.23.3", - "@babel/plugin-transform-unicode-escapes": "^7.23.3", - "@babel/plugin-transform-unicode-property-regex": "^7.23.3", - "@babel/plugin-transform-unicode-regex": "^7.23.3", - "@babel/plugin-transform-unicode-sets-regex": "^7.23.3", + "@babel/plugin-transform-arrow-functions": "^7.24.7", + "@babel/plugin-transform-async-generator-functions": "^7.25.0", + "@babel/plugin-transform-async-to-generator": "^7.24.7", + "@babel/plugin-transform-block-scoped-functions": "^7.24.7", + "@babel/plugin-transform-block-scoping": "^7.25.0", + "@babel/plugin-transform-class-properties": "^7.24.7", + "@babel/plugin-transform-class-static-block": "^7.24.7", + "@babel/plugin-transform-classes": "^7.25.0", + "@babel/plugin-transform-computed-properties": "^7.24.7", + "@babel/plugin-transform-destructuring": "^7.24.8", + "@babel/plugin-transform-dotall-regex": "^7.24.7", + "@babel/plugin-transform-duplicate-keys": "^7.24.7", + "@babel/plugin-transform-duplicate-named-capturing-groups-regex": "^7.25.0", + "@babel/plugin-transform-dynamic-import": "^7.24.7", + "@babel/plugin-transform-exponentiation-operator": "^7.24.7", + "@babel/plugin-transform-export-namespace-from": "^7.24.7", + "@babel/plugin-transform-for-of": "^7.24.7", + "@babel/plugin-transform-function-name": "^7.25.1", + "@babel/plugin-transform-json-strings": "^7.24.7", + "@babel/plugin-transform-literals": "^7.25.2", + "@babel/plugin-transform-logical-assignment-operators": "^7.24.7", + "@babel/plugin-transform-member-expression-literals": "^7.24.7", + "@babel/plugin-transform-modules-amd": "^7.24.7", + "@babel/plugin-transform-modules-commonjs": "^7.24.8", + "@babel/plugin-transform-modules-systemjs": "^7.25.0", + "@babel/plugin-transform-modules-umd": "^7.24.7", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.24.7", + "@babel/plugin-transform-new-target": "^7.24.7", + "@babel/plugin-transform-nullish-coalescing-operator": "^7.24.7", + "@babel/plugin-transform-numeric-separator": "^7.24.7", + "@babel/plugin-transform-object-rest-spread": "^7.24.7", + "@babel/plugin-transform-object-super": "^7.24.7", + "@babel/plugin-transform-optional-catch-binding": "^7.24.7", + "@babel/plugin-transform-optional-chaining": "^7.24.8", + "@babel/plugin-transform-parameters": "^7.24.7", + "@babel/plugin-transform-private-methods": "^7.24.7", + "@babel/plugin-transform-private-property-in-object": "^7.24.7", + "@babel/plugin-transform-property-literals": "^7.24.7", + "@babel/plugin-transform-regenerator": "^7.24.7", + "@babel/plugin-transform-reserved-words": "^7.24.7", + "@babel/plugin-transform-shorthand-properties": "^7.24.7", + "@babel/plugin-transform-spread": "^7.24.7", + "@babel/plugin-transform-sticky-regex": "^7.24.7", + "@babel/plugin-transform-template-literals": "^7.24.7", + "@babel/plugin-transform-typeof-symbol": "^7.24.8", + "@babel/plugin-transform-unicode-escapes": "^7.24.7", + "@babel/plugin-transform-unicode-property-regex": "^7.24.7", + "@babel/plugin-transform-unicode-regex": "^7.24.7", + "@babel/plugin-transform-unicode-sets-regex": "^7.24.7", "@babel/preset-modules": "0.1.6-no-external-plugins", - "babel-plugin-polyfill-corejs2": "^0.4.8", - "babel-plugin-polyfill-corejs3": "^0.9.0", - "babel-plugin-polyfill-regenerator": "^0.5.5", - "core-js-compat": "^3.31.0", + "babel-plugin-polyfill-corejs2": "^0.4.10", + "babel-plugin-polyfill-corejs3": "^0.10.4", + "babel-plugin-polyfill-regenerator": "^0.6.1", + "core-js-compat": "^3.37.1", "semver": "^6.3.1" }, "engines": { @@ -2632,9 +2367,9 @@ "dev": true }, "node_modules/@babel/runtime": { - "version": "7.24.0", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.24.0.tgz", - "integrity": "sha512-Chk32uHMg6TnQdvw2e9IlqPpFX/6NLuK0Ys2PqLb7/gL5uFn9mXvK715FGLlOLQrcO4qIkNHkvPGktzzXexsFw==", + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.25.0.tgz", + "integrity": "sha512-7dRy4DwXwtzBrPbZflqxnvfxLF8kdZXPkhymtDeFoFqE6ldzjQFgYTtYIFARcLEYDrqfBfYcZt1WqFxRoyC9Rw==", "dev": true, "dependencies": { "regenerator-runtime": "^0.14.0" @@ -2644,33 +2379,30 @@ } }, "node_modules/@babel/template": { - "version": "7.24.0", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.24.0.tgz", - "integrity": "sha512-Bkf2q8lMB0AFpX0NFEqSbx1OkTHf0f+0j82mkw+ZpzBnkk7e9Ql0891vlfgi+kHwOk8tQjiQHpqh4LaSa0fKEA==", + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.25.0.tgz", + "integrity": "sha512-aOOgh1/5XzKvg1jvVz7AVrx2piJ2XBi227DHmbY6y+bM9H2FlN+IfecYu4Xl0cNiiVejlsCri89LUsbj8vJD9Q==", "dev": true, "dependencies": { - "@babel/code-frame": "^7.23.5", - "@babel/parser": "^7.24.0", - "@babel/types": "^7.24.0" + "@babel/code-frame": "^7.24.7", + "@babel/parser": "^7.25.0", + "@babel/types": "^7.25.0" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/traverse": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.24.5.tgz", - "integrity": "sha512-7aaBLeDQ4zYcUFDUD41lJc1fG8+5IU9DaNSJAgal866FGvmD5EbWQgnEC6kO1gGLsX0esNkfnJSndbTXA3r7UA==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.24.2", - "@babel/generator": "^7.24.5", - "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-function-name": "^7.23.0", - "@babel/helper-hoist-variables": "^7.22.5", - "@babel/helper-split-export-declaration": "^7.24.5", - "@babel/parser": "^7.24.5", - "@babel/types": "^7.24.5", + "version": "7.25.6", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.25.6.tgz", + "integrity": "sha512-9Vrcx5ZW6UwK5tvqsj0nGpp/XzqthkT0dqIc9g1AdtygFToNtTF67XzYS//dm+SAK9cp3B9R4ZO/46p63SCjlQ==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.24.7", + "@babel/generator": "^7.25.6", + "@babel/parser": "^7.25.6", + "@babel/template": "^7.25.0", + "@babel/types": "^7.25.6", "debug": "^4.3.1", "globals": "^11.1.0" }, @@ -2679,12 +2411,12 @@ } }, "node_modules/@babel/traverse/node_modules/@babel/generator": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.24.5.tgz", - "integrity": "sha512-x32i4hEXvr+iI0NEoEfDKzlemF8AmtOP8CcrRaEcpzysWuoEb1KknpcvMsHKPONoKZiDuItklgWhB18xEhr9PA==", + "version": "7.25.6", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.25.6.tgz", + "integrity": "sha512-VPC82gr1seXOpkjAAKoLhP50vx4vGNlF4msF64dSFq1P8RfB+QAuJWGHPXXPc8QyfVWwwB/TNNU4+ayZmHNbZw==", "dev": true, "dependencies": { - "@babel/types": "^7.24.5", + "@babel/types": "^7.25.6", "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.25", "jsesc": "^2.5.1" @@ -2693,26 +2425,14 @@ "node": ">=6.9.0" } }, - "node_modules/@babel/traverse/node_modules/@babel/helper-split-export-declaration": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.24.5.tgz", - "integrity": "sha512-5CHncttXohrHk8GWOFCcCl4oRD9fKosWlIRgWm4ql9VYioKm52Mk2xsmoohvm7f3JoiLSM5ZgJuRaf5QZZYd3Q==", - "dev": true, - "dependencies": { - "@babel/types": "^7.24.5" - }, - "engines": { - "node": ">=6.9.0" - } - }, "node_modules/@babel/types": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.24.5.tgz", - "integrity": "sha512-6mQNsaLeXTw0nxYUYu+NSa4Hx4BlF1x1x8/PMFbiR+GBSr+2DkECc69b8hgy2frEodNcvPffeH8YfWd3LI6jhQ==", + "version": "7.25.6", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.25.6.tgz", + "integrity": "sha512-/l42B1qxpG6RdfYf343Uw1vmDjeNhneUXtzhojE7pDgfpEypmRhI6j1kr17XCVv4Cgl9HdAiQY2x0GwKm7rWCw==", "dev": true, "dependencies": { - "@babel/helper-string-parser": "^7.24.1", - "@babel/helper-validator-identifier": "^7.24.5", + "@babel/helper-string-parser": "^7.24.8", + "@babel/helper-validator-identifier": "^7.24.7", "to-fast-properties": "^2.0.0" }, "engines": { @@ -2729,18 +2449,18 @@ } }, "node_modules/@discoveryjs/json-ext": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz", - "integrity": "sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==", + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.6.1.tgz", + "integrity": "sha512-boghen8F0Q8D+0/Q1/1r6DUEieUJ8w2a1gIknExMSHBsJFOr2+0KUfHiVYBvucPwl3+RU5PFBK833FjFCh3BhA==", "dev": true, "engines": { - "node": ">=10.0.0" + "node": ">=14.17.0" } }, "node_modules/@esbuild/aix-ppc64": { - "version": "0.20.1", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.20.1.tgz", - "integrity": "sha512-m55cpeupQ2DbuRGQMMZDzbv9J9PgVelPjlcmM5kxHnrBdBx6REaEd7LamYV7Dm8N7rCyR/XwU6rVP8ploKtIkA==", + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.23.0.tgz", + "integrity": "sha512-3sG8Zwa5fMcA9bgqB8AfWPQ+HFke6uD3h1s3RIwUNK8EG7a4buxvuFTs3j1IMs2NXAk9F30C/FF4vxRgQCcmoQ==", "cpu": [ "ppc64" ], @@ -2750,13 +2470,13 @@ "aix" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/android-arm": { - "version": "0.20.1", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.20.1.tgz", - "integrity": "sha512-4j0+G27/2ZXGWR5okcJi7pQYhmkVgb4D7UKwxcqrjhvp5TKWx3cUjgB1CGj1mfdmJBQ9VnUGgUhign+FPF2Zgw==", + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.23.0.tgz", + "integrity": "sha512-+KuOHTKKyIKgEEqKbGTK8W7mPp+hKinbMBeEnNzjJGyFcWsfrXjSTNluJHCY1RqhxFurdD8uNXQDei7qDlR6+g==", "cpu": [ "arm" ], @@ -2766,13 +2486,13 @@ "android" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/android-arm64": { - "version": "0.20.1", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.20.1.tgz", - "integrity": "sha512-hCnXNF0HM6AjowP+Zou0ZJMWWa1VkD77BXe959zERgGJBBxB+sV+J9f/rcjeg2c5bsukD/n17RKWXGFCO5dD5A==", + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.23.0.tgz", + "integrity": "sha512-EuHFUYkAVfU4qBdyivULuu03FhJO4IJN9PGuABGrFy4vUuzk91P2d+npxHcFdpUnfYKy0PuV+n6bKIpHOB3prQ==", "cpu": [ "arm64" ], @@ -2782,13 +2502,13 @@ "android" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/android-x64": { - "version": "0.20.1", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.20.1.tgz", - "integrity": "sha512-MSfZMBoAsnhpS+2yMFYIQUPs8Z19ajwfuaSZx+tSl09xrHZCjbeXXMsUF/0oq7ojxYEpsSo4c0SfjxOYXRbpaA==", + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.23.0.tgz", + "integrity": "sha512-WRrmKidLoKDl56LsbBMhzTTBxrsVwTKdNbKDalbEZr0tcsBgCLbEtoNthOW6PX942YiYq8HzEnb4yWQMLQuipQ==", "cpu": [ "x64" ], @@ -2798,13 +2518,13 @@ "android" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/darwin-arm64": { - "version": "0.20.1", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.20.1.tgz", - "integrity": "sha512-Ylk6rzgMD8klUklGPzS414UQLa5NPXZD5tf8JmQU8GQrj6BrFA/Ic9tb2zRe1kOZyCbGl+e8VMbDRazCEBqPvA==", + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.23.0.tgz", + "integrity": "sha512-YLntie/IdS31H54Ogdn+v50NuoWF5BDkEUFpiOChVa9UnKpftgwzZRrI4J132ETIi+D8n6xh9IviFV3eXdxfow==", "cpu": [ "arm64" ], @@ -2814,13 +2534,13 @@ "darwin" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/darwin-x64": { - "version": "0.20.1", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.20.1.tgz", - "integrity": "sha512-pFIfj7U2w5sMp52wTY1XVOdoxw+GDwy9FsK3OFz4BpMAjvZVs0dT1VXs8aQm22nhwoIWUmIRaE+4xow8xfIDZA==", + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.23.0.tgz", + "integrity": "sha512-IMQ6eme4AfznElesHUPDZ+teuGwoRmVuuixu7sv92ZkdQcPbsNHzutd+rAfaBKo8YK3IrBEi9SLLKWJdEvJniQ==", "cpu": [ "x64" ], @@ -2830,13 +2550,13 @@ "darwin" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/freebsd-arm64": { - "version": "0.20.1", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.20.1.tgz", - "integrity": "sha512-UyW1WZvHDuM4xDz0jWun4qtQFauNdXjXOtIy7SYdf7pbxSWWVlqhnR/T2TpX6LX5NI62spt0a3ldIIEkPM6RHw==", + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.23.0.tgz", + "integrity": "sha512-0muYWCng5vqaxobq6LB3YNtevDFSAZGlgtLoAc81PjUfiFz36n4KMpwhtAd4he8ToSI3TGyuhyx5xmiWNYZFyw==", "cpu": [ "arm64" ], @@ -2846,13 +2566,13 @@ "freebsd" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/freebsd-x64": { - "version": "0.20.1", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.20.1.tgz", - "integrity": "sha512-itPwCw5C+Jh/c624vcDd9kRCCZVpzpQn8dtwoYIt2TJF3S9xJLiRohnnNrKwREvcZYx0n8sCSbvGH349XkcQeg==", + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.23.0.tgz", + "integrity": "sha512-XKDVu8IsD0/q3foBzsXGt/KjD/yTKBCIwOHE1XwiXmrRwrX6Hbnd5Eqn/WvDekddK21tfszBSrE/WMaZh+1buQ==", "cpu": [ "x64" ], @@ -2862,13 +2582,13 @@ "freebsd" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-arm": { - "version": "0.20.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.20.1.tgz", - "integrity": "sha512-LojC28v3+IhIbfQ+Vu4Ut5n3wKcgTu6POKIHN9Wpt0HnfgUGlBuyDDQR4jWZUZFyYLiz4RBBBmfU6sNfn6RhLw==", + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.23.0.tgz", + "integrity": "sha512-SEELSTEtOFu5LPykzA395Mc+54RMg1EUgXP+iw2SJ72+ooMwVsgfuwXo5Fn0wXNgWZsTVHwY2cg4Vi/bOD88qw==", "cpu": [ "arm" ], @@ -2878,13 +2598,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-arm64": { - "version": "0.20.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.20.1.tgz", - "integrity": "sha512-cX8WdlF6Cnvw/DO9/X7XLH2J6CkBnz7Twjpk56cshk9sjYVcuh4sXQBy5bmTwzBjNVZze2yaV1vtcJS04LbN8w==", + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.23.0.tgz", + "integrity": "sha512-j1t5iG8jE7BhonbsEg5d9qOYcVZv/Rv6tghaXM/Ug9xahM0nX/H2gfu6X6z11QRTMT6+aywOMA8TDkhPo8aCGw==", "cpu": [ "arm64" ], @@ -2894,13 +2614,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-ia32": { - "version": "0.20.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.20.1.tgz", - "integrity": "sha512-4H/sQCy1mnnGkUt/xszaLlYJVTz3W9ep52xEefGtd6yXDQbz/5fZE5dFLUgsPdbUOQANcVUa5iO6g3nyy5BJiw==", + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.23.0.tgz", + "integrity": "sha512-P7O5Tkh2NbgIm2R6x1zGJJsnacDzTFcRWZyTTMgFdVit6E98LTxO+v8LCCLWRvPrjdzXHx9FEOA8oAZPyApWUA==", "cpu": [ "ia32" ], @@ -2910,13 +2630,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-loong64": { - "version": "0.20.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.20.1.tgz", - "integrity": "sha512-c0jgtB+sRHCciVXlyjDcWb2FUuzlGVRwGXgI+3WqKOIuoo8AmZAddzeOHeYLtD+dmtHw3B4Xo9wAUdjlfW5yYA==", + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.23.0.tgz", + "integrity": "sha512-InQwepswq6urikQiIC/kkx412fqUZudBO4SYKu0N+tGhXRWUqAx+Q+341tFV6QdBifpjYgUndV1hhMq3WeJi7A==", "cpu": [ "loong64" ], @@ -2926,13 +2646,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-mips64el": { - "version": "0.20.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.20.1.tgz", - "integrity": "sha512-TgFyCfIxSujyuqdZKDZ3yTwWiGv+KnlOeXXitCQ+trDODJ+ZtGOzLkSWngynP0HZnTsDyBbPy7GWVXWaEl6lhA==", + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.23.0.tgz", + "integrity": "sha512-J9rflLtqdYrxHv2FqXE2i1ELgNjT+JFURt/uDMoPQLcjWQA5wDKgQA4t/dTqGa88ZVECKaD0TctwsUfHbVoi4w==", "cpu": [ "mips64el" ], @@ -2942,13 +2662,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-ppc64": { - "version": "0.20.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.20.1.tgz", - "integrity": "sha512-b+yuD1IUeL+Y93PmFZDZFIElwbmFfIKLKlYI8M6tRyzE6u7oEP7onGk0vZRh8wfVGC2dZoy0EqX1V8qok4qHaw==", + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.23.0.tgz", + "integrity": "sha512-cShCXtEOVc5GxU0fM+dsFD10qZ5UpcQ8AM22bYj0u/yaAykWnqXJDpd77ublcX6vdDsWLuweeuSNZk4yUxZwtw==", "cpu": [ "ppc64" ], @@ -2958,13 +2678,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-riscv64": { - "version": "0.20.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.20.1.tgz", - "integrity": "sha512-wpDlpE0oRKZwX+GfomcALcouqjjV8MIX8DyTrxfyCfXxoKQSDm45CZr9fanJ4F6ckD4yDEPT98SrjvLwIqUCgg==", + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.23.0.tgz", + "integrity": "sha512-HEtaN7Y5UB4tZPeQmgz/UhzoEyYftbMXrBCUjINGjh3uil+rB/QzzpMshz3cNUxqXN7Vr93zzVtpIDL99t9aRw==", "cpu": [ "riscv64" ], @@ -2974,13 +2694,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-s390x": { - "version": "0.20.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.20.1.tgz", - "integrity": "sha512-5BepC2Au80EohQ2dBpyTquqGCES7++p7G+7lXe1bAIvMdXm4YYcEfZtQrP4gaoZ96Wv1Ute61CEHFU7h4FMueQ==", + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.23.0.tgz", + "integrity": "sha512-WDi3+NVAuyjg/Wxi+o5KPqRbZY0QhI9TjrEEm+8dmpY9Xir8+HE/HNx2JoLckhKbFopW0RdO2D72w8trZOV+Wg==", "cpu": [ "s390x" ], @@ -2990,13 +2710,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-x64": { - "version": "0.20.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.20.1.tgz", - "integrity": "sha512-5gRPk7pKuaIB+tmH+yKd2aQTRpqlf1E4f/mC+tawIm/CGJemZcHZpp2ic8oD83nKgUPMEd0fNanrnFljiruuyA==", + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.23.0.tgz", + "integrity": "sha512-a3pMQhUEJkITgAw6e0bWA+F+vFtCciMjW/LPtoj99MhVt+Mfb6bbL9hu2wmTZgNd994qTAEw+U/r6k3qHWWaOQ==", "cpu": [ "x64" ], @@ -3006,13 +2726,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/netbsd-x64": { - "version": "0.20.1", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.20.1.tgz", - "integrity": "sha512-4fL68JdrLV2nVW2AaWZBv3XEm3Ae3NZn/7qy2KGAt3dexAgSVT+Hc97JKSZnqezgMlv9x6KV0ZkZY7UO5cNLCg==", + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.23.0.tgz", + "integrity": "sha512-cRK+YDem7lFTs2Q5nEv/HHc4LnrfBCbH5+JHu6wm2eP+d8OZNoSMYgPZJq78vqQ9g+9+nMuIsAO7skzphRXHyw==", "cpu": [ "x64" ], @@ -3022,13 +2742,29 @@ "netbsd" ], "engines": { - "node": ">=12" + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.23.0.tgz", + "integrity": "sha512-suXjq53gERueVWu0OKxzWqk7NxiUWSUlrxoZK7usiF50C6ipColGR5qie2496iKGYNLhDZkPxBI3erbnYkU0rQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" } }, "node_modules/@esbuild/openbsd-x64": { - "version": "0.20.1", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.20.1.tgz", - "integrity": "sha512-GhRuXlvRE+twf2ES+8REbeCb/zeikNqwD3+6S5y5/x+DYbAQUNl0HNBs4RQJqrechS4v4MruEr8ZtAin/hK5iw==", + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.23.0.tgz", + "integrity": "sha512-6p3nHpby0DM/v15IFKMjAaayFhqnXV52aEmv1whZHX56pdkK+MEaLoQWj+H42ssFarP1PcomVhbsR4pkz09qBg==", "cpu": [ "x64" ], @@ -3038,13 +2774,13 @@ "openbsd" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/sunos-x64": { - "version": "0.20.1", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.20.1.tgz", - "integrity": "sha512-ZnWEyCM0G1Ex6JtsygvC3KUUrlDXqOihw8RicRuQAzw+c4f1D66YlPNNV3rkjVW90zXVsHwZYWbJh3v+oQFM9Q==", + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.23.0.tgz", + "integrity": "sha512-BFelBGfrBwk6LVrmFzCq1u1dZbG4zy/Kp93w2+y83Q5UGYF1d8sCzeLI9NXjKyujjBBniQa8R8PzLFAUrSM9OA==", "cpu": [ "x64" ], @@ -3054,13 +2790,13 @@ "sunos" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/win32-arm64": { - "version": "0.20.1", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.20.1.tgz", - "integrity": "sha512-QZ6gXue0vVQY2Oon9WyLFCdSuYbXSoxaZrPuJ4c20j6ICedfsDilNPYfHLlMH7vGfU5DQR0czHLmJvH4Nzis/A==", + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.23.0.tgz", + "integrity": "sha512-lY6AC8p4Cnb7xYHuIxQ6iYPe6MfO2CC43XXKo9nBXDb35krYt7KGhQnOkRGar5psxYkircpCqfbNDB4uJbS2jQ==", "cpu": [ "arm64" ], @@ -3070,13 +2806,13 @@ "win32" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/win32-ia32": { - "version": "0.20.1", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.20.1.tgz", - "integrity": "sha512-HzcJa1NcSWTAU0MJIxOho8JftNp9YALui3o+Ny7hCh0v5f90nprly1U3Sj1Ldj/CvKKdvvFsCRvDkpsEMp4DNw==", + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.23.0.tgz", + "integrity": "sha512-7L1bHlOTcO4ByvI7OXVI5pNN6HSu6pUQq9yodga8izeuB1KcT2UkHaH6118QJwopExPn0rMHIseCTx1CRo/uNA==", "cpu": [ "ia32" ], @@ -3086,13 +2822,13 @@ "win32" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/win32-x64": { - "version": "0.20.1", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.20.1.tgz", - "integrity": "sha512-0MBh53o6XtI6ctDnRMeQ+xoCN8kD2qI1rY1KgF/xdWQwoFeKou7puvDfV8/Wv4Ctx2rRpET/gGdz3YlNtNACSA==", + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.23.0.tgz", + "integrity": "sha512-Arm+WgUFLUATuoxCJcahGuk6Yj9Pzxd6l11Zb/2aAuv5kWWvvfhLFo2fni4uSK5vzlUdCGZ/BdV5tH8klj8p8g==", "cpu": [ "x64" ], @@ -3102,7 +2838,7 @@ "win32" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@eslint-community/eslint-utils": { @@ -3121,9 +2857,9 @@ } }, "node_modules/@eslint-community/regexpp": { - "version": "4.10.0", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.10.0.tgz", - "integrity": "sha512-Cu96Sd2By9mCNTx2iyKOmq10v22jUVQv0lQnlGNy16oE9589yE+QADPbrMGCkA51cKZSg3Pu/aTJVTGfL/qjUA==", + "version": "4.11.1", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.11.1.tgz", + "integrity": "sha512-m4DVN9ZqskZoLU5GlWZadwDnYo3vAEydiUayB9widCl9ffWx2IvPnp6n3on5rJmziJSw9Bv+Z3ChDVdMwXCY8Q==", "dev": true, "engines": { "node": "^12.0.0 || ^14.0.0 || >=16.0.0" @@ -3168,12 +2904,6 @@ "url": "https://github.com/sponsors/epoberezkin" } }, - "node_modules/@eslint/eslintrc/node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true - }, "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -3199,18 +2929,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@eslint/eslintrc/node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dev": true, - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, "node_modules/@eslint/eslintrc/node_modules/json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", @@ -3242,21 +2960,22 @@ } }, "node_modules/@eslint/js": { - "version": "8.57.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.0.tgz", - "integrity": "sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g==", + "version": "8.57.1", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.1.tgz", + "integrity": "sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==", "dev": true, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, "node_modules/@humanwhocodes/config-array": { - "version": "0.11.14", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz", - "integrity": "sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==", + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz", + "integrity": "sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==", + "deprecated": "Use @eslint/config-array instead", "dev": true, "dependencies": { - "@humanwhocodes/object-schema": "^2.0.2", + "@humanwhocodes/object-schema": "^2.0.3", "debug": "^4.3.1", "minimatch": "^3.0.5" }, @@ -3303,1000 +3022,653 @@ "version": "2.0.3", "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", + "deprecated": "Use @eslint/object-schema instead", "dev": true }, - "node_modules/@isaacs/cliui": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", - "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "node_modules/@inquirer/checkbox": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@inquirer/checkbox/-/checkbox-2.5.0.tgz", + "integrity": "sha512-sMgdETOfi2dUHT8r7TT1BTKOwNvdDGFDXYWtQ2J69SvlYNntk9I/gJe7r5yvMwwsuKnYbuRs3pNhx4tgNck5aA==", "dev": true, "dependencies": { - "string-width": "^5.1.2", - "string-width-cjs": "npm:string-width@^4.2.0", - "strip-ansi": "^7.0.1", - "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", - "wrap-ansi": "^8.1.0", - "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + "@inquirer/core": "^9.1.0", + "@inquirer/figures": "^1.0.5", + "@inquirer/type": "^1.5.3", + "ansi-escapes": "^4.3.2", + "yoctocolors-cjs": "^2.1.2" }, "engines": { - "node": ">=12" + "node": ">=18" } }, - "node_modules/@isaacs/cliui/node_modules/ansi-regex": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", - "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "node_modules/@inquirer/confirm": { + "version": "3.1.22", + "resolved": "https://registry.npmjs.org/@inquirer/confirm/-/confirm-3.1.22.tgz", + "integrity": "sha512-gsAKIOWBm2Q87CDfs9fEo7wJT3fwWIJfnDGMn9Qy74gBnNFOACDNfhUzovubbJjWnKLGBln7/NcSmZwj5DuEXg==", "dev": true, - "engines": { - "node": ">=12" + "dependencies": { + "@inquirer/core": "^9.0.10", + "@inquirer/type": "^1.5.2" }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, - "node_modules/@isaacs/cliui/node_modules/ansi-styles": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", - "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", - "dev": true, "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "node": ">=18" } }, - "node_modules/@isaacs/cliui/node_modules/emoji-regex": { - "version": "9.2.2", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", - "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", - "dev": true - }, - "node_modules/@isaacs/cliui/node_modules/string-width": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", - "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "node_modules/@inquirer/core": { + "version": "9.2.1", + "resolved": "https://registry.npmjs.org/@inquirer/core/-/core-9.2.1.tgz", + "integrity": "sha512-F2VBt7W/mwqEU4bL0RnHNZmC/OxzNx9cOYxHqnXX3MP6ruYvZUZAW9imgN9+h/uBT/oP8Gh888J2OZSbjSeWcg==", "dev": true, "dependencies": { - "eastasianwidth": "^0.2.0", - "emoji-regex": "^9.2.2", - "strip-ansi": "^7.0.1" + "@inquirer/figures": "^1.0.6", + "@inquirer/type": "^2.0.0", + "@types/mute-stream": "^0.0.4", + "@types/node": "^22.5.5", + "@types/wrap-ansi": "^3.0.0", + "ansi-escapes": "^4.3.2", + "cli-width": "^4.1.0", + "mute-stream": "^1.0.0", + "signal-exit": "^4.1.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^6.2.0", + "yoctocolors-cjs": "^2.1.2" }, "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=18" } }, - "node_modules/@isaacs/cliui/node_modules/strip-ansi": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", - "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "node_modules/@inquirer/core/node_modules/@inquirer/type": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@inquirer/type/-/type-2.0.0.tgz", + "integrity": "sha512-XvJRx+2KR3YXyYtPUUy+qd9i7p+GO9Ko6VIIpWlBrpWwXDv8WLFeHTxz35CfQFUiBMLXlGHhGzys7lqit9gWag==", "dev": true, "dependencies": { - "ansi-regex": "^6.0.1" + "mute-stream": "^1.0.0" }, "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" + "node": ">=18" } }, - "node_modules/@isaacs/cliui/node_modules/wrap-ansi": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", - "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "node_modules/@inquirer/editor": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@inquirer/editor/-/editor-2.2.0.tgz", + "integrity": "sha512-9KHOpJ+dIL5SZli8lJ6xdaYLPPzB8xB9GZItg39MBybzhxA16vxmszmQFrRwbOA918WA2rvu8xhDEg/p6LXKbw==", "dev": true, "dependencies": { - "ansi-styles": "^6.1.0", - "string-width": "^5.0.1", - "strip-ansi": "^7.0.1" + "@inquirer/core": "^9.1.0", + "@inquirer/type": "^1.5.3", + "external-editor": "^3.1.0" }, "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + "node": ">=18" } }, - "node_modules/@istanbuljs/load-nyc-config": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", - "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", + "node_modules/@inquirer/expand": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@inquirer/expand/-/expand-2.3.0.tgz", + "integrity": "sha512-qnJsUcOGCSG1e5DTOErmv2BPQqrtT6uzqn1vI/aYGiPKq+FgslGZmtdnXbhuI7IlT7OByDoEEqdnhUnVR2hhLw==", "dev": true, "dependencies": { - "camelcase": "^5.3.1", - "find-up": "^4.1.0", - "get-package-type": "^0.1.0", - "js-yaml": "^3.13.1", - "resolve-from": "^5.0.0" + "@inquirer/core": "^9.1.0", + "@inquirer/type": "^1.5.3", + "yoctocolors-cjs": "^2.1.2" }, "engines": { - "node": ">=8" + "node": ">=18" } }, - "node_modules/@istanbuljs/schema": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", - "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "node_modules/@inquirer/figures": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@inquirer/figures/-/figures-1.0.6.tgz", + "integrity": "sha512-yfZzps3Cso2UbM7WlxKwZQh2Hs6plrbjs1QnzQDZhK2DgyCo6D8AaHps9olkNcUFlcYERMqU3uJSp1gmy3s/qQ==", "dev": true, "engines": { - "node": ">=8" + "node": ">=18" } }, - "node_modules/@jest/schemas": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", - "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", + "node_modules/@inquirer/input": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@inquirer/input/-/input-2.3.0.tgz", + "integrity": "sha512-XfnpCStx2xgh1LIRqPXrTNEEByqQWoxsWYzNRSEUxJ5c6EQlhMogJ3vHKu8aXuTacebtaZzMAHwEL0kAflKOBw==", "dev": true, "dependencies": { - "@sinclair/typebox": "^0.27.8" + "@inquirer/core": "^9.1.0", + "@inquirer/type": "^1.5.3" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">=18" } }, - "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", - "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==", + "node_modules/@inquirer/number": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@inquirer/number/-/number-1.1.0.tgz", + "integrity": "sha512-ilUnia/GZUtfSZy3YEErXLJ2Sljo/mf9fiKc08n18DdwdmDbOzRcTv65H1jjDvlsAuvdFXf4Sa/aL7iw/NanVA==", "dev": true, "dependencies": { - "@jridgewell/set-array": "^1.2.1", - "@jridgewell/sourcemap-codec": "^1.4.10", - "@jridgewell/trace-mapping": "^0.3.24" + "@inquirer/core": "^9.1.0", + "@inquirer/type": "^1.5.3" }, "engines": { - "node": ">=6.0.0" + "node": ">=18" } }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", - "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "node_modules/@inquirer/password": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@inquirer/password/-/password-2.2.0.tgz", + "integrity": "sha512-5otqIpgsPYIshqhgtEwSspBQE40etouR8VIxzpJkv9i0dVHIpyhiivbkH9/dGiMLdyamT54YRdGJLfl8TFnLHg==", "dev": true, + "dependencies": { + "@inquirer/core": "^9.1.0", + "@inquirer/type": "^1.5.3", + "ansi-escapes": "^4.3.2" + }, "engines": { - "node": ">=6.0.0" + "node": ">=18" } }, - "node_modules/@jridgewell/set-array": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", - "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", + "node_modules/@inquirer/prompts": { + "version": "5.3.8", + "resolved": "https://registry.npmjs.org/@inquirer/prompts/-/prompts-5.3.8.tgz", + "integrity": "sha512-b2BudQY/Si4Y2a0PdZZL6BeJtl8llgeZa7U2j47aaJSCeAl1e4UI7y8a9bSkO3o/ZbZrgT5muy/34JbsjfIWxA==", "dev": true, + "dependencies": { + "@inquirer/checkbox": "^2.4.7", + "@inquirer/confirm": "^3.1.22", + "@inquirer/editor": "^2.1.22", + "@inquirer/expand": "^2.1.22", + "@inquirer/input": "^2.2.9", + "@inquirer/number": "^1.0.10", + "@inquirer/password": "^2.1.22", + "@inquirer/rawlist": "^2.2.4", + "@inquirer/search": "^1.0.7", + "@inquirer/select": "^2.4.7" + }, "engines": { - "node": ">=6.0.0" + "node": ">=18" } }, - "node_modules/@jridgewell/source-map": { - "version": "0.3.6", - "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.6.tgz", - "integrity": "sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==", + "node_modules/@inquirer/rawlist": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@inquirer/rawlist/-/rawlist-2.3.0.tgz", + "integrity": "sha512-zzfNuINhFF7OLAtGHfhwOW2TlYJyli7lOUoJUXw/uyklcwalV6WRXBXtFIicN8rTRK1XTiPWB4UY+YuW8dsnLQ==", "dev": true, "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.25" + "@inquirer/core": "^9.1.0", + "@inquirer/type": "^1.5.3", + "yoctocolors-cjs": "^2.1.2" + }, + "engines": { + "node": ">=18" } }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.4.15", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", - "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", - "dev": true - }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.25", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", - "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "node_modules/@inquirer/search": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@inquirer/search/-/search-1.1.0.tgz", + "integrity": "sha512-h+/5LSj51dx7hp5xOn4QFnUaKeARwUCLs6mIhtkJ0JYPBLmEYjdHSYh7I6GrLg9LwpJ3xeX0FZgAG1q0QdCpVQ==", "dev": true, "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" + "@inquirer/core": "^9.1.0", + "@inquirer/figures": "^1.0.5", + "@inquirer/type": "^1.5.3", + "yoctocolors-cjs": "^2.1.2" + }, + "engines": { + "node": ">=18" } }, - "node_modules/@leichtgewicht/ip-codec": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.5.tgz", - "integrity": "sha512-Vo+PSpZG2/fmgmiNzYK9qWRh8h/CHrwD0mo1h1DzL4yzHNSfWYujGTYsWGreD000gcgmZ7K4Ys6Tx9TxtsKdDw==", - "dev": true - }, - "node_modules/@ljharb/through": { - "version": "2.3.13", - "resolved": "https://registry.npmjs.org/@ljharb/through/-/through-2.3.13.tgz", - "integrity": "sha512-/gKJun8NNiWGZJkGzI/Ragc53cOdcLNdzjLaIa+GEjguQs0ulsurx8WN0jijdK9yPqDvziX995sMRLyLt1uZMQ==", + "node_modules/@inquirer/select": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@inquirer/select/-/select-2.5.0.tgz", + "integrity": "sha512-YmDobTItPP3WcEI86GvPo+T2sRHkxxOq/kXmsBjHS5BVXUgvgZ5AfJjkvQvZr03T81NnI3KrrRuMzeuYUQRFOA==", "dev": true, "dependencies": { - "call-bind": "^1.0.7" + "@inquirer/core": "^9.1.0", + "@inquirer/figures": "^1.0.5", + "@inquirer/type": "^1.5.3", + "ansi-escapes": "^4.3.2", + "yoctocolors-cjs": "^2.1.2" }, "engines": { - "node": ">= 0.4" - } - }, - "node_modules/@material/animation": { - "version": "15.0.0-canary.7f224ddd4.0", - "resolved": "https://registry.npmjs.org/@material/animation/-/animation-15.0.0-canary.7f224ddd4.0.tgz", - "integrity": "sha512-1GSJaPKef+7HRuV+HusVZHps64cmZuOItDbt40tjJVaikcaZvwmHlcTxRIqzcRoCdt5ZKHh3NoO7GB9Khg4Jnw==", - "dependencies": { - "tslib": "^2.1.0" - } - }, - "node_modules/@material/auto-init": { - "version": "15.0.0-canary.7f224ddd4.0", - "resolved": "https://registry.npmjs.org/@material/auto-init/-/auto-init-15.0.0-canary.7f224ddd4.0.tgz", - "integrity": "sha512-t7ZGpRJ3ec0QDUO0nJu/SMgLW7qcuG2KqIsEYD1Ej8qhI2xpdR2ydSDQOkVEitXmKoGol1oq4nYSBjTlB65GqA==", - "dependencies": { - "@material/base": "15.0.0-canary.7f224ddd4.0", - "tslib": "^2.1.0" - } - }, - "node_modules/@material/banner": { - "version": "15.0.0-canary.7f224ddd4.0", - "resolved": "https://registry.npmjs.org/@material/banner/-/banner-15.0.0-canary.7f224ddd4.0.tgz", - "integrity": "sha512-g9wBUZzYBizyBcBQXTIafnRUUPi7efU9gPJfzeGgkynXiccP/vh5XMmH+PBxl5v+4MlP/d4cZ2NUYoAN7UTqSA==", - "dependencies": { - "@material/base": "15.0.0-canary.7f224ddd4.0", - "@material/button": "15.0.0-canary.7f224ddd4.0", - "@material/dom": "15.0.0-canary.7f224ddd4.0", - "@material/elevation": "15.0.0-canary.7f224ddd4.0", - "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", - "@material/ripple": "15.0.0-canary.7f224ddd4.0", - "@material/rtl": "15.0.0-canary.7f224ddd4.0", - "@material/shape": "15.0.0-canary.7f224ddd4.0", - "@material/theme": "15.0.0-canary.7f224ddd4.0", - "@material/tokens": "15.0.0-canary.7f224ddd4.0", - "@material/typography": "15.0.0-canary.7f224ddd4.0", - "tslib": "^2.1.0" - } - }, - "node_modules/@material/base": { - "version": "15.0.0-canary.7f224ddd4.0", - "resolved": "https://registry.npmjs.org/@material/base/-/base-15.0.0-canary.7f224ddd4.0.tgz", - "integrity": "sha512-I9KQOKXpLfJkP8MqZyr8wZIzdPHrwPjFvGd9zSK91/vPyE4hzHRJc/0njsh9g8Lm9PRYLbifXX+719uTbHxx+A==", - "dependencies": { - "tslib": "^2.1.0" - } - }, - "node_modules/@material/button": { - "version": "15.0.0-canary.7f224ddd4.0", - "resolved": "https://registry.npmjs.org/@material/button/-/button-15.0.0-canary.7f224ddd4.0.tgz", - "integrity": "sha512-BHB7iyHgRVH+JF16+iscR+Qaic+p7LU1FOLgP8KucRlpF9tTwIxQA6mJwGRi5gUtcG+vyCmzVS+hIQ6DqT/7BA==", - "dependencies": { - "@material/density": "15.0.0-canary.7f224ddd4.0", - "@material/dom": "15.0.0-canary.7f224ddd4.0", - "@material/elevation": "15.0.0-canary.7f224ddd4.0", - "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", - "@material/focus-ring": "15.0.0-canary.7f224ddd4.0", - "@material/ripple": "15.0.0-canary.7f224ddd4.0", - "@material/rtl": "15.0.0-canary.7f224ddd4.0", - "@material/shape": "15.0.0-canary.7f224ddd4.0", - "@material/theme": "15.0.0-canary.7f224ddd4.0", - "@material/tokens": "15.0.0-canary.7f224ddd4.0", - "@material/touch-target": "15.0.0-canary.7f224ddd4.0", - "@material/typography": "15.0.0-canary.7f224ddd4.0", - "tslib": "^2.1.0" - } - }, - "node_modules/@material/card": { - "version": "15.0.0-canary.7f224ddd4.0", - "resolved": "https://registry.npmjs.org/@material/card/-/card-15.0.0-canary.7f224ddd4.0.tgz", - "integrity": "sha512-kt7y9/IWOtJTr3Z/AoWJT3ZLN7CLlzXhx2udCLP9ootZU2bfGK0lzNwmo80bv/pJfrY9ihQKCtuGTtNxUy+vIw==", - "dependencies": { - "@material/dom": "15.0.0-canary.7f224ddd4.0", - "@material/elevation": "15.0.0-canary.7f224ddd4.0", - "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", - "@material/ripple": "15.0.0-canary.7f224ddd4.0", - "@material/rtl": "15.0.0-canary.7f224ddd4.0", - "@material/shape": "15.0.0-canary.7f224ddd4.0", - "@material/theme": "15.0.0-canary.7f224ddd4.0", - "@material/tokens": "15.0.0-canary.7f224ddd4.0", - "tslib": "^2.1.0" - } - }, - "node_modules/@material/checkbox": { - "version": "15.0.0-canary.7f224ddd4.0", - "resolved": "https://registry.npmjs.org/@material/checkbox/-/checkbox-15.0.0-canary.7f224ddd4.0.tgz", - "integrity": "sha512-rURcrL5O1u6hzWR+dNgiQ/n89vk6tdmdP3mZgnxJx61q4I/k1yijKqNJSLrkXH7Rto3bM5NRKMOlgvMvVd7UMQ==", - "dependencies": { - "@material/animation": "15.0.0-canary.7f224ddd4.0", - "@material/base": "15.0.0-canary.7f224ddd4.0", - "@material/density": "15.0.0-canary.7f224ddd4.0", - "@material/dom": "15.0.0-canary.7f224ddd4.0", - "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", - "@material/focus-ring": "15.0.0-canary.7f224ddd4.0", - "@material/ripple": "15.0.0-canary.7f224ddd4.0", - "@material/rtl": "15.0.0-canary.7f224ddd4.0", - "@material/theme": "15.0.0-canary.7f224ddd4.0", - "@material/touch-target": "15.0.0-canary.7f224ddd4.0", - "tslib": "^2.1.0" - } - }, - "node_modules/@material/chips": { - "version": "15.0.0-canary.7f224ddd4.0", - "resolved": "https://registry.npmjs.org/@material/chips/-/chips-15.0.0-canary.7f224ddd4.0.tgz", - "integrity": "sha512-AYAivV3GSk/T/nRIpH27sOHFPaSMrE3L0WYbnb5Wa93FgY8a0fbsFYtSH2QmtwnzXveg+B1zGTt7/xIIcynKdQ==", - "dependencies": { - "@material/animation": "15.0.0-canary.7f224ddd4.0", - "@material/base": "15.0.0-canary.7f224ddd4.0", - "@material/checkbox": "15.0.0-canary.7f224ddd4.0", - "@material/density": "15.0.0-canary.7f224ddd4.0", - "@material/dom": "15.0.0-canary.7f224ddd4.0", - "@material/elevation": "15.0.0-canary.7f224ddd4.0", - "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", - "@material/focus-ring": "15.0.0-canary.7f224ddd4.0", - "@material/ripple": "15.0.0-canary.7f224ddd4.0", - "@material/rtl": "15.0.0-canary.7f224ddd4.0", - "@material/shape": "15.0.0-canary.7f224ddd4.0", - "@material/theme": "15.0.0-canary.7f224ddd4.0", - "@material/tokens": "15.0.0-canary.7f224ddd4.0", - "@material/touch-target": "15.0.0-canary.7f224ddd4.0", - "@material/typography": "15.0.0-canary.7f224ddd4.0", - "safevalues": "^0.3.4", - "tslib": "^2.1.0" - } - }, - "node_modules/@material/circular-progress": { - "version": "15.0.0-canary.7f224ddd4.0", - "resolved": "https://registry.npmjs.org/@material/circular-progress/-/circular-progress-15.0.0-canary.7f224ddd4.0.tgz", - "integrity": "sha512-DJrqCKb+LuGtjNvKl8XigvyK02y36GRkfhMUYTcJEi3PrOE00bwXtyj7ilhzEVshQiXg6AHGWXtf5UqwNrx3Ow==", - "dependencies": { - "@material/animation": "15.0.0-canary.7f224ddd4.0", - "@material/base": "15.0.0-canary.7f224ddd4.0", - "@material/dom": "15.0.0-canary.7f224ddd4.0", - "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", - "@material/progress-indicator": "15.0.0-canary.7f224ddd4.0", - "@material/rtl": "15.0.0-canary.7f224ddd4.0", - "@material/theme": "15.0.0-canary.7f224ddd4.0", - "tslib": "^2.1.0" - } - }, - "node_modules/@material/data-table": { - "version": "15.0.0-canary.7f224ddd4.0", - "resolved": "https://registry.npmjs.org/@material/data-table/-/data-table-15.0.0-canary.7f224ddd4.0.tgz", - "integrity": "sha512-/2WZsuBIq9z9RWYF5Jo6b7P6u0fwit+29/mN7rmAZ6akqUR54nXyNfoSNiyydMkzPlZZsep5KrSHododDhBZbA==", - "dependencies": { - "@material/animation": "15.0.0-canary.7f224ddd4.0", - "@material/base": "15.0.0-canary.7f224ddd4.0", - "@material/checkbox": "15.0.0-canary.7f224ddd4.0", - "@material/density": "15.0.0-canary.7f224ddd4.0", - "@material/dom": "15.0.0-canary.7f224ddd4.0", - "@material/elevation": "15.0.0-canary.7f224ddd4.0", - "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", - "@material/icon-button": "15.0.0-canary.7f224ddd4.0", - "@material/linear-progress": "15.0.0-canary.7f224ddd4.0", - "@material/list": "15.0.0-canary.7f224ddd4.0", - "@material/menu": "15.0.0-canary.7f224ddd4.0", - "@material/rtl": "15.0.0-canary.7f224ddd4.0", - "@material/select": "15.0.0-canary.7f224ddd4.0", - "@material/shape": "15.0.0-canary.7f224ddd4.0", - "@material/theme": "15.0.0-canary.7f224ddd4.0", - "@material/tokens": "15.0.0-canary.7f224ddd4.0", - "@material/touch-target": "15.0.0-canary.7f224ddd4.0", - "@material/typography": "15.0.0-canary.7f224ddd4.0", - "tslib": "^2.1.0" - } - }, - "node_modules/@material/density": { - "version": "15.0.0-canary.7f224ddd4.0", - "resolved": "https://registry.npmjs.org/@material/density/-/density-15.0.0-canary.7f224ddd4.0.tgz", - "integrity": "sha512-o9EXmGKVpiQ6mHhyV3oDDzc78Ow3E7v8dlaOhgaDSXgmqaE8v5sIlLNa/LKSyUga83/fpGk3QViSGXotpQx0jA==", - "dependencies": { - "tslib": "^2.1.0" - } - }, - "node_modules/@material/dialog": { - "version": "15.0.0-canary.7f224ddd4.0", - "resolved": "https://registry.npmjs.org/@material/dialog/-/dialog-15.0.0-canary.7f224ddd4.0.tgz", - "integrity": "sha512-u0XpTlv1JqWC/bQ3DavJ1JguofTelLT2wloj59l3/1b60jv42JQ6Am7jU3I8/SIUB1MKaW7dYocXjDWtWJakLA==", - "dependencies": { - "@material/animation": "15.0.0-canary.7f224ddd4.0", - "@material/base": "15.0.0-canary.7f224ddd4.0", - "@material/button": "15.0.0-canary.7f224ddd4.0", - "@material/dom": "15.0.0-canary.7f224ddd4.0", - "@material/elevation": "15.0.0-canary.7f224ddd4.0", - "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", - "@material/icon-button": "15.0.0-canary.7f224ddd4.0", - "@material/ripple": "15.0.0-canary.7f224ddd4.0", - "@material/rtl": "15.0.0-canary.7f224ddd4.0", - "@material/shape": "15.0.0-canary.7f224ddd4.0", - "@material/theme": "15.0.0-canary.7f224ddd4.0", - "@material/tokens": "15.0.0-canary.7f224ddd4.0", - "@material/touch-target": "15.0.0-canary.7f224ddd4.0", - "@material/typography": "15.0.0-canary.7f224ddd4.0", - "tslib": "^2.1.0" + "node": ">=18" } }, - "node_modules/@material/dom": { - "version": "15.0.0-canary.7f224ddd4.0", - "resolved": "https://registry.npmjs.org/@material/dom/-/dom-15.0.0-canary.7f224ddd4.0.tgz", - "integrity": "sha512-mQ1HT186GPQSkRg5S18i70typ5ZytfjL09R0gJ2Qg5/G+MLCGi7TAjZZSH65tuD/QGOjel4rDdWOTmYbPYV6HA==", + "node_modules/@inquirer/type": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@inquirer/type/-/type-1.5.5.tgz", + "integrity": "sha512-MzICLu4yS7V8AA61sANROZ9vT1H3ooca5dSmI1FjZkzq7o/koMsRfQSzRtFo+F3Ao4Sf1C0bpLKejpKB/+j6MA==", + "dev": true, "dependencies": { - "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", - "@material/rtl": "15.0.0-canary.7f224ddd4.0", - "tslib": "^2.1.0" - } - }, - "node_modules/@material/drawer": { - "version": "15.0.0-canary.7f224ddd4.0", - "resolved": "https://registry.npmjs.org/@material/drawer/-/drawer-15.0.0-canary.7f224ddd4.0.tgz", - "integrity": "sha512-qyO0W0KBftfH8dlLR0gVAgv7ZHNvU8ae11Ao6zJif/YxcvK4+gph1z8AO4H410YmC2kZiwpSKyxM1iQCCzbb4g==", - "dependencies": { - "@material/animation": "15.0.0-canary.7f224ddd4.0", - "@material/base": "15.0.0-canary.7f224ddd4.0", - "@material/dom": "15.0.0-canary.7f224ddd4.0", - "@material/elevation": "15.0.0-canary.7f224ddd4.0", - "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", - "@material/list": "15.0.0-canary.7f224ddd4.0", - "@material/ripple": "15.0.0-canary.7f224ddd4.0", - "@material/rtl": "15.0.0-canary.7f224ddd4.0", - "@material/shape": "15.0.0-canary.7f224ddd4.0", - "@material/theme": "15.0.0-canary.7f224ddd4.0", - "@material/typography": "15.0.0-canary.7f224ddd4.0", - "tslib": "^2.1.0" + "mute-stream": "^1.0.0" + }, + "engines": { + "node": ">=18" } }, - "node_modules/@material/elevation": { - "version": "15.0.0-canary.7f224ddd4.0", - "resolved": "https://registry.npmjs.org/@material/elevation/-/elevation-15.0.0-canary.7f224ddd4.0.tgz", - "integrity": "sha512-tV6s4/pUBECedaI36Yj18KmRCk1vfue/JP/5yYRlFNnLMRVISePbZaKkn/BHXVf+26I3W879+XqIGlDVdmOoMA==", + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dev": true, "dependencies": { - "@material/animation": "15.0.0-canary.7f224ddd4.0", - "@material/base": "15.0.0-canary.7f224ddd4.0", - "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", - "@material/rtl": "15.0.0-canary.7f224ddd4.0", - "@material/theme": "15.0.0-canary.7f224ddd4.0", - "tslib": "^2.1.0" - } - }, - "node_modules/@material/fab": { - "version": "15.0.0-canary.7f224ddd4.0", - "resolved": "https://registry.npmjs.org/@material/fab/-/fab-15.0.0-canary.7f224ddd4.0.tgz", - "integrity": "sha512-4h76QrzfZTcPdd+awDPZ4Q0YdSqsXQnS540TPtyXUJ/5G99V6VwGpjMPIxAsW0y+pmI9UkLL/srrMaJec+7r4Q==", - "dependencies": { - "@material/animation": "15.0.0-canary.7f224ddd4.0", - "@material/dom": "15.0.0-canary.7f224ddd4.0", - "@material/elevation": "15.0.0-canary.7f224ddd4.0", - "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", - "@material/focus-ring": "15.0.0-canary.7f224ddd4.0", - "@material/ripple": "15.0.0-canary.7f224ddd4.0", - "@material/rtl": "15.0.0-canary.7f224ddd4.0", - "@material/shape": "15.0.0-canary.7f224ddd4.0", - "@material/theme": "15.0.0-canary.7f224ddd4.0", - "@material/tokens": "15.0.0-canary.7f224ddd4.0", - "@material/touch-target": "15.0.0-canary.7f224ddd4.0", - "@material/typography": "15.0.0-canary.7f224ddd4.0", - "tslib": "^2.1.0" + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" } }, - "node_modules/@material/feature-targeting": { - "version": "15.0.0-canary.7f224ddd4.0", - "resolved": "https://registry.npmjs.org/@material/feature-targeting/-/feature-targeting-15.0.0-canary.7f224ddd4.0.tgz", - "integrity": "sha512-SAjtxYh6YlKZriU83diDEQ7jNSP2MnxKsER0TvFeyG1vX/DWsUyYDOIJTOEa9K1N+fgJEBkNK8hY55QhQaspew==", - "dependencies": { - "tslib": "^2.1.0" + "node_modules/@isaacs/cliui/node_modules/ansi-regex": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", + "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" } }, - "node_modules/@material/floating-label": { - "version": "15.0.0-canary.7f224ddd4.0", - "resolved": "https://registry.npmjs.org/@material/floating-label/-/floating-label-15.0.0-canary.7f224ddd4.0.tgz", - "integrity": "sha512-0KMo5ijjYaEHPiZ2pCVIcbaTS2LycvH9zEhEMKwPPGssBCX7iz5ffYQFk7e5yrQand1r3jnQQgYfHAwtykArnQ==", - "dependencies": { - "@material/animation": "15.0.0-canary.7f224ddd4.0", - "@material/base": "15.0.0-canary.7f224ddd4.0", - "@material/dom": "15.0.0-canary.7f224ddd4.0", - "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", - "@material/rtl": "15.0.0-canary.7f224ddd4.0", - "@material/theme": "15.0.0-canary.7f224ddd4.0", - "@material/typography": "15.0.0-canary.7f224ddd4.0", - "tslib": "^2.1.0" + "node_modules/@isaacs/cliui/node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/@material/focus-ring": { - "version": "15.0.0-canary.7f224ddd4.0", - "resolved": "https://registry.npmjs.org/@material/focus-ring/-/focus-ring-15.0.0-canary.7f224ddd4.0.tgz", - "integrity": "sha512-Jmg1nltq4J6S6A10EGMZnvufrvU3YTi+8R8ZD9lkSbun0Fm2TVdICQt/Auyi6An9zP66oQN6c31eqO6KfIPsDg==", - "dependencies": { - "@material/dom": "15.0.0-canary.7f224ddd4.0", - "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", - "@material/rtl": "15.0.0-canary.7f224ddd4.0" - } + "node_modules/@isaacs/cliui/node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true }, - "node_modules/@material/form-field": { - "version": "15.0.0-canary.7f224ddd4.0", - "resolved": "https://registry.npmjs.org/@material/form-field/-/form-field-15.0.0-canary.7f224ddd4.0.tgz", - "integrity": "sha512-fEPWgDQEPJ6WF7hNnIStxucHR9LE4DoDSMqCsGWS2Yu+NLZYLuCEecgR0UqQsl1EQdNRaFh8VH93KuxGd2hiPg==", + "node_modules/@isaacs/cliui/node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, "dependencies": { - "@material/base": "15.0.0-canary.7f224ddd4.0", - "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", - "@material/ripple": "15.0.0-canary.7f224ddd4.0", - "@material/rtl": "15.0.0-canary.7f224ddd4.0", - "@material/theme": "15.0.0-canary.7f224ddd4.0", - "@material/typography": "15.0.0-canary.7f224ddd4.0", - "tslib": "^2.1.0" - } - }, - "node_modules/@material/icon-button": { - "version": "15.0.0-canary.7f224ddd4.0", - "resolved": "https://registry.npmjs.org/@material/icon-button/-/icon-button-15.0.0-canary.7f224ddd4.0.tgz", - "integrity": "sha512-DcK7IL4ICY/DW+48YQZZs9g0U1kRaW0Wb0BxhvppDMYziHo/CTpFdle4gjyuTyRxPOdHQz5a97ru48Z9O4muTw==", - "dependencies": { - "@material/base": "15.0.0-canary.7f224ddd4.0", - "@material/density": "15.0.0-canary.7f224ddd4.0", - "@material/dom": "15.0.0-canary.7f224ddd4.0", - "@material/elevation": "15.0.0-canary.7f224ddd4.0", - "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", - "@material/focus-ring": "15.0.0-canary.7f224ddd4.0", - "@material/ripple": "15.0.0-canary.7f224ddd4.0", - "@material/rtl": "15.0.0-canary.7f224ddd4.0", - "@material/theme": "15.0.0-canary.7f224ddd4.0", - "@material/touch-target": "15.0.0-canary.7f224ddd4.0", - "tslib": "^2.1.0" + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@material/image-list": { - "version": "15.0.0-canary.7f224ddd4.0", - "resolved": "https://registry.npmjs.org/@material/image-list/-/image-list-15.0.0-canary.7f224ddd4.0.tgz", - "integrity": "sha512-voMjG2p80XbjL1B2lmF65zO5gEgJOVKClLdqh4wbYzYfwY/SR9c8eLvlYG7DLdFaFBl/7gGxD8TvvZ329HUFPw==", + "node_modules/@isaacs/cliui/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, "dependencies": { - "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", - "@material/shape": "15.0.0-canary.7f224ddd4.0", - "@material/theme": "15.0.0-canary.7f224ddd4.0", - "@material/typography": "15.0.0-canary.7f224ddd4.0", - "tslib": "^2.1.0" + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" } }, - "node_modules/@material/layout-grid": { - "version": "15.0.0-canary.7f224ddd4.0", - "resolved": "https://registry.npmjs.org/@material/layout-grid/-/layout-grid-15.0.0-canary.7f224ddd4.0.tgz", - "integrity": "sha512-veDABLxMn2RmvfnUO2RUmC1OFfWr4cU+MrxKPoDD2hl3l3eDYv5fxws6r5T1JoSyXoaN+oEZpheS0+M9Ure8Pg==", + "node_modules/@isaacs/cliui/node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dev": true, "dependencies": { - "tslib": "^2.1.0" + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, - "node_modules/@material/line-ripple": { - "version": "15.0.0-canary.7f224ddd4.0", - "resolved": "https://registry.npmjs.org/@material/line-ripple/-/line-ripple-15.0.0-canary.7f224ddd4.0.tgz", - "integrity": "sha512-f60hVJhIU6I3/17Tqqzch1emUKEcfVVgHVqADbU14JD+oEIz429ZX9ksZ3VChoU3+eejFl+jVdZMLE/LrAuwpg==", - "dependencies": { - "@material/animation": "15.0.0-canary.7f224ddd4.0", - "@material/base": "15.0.0-canary.7f224ddd4.0", - "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", - "@material/theme": "15.0.0-canary.7f224ddd4.0", - "tslib": "^2.1.0" + "node_modules/@istanbuljs/schema": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", + "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "dev": true, + "engines": { + "node": ">=8" } }, - "node_modules/@material/linear-progress": { - "version": "15.0.0-canary.7f224ddd4.0", - "resolved": "https://registry.npmjs.org/@material/linear-progress/-/linear-progress-15.0.0-canary.7f224ddd4.0.tgz", - "integrity": "sha512-pRDEwPQielDiC9Sc5XhCXrGxP8wWOnAO8sQlMebfBYHYqy5hhiIzibezS8CSaW4MFQFyXmCmpmqWlbqGYRmiyg==", + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", + "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==", + "dev": true, "dependencies": { - "@material/animation": "15.0.0-canary.7f224ddd4.0", - "@material/base": "15.0.0-canary.7f224ddd4.0", - "@material/dom": "15.0.0-canary.7f224ddd4.0", - "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", - "@material/progress-indicator": "15.0.0-canary.7f224ddd4.0", - "@material/rtl": "15.0.0-canary.7f224ddd4.0", - "@material/theme": "15.0.0-canary.7f224ddd4.0", - "tslib": "^2.1.0" + "@jridgewell/set-array": "^1.2.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" } }, - "node_modules/@material/list": { - "version": "15.0.0-canary.7f224ddd4.0", - "resolved": "https://registry.npmjs.org/@material/list/-/list-15.0.0-canary.7f224ddd4.0.tgz", - "integrity": "sha512-Is0NV91sJlXF5pOebYAtWLF4wU2MJDbYqztML/zQNENkQxDOvEXu3nWNb3YScMIYJJXvARO0Liur5K4yPagS1Q==", - "dependencies": { - "@material/base": "15.0.0-canary.7f224ddd4.0", - "@material/density": "15.0.0-canary.7f224ddd4.0", - "@material/dom": "15.0.0-canary.7f224ddd4.0", - "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", - "@material/ripple": "15.0.0-canary.7f224ddd4.0", - "@material/rtl": "15.0.0-canary.7f224ddd4.0", - "@material/shape": "15.0.0-canary.7f224ddd4.0", - "@material/theme": "15.0.0-canary.7f224ddd4.0", - "@material/tokens": "15.0.0-canary.7f224ddd4.0", - "@material/typography": "15.0.0-canary.7f224ddd4.0", - "tslib": "^2.1.0" + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "engines": { + "node": ">=6.0.0" } }, - "node_modules/@material/menu": { - "version": "15.0.0-canary.7f224ddd4.0", - "resolved": "https://registry.npmjs.org/@material/menu/-/menu-15.0.0-canary.7f224ddd4.0.tgz", - "integrity": "sha512-D11QU1dXqLbh5X1zKlEhS3QWh0b5BPNXlafc5MXfkdJHhOiieb7LC9hMJhbrHtj24FadJ7evaFW/T2ugJbJNnQ==", - "dependencies": { - "@material/base": "15.0.0-canary.7f224ddd4.0", - "@material/dom": "15.0.0-canary.7f224ddd4.0", - "@material/elevation": "15.0.0-canary.7f224ddd4.0", - "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", - "@material/list": "15.0.0-canary.7f224ddd4.0", - "@material/menu-surface": "15.0.0-canary.7f224ddd4.0", - "@material/ripple": "15.0.0-canary.7f224ddd4.0", - "@material/rtl": "15.0.0-canary.7f224ddd4.0", - "@material/shape": "15.0.0-canary.7f224ddd4.0", - "@material/theme": "15.0.0-canary.7f224ddd4.0", - "@material/tokens": "15.0.0-canary.7f224ddd4.0", - "tslib": "^2.1.0" + "node_modules/@jridgewell/set-array": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", + "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", + "dev": true, + "engines": { + "node": ">=6.0.0" } }, - "node_modules/@material/menu-surface": { - "version": "15.0.0-canary.7f224ddd4.0", - "resolved": "https://registry.npmjs.org/@material/menu-surface/-/menu-surface-15.0.0-canary.7f224ddd4.0.tgz", - "integrity": "sha512-7RZHvw0gbwppaAJ/Oh5SWmfAKJ62aw1IMB3+3MRwsb5PLoV666wInYa+zJfE4i7qBeOn904xqT2Nko5hY0ssrg==", + "node_modules/@jridgewell/source-map": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.6.tgz", + "integrity": "sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==", + "dev": true, "dependencies": { - "@material/animation": "15.0.0-canary.7f224ddd4.0", - "@material/base": "15.0.0-canary.7f224ddd4.0", - "@material/elevation": "15.0.0-canary.7f224ddd4.0", - "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", - "@material/rtl": "15.0.0-canary.7f224ddd4.0", - "@material/shape": "15.0.0-canary.7f224ddd4.0", - "@material/theme": "15.0.0-canary.7f224ddd4.0", - "tslib": "^2.1.0" + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25" } }, - "node_modules/@material/notched-outline": { - "version": "15.0.0-canary.7f224ddd4.0", - "resolved": "https://registry.npmjs.org/@material/notched-outline/-/notched-outline-15.0.0-canary.7f224ddd4.0.tgz", - "integrity": "sha512-Yg2usuKB2DKlKIBISbie9BFsOVuffF71xjbxPbybvqemxqUBd+bD5/t6H1fLE+F8/NCu5JMigho4ewUU+0RCiw==", - "dependencies": { - "@material/base": "15.0.0-canary.7f224ddd4.0", - "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", - "@material/floating-label": "15.0.0-canary.7f224ddd4.0", - "@material/rtl": "15.0.0-canary.7f224ddd4.0", - "@material/shape": "15.0.0-canary.7f224ddd4.0", - "@material/theme": "15.0.0-canary.7f224ddd4.0", - "tslib": "^2.1.0" - } + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", + "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", + "dev": true }, - "node_modules/@material/progress-indicator": { - "version": "15.0.0-canary.7f224ddd4.0", - "resolved": "https://registry.npmjs.org/@material/progress-indicator/-/progress-indicator-15.0.0-canary.7f224ddd4.0.tgz", - "integrity": "sha512-UPbDjE5CqT+SqTs0mNFG6uFEw7wBlgYmh+noSkQ6ty/EURm8lF125dmi4dv4kW0+octonMXqkGtAoZwLIHKf/w==", + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "dev": true, "dependencies": { - "tslib": "^2.1.0" - } - }, - "node_modules/@material/radio": { - "version": "15.0.0-canary.7f224ddd4.0", - "resolved": "https://registry.npmjs.org/@material/radio/-/radio-15.0.0-canary.7f224ddd4.0.tgz", - "integrity": "sha512-wR1X0Sr0KmQLu6+YOFKAI84G3L6psqd7Kys5kfb8WKBM36zxO5HQXC5nJm/Y0rdn22ixzsIz2GBo0MNU4V4k1A==", - "dependencies": { - "@material/animation": "15.0.0-canary.7f224ddd4.0", - "@material/base": "15.0.0-canary.7f224ddd4.0", - "@material/density": "15.0.0-canary.7f224ddd4.0", - "@material/dom": "15.0.0-canary.7f224ddd4.0", - "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", - "@material/focus-ring": "15.0.0-canary.7f224ddd4.0", - "@material/ripple": "15.0.0-canary.7f224ddd4.0", - "@material/theme": "15.0.0-canary.7f224ddd4.0", - "@material/touch-target": "15.0.0-canary.7f224ddd4.0", - "tslib": "^2.1.0" + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" } }, - "node_modules/@material/ripple": { - "version": "15.0.0-canary.7f224ddd4.0", - "resolved": "https://registry.npmjs.org/@material/ripple/-/ripple-15.0.0-canary.7f224ddd4.0.tgz", - "integrity": "sha512-JqOsWM1f4aGdotP0rh1vZlPZTg6lZgh39FIYHFMfOwfhR+LAikUJ+37ciqZuewgzXB6iiRO6a8aUH6HR5SJYPg==", - "dependencies": { - "@material/animation": "15.0.0-canary.7f224ddd4.0", - "@material/base": "15.0.0-canary.7f224ddd4.0", - "@material/dom": "15.0.0-canary.7f224ddd4.0", - "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", - "@material/rtl": "15.0.0-canary.7f224ddd4.0", - "@material/theme": "15.0.0-canary.7f224ddd4.0", - "tslib": "^2.1.0" + "node_modules/@jsonjoy.com/base64": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/base64/-/base64-1.1.2.tgz", + "integrity": "sha512-q6XAnWQDIMA3+FTiOYajoYqySkO+JSat0ytXGSuRdq9uXE7o92gzuQwQM14xaCRlBLGq3v5miDGC4vkVTn54xA==", + "dev": true, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" } }, - "node_modules/@material/rtl": { - "version": "15.0.0-canary.7f224ddd4.0", - "resolved": "https://registry.npmjs.org/@material/rtl/-/rtl-15.0.0-canary.7f224ddd4.0.tgz", - "integrity": "sha512-UVf14qAtmPiaaZjuJtmN36HETyoKWmsZM/qn1L5ciR2URb8O035dFWnz4ZWFMmAYBno/L7JiZaCkPurv2ZNrGA==", + "node_modules/@jsonjoy.com/json-pack": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/json-pack/-/json-pack-1.1.0.tgz", + "integrity": "sha512-zlQONA+msXPPwHWZMKFVS78ewFczIll5lXiVPwFPCZUsrOKdxc2AvxU1HoNBmMRhqDZUR9HkC3UOm+6pME6Xsg==", + "dev": true, "dependencies": { - "@material/theme": "15.0.0-canary.7f224ddd4.0", - "tslib": "^2.1.0" + "@jsonjoy.com/base64": "^1.1.1", + "@jsonjoy.com/util": "^1.1.2", + "hyperdyperid": "^1.2.0", + "thingies": "^1.20.0" + }, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" } }, - "node_modules/@material/segmented-button": { - "version": "15.0.0-canary.7f224ddd4.0", - "resolved": "https://registry.npmjs.org/@material/segmented-button/-/segmented-button-15.0.0-canary.7f224ddd4.0.tgz", - "integrity": "sha512-LCnVRUSAhELTKI/9hSvyvIvQIpPpqF29BV+O9yM4WoNNmNWqTulvuiv7grHZl6Z+kJuxSg4BGbsPxxb9dXozPg==", - "dependencies": { - "@material/base": "15.0.0-canary.7f224ddd4.0", - "@material/elevation": "15.0.0-canary.7f224ddd4.0", - "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", - "@material/ripple": "15.0.0-canary.7f224ddd4.0", - "@material/theme": "15.0.0-canary.7f224ddd4.0", - "@material/touch-target": "15.0.0-canary.7f224ddd4.0", - "@material/typography": "15.0.0-canary.7f224ddd4.0", - "tslib": "^2.1.0" + "node_modules/@jsonjoy.com/util": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/util/-/util-1.3.0.tgz", + "integrity": "sha512-Cebt4Vk7k1xHy87kHY7KSPLT77A7Ev7IfOblyLZhtYEhrdQ6fX4EoLq3xOQ3O/DRMEh2ok5nyC180E+ABS8Wmw==", + "dev": true, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" } }, - "node_modules/@material/select": { - "version": "15.0.0-canary.7f224ddd4.0", - "resolved": "https://registry.npmjs.org/@material/select/-/select-15.0.0-canary.7f224ddd4.0.tgz", - "integrity": "sha512-WioZtQEXRpglum0cMSzSqocnhsGRr+ZIhvKb3FlaNrTaK8H3Y4QA7rVjv3emRtrLOOjaT6/RiIaUMTo9AGzWQQ==", - "dependencies": { - "@material/animation": "15.0.0-canary.7f224ddd4.0", - "@material/base": "15.0.0-canary.7f224ddd4.0", - "@material/density": "15.0.0-canary.7f224ddd4.0", - "@material/dom": "15.0.0-canary.7f224ddd4.0", - "@material/elevation": "15.0.0-canary.7f224ddd4.0", - "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", - "@material/floating-label": "15.0.0-canary.7f224ddd4.0", - "@material/line-ripple": "15.0.0-canary.7f224ddd4.0", - "@material/list": "15.0.0-canary.7f224ddd4.0", - "@material/menu": "15.0.0-canary.7f224ddd4.0", - "@material/menu-surface": "15.0.0-canary.7f224ddd4.0", - "@material/notched-outline": "15.0.0-canary.7f224ddd4.0", - "@material/ripple": "15.0.0-canary.7f224ddd4.0", - "@material/rtl": "15.0.0-canary.7f224ddd4.0", - "@material/shape": "15.0.0-canary.7f224ddd4.0", - "@material/theme": "15.0.0-canary.7f224ddd4.0", - "@material/tokens": "15.0.0-canary.7f224ddd4.0", - "@material/typography": "15.0.0-canary.7f224ddd4.0", - "tslib": "^2.1.0" - } + "node_modules/@leichtgewicht/ip-codec": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.5.tgz", + "integrity": "sha512-Vo+PSpZG2/fmgmiNzYK9qWRh8h/CHrwD0mo1h1DzL4yzHNSfWYujGTYsWGreD000gcgmZ7K4Ys6Tx9TxtsKdDw==", + "dev": true }, - "node_modules/@material/shape": { - "version": "15.0.0-canary.7f224ddd4.0", - "resolved": "https://registry.npmjs.org/@material/shape/-/shape-15.0.0-canary.7f224ddd4.0.tgz", - "integrity": "sha512-8z8l1W3+cymObunJoRhwFPKZ+FyECfJ4MJykNiaZq7XJFZkV6xNmqAVrrbQj93FtLsECn9g4PjjIomguVn/OEw==", + "node_modules/@listr2/prompt-adapter-inquirer": { + "version": "2.0.15", + "resolved": "https://registry.npmjs.org/@listr2/prompt-adapter-inquirer/-/prompt-adapter-inquirer-2.0.15.tgz", + "integrity": "sha512-MZrGem/Ujjd4cPTLYDfCZK2iKKeiO/8OX13S6jqxldLs0Prf2aGqVlJ77nMBqMv7fzqgXEgjrNHLXcKR8l9lOg==", + "dev": true, "dependencies": { - "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", - "@material/rtl": "15.0.0-canary.7f224ddd4.0", - "@material/theme": "15.0.0-canary.7f224ddd4.0", - "tslib": "^2.1.0" - } - }, - "node_modules/@material/slider": { - "version": "15.0.0-canary.7f224ddd4.0", - "resolved": "https://registry.npmjs.org/@material/slider/-/slider-15.0.0-canary.7f224ddd4.0.tgz", - "integrity": "sha512-QU/WSaSWlLKQRqOhJrPgm29wqvvzRusMqwAcrCh1JTrCl+xwJ43q5WLDfjYhubeKtrEEgGu9tekkAiYfMG7EBw==", - "dependencies": { - "@material/animation": "15.0.0-canary.7f224ddd4.0", - "@material/base": "15.0.0-canary.7f224ddd4.0", - "@material/dom": "15.0.0-canary.7f224ddd4.0", - "@material/elevation": "15.0.0-canary.7f224ddd4.0", - "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", - "@material/ripple": "15.0.0-canary.7f224ddd4.0", - "@material/rtl": "15.0.0-canary.7f224ddd4.0", - "@material/theme": "15.0.0-canary.7f224ddd4.0", - "@material/tokens": "15.0.0-canary.7f224ddd4.0", - "@material/typography": "15.0.0-canary.7f224ddd4.0", - "tslib": "^2.1.0" - } - }, - "node_modules/@material/snackbar": { - "version": "15.0.0-canary.7f224ddd4.0", - "resolved": "https://registry.npmjs.org/@material/snackbar/-/snackbar-15.0.0-canary.7f224ddd4.0.tgz", - "integrity": "sha512-sm7EbVKddaXpT/aXAYBdPoN0k8yeg9+dprgBUkrdqGzWJAeCkxb4fv2B3He88YiCtvkTz2KLY4CThPQBSEsMFQ==", - "dependencies": { - "@material/animation": "15.0.0-canary.7f224ddd4.0", - "@material/base": "15.0.0-canary.7f224ddd4.0", - "@material/button": "15.0.0-canary.7f224ddd4.0", - "@material/dom": "15.0.0-canary.7f224ddd4.0", - "@material/elevation": "15.0.0-canary.7f224ddd4.0", - "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", - "@material/icon-button": "15.0.0-canary.7f224ddd4.0", - "@material/ripple": "15.0.0-canary.7f224ddd4.0", - "@material/rtl": "15.0.0-canary.7f224ddd4.0", - "@material/shape": "15.0.0-canary.7f224ddd4.0", - "@material/theme": "15.0.0-canary.7f224ddd4.0", - "@material/tokens": "15.0.0-canary.7f224ddd4.0", - "@material/typography": "15.0.0-canary.7f224ddd4.0", - "tslib": "^2.1.0" + "@inquirer/type": "^1.5.1" + }, + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "@inquirer/prompts": ">= 3 < 6" } }, - "node_modules/@material/switch": { - "version": "15.0.0-canary.7f224ddd4.0", - "resolved": "https://registry.npmjs.org/@material/switch/-/switch-15.0.0-canary.7f224ddd4.0.tgz", - "integrity": "sha512-lEDJfRvkVyyeHWIBfoxYjJVl+WlEAE2kZ/+6OqB1FW0OV8ftTODZGhHRSzjVBA1/p4FPuhAtKtoK9jTpa4AZjA==", - "dependencies": { - "@material/animation": "15.0.0-canary.7f224ddd4.0", - "@material/base": "15.0.0-canary.7f224ddd4.0", - "@material/density": "15.0.0-canary.7f224ddd4.0", - "@material/dom": "15.0.0-canary.7f224ddd4.0", - "@material/elevation": "15.0.0-canary.7f224ddd4.0", - "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", - "@material/focus-ring": "15.0.0-canary.7f224ddd4.0", - "@material/ripple": "15.0.0-canary.7f224ddd4.0", - "@material/rtl": "15.0.0-canary.7f224ddd4.0", - "@material/shape": "15.0.0-canary.7f224ddd4.0", - "@material/theme": "15.0.0-canary.7f224ddd4.0", - "@material/tokens": "15.0.0-canary.7f224ddd4.0", - "safevalues": "^0.3.4", - "tslib": "^2.1.0" - } + "node_modules/@lmdb/lmdb-darwin-arm64": { + "version": "3.0.13", + "resolved": "https://registry.npmjs.org/@lmdb/lmdb-darwin-arm64/-/lmdb-darwin-arm64-3.0.13.tgz", + "integrity": "sha512-uiKPB0Fv6WEEOZjruu9a6wnW/8jrjzlZbxXscMB8kuCJ1k6kHpcBnuvaAWcqhbI7rqX5GKziwWEdD+wi2gNLfA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] }, - "node_modules/@material/tab": { - "version": "15.0.0-canary.7f224ddd4.0", - "resolved": "https://registry.npmjs.org/@material/tab/-/tab-15.0.0-canary.7f224ddd4.0.tgz", - "integrity": "sha512-E1xGACImyCLurhnizyOTCgOiVezce4HlBFAI6YhJo/AyVwjN2Dtas4ZLQMvvWWqpyhITNkeYdOchwCC1mrz3AQ==", - "dependencies": { - "@material/base": "15.0.0-canary.7f224ddd4.0", - "@material/elevation": "15.0.0-canary.7f224ddd4.0", - "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", - "@material/focus-ring": "15.0.0-canary.7f224ddd4.0", - "@material/ripple": "15.0.0-canary.7f224ddd4.0", - "@material/rtl": "15.0.0-canary.7f224ddd4.0", - "@material/tab-indicator": "15.0.0-canary.7f224ddd4.0", - "@material/theme": "15.0.0-canary.7f224ddd4.0", - "@material/tokens": "15.0.0-canary.7f224ddd4.0", - "@material/typography": "15.0.0-canary.7f224ddd4.0", - "tslib": "^2.1.0" - } + "node_modules/@lmdb/lmdb-darwin-x64": { + "version": "3.0.13", + "resolved": "https://registry.npmjs.org/@lmdb/lmdb-darwin-x64/-/lmdb-darwin-x64-3.0.13.tgz", + "integrity": "sha512-bEVIIfK5mSQoG1R19qA+fJOvCB+0wVGGnXHT3smchBVahYBdlPn2OsZZKzlHWfb1E+PhLBmYfqB5zQXFP7hJig==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] }, - "node_modules/@material/tab-bar": { - "version": "15.0.0-canary.7f224ddd4.0", - "resolved": "https://registry.npmjs.org/@material/tab-bar/-/tab-bar-15.0.0-canary.7f224ddd4.0.tgz", - "integrity": "sha512-p1Asb2NzrcECvAQU3b2SYrpyJGyJLQWR+nXTYzDKE8WOpLIRCXap2audNqD7fvN/A20UJ1J8U01ptrvCkwJ4eA==", - "dependencies": { - "@material/animation": "15.0.0-canary.7f224ddd4.0", - "@material/base": "15.0.0-canary.7f224ddd4.0", - "@material/density": "15.0.0-canary.7f224ddd4.0", - "@material/elevation": "15.0.0-canary.7f224ddd4.0", - "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", - "@material/tab": "15.0.0-canary.7f224ddd4.0", - "@material/tab-indicator": "15.0.0-canary.7f224ddd4.0", - "@material/tab-scroller": "15.0.0-canary.7f224ddd4.0", - "@material/theme": "15.0.0-canary.7f224ddd4.0", - "@material/tokens": "15.0.0-canary.7f224ddd4.0", - "@material/typography": "15.0.0-canary.7f224ddd4.0", - "tslib": "^2.1.0" - } + "node_modules/@lmdb/lmdb-linux-arm": { + "version": "3.0.13", + "resolved": "https://registry.npmjs.org/@lmdb/lmdb-linux-arm/-/lmdb-linux-arm-3.0.13.tgz", + "integrity": "sha512-Yml1KlMzOnXj/tnW7yX8U78iAzTk39aILYvCPbqeewAq1kSzl+w59k/fiVkTBfvDi/oW/5YRxL+Fq+Y1Fr1r2Q==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/@material/tab-indicator": { - "version": "15.0.0-canary.7f224ddd4.0", - "resolved": "https://registry.npmjs.org/@material/tab-indicator/-/tab-indicator-15.0.0-canary.7f224ddd4.0.tgz", - "integrity": "sha512-h9Td3MPqbs33spcPS7ecByRHraYgU4tNCZpZzZXw31RypjKvISDv/PS5wcA4RmWqNGih78T7xg4QIGsZg4Pk4w==", - "dependencies": { - "@material/animation": "15.0.0-canary.7f224ddd4.0", - "@material/base": "15.0.0-canary.7f224ddd4.0", - "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", - "@material/theme": "15.0.0-canary.7f224ddd4.0", - "tslib": "^2.1.0" - } + "node_modules/@lmdb/lmdb-linux-arm64": { + "version": "3.0.13", + "resolved": "https://registry.npmjs.org/@lmdb/lmdb-linux-arm64/-/lmdb-linux-arm64-3.0.13.tgz", + "integrity": "sha512-afbVrsMgZ9dUTNUchFpj5VkmJRxvht/u335jUJ7o23YTbNbnpmXif3VKQGCtnjSh+CZaqm6N3CPG8KO3zwyZ1Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/@material/tab-scroller": { - "version": "15.0.0-canary.7f224ddd4.0", - "resolved": "https://registry.npmjs.org/@material/tab-scroller/-/tab-scroller-15.0.0-canary.7f224ddd4.0.tgz", - "integrity": "sha512-LFeYNjQpdXecwECd8UaqHYbhscDCwhGln5Yh+3ctvcEgvmDPNjhKn/DL3sWprWvG8NAhP6sHMrsGhQFVdCWtTg==", - "dependencies": { - "@material/animation": "15.0.0-canary.7f224ddd4.0", - "@material/base": "15.0.0-canary.7f224ddd4.0", - "@material/dom": "15.0.0-canary.7f224ddd4.0", - "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", - "@material/tab": "15.0.0-canary.7f224ddd4.0", - "tslib": "^2.1.0" - } + "node_modules/@lmdb/lmdb-linux-x64": { + "version": "3.0.13", + "resolved": "https://registry.npmjs.org/@lmdb/lmdb-linux-x64/-/lmdb-linux-x64-3.0.13.tgz", + "integrity": "sha512-vOtxu0xC0SLdQ2WRXg8Qgd8T32ak4SPqk5zjItRszrJk2BdeXqfGxBJbP7o4aOvSPSmSSv46Lr1EP4HXU8v7Kg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/@material/textfield": { - "version": "15.0.0-canary.7f224ddd4.0", - "resolved": "https://registry.npmjs.org/@material/textfield/-/textfield-15.0.0-canary.7f224ddd4.0.tgz", - "integrity": "sha512-AExmFvgE5nNF0UA4l2cSzPghtxSUQeeoyRjFLHLy+oAaE4eKZFrSy0zEpqPeWPQpEMDZk+6Y+6T3cOFYBeSvsw==", - "dependencies": { - "@material/animation": "15.0.0-canary.7f224ddd4.0", - "@material/base": "15.0.0-canary.7f224ddd4.0", - "@material/density": "15.0.0-canary.7f224ddd4.0", - "@material/dom": "15.0.0-canary.7f224ddd4.0", - "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", - "@material/floating-label": "15.0.0-canary.7f224ddd4.0", - "@material/line-ripple": "15.0.0-canary.7f224ddd4.0", - "@material/notched-outline": "15.0.0-canary.7f224ddd4.0", - "@material/ripple": "15.0.0-canary.7f224ddd4.0", - "@material/rtl": "15.0.0-canary.7f224ddd4.0", - "@material/shape": "15.0.0-canary.7f224ddd4.0", - "@material/theme": "15.0.0-canary.7f224ddd4.0", - "@material/tokens": "15.0.0-canary.7f224ddd4.0", - "@material/typography": "15.0.0-canary.7f224ddd4.0", - "tslib": "^2.1.0" - } + "node_modules/@lmdb/lmdb-win32-x64": { + "version": "3.0.13", + "resolved": "https://registry.npmjs.org/@lmdb/lmdb-win32-x64/-/lmdb-win32-x64-3.0.13.tgz", + "integrity": "sha512-UCrMJQY/gJnOl3XgbWRZZUvGGBuKy6i0YNSptgMzHBjs+QYDYR1Mt/RLTOPy4fzzves65O1EDmlL//OzEqoLlA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] }, - "node_modules/@material/theme": { - "version": "15.0.0-canary.7f224ddd4.0", - "resolved": "https://registry.npmjs.org/@material/theme/-/theme-15.0.0-canary.7f224ddd4.0.tgz", - "integrity": "sha512-hs45hJoE9yVnoVOcsN1jklyOa51U4lzWsEnQEuJTPOk2+0HqCQ0yv/q0InpSnm2i69fNSyZC60+8HADZGF8ugQ==", - "dependencies": { - "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", - "tslib": "^2.1.0" - } + "node_modules/@msgpackr-extract/msgpackr-extract-darwin-arm64": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-darwin-arm64/-/msgpackr-extract-darwin-arm64-3.0.3.tgz", + "integrity": "sha512-QZHtlVgbAdy2zAqNA9Gu1UpIuI8Xvsd1v8ic6B2pZmeFnFcMWiPLfWXh7TVw4eGEZ/C9TH281KwhVoeQUKbyjw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] }, - "node_modules/@material/tokens": { - "version": "15.0.0-canary.7f224ddd4.0", - "resolved": "https://registry.npmjs.org/@material/tokens/-/tokens-15.0.0-canary.7f224ddd4.0.tgz", - "integrity": "sha512-r9TDoicmcT7FhUXC4eYMFnt9TZsz0G8T3wXvkKncLppYvZ517gPyD/1+yhuGfGOxAzxTrM66S/oEc1fFE2q4hw==", - "dependencies": { - "@material/elevation": "15.0.0-canary.7f224ddd4.0" - } + "node_modules/@msgpackr-extract/msgpackr-extract-darwin-x64": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-darwin-x64/-/msgpackr-extract-darwin-x64-3.0.3.tgz", + "integrity": "sha512-mdzd3AVzYKuUmiWOQ8GNhl64/IoFGol569zNRdkLReh6LRLHOXxU4U8eq0JwaD8iFHdVGqSy4IjFL4reoWCDFw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] }, - "node_modules/@material/tooltip": { - "version": "15.0.0-canary.7f224ddd4.0", - "resolved": "https://registry.npmjs.org/@material/tooltip/-/tooltip-15.0.0-canary.7f224ddd4.0.tgz", - "integrity": "sha512-8qNk3pmPLTnam3XYC1sZuplQXW9xLn4Z4MI3D+U17Q7pfNZfoOugGr+d2cLA9yWAEjVJYB0mj8Yu86+udo4N9w==", - "dependencies": { - "@material/animation": "15.0.0-canary.7f224ddd4.0", - "@material/base": "15.0.0-canary.7f224ddd4.0", - "@material/button": "15.0.0-canary.7f224ddd4.0", - "@material/dom": "15.0.0-canary.7f224ddd4.0", - "@material/elevation": "15.0.0-canary.7f224ddd4.0", - "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", - "@material/rtl": "15.0.0-canary.7f224ddd4.0", - "@material/shape": "15.0.0-canary.7f224ddd4.0", - "@material/theme": "15.0.0-canary.7f224ddd4.0", - "@material/tokens": "15.0.0-canary.7f224ddd4.0", - "@material/typography": "15.0.0-canary.7f224ddd4.0", - "safevalues": "^0.3.4", - "tslib": "^2.1.0" - } + "node_modules/@msgpackr-extract/msgpackr-extract-linux-arm": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-linux-arm/-/msgpackr-extract-linux-arm-3.0.3.tgz", + "integrity": "sha512-fg0uy/dG/nZEXfYilKoRe7yALaNmHoYeIoJuJ7KJ+YyU2bvY8vPv27f7UKhGRpY6euFYqEVhxCFZgAUNQBM3nw==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/@material/top-app-bar": { - "version": "15.0.0-canary.7f224ddd4.0", - "resolved": "https://registry.npmjs.org/@material/top-app-bar/-/top-app-bar-15.0.0-canary.7f224ddd4.0.tgz", - "integrity": "sha512-SARR5/ClYT4CLe9qAXakbr0i0cMY0V3V4pe3ElIJPfL2Z2c4wGR1mTR8m2LxU1MfGKK8aRoUdtfKaxWejp+eNA==", - "dependencies": { - "@material/animation": "15.0.0-canary.7f224ddd4.0", - "@material/base": "15.0.0-canary.7f224ddd4.0", - "@material/elevation": "15.0.0-canary.7f224ddd4.0", - "@material/ripple": "15.0.0-canary.7f224ddd4.0", - "@material/rtl": "15.0.0-canary.7f224ddd4.0", - "@material/shape": "15.0.0-canary.7f224ddd4.0", - "@material/theme": "15.0.0-canary.7f224ddd4.0", - "@material/typography": "15.0.0-canary.7f224ddd4.0", - "tslib": "^2.1.0" - } + "node_modules/@msgpackr-extract/msgpackr-extract-linux-arm64": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-linux-arm64/-/msgpackr-extract-linux-arm64-3.0.3.tgz", + "integrity": "sha512-YxQL+ax0XqBJDZiKimS2XQaf+2wDGVa1enVRGzEvLLVFeqa5kx2bWbtcSXgsxjQB7nRqqIGFIcLteF/sHeVtQg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/@material/touch-target": { - "version": "15.0.0-canary.7f224ddd4.0", - "resolved": "https://registry.npmjs.org/@material/touch-target/-/touch-target-15.0.0-canary.7f224ddd4.0.tgz", - "integrity": "sha512-BJo/wFKHPYLGsRaIpd7vsQwKr02LtO2e89Psv0on/p0OephlNIgeB9dD9W+bQmaeZsZ6liKSKRl6wJWDiK71PA==", - "dependencies": { - "@material/base": "15.0.0-canary.7f224ddd4.0", - "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", - "@material/rtl": "15.0.0-canary.7f224ddd4.0", - "@material/theme": "15.0.0-canary.7f224ddd4.0", - "tslib": "^2.1.0" - } + "node_modules/@msgpackr-extract/msgpackr-extract-linux-x64": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-linux-x64/-/msgpackr-extract-linux-x64-3.0.3.tgz", + "integrity": "sha512-cvwNfbP07pKUfq1uH+S6KJ7dT9K8WOE4ZiAcsrSes+UY55E/0jLYc+vq+DO7jlmqRb5zAggExKm0H7O/CBaesg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/@material/typography": { - "version": "15.0.0-canary.7f224ddd4.0", - "resolved": "https://registry.npmjs.org/@material/typography/-/typography-15.0.0-canary.7f224ddd4.0.tgz", - "integrity": "sha512-kBaZeCGD50iq1DeRRH5OM5Jl7Gdk+/NOfKArkY4ksBZvJiStJ7ACAhpvb8MEGm4s3jvDInQFLsDq3hL+SA79sQ==", - "dependencies": { - "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", - "@material/theme": "15.0.0-canary.7f224ddd4.0", - "tslib": "^2.1.0" - } + "node_modules/@msgpackr-extract/msgpackr-extract-win32-x64": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-win32-x64/-/msgpackr-extract-win32-x64-3.0.3.tgz", + "integrity": "sha512-x0fWaQtYp4E6sktbsdAqnehxDgEc/VwM7uLsRCYWaiGu0ykYdZPiS8zCWdnjHwyiumousxfBm4SO31eXqwEZhQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] }, "node_modules/@ngrx/component-store": { - "version": "17.2.0", - "resolved": "https://registry.npmjs.org/@ngrx/component-store/-/component-store-17.2.0.tgz", - "integrity": "sha512-ywhyoZpkbVIY1t5zf7xfWLGkY0A/fQdMjPehHloDI6bRLrmbllBhQRazwZ+FAGIi2myx1+mGcmAc6FbtIikedA==", + "version": "18.0.2", + "resolved": "https://registry.npmjs.org/@ngrx/component-store/-/component-store-18.0.2.tgz", + "integrity": "sha512-IB7ZKFqjDt4duQbfYqXxAOKf9Si9O1HFodqbNCSgi7gnovK/frf/H429a+lYOyItPcpno3ECom6/1k8pE8fWlg==", "dependencies": { - "@ngrx/operators": "17.0.0-beta.0", + "@ngrx/operators": "18.0.1", "tslib": "^2.0.0" }, "peerDependencies": { - "@angular/core": "^17.0.0", + "@angular/core": "^18.0.0", "rxjs": "^6.5.3 || ^7.5.0" } }, "node_modules/@ngrx/effects": { - "version": "17.2.0", - "resolved": "https://registry.npmjs.org/@ngrx/effects/-/effects-17.2.0.tgz", - "integrity": "sha512-tXDJNsuBtbvI/7+vYnkDKKpUvLbopw1U5G6LoPnKNrbTPsPcUGmCqF5Su/ZoRN3BhXjt2j+eoeVdpBkxdxMRgg==", + "version": "18.0.2", + "resolved": "https://registry.npmjs.org/@ngrx/effects/-/effects-18.0.2.tgz", + "integrity": "sha512-YojXcOD9Lsq4kl2HCjENccyUM/mOlgBdtddsg9j/ojzSUgu3ZuBVKLN3atrL2TJYkbMX1MN0RzafSkL3TPGFIA==", "dependencies": { - "@ngrx/operators": "17.0.0-beta.0", + "@ngrx/operators": "18.0.1", "tslib": "^2.0.0" }, "peerDependencies": { - "@angular/core": "^17.0.0", - "@ngrx/store": "17.2.0", + "@angular/core": "^18.0.0", + "@ngrx/store": "18.0.2", "rxjs": "^6.5.3 || ^7.5.0" } }, "node_modules/@ngrx/operators": { - "version": "17.0.0-beta.0", - "resolved": "https://registry.npmjs.org/@ngrx/operators/-/operators-17.0.0-beta.0.tgz", - "integrity": "sha512-EbO8AONuQ6zo2v/mPyBOi4y0CTAp1x4Z+bx7ZF+Pd8BL5ma53BTCL1TmzaeK5zPUe0yApudLk9/ZbHXPnVox5A==", + "version": "18.0.1", + "resolved": "https://registry.npmjs.org/@ngrx/operators/-/operators-18.0.1.tgz", + "integrity": "sha512-M+QMrHNKgcuiLaRGZxJ4aQi5/OCRfKC4+T/63dsHyLFZ53/FFpF6a/ytSO1Q+tzOplZ5o99S+i8FVaZqNQ3LmQ==", "dependencies": { "tslib": "^2.3.0" }, @@ -4305,30 +3677,30 @@ } }, "node_modules/@ngrx/store": { - "version": "17.2.0", - "resolved": "https://registry.npmjs.org/@ngrx/store/-/store-17.2.0.tgz", - "integrity": "sha512-7wKgZ59B/6yQSvvsU0DQXipDqpkAXv7LwcXLD5Ww7nvqN0fQoRPThMh4+Wv55DCJhE0bQc1NEMciLA47uRt7Wg==", + "version": "18.0.2", + "resolved": "https://registry.npmjs.org/@ngrx/store/-/store-18.0.2.tgz", + "integrity": "sha512-ajwv0+njsO4vzArp9esnFvs1wyUb1U1W8E8LSCKrcW2hWWo9o1Pezj+JRsdQwatxHfrrPFuTDyajsl6GQM/JSA==", "dependencies": { "tslib": "^2.0.0" }, "peerDependencies": { - "@angular/core": "^17.0.0", + "@angular/core": "^18.0.0", "rxjs": "^6.5.3 || ^7.5.0" } }, "node_modules/@ngtools/webpack": { - "version": "17.3.6", - "resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-17.3.6.tgz", - "integrity": "sha512-equxbgh2DKzZtiFMoVf1KD4yJcH1q8lpqQ/GSPPQUvONcmHrr+yqdRUdaJ7oZCyCYmXF/nByBxtMKtJr6nKZVg==", + "version": "18.2.6", + "resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-18.2.6.tgz", + "integrity": "sha512-7HwOPE1EOgcHnpt4brSiT8G2CcXB50G0+CbCBaKGy4LYCG3Y3mrlzF5Fup9HvMJ6Tzqd62RqzpKKYBiGUT7hxg==", "dev": true, "engines": { - "node": "^18.13.0 || >=20.9.0", + "node": "^18.19.1 || ^20.11.1 || >=22.0.0", "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", "yarn": ">= 1.13.0" }, "peerDependencies": { - "@angular/compiler-cli": "^17.0.0", - "typescript": ">=5.2 <5.5", + "@angular/compiler-cli": "^18.0.0", + "typescript": ">=5.4 <5.6", "webpack": "^5.54.0" } }, @@ -4384,18 +3756,15 @@ } }, "node_modules/@npmcli/agent/node_modules/lru-cache": { - "version": "10.2.2", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.2.2.tgz", - "integrity": "sha512-9hp3Vp2/hFQUiIwKo8XCeFVnrg8Pk3TYNPIR7tJADKi5YfcF7vEaK7avFHTlSy3kOKYaJQaalfEo6YuXdceBOQ==", - "dev": true, - "engines": { - "node": "14 || >=16.14" - } + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true }, "node_modules/@npmcli/fs": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-3.1.0.tgz", - "integrity": "sha512-7kZUAaLscfgbwBQRbvdMYaZOWyMEcPTH/tJjnyAWJ/dvvs9Ef+CERx/qJb9GExJpl1qipaDGn7KqHnFGGixd0w==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-3.1.1.tgz", + "integrity": "sha512-q9CRWjpHCMIh5sVyefoD1cA7PkvILqCZsnSOEUUivORLjxCO/Irmue2DprETiNgEqktDBZaM1Bi+jrarx1XdCg==", "dev": true, "dependencies": { "semver": "^7.3.5" @@ -4405,12 +3774,13 @@ } }, "node_modules/@npmcli/git": { - "version": "5.0.7", - "resolved": "https://registry.npmjs.org/@npmcli/git/-/git-5.0.7.tgz", - "integrity": "sha512-WaOVvto604d5IpdCRV2KjQu8PzkfE96d50CQGKgywXh2GxXmDeUO5EWcBC4V57uFyrNqx83+MewuJh3WTR3xPA==", + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/@npmcli/git/-/git-5.0.8.tgz", + "integrity": "sha512-liASfw5cqhjNW9UFd+ruwwdEf/lbOAQjLL2XY2dFW/bkJheXDYZgOyul/4gVvEV4BWkTXjYGmDqMw9uegdbJNQ==", "dev": true, "dependencies": { "@npmcli/promise-spawn": "^7.0.0", + "ini": "^4.1.3", "lru-cache": "^10.0.1", "npm-pick-manifest": "^9.0.0", "proc-log": "^4.0.0", @@ -4433,22 +3803,10 @@ } }, "node_modules/@npmcli/git/node_modules/lru-cache": { - "version": "10.2.2", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.2.2.tgz", - "integrity": "sha512-9hp3Vp2/hFQUiIwKo8XCeFVnrg8Pk3TYNPIR7tJADKi5YfcF7vEaK7avFHTlSy3kOKYaJQaalfEo6YuXdceBOQ==", - "dev": true, - "engines": { - "node": "14 || >=16.14" - } - }, - "node_modules/@npmcli/git/node_modules/proc-log": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-4.2.0.tgz", - "integrity": "sha512-g8+OnU/L2v+wyiVK+D5fA34J7EH8jZ8DDlvwhRCMxmMj7UCBvxiO1mGeN+36JXIKF4zevU4kRBd8lVgG9vLelA==", - "dev": true, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true }, "node_modules/@npmcli/git/node_modules/which": { "version": "4.0.0", @@ -4491,9 +3849,9 @@ } }, "node_modules/@npmcli/package-json": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/@npmcli/package-json/-/package-json-5.1.0.tgz", - "integrity": "sha512-1aL4TuVrLS9sf8quCLerU3H9J4vtCtgu8VauYozrmEyU57i/EdKleCnsQ7vpnABIH6c9mnTxcH5sFkO3BlV8wQ==", + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/@npmcli/package-json/-/package-json-5.2.1.tgz", + "integrity": "sha512-f7zYC6kQautXHvNbLEWgD/uGu1+xCn9izgqBfgItWSx22U0ZDekxN08A1vM8cTxj/cRVe0Q94Ode+tdoYmIOOQ==", "dev": true, "dependencies": { "@npmcli/git": "^5.0.0", @@ -4509,36 +3867,25 @@ } }, "node_modules/@npmcli/package-json/node_modules/glob": { - "version": "10.3.12", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.12.tgz", - "integrity": "sha512-TCNv8vJ+xz4QiqTpfOJA7HvYv+tNIRHKfUWw/q+v2jdgN4ebz+KY9tGx5J4rHP0o84mNP+ApH66HRX8us3Khqg==", + "version": "10.4.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", + "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", "dev": true, "dependencies": { "foreground-child": "^3.1.0", - "jackspeak": "^2.3.6", - "minimatch": "^9.0.1", - "minipass": "^7.0.4", - "path-scurry": "^1.10.2" + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" }, "bin": { "glob": "dist/esm/bin.mjs" }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, "funding": { "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/@npmcli/package-json/node_modules/proc-log": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-4.2.0.tgz", - "integrity": "sha512-g8+OnU/L2v+wyiVK+D5fA34J7EH8jZ8DDlvwhRCMxmMj7UCBvxiO1mGeN+36JXIKF4zevU4kRBd8lVgG9vLelA==", - "dev": true, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, "node_modules/@npmcli/promise-spawn": { "version": "7.0.2", "resolved": "https://registry.npmjs.org/@npmcli/promise-spawn/-/promise-spawn-7.0.2.tgz", @@ -4562,299 +3909,67 @@ }, "node_modules/@npmcli/promise-spawn/node_modules/which": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/which/-/which-4.0.0.tgz", - "integrity": "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==", - "dev": true, - "dependencies": { - "isexe": "^3.1.1" - }, - "bin": { - "node-which": "bin/which.js" - }, - "engines": { - "node": "^16.13.0 || >=18.0.0" - } - }, - "node_modules/@npmcli/redact": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@npmcli/redact/-/redact-1.1.0.tgz", - "integrity": "sha512-PfnWuOkQgu7gCbnSsAisaX7hKOdZ4wSAhAzH3/ph5dSGau52kCRrMMGbiSQLwyTZpgldkZ49b0brkOr1AzGBHQ==", - "dev": true, - "engines": { - "node": "^16.14.0 || >=18.0.0" - } - }, - "node_modules/@npmcli/run-script": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/@npmcli/run-script/-/run-script-7.0.4.tgz", - "integrity": "sha512-9ApYM/3+rBt9V80aYg6tZfzj3UWdiYyCt7gJUD1VJKvWF5nwKDSICXbYIQbspFTq6TOpbsEtIC0LArB8d9PFmg==", - "dev": true, - "dependencies": { - "@npmcli/node-gyp": "^3.0.0", - "@npmcli/package-json": "^5.0.0", - "@npmcli/promise-spawn": "^7.0.0", - "node-gyp": "^10.0.0", - "which": "^4.0.0" - }, - "engines": { - "node": "^16.14.0 || >=18.0.0" - } - }, - "node_modules/@npmcli/run-script/node_modules/isexe": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz", - "integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==", - "dev": true, - "engines": { - "node": ">=16" - } - }, - "node_modules/@npmcli/run-script/node_modules/which": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/which/-/which-4.0.0.tgz", - "integrity": "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==", - "dev": true, - "dependencies": { - "isexe": "^3.1.1" - }, - "bin": { - "node-which": "bin/which.js" - }, - "engines": { - "node": "^16.13.0 || >=18.0.0" - } - }, - "node_modules/@nrwl/devkit": { - "version": "17.2.8", - "resolved": "https://registry.npmjs.org/@nrwl/devkit/-/devkit-17.2.8.tgz", - "integrity": "sha512-l2dFy5LkWqSA45s6pee6CoqJeluH+sjRdVnAAQfjLHRNSx6mFAKblyzq5h1f4P0EUCVVVqLs+kVqmNx5zxYqvw==", - "dev": true, - "dependencies": { - "@nx/devkit": "17.2.8" - } - }, - "node_modules/@nrwl/tao": { - "version": "17.2.8", - "resolved": "https://registry.npmjs.org/@nrwl/tao/-/tao-17.2.8.tgz", - "integrity": "sha512-Qpk5YKeJ+LppPL/wtoDyNGbJs2MsTi6qyX/RdRrEc8lc4bk6Cw3Oul1qTXCI6jT0KzTz+dZtd0zYD/G7okkzvg==", - "dev": true, - "dependencies": { - "nx": "17.2.8", - "tslib": "^2.3.0" - }, - "bin": { - "tao": "index.js" - } - }, - "node_modules/@nx/devkit": { - "version": "17.2.8", - "resolved": "https://registry.npmjs.org/@nx/devkit/-/devkit-17.2.8.tgz", - "integrity": "sha512-6LtiQihtZwqz4hSrtT5cCG5XMCWppG6/B8c1kNksg97JuomELlWyUyVF+sxmeERkcLYFaKPTZytP0L3dmCFXaw==", - "dev": true, - "dependencies": { - "@nrwl/devkit": "17.2.8", - "ejs": "^3.1.7", - "enquirer": "~2.3.6", - "ignore": "^5.0.4", - "semver": "7.5.3", - "tmp": "~0.2.1", - "tslib": "^2.3.0" - }, - "peerDependencies": { - "nx": ">= 16 <= 18" - } - }, - "node_modules/@nx/devkit/node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@nx/devkit/node_modules/semver": { - "version": "7.5.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.3.tgz", - "integrity": "sha512-QBlUtyVk/5EeHbi7X0fw6liDZc7BBmEaSYn01fMU1OUYbf6GPsbTtd8WmnqbI20SeycoHSeiybkE/q1Q+qlThQ==", - "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "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": "17.2.8", - "resolved": "https://registry.npmjs.org/@nx/nx-darwin-arm64/-/nx-darwin-arm64-17.2.8.tgz", - "integrity": "sha512-dMb0uxug4hM7tusISAU1TfkDK3ixYmzc1zhHSZwpR7yKJIyKLtUpBTbryt8nyso37AS1yH+dmfh2Fj2WxfBHTg==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@nx/nx-darwin-x64": { - "version": "17.2.8", - "resolved": "https://registry.npmjs.org/@nx/nx-darwin-x64/-/nx-darwin-x64-17.2.8.tgz", - "integrity": "sha512-0cXzp1tGr7/6lJel102QiLA4NkaLCkQJj6VzwbwuvmuCDxPbpmbz7HC1tUteijKBtOcdXit1/MEoEU007To8Bw==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@nx/nx-freebsd-x64": { - "version": "17.2.8", - "resolved": "https://registry.npmjs.org/@nx/nx-freebsd-x64/-/nx-freebsd-x64-17.2.8.tgz", - "integrity": "sha512-YFMgx5Qpp2btCgvaniDGdu7Ctj56bfFvbbaHQWmOeBPK1krNDp2mqp8HK6ZKOfEuDJGOYAp7HDtCLvdZKvJxzA==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@nx/nx-linux-arm-gnueabihf": { - "version": "17.2.8", - "resolved": "https://registry.npmjs.org/@nx/nx-linux-arm-gnueabihf/-/nx-linux-arm-gnueabihf-17.2.8.tgz", - "integrity": "sha512-iN2my6MrhLRkVDtdivQHugK8YmR7URo1wU9UDuHQ55z3tEcny7LV3W9NSsY9UYPK/FrxdDfevj0r2hgSSdhnzA==", - "cpu": [ - "arm" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@nx/nx-linux-arm64-gnu": { - "version": "17.2.8", - "resolved": "https://registry.npmjs.org/@nx/nx-linux-arm64-gnu/-/nx-linux-arm64-gnu-17.2.8.tgz", - "integrity": "sha512-Iy8BjoW6mOKrSMiTGujUcNdv+xSM1DALTH6y3iLvNDkGbjGK1Re6QNnJAzqcXyDpv32Q4Fc57PmuexyysZxIGg==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@nx/nx-linux-arm64-musl": { - "version": "17.2.8", - "resolved": "https://registry.npmjs.org/@nx/nx-linux-arm64-musl/-/nx-linux-arm64-musl-17.2.8.tgz", - "integrity": "sha512-9wkAxWzknjpzdofL1xjtU6qPFF1PHlvKCZI3hgEYJDo4mQiatGI+7Ttko+lx/ZMP6v4+Umjtgq7+qWrApeKamQ==", - "cpu": [ - "arm64" - ], + "resolved": "https://registry.npmjs.org/which/-/which-4.0.0.tgz", + "integrity": "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==", "dev": true, - "optional": true, - "os": [ - "linux" - ], + "dependencies": { + "isexe": "^3.1.1" + }, + "bin": { + "node-which": "bin/which.js" + }, "engines": { - "node": ">= 10" + "node": "^16.13.0 || >=18.0.0" } }, - "node_modules/@nx/nx-linux-x64-gnu": { - "version": "17.2.8", - "resolved": "https://registry.npmjs.org/@nx/nx-linux-x64-gnu/-/nx-linux-x64-gnu-17.2.8.tgz", - "integrity": "sha512-sjG1bwGsjLxToasZ3lShildFsF0eyeGu+pOQZIp9+gjFbeIkd19cTlCnHrOV9hoF364GuKSXQyUlwtFYFR4VTQ==", - "cpu": [ - "x64" - ], + "node_modules/@npmcli/redact": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@npmcli/redact/-/redact-2.0.1.tgz", + "integrity": "sha512-YgsR5jCQZhVmTJvjduTOIHph0L73pK8xwMVaDY0PatySqVM9AZj93jpoXYSJqfHFxFkN9dmqTw6OiqExsS3LPw==", "dev": true, - "optional": true, - "os": [ - "linux" - ], "engines": { - "node": ">= 10" + "node": "^16.14.0 || >=18.0.0" } }, - "node_modules/@nx/nx-linux-x64-musl": { - "version": "17.2.8", - "resolved": "https://registry.npmjs.org/@nx/nx-linux-x64-musl/-/nx-linux-x64-musl-17.2.8.tgz", - "integrity": "sha512-QiakXZ1xBCIptmkGEouLHQbcM4klQkcr+kEaz2PlNwy/sW3gH1b/1c0Ed5J1AN9xgQxWspriAONpScYBRgxdhA==", - "cpu": [ - "x64" - ], + "node_modules/@npmcli/run-script": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@npmcli/run-script/-/run-script-8.1.0.tgz", + "integrity": "sha512-y7efHHwghQfk28G2z3tlZ67pLG0XdfYbcVG26r7YIXALRsrVQcTq4/tdenSmdOrEsNahIYA/eh8aEVROWGFUDg==", "dev": true, - "optional": true, - "os": [ - "linux" - ], + "dependencies": { + "@npmcli/node-gyp": "^3.0.0", + "@npmcli/package-json": "^5.0.0", + "@npmcli/promise-spawn": "^7.0.0", + "node-gyp": "^10.0.0", + "proc-log": "^4.0.0", + "which": "^4.0.0" + }, "engines": { - "node": ">= 10" + "node": "^16.14.0 || >=18.0.0" } }, - "node_modules/@nx/nx-win32-arm64-msvc": { - "version": "17.2.8", - "resolved": "https://registry.npmjs.org/@nx/nx-win32-arm64-msvc/-/nx-win32-arm64-msvc-17.2.8.tgz", - "integrity": "sha512-XBWUY/F/GU3vKN9CAxeI15gM4kr3GOBqnzFZzoZC4qJt2hKSSUEWsMgeZtsMgeqEClbi4ZyCCkY7YJgU32WUGA==", - "cpu": [ - "arm64" - ], + "node_modules/@npmcli/run-script/node_modules/isexe": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz", + "integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==", "dev": true, - "optional": true, - "os": [ - "win32" - ], "engines": { - "node": ">= 10" + "node": ">=16" } }, - "node_modules/@nx/nx-win32-x64-msvc": { - "version": "17.2.8", - "resolved": "https://registry.npmjs.org/@nx/nx-win32-x64-msvc/-/nx-win32-x64-msvc-17.2.8.tgz", - "integrity": "sha512-HTqDv+JThlLzbcEm/3f+LbS5/wYQWzb5YDXbP1wi7nlCTihNZOLNqGOkEmwlrR5tAdNHPRpHSmkYg4305W0CtA==", - "cpu": [ - "x64" - ], + "node_modules/@npmcli/run-script/node_modules/which": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/which/-/which-4.0.0.tgz", + "integrity": "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==", "dev": true, - "optional": true, - "os": [ - "win32" - ], + "dependencies": { + "isexe": "^3.1.1" + }, + "bin": { + "node-which": "bin/which.js" + }, "engines": { - "node": ">= 10" + "node": "^16.13.0 || >=18.0.0" } }, "node_modules/@pkgjs/parseargs": { @@ -4880,9 +3995,9 @@ } }, "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.17.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.17.2.tgz", - "integrity": "sha512-NM0jFxY8bB8QLkoKxIQeObCaDlJKewVlIEkuyYKm5An1tdVZ966w2+MPQ2l8LBZLjR+SgyV+nRkTIunzOYBMLQ==", + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.22.4.tgz", + "integrity": "sha512-Fxamp4aEZnfPOcGA8KSNEohV8hX7zVHOemC8jVBoBUHu5zpJK/Eu3uJwt6BMgy9fkvzxDaurgj96F/NiLukF2w==", "cpu": [ "arm" ], @@ -4893,9 +4008,9 @@ ] }, "node_modules/@rollup/rollup-android-arm64": { - "version": "4.17.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.17.2.tgz", - "integrity": "sha512-yeX/Usk7daNIVwkq2uGoq2BYJKZY1JfyLTaHO/jaiSwi/lsf8fTFoQW/n6IdAsx5tx+iotu2zCJwz8MxI6D/Bw==", + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.22.4.tgz", + "integrity": "sha512-VXoK5UMrgECLYaMuGuVTOx5kcuap1Jm8g/M83RnCHBKOqvPPmROFJGQaZhGccnsFtfXQ3XYa4/jMCJvZnbJBdA==", "cpu": [ "arm64" ], @@ -4906,9 +4021,9 @@ ] }, "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.17.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.17.2.tgz", - "integrity": "sha512-kcMLpE6uCwls023+kknm71ug7MZOrtXo+y5p/tsg6jltpDtgQY1Eq5sGfHcQfb+lfuKwhBmEURDga9N0ol4YPw==", + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.22.4.tgz", + "integrity": "sha512-xMM9ORBqu81jyMKCDP+SZDhnX2QEVQzTcC6G18KlTQEzWK8r/oNZtKuZaCcHhnsa6fEeOBionoyl5JsAbE/36Q==", "cpu": [ "arm64" ], @@ -4919,9 +4034,9 @@ ] }, "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.17.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.17.2.tgz", - "integrity": "sha512-AtKwD0VEx0zWkL0ZjixEkp5tbNLzX+FCqGG1SvOu993HnSz4qDI6S4kGzubrEJAljpVkhRSlg5bzpV//E6ysTQ==", + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.22.4.tgz", + "integrity": "sha512-aJJyYKQwbHuhTUrjWjxEvGnNNBCnmpHDvrb8JFDbeSH3m2XdHcxDd3jthAzvmoI8w/kSjd2y0udT+4okADsZIw==", "cpu": [ "x64" ], @@ -4932,9 +4047,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.17.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.17.2.tgz", - "integrity": "sha512-3reX2fUHqN7sffBNqmEyMQVj/CKhIHZd4y631duy0hZqI8Qoqf6lTtmAKvJFYa6bhU95B1D0WgzHkmTg33In0A==", + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.22.4.tgz", + "integrity": "sha512-j63YtCIRAzbO+gC2L9dWXRh5BFetsv0j0va0Wi9epXDgU/XUi5dJKo4USTttVyK7fGw2nPWK0PbAvyliz50SCQ==", "cpu": [ "arm" ], @@ -4945,9 +4060,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.17.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.17.2.tgz", - "integrity": "sha512-uSqpsp91mheRgw96xtyAGP9FW5ChctTFEoXP0r5FAzj/3ZRv3Uxjtc7taRQSaQM/q85KEKjKsZuiZM3GyUivRg==", + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.22.4.tgz", + "integrity": "sha512-dJnWUgwWBX1YBRsuKKMOlXCzh2Wu1mlHzv20TpqEsfdZLb3WoJW2kIEsGwLkroYf24IrPAvOT/ZQ2OYMV6vlrg==", "cpu": [ "arm" ], @@ -4958,9 +4073,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.17.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.17.2.tgz", - "integrity": "sha512-EMMPHkiCRtE8Wdk3Qhtciq6BndLtstqZIroHiiGzB3C5LDJmIZcSzVtLRbwuXuUft1Cnv+9fxuDtDxz3k3EW2A==", + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.22.4.tgz", + "integrity": "sha512-AdPRoNi3NKVLolCN/Sp4F4N1d98c4SBnHMKoLuiG6RXgoZ4sllseuGioszumnPGmPM2O7qaAX/IJdeDU8f26Aw==", "cpu": [ "arm64" ], @@ -4971,9 +4086,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.17.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.17.2.tgz", - "integrity": "sha512-NMPylUUZ1i0z/xJUIx6VUhISZDRT+uTWpBcjdv0/zkp7b/bQDF+NfnfdzuTiB1G6HTodgoFa93hp0O1xl+/UbA==", + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.22.4.tgz", + "integrity": "sha512-Gl0AxBtDg8uoAn5CCqQDMqAx22Wx22pjDOjBdmG0VIWX3qUBHzYmOKh8KXHL4UpogfJ14G4wk16EQogF+v8hmA==", "cpu": [ "arm64" ], @@ -4984,9 +4099,9 @@ ] }, "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { - "version": "4.17.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.17.2.tgz", - "integrity": "sha512-T19My13y8uYXPw/L/k0JYaX1fJKFT/PWdXiHr8mTbXWxjVF1t+8Xl31DgBBvEKclw+1b00Chg0hxE2O7bTG7GQ==", + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.22.4.tgz", + "integrity": "sha512-3aVCK9xfWW1oGQpTsYJJPF6bfpWfhbRnhdlyhak2ZiyFLDaayz0EP5j9V1RVLAAxlmWKTDfS9wyRyY3hvhPoOg==", "cpu": [ "ppc64" ], @@ -4997,9 +4112,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.17.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.17.2.tgz", - "integrity": "sha512-BOaNfthf3X3fOWAB+IJ9kxTgPmMqPPH5f5k2DcCsRrBIbWnaJCgX2ll77dV1TdSy9SaXTR5iDXRL8n7AnoP5cg==", + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.22.4.tgz", + "integrity": "sha512-ePYIir6VYnhgv2C5Xe9u+ico4t8sZWXschR6fMgoPUK31yQu7hTEJb7bCqivHECwIClJfKgE7zYsh1qTP3WHUA==", "cpu": [ "riscv64" ], @@ -5010,9 +4125,9 @@ ] }, "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.17.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.17.2.tgz", - "integrity": "sha512-W0UP/x7bnn3xN2eYMql2T/+wpASLE5SjObXILTMPUBDB/Fg/FxC+gX4nvCfPBCbNhz51C+HcqQp2qQ4u25ok6g==", + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.22.4.tgz", + "integrity": "sha512-GqFJ9wLlbB9daxhVlrTe61vJtEY99/xB3C8e4ULVsVfflcpmR6c8UZXjtkMA6FhNONhj2eA5Tk9uAVw5orEs4Q==", "cpu": [ "s390x" ], @@ -5023,9 +4138,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.17.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.17.2.tgz", - "integrity": "sha512-Hy7pLwByUOuyaFC6mAr7m+oMC+V7qyifzs/nW2OJfC8H4hbCzOX07Ov0VFk/zP3kBsELWNFi7rJtgbKYsav9QQ==", + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.22.4.tgz", + "integrity": "sha512-87v0ol2sH9GE3cLQLNEy0K/R0pz1nvg76o8M5nhMR0+Q+BBGLnb35P0fVz4CQxHYXaAOhE8HhlkaZfsdUOlHwg==", "cpu": [ "x64" ], @@ -5036,9 +4151,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.17.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.17.2.tgz", - "integrity": "sha512-h1+yTWeYbRdAyJ/jMiVw0l6fOOm/0D1vNLui9iPuqgRGnXA0u21gAqOyB5iHjlM9MMfNOm9RHCQ7zLIzT0x11Q==", + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.22.4.tgz", + "integrity": "sha512-UV6FZMUgePDZrFjrNGIWzDo/vABebuXBhJEqrHxrGiU6HikPy0Z3LfdtciIttEUQfuDdCn8fqh7wiFJjCNwO+g==", "cpu": [ "x64" ], @@ -5049,9 +4164,9 @@ ] }, "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.17.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.17.2.tgz", - "integrity": "sha512-tmdtXMfKAjy5+IQsVtDiCfqbynAQE/TQRpWdVataHmhMb9DCoJxp9vLcCBjEQWMiUYxO1QprH/HbY9ragCEFLA==", + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.22.4.tgz", + "integrity": "sha512-BjI+NVVEGAXjGWYHz/vv0pBqfGoUH0IGZ0cICTn7kB9PyjrATSkX+8WkguNjWoj2qSr1im/+tTGRaY+4/PdcQw==", "cpu": [ "arm64" ], @@ -5062,9 +4177,9 @@ ] }, "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.17.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.17.2.tgz", - "integrity": "sha512-7II/QCSTAHuE5vdZaQEwJq2ZACkBpQDOmQsE6D6XUbnBHW8IAhm4eTufL6msLJorzrHDFv3CF8oCA/hSIRuZeQ==", + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.22.4.tgz", + "integrity": "sha512-SiWG/1TuUdPvYmzmYnmd3IEifzR61Tragkbx9D3+R8mzQqDBz8v+BvZNDlkiTtI9T15KYZhP0ehn3Dld4n9J5g==", "cpu": [ "ia32" ], @@ -5075,9 +4190,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.17.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.17.2.tgz", - "integrity": "sha512-TGGO7v7qOq4CYmSBVEYpI1Y5xDuCEnbVC5Vth8mOsW0gDSzxNrVERPc790IGHsrT2dQSimgMr9Ub3Y1Jci5/8w==", + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.22.4.tgz", + "integrity": "sha512-j8pPKp53/lq9lMXN57S8cFz0MynJk8OWNuUnXct/9KCpKU7DgU3bYMJhwWmcqC0UU29p8Lr0/7KEVcaM6bf47Q==", "cpu": [ "x64" ], @@ -5088,73 +4203,28 @@ ] }, "node_modules/@schematics/angular": { - "version": "17.0.10", - "resolved": "https://registry.npmjs.org/@schematics/angular/-/angular-17.0.10.tgz", - "integrity": "sha512-rRBlDMXfVPkW3CqVQxazFqkuJXd0BFnD1zjI9WtDiNt3o2pTHbLzuWJnXKuIt5rzv0x/bFwNqIt4CPW2DYGNMg==", - "dev": true, - "dependencies": { - "@angular-devkit/core": "17.0.10", - "@angular-devkit/schematics": "17.0.10", - "jsonc-parser": "3.2.0" - }, - "engines": { - "node": "^18.13.0 || >=20.9.0", - "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", - "yarn": ">= 1.13.0" - } - }, - "node_modules/@schematics/angular/node_modules/@angular-devkit/core": { - "version": "17.0.10", - "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-17.0.10.tgz", - "integrity": "sha512-93N6oHnmtRt0hL3AXxvnk47sN1rHndfj+pqI5haEY41AGWzIWv9cSBsqlM0PWltNpo6VivcExZESvbLJ71wqbQ==", + "version": "18.2.6", + "resolved": "https://registry.npmjs.org/@schematics/angular/-/angular-18.2.6.tgz", + "integrity": "sha512-Y988EoOEQDLEyHu3414T6AeVUyx21AexBHQNbUNQkK8cxlxyB6m1eH1cx6vFgLRFUTsLVv+C6Ln/ICNTfLcG4A==", "dev": true, "dependencies": { - "ajv": "8.12.0", - "ajv-formats": "2.1.1", - "jsonc-parser": "3.2.0", - "picomatch": "3.0.1", - "rxjs": "7.8.1", - "source-map": "0.7.4" + "@angular-devkit/core": "18.2.6", + "@angular-devkit/schematics": "18.2.6", + "jsonc-parser": "3.3.1" }, "engines": { - "node": "^18.13.0 || >=20.9.0", + "node": "^18.19.1 || ^20.11.1 || >=22.0.0", "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", "yarn": ">= 1.13.0" - }, - "peerDependencies": { - "chokidar": "^3.5.2" - }, - "peerDependenciesMeta": { - "chokidar": { - "optional": true - } - } - }, - "node_modules/@schematics/angular/node_modules/jsonc-parser": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.0.tgz", - "integrity": "sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==", - "dev": true - }, - "node_modules/@schematics/angular/node_modules/picomatch": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-3.0.1.tgz", - "integrity": "sha512-I3EurrIQMlRc9IaAZnqRR044Phh2DXY+55o7uJ0V+hYZAcQYSuFWsc9q5PvyDHUSCe1Qxn/iBz+78s86zWnGag==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" } }, "node_modules/@sigstore/bundle": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/@sigstore/bundle/-/bundle-2.3.1.tgz", - "integrity": "sha512-eqV17lO3EIFqCWK3969Rz+J8MYrRZKw9IBHpSo6DEcEX2c+uzDFOgHE9f2MnyDpfs48LFO4hXmk9KhQ74JzU1g==", + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/@sigstore/bundle/-/bundle-2.3.2.tgz", + "integrity": "sha512-wueKWDk70QixNLB363yHc2D2ItTgYiMTdPwK8D9dKQMR3ZQ0c35IxP5xnwQ8cNLoCgCRcHf14kE+CLIvNX1zmA==", "dev": true, "dependencies": { - "@sigstore/protobuf-specs": "^0.3.1" + "@sigstore/protobuf-specs": "^0.3.2" }, "engines": { "node": "^16.14.0 || >=18.0.0" @@ -5170,61 +4240,69 @@ } }, "node_modules/@sigstore/protobuf-specs": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/@sigstore/protobuf-specs/-/protobuf-specs-0.3.1.tgz", - "integrity": "sha512-aIL8Z9NsMr3C64jyQzE0XlkEyBLpgEJJFDHLVVStkFV5Q3Il/r/YtY6NJWKQ4cy4AE7spP1IX5Jq7VCAxHHMfQ==", + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@sigstore/protobuf-specs/-/protobuf-specs-0.3.2.tgz", + "integrity": "sha512-c6B0ehIWxMI8wiS/bj6rHMPqeFvngFV7cDU/MY+B16P9Z3Mp9k8L93eYZ7BYzSickzuqAQqAq0V956b3Ju6mLw==", "dev": true, "engines": { "node": "^16.14.0 || >=18.0.0" } }, "node_modules/@sigstore/sign": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@sigstore/sign/-/sign-2.3.0.tgz", - "integrity": "sha512-tsAyV6FC3R3pHmKS880IXcDJuiFJiKITO1jxR1qbplcsBkZLBmjrEw5GbC7ikD6f5RU1hr7WnmxB/2kKc1qUWQ==", + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/@sigstore/sign/-/sign-2.3.2.tgz", + "integrity": "sha512-5Vz5dPVuunIIvC5vBb0APwo7qKA4G9yM48kPWJT+OEERs40md5GoUR1yedwpekWZ4m0Hhw44m6zU+ObsON+iDA==", "dev": true, "dependencies": { - "@sigstore/bundle": "^2.3.0", + "@sigstore/bundle": "^2.3.2", "@sigstore/core": "^1.0.0", - "@sigstore/protobuf-specs": "^0.3.1", - "make-fetch-happen": "^13.0.0" + "@sigstore/protobuf-specs": "^0.3.2", + "make-fetch-happen": "^13.0.1", + "proc-log": "^4.2.0", + "promise-retry": "^2.0.1" }, "engines": { "node": "^16.14.0 || >=18.0.0" } }, "node_modules/@sigstore/tuf": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/@sigstore/tuf/-/tuf-2.3.2.tgz", - "integrity": "sha512-mwbY1VrEGU4CO55t+Kl6I7WZzIl+ysSzEYdA1Nv/FTrl2bkeaPXo5PnWZAVfcY2zSdhOpsUTJW67/M2zHXGn5w==", + "version": "2.3.4", + "resolved": "https://registry.npmjs.org/@sigstore/tuf/-/tuf-2.3.4.tgz", + "integrity": "sha512-44vtsveTPUpqhm9NCrbU8CWLe3Vck2HO1PNLw7RIajbB7xhtn5RBPm1VNSCMwqGYHhDsBJG8gDF0q4lgydsJvw==", "dev": true, "dependencies": { - "@sigstore/protobuf-specs": "^0.3.0", - "tuf-js": "^2.2.0" + "@sigstore/protobuf-specs": "^0.3.2", + "tuf-js": "^2.2.1" }, "engines": { "node": "^16.14.0 || >=18.0.0" } }, "node_modules/@sigstore/verify": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@sigstore/verify/-/verify-1.2.0.tgz", - "integrity": "sha512-hQF60nc9yab+Csi4AyoAmilGNfpXT+EXdBgFkP9OgPwIBPwyqVf7JAWPtmqrrrneTmAT6ojv7OlH1f6Ix5BG4Q==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@sigstore/verify/-/verify-1.2.1.tgz", + "integrity": "sha512-8iKx79/F73DKbGfRf7+t4dqrc0bRr0thdPrxAtCKWRm/F0tG71i6O1rvlnScncJLLBZHn3h8M3c1BSUAb9yu8g==", "dev": true, "dependencies": { - "@sigstore/bundle": "^2.3.1", + "@sigstore/bundle": "^2.3.2", "@sigstore/core": "^1.1.0", - "@sigstore/protobuf-specs": "^0.3.1" + "@sigstore/protobuf-specs": "^0.3.2" }, "engines": { "node": "^16.14.0 || >=18.0.0" } }, - "node_modules/@sinclair/typebox": { - "version": "0.27.8", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", - "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", - "dev": true + "node_modules/@sindresorhus/merge-streams": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/merge-streams/-/merge-streams-2.3.0.tgz", + "integrity": "sha512-LtoMMhxAlorcGhmFYI+LhPgbPZCkgP6ra1YL604EeF6U98pLlQ3iWIGMdWSC+vWmPBWBNgmDBAhnAobLROJmwg==", + "dev": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } }, "node_modules/@socket.io/component-emitter": { "version": "3.1.2", @@ -5254,21 +4332,6 @@ "node": "^16.14.0 || >=18.0.0" } }, - "node_modules/@tufjs/models/node_modules/minimatch": { - "version": "9.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.4.tgz", - "integrity": "sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==", - "dev": true, - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/@types/body-parser": { "version": "1.19.5", "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.5.tgz", @@ -5322,25 +4385,11 @@ "@types/node": "*" } }, - "node_modules/@types/eslint": { - "version": "8.56.10", - "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.56.10.tgz", - "integrity": "sha512-Shavhk87gCtY2fhXDctcfS3e6FdxWkCx1iUZ9eEUbh7rTqlZT0/IzOkCOVt0fCjcFuZ9FPYfuezTBImfHCDBGQ==", - "dev": true, - "dependencies": { - "@types/estree": "*", - "@types/json-schema": "*" - } - }, - "node_modules/@types/eslint-scope": { - "version": "3.7.7", - "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.7.tgz", - "integrity": "sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==", - "dev": true, - "dependencies": { - "@types/eslint": "*", - "@types/estree": "*" - } + "node_modules/@types/eslint-visitor-keys": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@types/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz", + "integrity": "sha512-OCutwjDZ4aFS6PB1UZ988C4YgwlBHJd6wCeQqaLdmadZ/7e+w79+hbMUFC1QXDNCmdyoRfAFdm0RypzwR+Qpag==", + "dev": true }, "node_modules/@types/estree": { "version": "1.0.5", @@ -5361,9 +4410,21 @@ } }, "node_modules/@types/express-serve-static-core": { - "version": "4.19.0", - "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.19.0.tgz", - "integrity": "sha512-bGyep3JqPCRry1wq+O5n7oiBgGWmeIJXPjXXCo8EK0u8duZGSYar7cGqd3ML2JUsLGeB7fmc06KYo9fLGWqPvQ==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-5.0.0.tgz", + "integrity": "sha512-AbXMTZGt40T+KON9/Fdxx0B2WK5hsgxcfXJLr5bFpZ7b4JCex2WyQPTEKdXqfHiY5nKKBScZ7yCoO6Pvgxfvnw==", + "dev": true, + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" + } + }, + "node_modules/@types/express/node_modules/@types/express-serve-static-core": { + "version": "4.19.6", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.19.6.tgz", + "integrity": "sha512-N4LZ2xG7DatVqhCZzOGb1Yi5lMbXSZcmdLDe9EzSndPV2HpWYWzRbaerl2n27irrm94EPpprqa8KpskPT085+A==", "dev": true, "dependencies": { "@types/node": "*", @@ -5379,9 +4440,9 @@ "dev": true }, "node_modules/@types/http-proxy": { - "version": "1.17.14", - "resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.14.tgz", - "integrity": "sha512-SSrD0c1OQzlFX7pGu1eXxSEjemej64aaNPRhhVYUGqXh0BtldAAx37MG8btcumvpgKyZp1F5Gn3JkktdxiFv6w==", + "version": "1.17.15", + "resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.15.tgz", + "integrity": "sha512-25g5atgiVNTIv0LBDTg1H74Hvayx0ajtJPLLcYE3whFv75J0pWNtOBzaXJQgDTmrX1bx5U9YC2w/n65BN1HwRQ==", "dev": true, "dependencies": { "@types/node": "*" @@ -5405,13 +4466,22 @@ "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==", "dev": true }, + "node_modules/@types/mute-stream": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/@types/mute-stream/-/mute-stream-0.0.4.tgz", + "integrity": "sha512-CPM9nzrCPPJHQNA9keH9CVkVI+WR5kMa+7XEs5jcGQ0VoAGnLv242w8lIVgwAEfmE4oufJRaTc9PNLQl0ioAow==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/node": { - "version": "20.12.10", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.12.10.tgz", - "integrity": "sha512-Eem5pH9pmWBHoGAT8Dr5fdc5rYA+4NAovdM4EktRPVAAiJhmWWfQrA0cFhAbOsQdSfIHjAud6YdkbL69+zSKjw==", + "version": "22.7.4", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.7.4.tgz", + "integrity": "sha512-y+NPi1rFzDs1NdQHHToqeiX2TIS79SWEAw9GYhkkx8bD0ChpfqC+n2j5OXOCpzfojBEBt6DnEnnG9MY0zk1XLg==", "dev": true, "dependencies": { - "undici-types": "~5.26.4" + "undici-types": "~6.19.2" } }, "node_modules/@types/node-forge": { @@ -5424,9 +4494,9 @@ } }, "node_modules/@types/qs": { - "version": "6.9.15", - "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.15.tgz", - "integrity": "sha512-uXHQKES6DQKKCLh441Xv/dwxOq1TVS3JPUMlEqoEglvlhR6Mxnlew/Xq/LRVHpLyk7iK3zODe1qYHIMltO7XGg==", + "version": "6.9.16", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.16.tgz", + "integrity": "sha512-7i+zxXdPD0T4cKDuxCUXJ4wHcsJLwENa6Z3dCu8cfCK743OGy5Nu1RmAGqDPsoTDINVEcdXKRvR/zre+P2Ku1A==", "dev": true }, "node_modules/@types/range-parser": { @@ -5436,15 +4506,9 @@ "dev": true }, "node_modules/@types/retry": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.0.tgz", - "integrity": "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==", - "dev": true - }, - "node_modules/@types/semver": { - "version": "7.5.8", - "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.8.tgz", - "integrity": "sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ==", + "version": "0.12.2", + "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.2.tgz", + "integrity": "sha512-XISRgDJ2Tc5q4TRqvgJtzsRkFYNJzZrhTdtMoGVBttwzzQJkPnS3WWTFc7kuDRoPtPakl+T+OfdEUjYJj7Jbow==", "dev": true }, "node_modules/@types/send": { @@ -5486,43 +4550,47 @@ "@types/node": "*" } }, + "node_modules/@types/wrap-ansi": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/wrap-ansi/-/wrap-ansi-3.0.0.tgz", + "integrity": "sha512-ltIpx+kM7g/MLRZfkbL7EsCEjfzCcScLpkg37eXEtx5kmrAKBkTJwd1GIAjDSL8wTpM6Hzn5YO4pSb91BEwu1g==", + "dev": true + }, "node_modules/@types/ws": { - "version": "8.5.10", - "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.10.tgz", - "integrity": "sha512-vmQSUcfalpIq0R9q7uTo2lXs6eGIpt9wtnLdMv9LVpIjCA/+ufZRozlVoVelIYixx1ugCBKDhn89vnsEGOCx9A==", + "version": "8.5.12", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.12.tgz", + "integrity": "sha512-3tPRkv1EtkDpzlgyKyI8pGsGZAGPEaXeu0DOj5DI25Ja91bdAYddYHbADRYVrZMRbfW+1l5YwXVDKohDJNQxkQ==", "dev": true, "dependencies": { "@types/node": "*" } }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.21.0.tgz", - "integrity": "sha512-oy9+hTPCUFpngkEZUSzbf9MxI65wbKFoQYsgPdILTfbUldp5ovUuphZVe4i30emU9M/kP+T64Di0mxl7dSw3MA==", + "version": "8.8.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.8.0.tgz", + "integrity": "sha512-wORFWjU30B2WJ/aXBfOm1LX9v9nyt9D3jsSOxC3cCaTQGCW5k4jNpmjFv3U7p/7s4yvdjHzwtv2Sd2dOyhjS0A==", "dev": true, "dependencies": { - "@eslint-community/regexpp": "^4.5.1", - "@typescript-eslint/scope-manager": "6.21.0", - "@typescript-eslint/type-utils": "6.21.0", - "@typescript-eslint/utils": "6.21.0", - "@typescript-eslint/visitor-keys": "6.21.0", - "debug": "^4.3.4", + "@eslint-community/regexpp": "^4.10.0", + "@typescript-eslint/scope-manager": "8.8.0", + "@typescript-eslint/type-utils": "8.8.0", + "@typescript-eslint/utils": "8.8.0", + "@typescript-eslint/visitor-keys": "8.8.0", "graphemer": "^1.4.0", - "ignore": "^5.2.4", + "ignore": "^5.3.1", "natural-compare": "^1.4.0", - "semver": "^7.5.4", - "ts-api-utils": "^1.0.1" + "ts-api-utils": "^1.3.0" }, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "@typescript-eslint/parser": "^6.0.0 || ^6.0.0-alpha", - "eslint": "^7.0.0 || ^8.0.0" + "@typescript-eslint/parser": "^8.0.0 || ^8.0.0-alpha.0", + "eslint": "^8.57.0 || ^9.0.0" }, "peerDependenciesMeta": { "typescript": { @@ -5530,301 +4598,216 @@ } } }, - "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/type-utils": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.21.0.tgz", - "integrity": "sha512-rZQI7wHfao8qMX3Rd3xqeYSMCL3SoiSQLBATSiVKARdFGCYSRvmViieZjqc58jKgs8Y8i9YvVVhRbHSTA4VBag==", + "node_modules/@typescript-eslint/experimental-utils": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-3.10.1.tgz", + "integrity": "sha512-DewqIgscDzmAfd5nOGe4zm6Bl7PKtMG2Ad0KG8CUZAHlXfAKTF9Ol5PXhiMh39yRL2ChRH1cuuUGOcVyyrhQIw==", "dev": true, "dependencies": { - "@typescript-eslint/typescript-estree": "6.21.0", - "@typescript-eslint/utils": "6.21.0", - "debug": "^4.3.4", - "ts-api-utils": "^1.0.1" + "@types/json-schema": "^7.0.3", + "@typescript-eslint/types": "3.10.1", + "@typescript-eslint/typescript-estree": "3.10.1", + "eslint-scope": "^5.0.0", + "eslint-utils": "^2.0.0" }, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^10.12.0 || >=12.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^7.0.0 || ^8.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "eslint": "*" } }, - "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/utils": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.21.0.tgz", - "integrity": "sha512-NfWVaC8HP9T8cbKQxHcsJBY5YE1O33+jpMwN45qzWWaPDZgLIbo12toGMWnmhvCpd3sIxkpDw3Wv1B3dYrbDQQ==", + "node_modules/@typescript-eslint/experimental-utils/node_modules/@typescript-eslint/types": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-3.10.1.tgz", + "integrity": "sha512-+3+FCUJIahE9q0lDi1WleYzjCwJs5hIsbugIgnbB+dSCYUxl8L6PwmsyOPFZde2hc1DlTo/xnkOgiTLSyAbHiQ==", "dev": true, - "dependencies": { - "@eslint-community/eslint-utils": "^4.4.0", - "@types/json-schema": "^7.0.12", - "@types/semver": "^7.5.0", - "@typescript-eslint/scope-manager": "6.21.0", - "@typescript-eslint/types": "6.21.0", - "@typescript-eslint/typescript-estree": "6.21.0", - "semver": "^7.5.4" - }, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^8.10.0 || ^10.13.0 || >=11.10.1" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^7.0.0 || ^8.0.0" } }, - "node_modules/@typescript-eslint/parser": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.21.0.tgz", - "integrity": "sha512-tbsV1jPne5CkFQCgPBcDOt30ItF7aJoZL997JSF7MhGQqOeT3svWRYxiqlfA5RUdlHN6Fi+EI9bxqbdyAUZjYQ==", + "node_modules/@typescript-eslint/experimental-utils/node_modules/@typescript-eslint/typescript-estree": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-3.10.1.tgz", + "integrity": "sha512-QbcXOuq6WYvnB3XPsZpIwztBoquEYLXh2MtwVU+kO8jgYCiv4G5xrSP/1wg4tkvrEE+esZVquIPX/dxPlePk1w==", "dev": true, "dependencies": { - "@typescript-eslint/scope-manager": "6.21.0", - "@typescript-eslint/types": "6.21.0", - "@typescript-eslint/typescript-estree": "6.21.0", - "@typescript-eslint/visitor-keys": "6.21.0", - "debug": "^4.3.4" + "@typescript-eslint/types": "3.10.1", + "@typescript-eslint/visitor-keys": "3.10.1", + "debug": "^4.1.1", + "glob": "^7.1.6", + "is-glob": "^4.0.1", + "lodash": "^4.17.15", + "semver": "^7.3.2", + "tsutils": "^3.17.1" }, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^10.12.0 || >=12.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, - "peerDependencies": { - "eslint": "^7.0.0 || ^8.0.0" - }, "peerDependenciesMeta": { "typescript": { "optional": true } } }, - "node_modules/@typescript-eslint/scope-manager": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.21.0.tgz", - "integrity": "sha512-OwLUIWZJry80O99zvqXVEioyniJMa+d2GrqpUTqi5/v5D5rOrppJVBPa0yKCblcigC0/aYAzxxqQ1B+DS2RYsg==", - "dev": true, - "dependencies": { - "@typescript-eslint/types": "6.21.0", - "@typescript-eslint/visitor-keys": "6.21.0" - }, - "engines": { - "node": "^16.0.0 || >=18.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/type-utils": { - "version": "6.18.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.18.0.tgz", - "integrity": "sha512-ZeMtrXnGmTcHciJN1+u2CigWEEXgy1ufoxtWcHORt5kGvpjjIlK9MUhzHm4RM8iVy6dqSaZA/6PVkX6+r+ChjQ==", + "node_modules/@typescript-eslint/experimental-utils/node_modules/@typescript-eslint/visitor-keys": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-3.10.1.tgz", + "integrity": "sha512-9JgC82AaQeglebjZMgYR5wgmfUdUc+EitGUUMW8u2nDckaeimzW+VsoLV6FoimPv2id3VQzfjwBxEMVz08ameQ==", "dev": true, "dependencies": { - "@typescript-eslint/typescript-estree": "6.18.0", - "@typescript-eslint/utils": "6.18.0", - "debug": "^4.3.4", - "ts-api-utils": "^1.0.1" - }, - "engines": { - "node": "^16.0.0 || >=18.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^7.0.0 || ^8.0.0" + "eslint-visitor-keys": "^1.1.0" }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/types": { - "version": "6.18.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.18.0.tgz", - "integrity": "sha512-/RFVIccwkwSdW/1zeMx3hADShWbgBxBnV/qSrex6607isYjj05t36P6LyONgqdUrNLl5TYU8NIKdHUYpFvExkA==", - "dev": true, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^8.10.0 || ^10.13.0 || >=11.10.1" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" } }, - "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/typescript-estree": { - "version": "6.18.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.18.0.tgz", - "integrity": "sha512-klNvl+Ql4NsBNGB4W9TZ2Od03lm7aGvTbs0wYaFYsplVPhr+oeXjlPZCDI4U9jgJIDK38W1FKhacCFzCC+nbIg==", + "node_modules/@typescript-eslint/experimental-utils/node_modules/eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", "dev": true, "dependencies": { - "@typescript-eslint/types": "6.18.0", - "@typescript-eslint/visitor-keys": "6.18.0", - "debug": "^4.3.4", - "globby": "^11.1.0", - "is-glob": "^4.0.3", - "minimatch": "9.0.3", - "semver": "^7.5.4", - "ts-api-utils": "^1.0.1" + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" }, "engines": { - "node": "^16.0.0 || >=18.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "node": ">=8.0.0" } }, - "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/visitor-keys": { - "version": "6.18.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.18.0.tgz", - "integrity": "sha512-1wetAlSZpewRDb2h9p/Q8kRjdGuqdTAQbkJIOUMLug2LBLG+QOjiWoSj6/3B/hA9/tVTFFdtiKvAYoYnSRW/RA==", + "node_modules/@typescript-eslint/experimental-utils/node_modules/eslint-visitor-keys": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", + "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", "dev": true, - "dependencies": { - "@typescript-eslint/types": "6.18.0", - "eslint-visitor-keys": "^3.4.1" - }, "engines": { - "node": "^16.0.0 || >=18.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" + "node": ">=4" } }, - "node_modules/@typescript-eslint/types": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.21.0.tgz", - "integrity": "sha512-1kFmZ1rOm5epu9NZEZm1kckCDGj5UJEf7P1kliH4LKu/RkwpsfqqGmY2OOcUs18lSlQBKLDYBOGxRVtrMN5lpg==", + "node_modules/@typescript-eslint/experimental-utils/node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", "dev": true, "engines": { - "node": "^16.0.0 || >=18.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" + "node": ">=4.0" } }, - "node_modules/@typescript-eslint/typescript-estree": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.21.0.tgz", - "integrity": "sha512-6npJTkZcO+y2/kr+z0hc4HwNfrrP4kNYh57ek7yCNlrBjWQ1Y0OS7jiZTkgumrvkX5HkEKXFZkkdFNkaW2wmUQ==", + "node_modules/@typescript-eslint/parser": { + "version": "8.8.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.8.0.tgz", + "integrity": "sha512-uEFUsgR+tl8GmzmLjRqz+VrDv4eoaMqMXW7ruXfgThaAShO9JTciKpEsB+TvnfFfbg5IpujgMXVV36gOJRLtZg==", "dev": true, "dependencies": { - "@typescript-eslint/types": "6.21.0", - "@typescript-eslint/visitor-keys": "6.21.0", - "debug": "^4.3.4", - "globby": "^11.1.0", - "is-glob": "^4.0.3", - "minimatch": "9.0.3", - "semver": "^7.5.4", - "ts-api-utils": "^1.0.1" + "@typescript-eslint/scope-manager": "8.8.0", + "@typescript-eslint/types": "8.8.0", + "@typescript-eslint/typescript-estree": "8.8.0", + "@typescript-eslint/visitor-keys": "8.8.0", + "debug": "^4.3.4" }, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0" + }, "peerDependenciesMeta": { "typescript": { "optional": true } } }, - "node_modules/@typescript-eslint/utils": { - "version": "6.18.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.18.0.tgz", - "integrity": "sha512-wiKKCbUeDPGaYEYQh1S580dGxJ/V9HI7K5sbGAVklyf+o5g3O+adnS4UNJajplF4e7z2q0uVBaTdT/yLb4XAVA==", + "node_modules/@typescript-eslint/scope-manager": { + "version": "8.8.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.8.0.tgz", + "integrity": "sha512-EL8eaGC6gx3jDd8GwEFEV091210U97J0jeEHrAYvIYosmEGet4wJ+g0SYmLu+oRiAwbSA5AVrt6DxLHfdd+bUg==", "dev": true, "dependencies": { - "@eslint-community/eslint-utils": "^4.4.0", - "@types/json-schema": "^7.0.12", - "@types/semver": "^7.5.0", - "@typescript-eslint/scope-manager": "6.18.0", - "@typescript-eslint/types": "6.18.0", - "@typescript-eslint/typescript-estree": "6.18.0", - "semver": "^7.5.4" + "@typescript-eslint/types": "8.8.0", + "@typescript-eslint/visitor-keys": "8.8.0" }, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^7.0.0 || ^8.0.0" } }, - "node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/scope-manager": { - "version": "6.18.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.18.0.tgz", - "integrity": "sha512-o/UoDT2NgOJ2VfHpfr+KBY2ErWvCySNUIX/X7O9g8Zzt/tXdpfEU43qbNk8LVuWUT2E0ptzTWXh79i74PP0twA==", + "node_modules/@typescript-eslint/type-utils": { + "version": "8.8.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.8.0.tgz", + "integrity": "sha512-IKwJSS7bCqyCeG4NVGxnOP6lLT9Okc3Zj8hLO96bpMkJab+10HIfJbMouLrlpyOr3yrQ1cA413YPFiGd1mW9/Q==", "dev": true, "dependencies": { - "@typescript-eslint/types": "6.18.0", - "@typescript-eslint/visitor-keys": "6.18.0" + "@typescript-eslint/typescript-estree": "8.8.0", + "@typescript-eslint/utils": "8.8.0", + "debug": "^4.3.4", + "ts-api-utils": "^1.3.0" }, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } } }, - "node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/types": { - "version": "6.18.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.18.0.tgz", - "integrity": "sha512-/RFVIccwkwSdW/1zeMx3hADShWbgBxBnV/qSrex6607isYjj05t36P6LyONgqdUrNLl5TYU8NIKdHUYpFvExkA==", + "node_modules/@typescript-eslint/types": { + "version": "8.8.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.8.0.tgz", + "integrity": "sha512-QJwc50hRCgBd/k12sTykOJbESe1RrzmX6COk8Y525C9l7oweZ+1lw9JiU56im7Amm8swlz00DRIlxMYLizr2Vw==", "dev": true, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" } }, - "node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/typescript-estree": { - "version": "6.18.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.18.0.tgz", - "integrity": "sha512-klNvl+Ql4NsBNGB4W9TZ2Od03lm7aGvTbs0wYaFYsplVPhr+oeXjlPZCDI4U9jgJIDK38W1FKhacCFzCC+nbIg==", + "node_modules/@typescript-eslint/typescript-estree": { + "version": "8.8.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.8.0.tgz", + "integrity": "sha512-ZaMJwc/0ckLz5DaAZ+pNLmHv8AMVGtfWxZe/x2JVEkD5LnmhWiQMMcYT7IY7gkdJuzJ9P14fRy28lUrlDSWYdw==", "dev": true, "dependencies": { - "@typescript-eslint/types": "6.18.0", - "@typescript-eslint/visitor-keys": "6.18.0", + "@typescript-eslint/types": "8.8.0", + "@typescript-eslint/visitor-keys": "8.8.0", "debug": "^4.3.4", - "globby": "^11.1.0", + "fast-glob": "^3.3.2", "is-glob": "^4.0.3", - "minimatch": "9.0.3", - "semver": "^7.5.4", - "ts-api-utils": "^1.0.1" + "minimatch": "^9.0.4", + "semver": "^7.6.0", + "ts-api-utils": "^1.3.0" }, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", @@ -5836,34 +4819,39 @@ } } }, - "node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/visitor-keys": { - "version": "6.18.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.18.0.tgz", - "integrity": "sha512-1wetAlSZpewRDb2h9p/Q8kRjdGuqdTAQbkJIOUMLug2LBLG+QOjiWoSj6/3B/hA9/tVTFFdtiKvAYoYnSRW/RA==", + "node_modules/@typescript-eslint/utils": { + "version": "8.8.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.8.0.tgz", + "integrity": "sha512-QE2MgfOTem00qrlPgyByaCHay9yb1+9BjnMFnSFkUKQfu7adBXDTnCAivURnuPPAG/qiB+kzKkZKmKfaMT0zVg==", "dev": true, "dependencies": { - "@typescript-eslint/types": "6.18.0", - "eslint-visitor-keys": "^3.4.1" + "@eslint-community/eslint-utils": "^4.4.0", + "@typescript-eslint/scope-manager": "8.8.0", + "@typescript-eslint/types": "8.8.0", + "@typescript-eslint/typescript-estree": "8.8.0" }, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0" } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.21.0.tgz", - "integrity": "sha512-JJtkDduxLi9bivAB+cYOVMtbkqdPOhZ+ZI5LC47MIRrDV4Yn2o+ZnW10Nkmr28xRpSpdJ6Sm42Hjf2+REYXm0A==", + "version": "8.8.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.8.0.tgz", + "integrity": "sha512-8mq51Lx6Hpmd7HnA2fcHQo3YgfX1qbccxQOgZcb4tvasu//zXRaA1j5ZRFeCw/VRAdFi4mRM9DnZw0Nu0Q2d1g==", "dev": true, "dependencies": { - "@typescript-eslint/types": "6.21.0", - "eslint-visitor-keys": "^3.4.1" + "@typescript-eslint/types": "8.8.0", + "eslint-visitor-keys": "^3.4.3" }, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", @@ -6052,37 +5040,6 @@ "integrity": "sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ==", "dev": true }, - "node_modules/@yarnpkg/parsers": { - "version": "3.0.0-rc.46", - "resolved": "https://registry.npmjs.org/@yarnpkg/parsers/-/parsers-3.0.0-rc.46.tgz", - "integrity": "sha512-aiATs7pSutzda/rq8fnuPwTglyVwjM22bNnK2ZgjrpAjQHSSl3lztd2f9evst1W/qnC58DRz7T7QndUDumAR4Q==", - "dev": true, - "dependencies": { - "js-yaml": "^3.10.0", - "tslib": "^2.4.0" - }, - "engines": { - "node": ">=14.15.0" - } - }, - "node_modules/@zkochan/js-yaml": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/@zkochan/js-yaml/-/js-yaml-0.0.6.tgz", - "integrity": "sha512-nzvgl3VfhcELQ8LyVrYOru+UtAy1nrygk2+AGbTm8a5YcO6o8lSjAT+pfg3vJWxIoZKOUhrK6UU7xW/+00kQrg==", - "dev": true, - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/@zkochan/js-yaml/node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true - }, "node_modules/abbrev": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-2.0.0.tgz", @@ -6106,9 +5063,9 @@ } }, "node_modules/acorn": { - "version": "8.11.3", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", - "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==", + "version": "8.12.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.1.tgz", + "integrity": "sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==", "dev": true, "bin": { "acorn": "bin/acorn" @@ -6117,10 +5074,10 @@ "node": ">=0.4.0" } }, - "node_modules/acorn-import-assertions": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.9.0.tgz", - "integrity": "sha512-cmMwop9x+8KFhxvKrKfPYmN6/pKTYYHBqLa0DfvVZcKMJWNyWLnaqND7dx/qn66R7ewM1UX5XMaDVP5wlVTaVA==", + "node_modules/acorn-import-attributes": { + "version": "1.9.5", + "resolved": "https://registry.npmjs.org/acorn-import-attributes/-/acorn-import-attributes-1.9.5.tgz", + "integrity": "sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ==", "dev": true, "peerDependencies": { "acorn": "^8" @@ -6188,15 +5145,15 @@ } }, "node_modules/ajv": { - "version": "8.12.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", - "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", + "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", "dev": true, "dependencies": { - "fast-deep-equal": "^3.1.1", + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" + "require-from-string": "^2.0.2" }, "funding": { "type": "github", @@ -6204,9 +5161,9 @@ } }, "node_modules/ajv-formats": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", - "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz", + "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==", "dev": true, "dependencies": { "ajv": "^8.0.0" @@ -6269,12 +5226,12 @@ } }, "node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.1.tgz", + "integrity": "sha512-+O9Jct8wf++lXxxFc4hc8LsjaSq0HFzzL7cVsw8pRDIPdjKD2mT4ytDZlLuSBZ4cLKZFXIrMGO7DbQCtMJJMKw==", "dev": true, "engines": { - "node": ">=8" + "node": ">=4" } }, "node_modules/ansi-styles": { @@ -6315,13 +5272,10 @@ } }, "node_modules/argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "dev": true, - "dependencies": { - "sprintf-js": "~1.0.2" - } + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true }, "node_modules/aria-query": { "version": "5.3.0", @@ -6338,31 +5292,19 @@ "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", "dev": true }, - "node_modules/array-union": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", - "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "node_modules/astral-regex": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", + "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", "dev": true, "engines": { "node": ">=8" } }, - "node_modules/async": { - "version": "3.2.5", - "resolved": "https://registry.npmjs.org/async/-/async-3.2.5.tgz", - "integrity": "sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg==", - "dev": true - }, - "node_modules/asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", - "dev": true - }, "node_modules/autoprefixer": { - "version": "10.4.18", - "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.18.tgz", - "integrity": "sha512-1DKbDfsr6KUElM6wg+0zRNkB/Q7WcKYAaK+pzXn+Xqmszm/5Xa9coeNdtP88Vi+dPzZnMjhge8GIV49ZQkDa+g==", + "version": "10.4.20", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.20.tgz", + "integrity": "sha512-XY25y5xSv/wEoqzDyXXME4AFfkZI0P23z6Fs3YgymDnKJkCGOnkL0iTxCa85UTqaSgfcqyf3UA6+c7wUvx/16g==", "dev": true, "funding": [ { @@ -6379,11 +5321,11 @@ } ], "dependencies": { - "browserslist": "^4.23.0", - "caniuse-lite": "^1.0.30001591", + "browserslist": "^4.23.3", + "caniuse-lite": "^1.0.30001646", "fraction.js": "^4.3.7", "normalize-range": "^0.1.2", - "picocolors": "^1.0.0", + "picocolors": "^1.0.1", "postcss-value-parser": "^4.2.0" }, "bin": { @@ -6396,24 +5338,13 @@ "postcss": "^8.1.0" } }, - "node_modules/axios": { - "version": "1.6.8", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.8.tgz", - "integrity": "sha512-v/ZHtJDU39mDpyBoFVkETcd/uNdxrWRrg3bKpOKzXFA6Bvqopts6ALSMU3y6ijYxbw2B+wPrIv46egTzJXCLGQ==", - "dev": true, - "dependencies": { - "follow-redirects": "^1.15.6", - "form-data": "^4.0.0", - "proxy-from-env": "^1.1.0" - } - }, "node_modules/axobject-query": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.0.0.tgz", - "integrity": "sha512-+60uv1hiVFhHZeO+Lz0RYzsVHy5Wr1ayX0mwda9KPDVLNJgZ1T9Ny7VmFbLDzxsH0D87I86vgj3gFrjTJUYznw==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz", + "integrity": "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==", "dev": true, - "dependencies": { - "dequal": "^2.0.3" + "engines": { + "node": ">= 0.4" } }, "node_modules/babel-loader": { @@ -6433,22 +5364,6 @@ "webpack": ">=5" } }, - "node_modules/babel-plugin-istanbul": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", - "integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.0.0", - "@istanbuljs/load-nyc-config": "^1.0.0", - "@istanbuljs/schema": "^0.1.2", - "istanbul-lib-instrument": "^5.0.4", - "test-exclude": "^6.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/babel-plugin-polyfill-corejs2": { "version": "0.4.11", "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.11.tgz", @@ -6473,57 +5388,25 @@ } }, "node_modules/babel-plugin-polyfill-corejs3": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.9.0.tgz", - "integrity": "sha512-7nZPG1uzK2Ymhy/NbaOWTg3uibM2BmGASS4vHS4szRZAIR8R6GwA/xAujpdrXU5iyklrimWnLWU+BLF9suPTqg==", - "dev": true, - "dependencies": { - "@babel/helper-define-polyfill-provider": "^0.5.0", - "core-js-compat": "^3.34.0" - }, - "peerDependencies": { - "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" - } - }, - "node_modules/babel-plugin-polyfill-corejs3/node_modules/@babel/helper-define-polyfill-provider": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.5.0.tgz", - "integrity": "sha512-NovQquuQLAQ5HuyjCz7WQP9MjRj7dx++yspwiyUiGl9ZyadHRSql1HZh5ogRd8W8w6YM6EQ/NTB8rgjLt5W65Q==", + "version": "0.10.6", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.10.6.tgz", + "integrity": "sha512-b37+KR2i/khY5sKmWNVQAnitvquQbNdWy6lJdsr0kmquCKEEUgMKK4SboVM3HtfnZilfjr4MMQ7vY58FVWDtIA==", "dev": true, "dependencies": { - "@babel/helper-compilation-targets": "^7.22.6", - "@babel/helper-plugin-utils": "^7.22.5", - "debug": "^4.1.1", - "lodash.debounce": "^4.0.8", - "resolve": "^1.14.2" + "@babel/helper-define-polyfill-provider": "^0.6.2", + "core-js-compat": "^3.38.0" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, "node_modules/babel-plugin-polyfill-regenerator": { - "version": "0.5.5", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.5.5.tgz", - "integrity": "sha512-OJGYZlhLqBh2DDHeqAxWB1XIvr49CxiJ2gIt61/PU55CQK4Z58OzMqjDe1zwQdQk+rBYsRc+1rJmdajM3gimHg==", - "dev": true, - "dependencies": { - "@babel/helper-define-polyfill-provider": "^0.5.0" - }, - "peerDependencies": { - "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" - } - }, - "node_modules/babel-plugin-polyfill-regenerator/node_modules/@babel/helper-define-polyfill-provider": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.5.0.tgz", - "integrity": "sha512-NovQquuQLAQ5HuyjCz7WQP9MjRj7dx++yspwiyUiGl9ZyadHRSql1HZh5ogRd8W8w6YM6EQ/NTB8rgjLt5W65Q==", + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.2.tgz", + "integrity": "sha512-2R25rQZWP63nGwaAswvDazbPXfrM3HwVoBXK6HcqeKrSrL/JqcC/rDcf95l4r7LXLyxDXc8uQDa064GubtCABg==", "dev": true, "dependencies": { - "@babel/helper-compilation-targets": "^7.22.6", - "@babel/helper-plugin-utils": "^7.22.5", - "debug": "^4.1.1", - "lodash.debounce": "^4.0.8", - "resolve": "^1.14.2" + "@babel/helper-define-polyfill-provider": "^0.6.2" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" @@ -6600,9 +5483,9 @@ } }, "node_modules/body-parser": { - "version": "1.20.2", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz", - "integrity": "sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==", + "version": "1.20.3", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", + "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", "dev": true, "dependencies": { "bytes": "3.1.2", @@ -6613,7 +5496,7 @@ "http-errors": "2.0.0", "iconv-lite": "0.4.24", "on-finished": "2.4.1", - "qs": "6.11.0", + "qs": "6.13.0", "raw-body": "2.5.2", "type-is": "~1.6.18", "unpipe": "1.0.0" @@ -6676,9 +5559,9 @@ } }, "node_modules/browserslist": { - "version": "4.23.0", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.0.tgz", - "integrity": "sha512-QW8HiM1shhT2GuzkvklfjcKDiWFXHOeFCIA/huJPwHsslwcydgk7X+z2zXpEijP98UCY7HbubZt5J2Zgvf0CaQ==", + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.0.tgz", + "integrity": "sha512-Rmb62sR1Zpjql25eSanFGEhAxcFwfA1K0GuQcLoaJBAcENegrQut3hYdhXFF1obQfiDyqIW/cLM5HSJ/9k884A==", "dev": true, "funding": [ { @@ -6695,10 +5578,10 @@ } ], "dependencies": { - "caniuse-lite": "^1.0.30001587", - "electron-to-chromium": "^1.4.668", - "node-releases": "^2.0.14", - "update-browserslist-db": "^1.0.13" + "caniuse-lite": "^1.0.30001663", + "electron-to-chromium": "^1.5.28", + "node-releases": "^2.0.18", + "update-browserslist-db": "^1.1.0" }, "bin": { "browserslist": "cli.js" @@ -6735,6 +5618,21 @@ "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==" }, + "node_modules/bundle-name": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bundle-name/-/bundle-name-4.1.0.tgz", + "integrity": "sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==", + "dev": true, + "dependencies": { + "run-applescript": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "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", @@ -6745,9 +5643,9 @@ } }, "node_modules/cacache": { - "version": "18.0.3", - "resolved": "https://registry.npmjs.org/cacache/-/cacache-18.0.3.tgz", - "integrity": "sha512-qXCd4rh6I07cnDqh8V48/94Tc/WSfj+o3Gn6NZ0aZovS255bUx8O13uKxRFd2eWG0xgsco7+YItQNPaa5E85hg==", + "version": "18.0.4", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-18.0.4.tgz", + "integrity": "sha512-B+L5iIa9mgcjLbliir2th36yEwPftrzteHYujzsx3dFP/31GCHcIeS8f5MGd80odLOjaOvSpU3EEAmRQptkxLQ==", "dev": true, "dependencies": { "@npmcli/fs": "^3.1.0", @@ -6768,35 +5666,30 @@ } }, "node_modules/cacache/node_modules/glob": { - "version": "10.3.12", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.12.tgz", - "integrity": "sha512-TCNv8vJ+xz4QiqTpfOJA7HvYv+tNIRHKfUWw/q+v2jdgN4ebz+KY9tGx5J4rHP0o84mNP+ApH66HRX8us3Khqg==", + "version": "10.4.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", + "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", "dev": true, "dependencies": { "foreground-child": "^3.1.0", - "jackspeak": "^2.3.6", - "minimatch": "^9.0.1", - "minipass": "^7.0.4", - "path-scurry": "^1.10.2" + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" }, "bin": { "glob": "dist/esm/bin.mjs" }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, "funding": { "url": "https://github.com/sponsors/isaacs" } }, "node_modules/cacache/node_modules/lru-cache": { - "version": "10.2.2", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.2.2.tgz", - "integrity": "sha512-9hp3Vp2/hFQUiIwKo8XCeFVnrg8Pk3TYNPIR7tJADKi5YfcF7vEaK7avFHTlSy3kOKYaJQaalfEo6YuXdceBOQ==", - "dev": true, - "engines": { - "node": "14 || >=16.14" - } + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true }, "node_modules/call-bind": { "version": "1.0.7", @@ -6826,19 +5719,10 @@ "node": ">=6" } }, - "node_modules/camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "dev": true, - "engines": { - "node": ">=6" - } - }, "node_modules/caniuse-lite": { - "version": "1.0.30001616", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001616.tgz", - "integrity": "sha512-RHVYKov7IcdNjVHJFNY/78RdG4oGVjbayxv8u5IO74Wv7Hlq4PnJE6mo/OjFijjVFNy5ijnCt6H3IIo4t+wfEw==", + "version": "1.0.30001664", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001664.tgz", + "integrity": "sha512-AmE7k4dXiNKQipgn7a2xg558IRqPN3jMQY/rOsbxDhrd0tyChwbITBfiwtnqz8bi2M5mIWbxAYBvk7W7QBUS2g==", "dev": true, "funding": [ { @@ -6909,9 +5793,9 @@ } }, "node_modules/chrome-trace-event": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz", - "integrity": "sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg==", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.4.tgz", + "integrity": "sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ==", "dev": true, "engines": { "node": ">=6.0" @@ -6927,21 +5811,24 @@ } }, "node_modules/cli-cursor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", - "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-5.0.0.tgz", + "integrity": "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==", "dev": true, "dependencies": { - "restore-cursor": "^3.1.0" + "restore-cursor": "^5.0.0" }, "engines": { - "node": ">=8" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/cli-spinners": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.6.1.tgz", - "integrity": "sha512-x/5fWmGMnbKQAaNwN+UZlV79qBLM9JFnJuJ03gIi5whrob0xV0ofNVHy9DhwGdsMJQc2OKv0oGmLzvaqvAVv+g==", + "version": "2.9.2", + "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz", + "integrity": "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==", "dev": true, "engines": { "node": ">=6" @@ -6950,6 +5837,22 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/cli-truncate": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-4.0.0.tgz", + "integrity": "sha512-nPdaFdQ0h/GEigbPClz11D0v/ZJEwxmeVZGeMo3Z5StPtUTkA9o1lD6QwoirYiSDzbcwn2XcjwmCp68W1IS4TA==", + "dev": true, + "dependencies": { + "slice-ansi": "^5.0.0", + "string-width": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/cli-width": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-4.1.0.tgz", @@ -7006,6 +5909,35 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, + "node_modules/cliui/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/cliui/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/cliui/node_modules/wrap-ansi": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", @@ -7067,18 +5999,6 @@ "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", "dev": true }, - "node_modules/combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "dev": true, - "dependencies": { - "delayed-stream": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, "node_modules/commander": { "version": "2.20.3", "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", @@ -7282,20 +6202,20 @@ } }, "node_modules/copy-webpack-plugin": { - "version": "11.0.0", - "resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-11.0.0.tgz", - "integrity": "sha512-fX2MWpamkW0hZxMEg0+mYnA40LTosOSa5TqZ9GYIBzyJa9C3QUaMPSE2xAi/buNr8u89SfD9wHSQVBzrRa/SOQ==", + "version": "12.0.2", + "resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-12.0.2.tgz", + "integrity": "sha512-SNwdBeHyII+rWvee/bTnAYyO8vfVdcSTud4EIb6jcZ8inLeWucJE0DnxXQBjlQ5zlteuuvooGQy3LIyGxhvlOA==", "dev": true, "dependencies": { - "fast-glob": "^3.2.11", + "fast-glob": "^3.3.2", "glob-parent": "^6.0.1", - "globby": "^13.1.1", + "globby": "^14.0.0", "normalize-path": "^3.0.0", - "schema-utils": "^4.0.0", - "serialize-javascript": "^6.0.0" + "schema-utils": "^4.2.0", + "serialize-javascript": "^6.0.2" }, "engines": { - "node": ">= 14.15.0" + "node": ">= 18.12.0" }, "funding": { "type": "opencollective", @@ -7317,44 +6237,13 @@ "node": ">=10.13.0" } }, - "node_modules/copy-webpack-plugin/node_modules/globby": { - "version": "13.2.2", - "resolved": "https://registry.npmjs.org/globby/-/globby-13.2.2.tgz", - "integrity": "sha512-Y1zNGV+pzQdh7H39l9zgB4PJqjRNqydvdYCDG4HFXM4XuvSaQQlEc91IU1yALL8gUTDomgBAfz3XJdmUS+oo0w==", - "dev": true, - "dependencies": { - "dir-glob": "^3.0.1", - "fast-glob": "^3.3.0", - "ignore": "^5.2.4", - "merge2": "^1.4.1", - "slash": "^4.0.0" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/copy-webpack-plugin/node_modules/slash": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-4.0.0.tgz", - "integrity": "sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew==", - "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/core-js-compat": { - "version": "3.37.0", - "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.37.0.tgz", - "integrity": "sha512-vYq4L+T8aS5UuFg4UwDhc7YNRWVeVZwltad9C/jV3R2LgVOpS9BDr7l/WL6BN0dbV3k1XejPTHqqEzJgsa0frA==", + "version": "3.38.1", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.38.1.tgz", + "integrity": "sha512-JRH6gfXxGmrzF3tZ57lFx97YARxCXPaMzPo6jELZhv88pBH5VXpQ+y0znKGlFnzuaihqhLbefxSJxWJMPtfDzw==", "dev": true, "dependencies": { - "browserslist": "^4.23.0" + "browserslist": "^4.23.3" }, "funding": { "type": "opencollective", @@ -7395,39 +6284,21 @@ "node": ">=14" }, "funding": { - "url": "https://github.com/sponsors/d-fischer" - }, - "peerDependencies": { - "typescript": ">=4.9.5" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/cosmiconfig/node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true - }, - "node_modules/cosmiconfig/node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dev": true, - "dependencies": { - "argparse": "^2.0.1" + "url": "https://github.com/sponsors/d-fischer" }, - "bin": { - "js-yaml": "bin/js-yaml.js" + "peerDependencies": { + "typescript": ">=4.9.5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } } }, "node_modules/critters": { - "version": "0.0.22", - "resolved": "https://registry.npmjs.org/critters/-/critters-0.0.22.tgz", - "integrity": "sha512-NU7DEcQZM2Dy8XTKFHxtdnIM/drE312j2T4PCVaSUcS0oBeyT/NImpRw/Ap0zOr/1SE7SgPK9tGPg1WK/sVakw==", + "version": "0.0.24", + "resolved": "https://registry.npmjs.org/critters/-/critters-0.0.24.tgz", + "integrity": "sha512-Oyqew0FGM0wYUSNqR0L6AteO5MpMoUU0rhKRieXeiKs+PmRTxiJMyaunYB2KF6fQ3dzChXKCpbFOEJx3OQ1v/Q==", "dev": true, "dependencies": { "chalk": "^4.1.0", @@ -7524,22 +6395,22 @@ } }, "node_modules/css-loader": { - "version": "6.10.0", - "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-6.10.0.tgz", - "integrity": "sha512-LTSA/jWbwdMlk+rhmElbDR2vbtQoTBPr7fkJE+mxrHj+7ru0hUmHafDRzWIjIHTwpitWVaqY2/UWGRca3yUgRw==", + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-7.1.2.tgz", + "integrity": "sha512-6WvYYn7l/XEGN8Xu2vWFt9nVzrCn39vKyTEFf/ExEyoksJjjSZV/0/35XPlMbpnr6VGhZIUg5yJrL8tGfes/FA==", "dev": true, "dependencies": { "icss-utils": "^5.1.0", "postcss": "^8.4.33", - "postcss-modules-extract-imports": "^3.0.0", - "postcss-modules-local-by-default": "^4.0.4", - "postcss-modules-scope": "^3.1.1", + "postcss-modules-extract-imports": "^3.1.0", + "postcss-modules-local-by-default": "^4.0.5", + "postcss-modules-scope": "^3.2.0", "postcss-modules-values": "^4.0.0", "postcss-value-parser": "^4.2.0", "semver": "^7.5.4" }, "engines": { - "node": ">= 12.13.0" + "node": ">= 18.12.0" }, "funding": { "type": "opencollective", @@ -7547,7 +6418,7 @@ }, "peerDependencies": { "@rspack/core": "0.x || 1.x", - "webpack": "^5.0.0" + "webpack": "^5.27.0" }, "peerDependenciesMeta": { "@rspack/core": { @@ -7614,11 +6485,11 @@ } }, "node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", "dependencies": { - "ms": "2.1.2" + "ms": "^2.1.3" }, "engines": { "node": ">=6.0" @@ -7635,6 +6506,34 @@ "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", "dev": true }, + "node_modules/default-browser": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/default-browser/-/default-browser-5.2.1.tgz", + "integrity": "sha512-WY/3TUME0x3KPYdRRxEJJvXRHV4PyPoUsxtZa78lwItwRQRHhd2U9xOscaT/YTf8uCXIAjeJOFBVEh/7FtD8Xg==", + "dev": true, + "dependencies": { + "bundle-name": "^4.1.0", + "default-browser-id": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/default-browser-id": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/default-browser-id/-/default-browser-id-5.0.0.tgz", + "integrity": "sha512-A6p/pu/6fyBcA1TRz/GqWYPViplrftcW2gZC9q79ngNCKAeR/X3gcEdXQHl4KNXV+3wgIJ1CPkJQ3IHM6lcsyA==", + "dev": true, + "engines": { + "node": ">=18" + }, + "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", @@ -7677,21 +6576,15 @@ } }, "node_modules/define-lazy-prop": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz", - "integrity": "sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "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": ">=0.4.0" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/depd": { @@ -7722,6 +6615,15 @@ "npm": "1.2.8000 || >= 1.4.16" } }, + "node_modules/detect-libc": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz", + "integrity": "sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/detect-node": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz", @@ -7734,27 +6636,6 @@ "integrity": "sha512-uJaamHkagcZtHPqCIHZxnFrXlunQXgBOsZSUOWwFw31QJCAbyTBoHMW75YOTur5ZNx8pIeAKgf6GWIgaqqiLhA==", "dev": true }, - "node_modules/diff-sequences": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", - "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==", - "dev": true, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/dir-glob": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", - "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", - "dev": true, - "dependencies": { - "path-type": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/dlv": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", @@ -7852,33 +6733,6 @@ "url": "https://github.com/fb55/domutils?sponsor=1" } }, - "node_modules/dotenv": { - "version": "16.3.2", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.3.2.tgz", - "integrity": "sha512-HTlk5nmhkm8F6JcdXvHIzaorzCoziNQT9mGxLPVXW8wJF1TiGSL60ZGB4gHWabHOaMmWmhvk2/lPHfnBiT78AQ==", - "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/motdotla/dotenv?sponsor=1" - } - }, - "node_modules/dotenv-expand": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-10.0.0.tgz", - "integrity": "sha512-GopVGCpVS1UKH75VKHGuQFqS1Gusej0z4FyQkPdwjil2gNIv+LNsqBlboOzpJFZKVT95GkCyWJbBSdFEFUWI2A==", - "dev": true, - "engines": { - "node": ">=12" - } - }, - "node_modules/duplexer": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz", - "integrity": "sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==", - "dev": true - }, "node_modules/duplexify": { "version": "4.1.3", "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-4.1.3.tgz", @@ -7902,31 +6756,16 @@ "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", "dev": true }, - "node_modules/ejs": { - "version": "3.1.10", - "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz", - "integrity": "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==", - "dev": true, - "dependencies": { - "jake": "^10.8.5" - }, - "bin": { - "ejs": "bin/cli.js" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/electron-to-chromium": { - "version": "1.4.757", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.757.tgz", - "integrity": "sha512-jftDaCknYSSt/+KKeXzH3LX5E2CvRLm75P3Hj+J/dv3CL0qUYcOt13d5FN1NiL5IJbbhzHrb3BomeG2tkSlZmw==", + "version": "1.5.30", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.30.tgz", + "integrity": "sha512-sXI35EBN4lYxzc/pIGorlymYNzDBOqkSlVRe6MkgBsW/hW1tpC/HDJ2fjG7XnjakzfLEuvdmux0Mjs6jHq4UOA==", "dev": true }, "node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.4.0.tgz", + "integrity": "sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw==", "dev": true }, "node_modules/emojis-list": { @@ -7979,9 +6818,9 @@ } }, "node_modules/engine.io": { - "version": "6.5.5", - "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.5.5.tgz", - "integrity": "sha512-C5Pn8Wk+1vKBoHghJODM63yk8MvrO9EWZUfkAt5HAqIgPE4/8FF0PEGHXtEd40l223+cE5ABWuPzm38PHFXfMA==", + "version": "6.6.1", + "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.6.1.tgz", + "integrity": "sha512-NEpDCw9hrvBW+hVEOK4T7v0jFJ++KgtPl4jKFwsZVfG1XhS0dCrSb3VMb9gPAd7VAdW52VT1EnaNiU2vM8C0og==", "dev": true, "dependencies": { "@types/cookie": "^0.4.1", @@ -8000,18 +6839,39 @@ } }, "node_modules/engine.io-parser": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.2.tgz", - "integrity": "sha512-RcyUFKA93/CXH20l4SoVvzZfrSDMOTUS3bWVpTt2FuFP+XYrL8i8oonHP7WInRyVHXh0n/ORtoeiE1os+8qkSw==", + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.3.tgz", + "integrity": "sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==", "dev": true, "engines": { "node": ">=10.0.0" } }, + "node_modules/engine.io/node_modules/ws": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", + "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", + "dev": true, + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, "node_modules/enhanced-resolve": { - "version": "5.16.0", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.16.0.tgz", - "integrity": "sha512-O+QWCviPNSSLAD9Ucn8Awv+poAkqn3T1XY5/N7kR7rQO9yfSGWkYZDwpJ+iKF7B8rxaQKWngSqACpgzeapSyoA==", + "version": "5.17.1", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.17.1.tgz", + "integrity": "sha512-LMHl3dXhTcfv8gM4kEzIUeTQ+7fpdA0l2tUf34BddXPkz2A5xJ5L/Pchd5BL6rdccM9QGvu0sWZzK1Z1t4wwyg==", "dev": true, "dependencies": { "graceful-fs": "^4.2.4", @@ -8022,22 +6882,29 @@ } }, "node_modules/enquirer": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz", - "integrity": "sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==", + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.4.1.tgz", + "integrity": "sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ==", "dev": true, "dependencies": { - "ansi-colors": "^4.1.1" + "ansi-colors": "^4.1.1", + "strip-ansi": "^6.0.1" }, "engines": { "node": ">=8.6" } }, "node_modules/ent": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/ent/-/ent-2.2.0.tgz", - "integrity": "sha512-GHrMyVZQWvTIdDtpiEXdHZnFQKzeO09apj8Cbl4pKWy4i0Oprcq17usfDt5aO63swf0JOeMWjWQE/LzgSRuWpA==", - "dev": true + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ent/-/ent-2.2.1.tgz", + "integrity": "sha512-QHuXVeZx9d+tIQAz/XztU0ZwZf2Agg9CcXcgE1rurqvdBeDBrpSwjl8/6XUqMg7tw2Y7uAdKb2sRv+bSEFqQ5A==", + "dev": true, + "dependencies": { + "punycode": "^1.4.1" + }, + "engines": { + "node": ">= 0.4" + } }, "node_modules/entities": { "version": "4.5.0", @@ -8060,6 +6927,18 @@ "node": ">=6" } }, + "node_modules/environment": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/environment/-/environment-1.1.0.tgz", + "integrity": "sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==", + "dev": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/err-code": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/err-code/-/err-code-2.0.3.tgz", @@ -8110,66 +6989,66 @@ } }, "node_modules/es-module-lexer": { - "version": "1.5.2", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.5.2.tgz", - "integrity": "sha512-l60ETUTmLqbVbVHv1J4/qj+M8nq7AwMzEcg3kmJDt9dCNrTk+yHcYFf/Kw75pMDwd9mPcIGCG5LcS20SxYRzFA==", + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.5.4.tgz", + "integrity": "sha512-MVNK56NiMrOwitFB7cqDwq0CQutbw+0BvLshJSse0MUNU+y1FC3bUS/AQg7oUng+/wKrrki7JfmwtVHkVfPLlw==", "dev": true }, "node_modules/esbuild": { - "version": "0.20.1", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.20.1.tgz", - "integrity": "sha512-OJwEgrpWm/PCMsLVWXKqvcjme3bHNpOgN7Tb6cQnR5n0TPbQx1/Xrn7rqM+wn17bYeT6MGB5sn1Bh5YiGi70nA==", + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.23.0.tgz", + "integrity": "sha512-1lvV17H2bMYda/WaFb2jLPeHU3zml2k4/yagNMG8Q/YtfMjCwEUZa2eXXMgZTVSL5q1n4H7sQ0X6CdJDqqeCFA==", "dev": true, "hasInstallScript": true, - "optional": true, "bin": { "esbuild": "bin/esbuild" }, "engines": { - "node": ">=12" + "node": ">=18" }, "optionalDependencies": { - "@esbuild/aix-ppc64": "0.20.1", - "@esbuild/android-arm": "0.20.1", - "@esbuild/android-arm64": "0.20.1", - "@esbuild/android-x64": "0.20.1", - "@esbuild/darwin-arm64": "0.20.1", - "@esbuild/darwin-x64": "0.20.1", - "@esbuild/freebsd-arm64": "0.20.1", - "@esbuild/freebsd-x64": "0.20.1", - "@esbuild/linux-arm": "0.20.1", - "@esbuild/linux-arm64": "0.20.1", - "@esbuild/linux-ia32": "0.20.1", - "@esbuild/linux-loong64": "0.20.1", - "@esbuild/linux-mips64el": "0.20.1", - "@esbuild/linux-ppc64": "0.20.1", - "@esbuild/linux-riscv64": "0.20.1", - "@esbuild/linux-s390x": "0.20.1", - "@esbuild/linux-x64": "0.20.1", - "@esbuild/netbsd-x64": "0.20.1", - "@esbuild/openbsd-x64": "0.20.1", - "@esbuild/sunos-x64": "0.20.1", - "@esbuild/win32-arm64": "0.20.1", - "@esbuild/win32-ia32": "0.20.1", - "@esbuild/win32-x64": "0.20.1" + "@esbuild/aix-ppc64": "0.23.0", + "@esbuild/android-arm": "0.23.0", + "@esbuild/android-arm64": "0.23.0", + "@esbuild/android-x64": "0.23.0", + "@esbuild/darwin-arm64": "0.23.0", + "@esbuild/darwin-x64": "0.23.0", + "@esbuild/freebsd-arm64": "0.23.0", + "@esbuild/freebsd-x64": "0.23.0", + "@esbuild/linux-arm": "0.23.0", + "@esbuild/linux-arm64": "0.23.0", + "@esbuild/linux-ia32": "0.23.0", + "@esbuild/linux-loong64": "0.23.0", + "@esbuild/linux-mips64el": "0.23.0", + "@esbuild/linux-ppc64": "0.23.0", + "@esbuild/linux-riscv64": "0.23.0", + "@esbuild/linux-s390x": "0.23.0", + "@esbuild/linux-x64": "0.23.0", + "@esbuild/netbsd-x64": "0.23.0", + "@esbuild/openbsd-arm64": "0.23.0", + "@esbuild/openbsd-x64": "0.23.0", + "@esbuild/sunos-x64": "0.23.0", + "@esbuild/win32-arm64": "0.23.0", + "@esbuild/win32-ia32": "0.23.0", + "@esbuild/win32-x64": "0.23.0" } }, "node_modules/esbuild-wasm": { - "version": "0.20.1", - "resolved": "https://registry.npmjs.org/esbuild-wasm/-/esbuild-wasm-0.20.1.tgz", - "integrity": "sha512-6v/WJubRsjxBbQdz6izgvx7LsVFvVaGmSdwrFHmEzoVgfXL89hkKPoQHsnVI2ngOkcBUQT9kmAM1hVL1k/Av4A==", + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/esbuild-wasm/-/esbuild-wasm-0.23.0.tgz", + "integrity": "sha512-6jP8UmWy6R6TUUV8bMuC3ZyZ6lZKI56x0tkxyCIqWwRRJ/DgeQKneh/Oid5EoGoPFLrGNkz47ZEtWAYuiY/u9g==", "dev": true, "bin": { "esbuild": "bin/esbuild" }, "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/escalade": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz", - "integrity": "sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", "dev": true, "engines": { "node": ">=6" @@ -8191,16 +7070,16 @@ } }, "node_modules/eslint": { - "version": "8.57.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.0.tgz", - "integrity": "sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==", + "version": "8.57.1", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.1.tgz", + "integrity": "sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", "@eslint/eslintrc": "^2.1.4", - "@eslint/js": "8.57.0", - "@humanwhocodes/config-array": "^0.11.14", + "@eslint/js": "8.57.1", + "@humanwhocodes/config-array": "^0.13.0", "@humanwhocodes/module-importer": "^1.0.1", "@nodelib/fs.walk": "^1.2.8", "@ungap/structured-clone": "^1.2.0", @@ -8258,13 +7137,13 @@ } }, "node_modules/eslint-plugin-prettier": { - "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==", + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.2.1.tgz", + "integrity": "sha512-gH3iR3g4JfF+yYPaJYkN7jEl9QbweL/YfkoRlNnuIEHEz1vHVlCmWOS+eGGiRuzHQXdJFCOTxRgvju9b8VUmrw==", "dev": true, "dependencies": { "prettier-linter-helpers": "^1.0.0", - "synckit": "^0.8.6" + "synckit": "^0.9.1" }, "engines": { "node": "^14.18.0 || >=16.0.0" @@ -8288,9 +7167,9 @@ } }, "node_modules/eslint-scope": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.0.1.tgz", - "integrity": "sha512-pL8XjgP4ZOmmwfFE8mEhSxA7ZY4C+LWyqjQ3o4yWkkmD0qcMT9kkW3zWHOczhWcjTSgqycYAgwSlXvZltv65og==", + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.1.0.tgz", + "integrity": "sha512-14dSvlhaVhKKsa9Fx1l8A17s7ah7Ef7wCakJ10LYk6+GYmP9yDti2oq2SEwcyndt6knfcZyhyxwY3i9yL78EQw==", "dev": true, "dependencies": { "esrecurse": "^4.3.0", @@ -8303,6 +7182,30 @@ "url": "https://opencollective.com/eslint" } }, + "node_modules/eslint-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz", + "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==", + "dev": true, + "dependencies": { + "eslint-visitor-keys": "^1.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + } + }, + "node_modules/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", + "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", + "dev": true, + "engines": { + "node": ">=4" + } + }, "node_modules/eslint-visitor-keys": { "version": "3.4.3", "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", @@ -8346,12 +7249,6 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/eslint/node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true - }, "node_modules/eslint/node_modules/brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -8424,22 +7321,6 @@ "url": "https://opencollective.com/eslint" } }, - "node_modules/eslint/node_modules/find-up": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", - "dev": true, - "dependencies": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/eslint/node_modules/glob-parent": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", @@ -8451,104 +7332,47 @@ "engines": { "node": ">=10.13.0" } - }, - "node_modules/eslint/node_modules/globals": { - "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" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/eslint/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/eslint/node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dev": true, - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/eslint/node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true - }, - "node_modules/eslint/node_modules/locate-path": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", - "dev": true, - "dependencies": { - "p-locate": "^5.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/eslint/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/eslint/node_modules/p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + }, + "node_modules/eslint/node_modules/globals": { + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", "dev": true, "dependencies": { - "yocto-queue": "^0.1.0" + "type-fest": "^0.20.2" }, "engines": { - "node": ">=10" + "node": ">=8" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/eslint/node_modules/p-locate": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "node_modules/eslint/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/eslint/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "node_modules/eslint/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "dev": true, "dependencies": { - "p-limit": "^3.0.2" + "brace-expansion": "^1.1.7" }, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": "*" } }, "node_modules/eslint/node_modules/supports-color": { @@ -8606,9 +7430,9 @@ } }, "node_modules/esquery": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", - "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==", + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", + "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", "dev": true, "dependencies": { "estraverse": "^5.1.0" @@ -8694,6 +7518,27 @@ "url": "https://github.com/sindresorhus/execa?sponsor=1" } }, + "node_modules/execa/node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/execa/node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true + }, "node_modules/exponential-backoff": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/exponential-backoff/-/exponential-backoff-3.1.1.tgz", @@ -8701,37 +7546,37 @@ "dev": true }, "node_modules/express": { - "version": "4.19.2", - "resolved": "https://registry.npmjs.org/express/-/express-4.19.2.tgz", - "integrity": "sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==", + "version": "4.21.0", + "resolved": "https://registry.npmjs.org/express/-/express-4.21.0.tgz", + "integrity": "sha512-VqcNGcj/Id5ZT1LZ/cfihi3ttTn+NJmkli2eZADigjq29qTlWi/hAQ43t/VLPq8+UX06FCEx3ByOYet6ZFblng==", "dev": true, "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", - "body-parser": "1.20.2", + "body-parser": "1.20.3", "content-disposition": "0.5.4", "content-type": "~1.0.4", "cookie": "0.6.0", "cookie-signature": "1.0.6", "debug": "2.6.9", "depd": "2.0.0", - "encodeurl": "~1.0.2", + "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "etag": "~1.8.1", - "finalhandler": "1.2.0", + "finalhandler": "1.3.1", "fresh": "0.5.2", "http-errors": "2.0.0", - "merge-descriptors": "1.0.1", + "merge-descriptors": "1.0.3", "methods": "~1.1.2", "on-finished": "2.4.1", "parseurl": "~1.3.3", - "path-to-regexp": "0.1.7", + "path-to-regexp": "0.1.10", "proxy-addr": "~2.0.7", - "qs": "6.11.0", + "qs": "6.13.0", "range-parser": "~1.2.1", "safe-buffer": "5.2.1", - "send": "0.18.0", - "serve-static": "1.15.0", + "send": "0.19.0", + "serve-static": "1.16.2", "setprototypeof": "1.2.0", "statuses": "2.0.1", "type-is": "~1.6.18", @@ -8760,14 +7605,23 @@ "ms": "2.0.0" } }, + "node_modules/express/node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/express/node_modules/finalhandler": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", - "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", + "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", "dev": true, "dependencies": { "debug": "2.6.9", - "encodeurl": "~1.0.2", + "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "on-finished": "2.4.1", "parseurl": "~1.3.3", @@ -8813,18 +7667,6 @@ "node": ">=4" } }, - "node_modules/external-editor/node_modules/tmp": { - "version": "0.0.33", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", - "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", - "dev": true, - "dependencies": { - "os-tmpdir": "~1.0.2" - }, - "engines": { - "node": ">=0.6.0" - } - }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -8865,6 +7707,12 @@ "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", "dev": true }, + "node_modules/fast-uri": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.0.2.tgz", + "integrity": "sha512-GR6f0hD7XXyNJa25Tb9BuIdN0tdr+0BMi6/CJPH3wJO1JjNG3n/VsSw38AwRdKZABm8lGbPfakLRkYzx2V9row==", + "dev": true + }, "node_modules/fastq": { "version": "1.17.1", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz", @@ -8886,21 +7734,6 @@ "node": ">=0.8.0" } }, - "node_modules/figures": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", - "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==", - "dev": true, - "dependencies": { - "escape-string-regexp": "^1.0.5" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/file-entry-cache": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", @@ -8913,27 +7746,6 @@ "node": "^10.12.0 || >=12.0.0" } }, - "node_modules/filelist": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.4.tgz", - "integrity": "sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==", - "dev": true, - "dependencies": { - "minimatch": "^5.0.1" - } - }, - "node_modules/filelist/node_modules/minimatch": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", - "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", - "dev": true, - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/fill-range": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", @@ -9008,16 +7820,19 @@ } }, "node_modules/find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", "dev": true, "dependencies": { - "locate-path": "^5.0.0", + "locate-path": "^6.0.0", "path-exists": "^4.0.0" }, "engines": { - "node": ">=8" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/flat": { @@ -9050,9 +7865,9 @@ "dev": true }, "node_modules/follow-redirects": { - "version": "1.15.6", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz", - "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==", + "version": "1.15.9", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz", + "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==", "dev": true, "funding": [ { @@ -9070,9 +7885,9 @@ } }, "node_modules/foreground-child": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.1.1.tgz", - "integrity": "sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg==", + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.0.tgz", + "integrity": "sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==", "dev": true, "dependencies": { "cross-spawn": "^7.0.0", @@ -9085,32 +7900,6 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/foreground-child/node_modules/signal-exit": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", - "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", - "dev": true, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/form-data": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", - "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", - "dev": true, - "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "mime-types": "^2.1.12" - }, - "engines": { - "node": ">= 6" - } - }, "node_modules/forwarded": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", @@ -9142,24 +7931,18 @@ "node": ">= 0.6" } }, - "node_modules/fs-constants": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", - "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", - "dev": true - }, "node_modules/fs-extra": { - "version": "11.2.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.2.0.tgz", - "integrity": "sha512-PmDi3uwK5nFuXh7XDTlVnS17xJS7vW36is2+w3xcv8SVxiB4NyATf4ctkVY5bkSjX0Y4nbvZCq1/EjtEyr9ktw==", + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", + "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", "dev": true, "dependencies": { "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" }, "engines": { - "node": ">=14.14" + "node": ">=6 <7 || >=8" } }, "node_modules/fs-minipass": { @@ -9174,12 +7957,6 @@ "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, - "node_modules/fs-monkey": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/fs-monkey/-/fs-monkey-1.0.6.tgz", - "integrity": "sha512-b1FMfwetIKymC0eioW7mTywihSQE4oLzQn1dB6rZB5fx/3NpNEdAWeCSMB+60/AeT0TCXsxzAlcYVEFCTAksWg==", - "dev": true - }, "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", @@ -9208,6 +7985,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/functional-red-black-tree": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", + "integrity": "sha512-dsKNQNdj6xA3T+QlADDA7mOSlX0qiMINjn0cgr+eGHGsbSHzTabcIogz2+p/iqP1Xs6EP/sS2SbqH+brGTbq0g==", + "dev": true + }, "node_modules/gensync": { "version": "1.0.0-beta.2", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", @@ -9226,6 +8009,18 @@ "node": "6.* || 8.* || >= 10.*" } }, + "node_modules/get-east-asian-width": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.2.0.tgz", + "integrity": "sha512-2nk+7SIVb14QrgXFHcm84tD4bKQz0RxPuMT8Ag5KPOq7J5fEmAg0UbXdTOSHqNuHSU28k55qnceesxXRZGzKWA==", + "dev": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/get-intrinsic": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", @@ -9245,15 +8040,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/get-package-type": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", - "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", - "dev": true, - "engines": { - "node": ">=8.0.0" - } - }, "node_modules/get-stream": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", @@ -9270,6 +8056,7 @@ "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -9333,20 +8120,20 @@ } }, "node_modules/globby": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", - "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "version": "14.0.2", + "resolved": "https://registry.npmjs.org/globby/-/globby-14.0.2.tgz", + "integrity": "sha512-s3Fq41ZVh7vbbe2PN3nrW7yC7U7MFVc5c98/iTl9c2GawNMKx/J648KQRW6WKkuU8GIbbh2IXfIRQjOZnXcTnw==", "dev": true, "dependencies": { - "array-union": "^2.1.0", - "dir-glob": "^3.0.1", - "fast-glob": "^3.2.9", - "ignore": "^5.2.0", - "merge2": "^1.4.1", - "slash": "^3.0.0" + "@sindresorhus/merge-streams": "^2.1.0", + "fast-glob": "^3.3.2", + "ignore": "^5.2.4", + "path-type": "^5.0.0", + "slash": "^5.1.0", + "unicorn-magic": "^0.1.0" }, "engines": { - "node": ">=10" + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -9482,13 +8269,10 @@ } }, "node_modules/hosted-git-info/node_modules/lru-cache": { - "version": "10.2.2", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.2.2.tgz", - "integrity": "sha512-9hp3Vp2/hFQUiIwKo8XCeFVnrg8Pk3TYNPIR7tJADKi5YfcF7vEaK7avFHTlSy3kOKYaJQaalfEo6YuXdceBOQ==", - "dev": true, - "engines": { - "node": "14 || >=16.14" - } + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true }, "node_modules/hpack.js": { "version": "2.1.6", @@ -9644,33 +8428,26 @@ } }, "node_modules/http-proxy-middleware": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.6.tgz", - "integrity": "sha512-ya/UeJ6HVBYxrgYotAZo1KvPWlgB48kUJLDePFeneHsVujFaW5WNj2NgWCAE//B1Dl02BIfYlpNgBy8Kf8Rjmw==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-3.0.0.tgz", + "integrity": "sha512-36AV1fIaI2cWRzHo+rbcxhe3M3jUDCNzc4D5zRl57sEWRAxdXYtw7FSQKYY6PDKssiAKjLYypbssHk+xs/kMXw==", "dev": true, "dependencies": { - "@types/http-proxy": "^1.17.8", + "@types/http-proxy": "^1.17.10", + "debug": "^4.3.4", "http-proxy": "^1.18.1", "is-glob": "^4.0.1", "is-plain-obj": "^3.0.0", - "micromatch": "^4.0.2" + "micromatch": "^4.0.5" }, "engines": { - "node": ">=12.0.0" - }, - "peerDependencies": { - "@types/express": "^4.17.13" - }, - "peerDependenciesMeta": { - "@types/express": { - "optional": true - } + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/https-proxy-agent": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.4.tgz", - "integrity": "sha512-wlwpilI7YdjSkWaQ/7omYBMTliDcmCN8OLihO6I9B86g06lMyAoqgoDpV0XqoaPOKj+0DIdAvnsWfyAAhmimcg==", + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.5.tgz", + "integrity": "sha512-1e4Wqeblerz+tMKPIq2EMGiiWW1dIjZOksyHWSUm1rmuvw/how9hBHZ38lAGj5ID4Ik6EdkOw7NmWPy6LAwalw==", "dev": true, "dependencies": { "agent-base": "^7.0.2", @@ -9689,6 +8466,15 @@ "node": ">=10.17.0" } }, + "node_modules/hyperdyperid": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/hyperdyperid/-/hyperdyperid-1.2.0.tgz", + "integrity": "sha512-Y93lCzHYgGWdrJ66yIktxiaGULYc6oGiABxhcO5AufBeOyoIdZF7bIfLaOrbM0iGIOXQQgxxRrFEnb+Y6w1n4A==", + "dev": true, + "engines": { + "node": ">=10.18" + } + }, "node_modules/iconv-lite": { "version": "0.4.24", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", @@ -9733,9 +8519,9 @@ ] }, "node_modules/ignore": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.0.tgz", - "integrity": "sha512-g7dmpshy+gD7mh88OC9NwSGTKoc3kyLAZQRU1mt53Aw/vnvfXnbC+F/7F7QoYVKbV+KNvJx8wArewKy1vXMtlg==", + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", "dev": true, "engines": { "node": ">= 4" @@ -9767,9 +8553,9 @@ } }, "node_modules/immutable": { - "version": "4.3.5", - "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.5.tgz", - "integrity": "sha512-8eabxkth9gZatlwl5TBuJnCsoTADlL6ftEr7A4qgdaTsPyreilDSnUk57SO+jfKcNtxPa22U5KK6DSeAYhpBJw==", + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.7.tgz", + "integrity": "sha512-1hqclzwYwjRDFLjcFxOM5AYkkG0rpFPpr1RLPMEuGczoS7YA8gLhy8SWXYRAA/XwfEHpfo3cw5JGioS32fnMRw==", "dev": true }, "node_modules/import-fresh": { @@ -9788,15 +8574,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/import-fresh/node_modules/resolve-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "dev": true, - "engines": { - "node": ">=4" - } - }, "node_modules/imurmurhash": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", @@ -9819,6 +8596,7 @@ "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", "dependencies": { "once": "^1.3.0", "wrappy": "1" @@ -9830,52 +8608,14 @@ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, "node_modules/ini": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/ini/-/ini-4.1.1.tgz", - "integrity": "sha512-QQnnxNyfvmHFIsj7gkPcYymR8Jdw/o7mp5ZFihxn6h8Ci6fh3Dx4E1gPjpQEpIuPo9XVNY/ZUwh4BPMjGyL01g==", + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/ini/-/ini-4.1.3.tgz", + "integrity": "sha512-X7rqawQBvfdjS10YU1y1YVreA3SsLrW9dX2CewP2EbBJM4ypVNLDkO5y04gejPwKIY9lR+7r9gn3rFPt/kmWFg==", "dev": true, "engines": { "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, - "node_modules/inquirer": { - "version": "9.2.15", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-9.2.15.tgz", - "integrity": "sha512-vI2w4zl/mDluHt9YEQ/543VTCwPKWiHzKtm9dM2V0NdFcqEexDAjUHzO1oA60HRNaVifGXXM1tRRNluLVHa0Kg==", - "dev": true, - "dependencies": { - "@ljharb/through": "^2.3.12", - "ansi-escapes": "^4.3.2", - "chalk": "^5.3.0", - "cli-cursor": "^3.1.0", - "cli-width": "^4.1.0", - "external-editor": "^3.1.0", - "figures": "^3.2.0", - "lodash": "^4.17.21", - "mute-stream": "1.0.0", - "ora": "^5.4.1", - "run-async": "^3.0.0", - "rxjs": "^7.8.1", - "string-width": "^4.2.3", - "strip-ansi": "^6.0.1", - "wrap-ansi": "^6.2.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/inquirer/node_modules/chalk": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", - "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", - "dev": true, - "engines": { - "node": "^12.17.0 || ^14.13 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, "node_modules/ip-address": { "version": "9.0.5", "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-9.0.5.tgz", @@ -9889,12 +8629,6 @@ "node": ">= 12" } }, - "node_modules/ip-address/node_modules/sprintf-js": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz", - "integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==", - "dev": true - }, "node_modules/ipaddr.js": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.2.0.tgz", @@ -9923,27 +8657,30 @@ } }, "node_modules/is-core-module": { - "version": "2.13.1", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz", - "integrity": "sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==", + "version": "2.15.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.15.1.tgz", + "integrity": "sha512-z0vtXSwucUJtANQWldhbtbt7BnL0vxiFjIdDLAatwhDYty2bad6s+rijD6Ri4YuYJubLzIJLUidCh09e1djEVQ==", "dev": true, "dependencies": { - "hasown": "^2.0.0" + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/is-docker": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", - "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", + "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": ">=8" + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -9959,12 +8696,15 @@ } }, "node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-4.0.0.tgz", + "integrity": "sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ==", "dev": true, "engines": { - "node": ">=8" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/is-glob": { @@ -9979,6 +8719,24 @@ "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-interactive": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz", @@ -9994,6 +8752,18 @@ "integrity": "sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ==", "dev": true }, + "node_modules/is-network-error": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-network-error/-/is-network-error-1.1.0.tgz", + "integrity": "sha512-tUdRRAnhT+OtCZR/LxZelH/C7QtjtFrTu5tXCA8pl55eTUElUHT+GPYV8MBMBvea/j+NxQqVt3LbWMRir7Gx9g==", + "dev": true, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-number": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", @@ -10067,15 +8837,18 @@ "dev": true }, "node_modules/is-wsl": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", - "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.0.tgz", + "integrity": "sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw==", "dev": true, "dependencies": { - "is-docker": "^2.0.0" + "is-inside-container": "^1.0.0" }, "engines": { - "node": ">=8" + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/isarray": { @@ -10097,324 +8870,60 @@ } }, "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true - }, - "node_modules/isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/istanbul-lib-coverage": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", - "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/istanbul-lib-instrument": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz", - "integrity": "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==", - "dev": true, - "dependencies": { - "@babel/core": "^7.12.3", - "@babel/parser": "^7.14.7", - "@istanbuljs/schema": "^0.1.2", - "istanbul-lib-coverage": "^3.2.0", - "semver": "^6.3.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/istanbul-lib-instrument/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/istanbul-lib-report": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", - "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", - "dev": true, - "dependencies": { - "istanbul-lib-coverage": "^3.0.0", - "make-dir": "^4.0.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/istanbul-lib-report/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/istanbul-lib-report/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/istanbul-lib-source-maps": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", - "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", - "dev": true, - "dependencies": { - "debug": "^4.1.1", - "istanbul-lib-coverage": "^3.0.0", - "source-map": "^0.6.1" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/istanbul-lib-source-maps/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/istanbul-reports": { - "version": "3.1.7", - "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.7.tgz", - "integrity": "sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==", - "dev": true, - "dependencies": { - "html-escaper": "^2.0.0", - "istanbul-lib-report": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jackspeak": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-2.3.6.tgz", - "integrity": "sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ==", - "dev": true, - "dependencies": { - "@isaacs/cliui": "^8.0.2" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - }, - "optionalDependencies": { - "@pkgjs/parseargs": "^0.11.0" - } - }, - "node_modules/jake": { - "version": "10.9.1", - "resolved": "https://registry.npmjs.org/jake/-/jake-10.9.1.tgz", - "integrity": "sha512-61btcOHNnLnsOdtLgA5efqQWjnSi/vow5HbI7HMdKKWqvrKR1bLK3BPlJn9gcSaP2ewuamUSMB5XEy76KUIS2w==", - "dev": true, - "dependencies": { - "async": "^3.2.3", - "chalk": "^4.0.2", - "filelist": "^1.0.4", - "minimatch": "^3.1.2" - }, - "bin": { - "jake": "bin/cli.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/jake/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jake/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/jake/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jake/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jake/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/jake/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/jake/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/jake/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jasmine-core": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-4.6.0.tgz", - "integrity": "sha512-O236+gd0ZXS8YAjFx8xKaJ94/erqUliEkJTDedyE7iHvv4ZVqi+q+8acJxu05/WJDKm512EUNn809In37nWlAQ==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", "dev": true }, - "node_modules/jest-diff": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.7.0.tgz", - "integrity": "sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==", + "node_modules/isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", "dev": true, - "dependencies": { - "chalk": "^4.0.0", - "diff-sequences": "^29.6.3", - "jest-get-type": "^29.6.3", - "pretty-format": "^29.7.0" - }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">=0.10.0" } }, - "node_modules/jest-diff/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "node_modules/istanbul-lib-coverage": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", + "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, "engines": { "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/jest-diff/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "node_modules/istanbul-lib-instrument": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.3.tgz", + "integrity": "sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q==", "dev": true, "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" + "@babel/core": "^7.23.9", + "@babel/parser": "^7.23.9", + "@istanbuljs/schema": "^0.1.3", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^7.5.4" }, "engines": { "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/jest-diff/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "node_modules/istanbul-lib-report": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", + "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", "dev": true, "dependencies": { - "color-name": "~1.1.4" + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^4.0.0", + "supports-color": "^7.1.0" }, "engines": { - "node": ">=7.0.0" + "node": ">=10" } }, - "node_modules/jest-diff/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/jest-diff/node_modules/has-flag": { + "node_modules/istanbul-lib-report/node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", @@ -10423,7 +8932,7 @@ "node": ">=8" } }, - "node_modules/jest-diff/node_modules/supports-color": { + "node_modules/istanbul-lib-report/node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", @@ -10435,15 +8944,63 @@ "node": ">=8" } }, - "node_modules/jest-get-type": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.6.3.tgz", - "integrity": "sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==", + "node_modules/istanbul-lib-source-maps": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", + "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", + "dev": true, + "dependencies": { + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0", + "source-map": "^0.6.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-source-maps/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "dev": true, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">=0.10.0" + } + }, + "node_modules/istanbul-reports": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.7.tgz", + "integrity": "sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==", + "dev": true, + "dependencies": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "dev": true, + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" } }, + "node_modules/jasmine-core": { + "version": "4.6.1", + "resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-4.6.1.tgz", + "integrity": "sha512-VYz/BjjmC3klLJlLwA4Kw8ytk0zDSmbbDLNs794VnWmkcCB7I9aAL/D48VNQtmITyPvea2C3jdUMfc3kAoy0PQ==", + "dev": true + }, "node_modules/jest-worker": { "version": "27.5.1", "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", @@ -10483,9 +9040,9 @@ } }, "node_modules/jiti": { - "version": "1.21.0", - "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.0.tgz", - "integrity": "sha512-gFqAIbuKyyso/3G2qhiO2OM6shY6EPP/R0+mkDbyspxKazh8BXDC5FiFsUjlczgdNz/vfra0da2y+aHrusLG/Q==", + "version": "1.21.6", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.6.tgz", + "integrity": "sha512-2yTgeWTWzMWkHu6Jp9NKgePDaYHbntiwvYuuJLbbN9vl7DC9DvXKOB2BC3ZZ92D3cvV/aflH0osDfwpHepQ53w==", "dev": true, "bin": { "jiti": "bin/jiti.js" @@ -10507,13 +9064,12 @@ "dev": true }, "node_modules/js-yaml": { - "version": "3.14.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", - "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", "dev": true, "dependencies": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" + "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" @@ -10577,19 +9133,16 @@ } }, "node_modules/jsonc-parser": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.1.tgz", - "integrity": "sha512-AilxAyFOAcK5wA1+LeaySVBrHsGQvUFCDWXKpZjzaL0PqW+xfBOttn8GNtWKFWqneyMZj41MWF9Kl6iPWLwgOA==", + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.3.1.tgz", + "integrity": "sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==", "dev": true }, "node_modules/jsonfile": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", - "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", "dev": true, - "dependencies": { - "universalify": "^2.0.0" - }, "optionalDependencies": { "graceful-fs": "^4.1.6" } @@ -10604,9 +9157,9 @@ ] }, "node_modules/karma": { - "version": "6.4.3", - "resolved": "https://registry.npmjs.org/karma/-/karma-6.4.3.tgz", - "integrity": "sha512-LuucC/RE92tJ8mlCwqEoRWXP38UMAqpnq98vktmS9SznSoUPPUJQbc91dHcxcunROvfQjdORVA/YFviH+Xci9Q==", + "version": "6.4.4", + "resolved": "https://registry.npmjs.org/karma/-/karma-6.4.4.tgz", + "integrity": "sha512-LrtUxbdvt1gOpo3gxG+VAJlJAEMhbWlM4YrFQgql98FwF7+K8K12LYO4hnDdUkNjeztYrOXEMqgTajSWgmtI/w==", "dev": true, "dependencies": { "@colors/colors": "1.5.0", @@ -10689,6 +9242,22 @@ "concat-map": "0.0.1" } }, + "node_modules/karma-coverage/node_modules/istanbul-lib-instrument": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz", + "integrity": "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==", + "dev": true, + "dependencies": { + "@babel/core": "^7.12.3", + "@babel/parser": "^7.14.7", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/karma-coverage/node_modules/minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", @@ -10701,6 +9270,15 @@ "node": "*" } }, + "node_modules/karma-coverage/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, "node_modules/karma-jasmine": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/karma-jasmine/-/karma-jasmine-5.1.0.tgz", @@ -10790,6 +9368,21 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, + "node_modules/karma/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/karma/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/karma/node_modules/minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", @@ -10811,6 +9404,29 @@ "node": ">=0.10.0" } }, + "node_modules/karma/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/karma/node_modules/tmp": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.3.tgz", + "integrity": "sha512-nZD7m9iCPC5g0pYmcaxogYKggSfLsdxl8of3Q/oIbqCqLLIO9IAF0GWjX1z9NZRHPiXv8Wex4yDCaZsgEw0Y8w==", + "dev": true, + "engines": { + "node": ">=14.14" + } + }, "node_modules/karma/node_modules/wrap-ansi": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", @@ -10873,19 +9489,10 @@ "node": ">=0.10.0" } }, - "node_modules/klona": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/klona/-/klona-2.0.6.tgz", - "integrity": "sha512-dhG34DXATL5hSxJbIexCft8FChFXtmskoZYnoPWjXQuebWYCNkVeV3KkGegCK9CP1oswI/vQibS2GY7Em/sJJA==", - "dev": true, - "engines": { - "node": ">= 8" - } - }, "node_modules/launch-editor": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/launch-editor/-/launch-editor-2.6.1.tgz", - "integrity": "sha512-eB/uXmFVpY4zezmGp5XtU21kwo7GBbKB+EQ+UZeWtGb9yAM5xt/Evk+lYH3eRNAtId+ej4u7TYPFZ07w4s7rRw==", + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/launch-editor/-/launch-editor-2.9.1.tgz", + "integrity": "sha512-Gcnl4Bd+hRO9P9icCP/RVVT2o8SFlPXofuCxvA2SaZuH45whSvf5p8x5oih5ftLiVhEI4sp5xDY+R+b3zJBh5w==", "dev": true, "dependencies": { "picocolors": "^1.0.0", @@ -10919,23 +9526,29 @@ } }, "node_modules/less-loader": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/less-loader/-/less-loader-11.1.0.tgz", - "integrity": "sha512-C+uDBV7kS7W5fJlUjq5mPBeBVhYpTIm5gB09APT9o3n/ILeaXVsiSFTbZpTJCJwQ/Crczfn3DmfQFwxYusWFug==", + "version": "12.2.0", + "resolved": "https://registry.npmjs.org/less-loader/-/less-loader-12.2.0.tgz", + "integrity": "sha512-MYUxjSQSBUQmowc0l5nPieOYwMzGPUaTzB6inNW/bdPEG9zOL3eAAD1Qw5ZxSPk7we5dMojHwNODYMV1hq4EVg==", "dev": true, - "dependencies": { - "klona": "^2.0.4" - }, "engines": { - "node": ">= 14.15.0" + "node": ">= 18.12.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/webpack" }, "peerDependencies": { + "@rspack/core": "0.x || 1.x", "less": "^3.5.0 || ^4.0.0", "webpack": "^5.0.0" + }, + "peerDependenciesMeta": { + "@rspack/core": { + "optional": true + }, + "webpack": { + "optional": true + } } }, "node_modules/less/node_modules/make-dir": { @@ -10990,46 +9603,147 @@ "resolved": "https://registry.npmjs.org/leven/-/leven-2.1.0.tgz", "integrity": "sha512-nvVPLpIHUxCUoRLrFqTgSxXJ614d8AgQoWl7zPe/2VadE8+1dpU3LBhowRuBAcuwruWtOdD8oYC9jDNJjXDPyA==", "engines": { - "node": ">=0.10.0" + "node": ">=0.10.0" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/license-webpack-plugin": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/license-webpack-plugin/-/license-webpack-plugin-4.0.2.tgz", + "integrity": "sha512-771TFWFD70G1wLTC4oU2Cw4qvtmNrIw+wRvBtn+okgHl7slJVi7zfNcdmqDL72BojM30VNJ2UHylr1o77U37Jw==", + "dev": true, + "dependencies": { + "webpack-sources": "^3.0.0" + }, + "peerDependenciesMeta": { + "webpack": { + "optional": true + }, + "webpack-sources": { + "optional": true + } + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true + }, + "node_modules/listr2": { + "version": "8.2.4", + "resolved": "https://registry.npmjs.org/listr2/-/listr2-8.2.4.tgz", + "integrity": "sha512-opevsywziHd3zHCVQGAj8zu+Z3yHNkkoYhWIGnq54RrCVwLz0MozotJEDnKsIBLvkfLGN6BLOyAeRrYI0pKA4g==", + "dev": true, + "dependencies": { + "cli-truncate": "^4.0.0", + "colorette": "^2.0.20", + "eventemitter3": "^5.0.1", + "log-update": "^6.1.0", + "rfdc": "^1.4.1", + "wrap-ansi": "^9.0.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/listr2/node_modules/ansi-regex": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", + "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/listr2/node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/levn": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", - "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "node_modules/listr2/node_modules/eventemitter3": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", + "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==", + "dev": true + }, + "node_modules/listr2/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", "dev": true, "dependencies": { - "prelude-ls": "^1.2.1", - "type-check": "~0.4.0" + "ansi-regex": "^6.0.1" }, "engines": { - "node": ">= 0.8.0" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" } }, - "node_modules/license-webpack-plugin": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/license-webpack-plugin/-/license-webpack-plugin-4.0.2.tgz", - "integrity": "sha512-771TFWFD70G1wLTC4oU2Cw4qvtmNrIw+wRvBtn+okgHl7slJVi7zfNcdmqDL72BojM30VNJ2UHylr1o77U37Jw==", + "node_modules/listr2/node_modules/wrap-ansi": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.0.tgz", + "integrity": "sha512-G8ura3S+3Z2G+mkgNRq8dqaFZAuxfsxpBB8OCTGRTCtp+l/v9nbFNmCUP1BZMts3G1142MsZfn6eeUKrr4PD1Q==", "dev": true, "dependencies": { - "webpack-sources": "^3.0.0" + "ansi-styles": "^6.2.1", + "string-width": "^7.0.0", + "strip-ansi": "^7.1.0" }, - "peerDependenciesMeta": { - "webpack": { - "optional": true - }, - "webpack-sources": { - "optional": true - } + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, - "node_modules/lines-and-columns": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-2.0.4.tgz", - "integrity": "sha512-wM1+Z03eypVAVUCE7QdSqpVIvelbOakn1M0bPDoA4SGWPx3sNDVUiMo3L6To6WWGClB7VyXnhQ4Sn7gxiJbE6A==", + "node_modules/lmdb": { + "version": "3.0.13", + "resolved": "https://registry.npmjs.org/lmdb/-/lmdb-3.0.13.tgz", + "integrity": "sha512-UGe+BbaSUQtAMZobTb4nHvFMrmvuAQKSeaqAX2meTEQjfsbpl5sxdHD8T72OnwD4GU9uwNhYXIVe4QGs8N9Zyw==", "dev": true, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + "hasInstallScript": true, + "dependencies": { + "msgpackr": "^1.10.2", + "node-addon-api": "^6.1.0", + "node-gyp-build-optional-packages": "5.2.2", + "ordered-binary": "^1.4.1", + "weak-lru-cache": "^1.2.2" + }, + "bin": { + "download-lmdb-prebuilds": "bin/download-prebuilds.js" + }, + "optionalDependencies": { + "@lmdb/lmdb-darwin-arm64": "3.0.13", + "@lmdb/lmdb-darwin-x64": "3.0.13", + "@lmdb/lmdb-linux-arm": "3.0.13", + "@lmdb/lmdb-linux-arm64": "3.0.13", + "@lmdb/lmdb-linux-x64": "3.0.13", + "@lmdb/lmdb-win32-x64": "3.0.13" } }, "node_modules/loader-runner": { @@ -11042,24 +9756,27 @@ } }, "node_modules/loader-utils": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-3.2.1.tgz", - "integrity": "sha512-ZvFw1KWS3GVyYBYb7qkmRM/WwL2TQQBxgCK62rlvm4WpVQ23Nb4tYjApUlfjrEGvOs7KHEsmyUn75OHZrJMWPw==", + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-3.3.1.tgz", + "integrity": "sha512-FMJTLMXfCLMLfJxcX9PFqX5qD88Z5MRGaZCVzfuqeZSPsyiBzs+pahDQjbIWz2QIzPZz0NX9Zy4FX3lmK6YHIg==", "dev": true, "engines": { "node": ">= 12.13.0" } }, "node_modules/locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", "dev": true, "dependencies": { - "p-locate": "^4.1.0" + "p-locate": "^5.0.0" }, "engines": { - "node": ">=8" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/lodash": { @@ -11080,6 +9797,12 @@ "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", "dev": true }, + "node_modules/lodash.truncate": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz", + "integrity": "sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw==", + "dev": true + }, "node_modules/log-symbols": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", @@ -11166,6 +9889,127 @@ "node": ">=8" } }, + "node_modules/log-update": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/log-update/-/log-update-6.1.0.tgz", + "integrity": "sha512-9ie8ItPR6tjY5uYJh8K/Zrv/RMZ5VOlOWvtZdEHYSTFKZfIBPQa9tOAEeAWhd+AnIneLJ22w5fjOYtoutpWq5w==", + "dev": true, + "dependencies": { + "ansi-escapes": "^7.0.0", + "cli-cursor": "^5.0.0", + "slice-ansi": "^7.1.0", + "strip-ansi": "^7.1.0", + "wrap-ansi": "^9.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-update/node_modules/ansi-escapes": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-7.0.0.tgz", + "integrity": "sha512-GdYO7a61mR0fOlAsvC9/rIHf7L96sBc6dEWzeOu+KAea5bZyQRPIpojrVoI4AXGJS/ycu/fBTdLrUkA4ODrvjw==", + "dev": true, + "dependencies": { + "environment": "^1.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-update/node_modules/ansi-regex": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", + "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/log-update/node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/log-update/node_modules/is-fullwidth-code-point": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-5.0.0.tgz", + "integrity": "sha512-OVa3u9kkBbw7b8Xw5F9P+D/T9X+Z4+JruYVNapTjPYZYUznQ5YfWeFkOj606XYYW8yugTfC8Pj0hYqvi4ryAhA==", + "dev": true, + "dependencies": { + "get-east-asian-width": "^1.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-update/node_modules/slice-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-7.1.0.tgz", + "integrity": "sha512-bSiSngZ/jWeX93BqeIAbImyTbEihizcwNjFoRUIY/T1wWQsfsm2Vw1agPKylXvQTU7iASGdHhyqRlqQzfz+Htg==", + "dev": true, + "dependencies": { + "ansi-styles": "^6.2.1", + "is-fullwidth-code-point": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, + "node_modules/log-update/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/log-update/node_modules/wrap-ansi": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.0.tgz", + "integrity": "sha512-G8ura3S+3Z2G+mkgNRq8dqaFZAuxfsxpBB8OCTGRTCtp+l/v9nbFNmCUP1BZMts3G1142MsZfn6eeUKrr4PD1Q==", + "dev": true, + "dependencies": { + "ansi-styles": "^6.2.1", + "string-width": "^7.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, "node_modules/log4js": { "version": "6.9.1", "resolved": "https://registry.npmjs.org/log4js/-/log4js-6.9.1.tgz", @@ -11183,9 +10027,9 @@ } }, "node_modules/loglevel": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.9.1.tgz", - "integrity": "sha512-hP3I3kCrDIMuRwAwHltphhDM1r8i55H33GgqjXbrisuJhF4kRhW1dNuxsRklp4bXl8DSdLaNLuiL4A/LWRfxvg==", + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.9.2.tgz", + "integrity": "sha512-HgMmCqIJSAKqo68l0rS2AanEWfkxaZ5wNiEFb5ggm08lDs9Xl2KxBlX3PTcaD2chBM1gXAYf491/M2Rv8Jwayg==", "dev": true, "engines": { "node": ">= 0.6.0" @@ -11270,15 +10114,12 @@ } }, "node_modules/magic-string": { - "version": "0.30.8", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.8.tgz", - "integrity": "sha512-ISQTe55T2ao7XtlAStud6qwYPZjE4GK1S/BeVPus4jrq6JuOnQ00YKQC581RWhR122W7msZV263KzVeLoqidyQ==", + "version": "0.30.11", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.11.tgz", + "integrity": "sha512-+Wri9p0QHMy+545hKww7YAu5NyzF8iomPL/RQazugQ9+Ez4Ic3mERMd8ZTX5rfK944j+560ZJi8iAwgak1Ac7A==", "dev": true, "dependencies": { - "@jridgewell/sourcemap-codec": "^1.4.15" - }, - "engines": { - "node": ">=12" + "@jridgewell/sourcemap-codec": "^1.5.0" } }, "node_modules/make-dir": { @@ -11319,15 +10160,6 @@ "node": "^16.14.0 || >=18.0.0" } }, - "node_modules/make-fetch-happen/node_modules/proc-log": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-4.2.0.tgz", - "integrity": "sha512-g8+OnU/L2v+wyiVK+D5fA34J7EH8jZ8DDlvwhRCMxmMj7UCBvxiO1mGeN+36JXIKF4zevU4kRBd8lVgG9vLelA==", - "dev": true, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, "node_modules/media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", @@ -11338,22 +10170,32 @@ } }, "node_modules/memfs": { - "version": "3.5.3", - "resolved": "https://registry.npmjs.org/memfs/-/memfs-3.5.3.tgz", - "integrity": "sha512-UERzLsxzllchadvbPs5aolHh65ISpKpM+ccLbOJ8/vvpBKmAWf+la7dXFy7Mr0ySHbdHrFv5kGFCUHHe6GFEmw==", + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/memfs/-/memfs-4.12.0.tgz", + "integrity": "sha512-74wDsex5tQDSClVkeK1vtxqYCAgCoXxx+K4NSHzgU/muYVYByFqa+0RnrPO9NM6naWm1+G9JmZ0p6QHhXmeYfA==", "dev": true, "dependencies": { - "fs-monkey": "^1.0.4" + "@jsonjoy.com/json-pack": "^1.0.3", + "@jsonjoy.com/util": "^1.3.0", + "tree-dump": "^1.0.1", + "tslib": "^2.0.0" }, "engines": { "node": ">= 4.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" } }, "node_modules/merge-descriptors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", - "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==", - "dev": true + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", + "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } }, "node_modules/merge-stream": { "version": "2.0.0", @@ -11380,12 +10222,12 @@ } }, "node_modules/micromatch": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", - "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", "dev": true, "dependencies": { - "braces": "^3.0.2", + "braces": "^3.0.3", "picomatch": "^2.3.1" }, "engines": { @@ -11446,10 +10288,22 @@ "node": ">=6" } }, + "node_modules/mimic-function": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/mimic-function/-/mimic-function-5.0.1.tgz", + "integrity": "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==", + "dev": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/mini-css-extract-plugin": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-2.8.1.tgz", - "integrity": "sha512-/1HDlyFRxWIZPI1ZpgqlZ8jMw/1Dp/dl3P0L1jtZ+zVcHqwPhGwaJwKL00WVgfnBy6PWCde9W65or7IIETImuA==", + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-2.9.0.tgz", + "integrity": "sha512-Zs1YsZVfemekSZG+44vBsYTLQORkPMwnlv+aehcxK/NLKC+EGhDB39/YePYYqx/sTk6NnYpuqikhSn7+JIevTA==", "dev": true, "dependencies": { "schema-utils": "^4.0.0", @@ -11473,9 +10327,9 @@ "dev": true }, "node_modules/minimatch": { - "version": "9.0.3", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", - "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", "dev": true, "dependencies": { "brace-expansion": "^2.0.1" @@ -11496,9 +10350,9 @@ } }, "node_modules/minipass": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.0.tgz", - "integrity": "sha512-oGZRv2OT1lO2UF1zUcwdTb3wqUwI0kBGTgt/T7OdSj6M6N5m3o5uPf0AIW6lVxGGoiWUR7e2AwTE+xiwK8WQig==", + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", "dev": true, "engines": { "node": ">=16 || 14 >=14.17" @@ -11563,34 +10417,6 @@ "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", "dev": true }, - "node_modules/minipass-json-stream": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/minipass-json-stream/-/minipass-json-stream-1.0.1.tgz", - "integrity": "sha512-ODqY18UZt/I8k+b7rl2AENgbWE8IDYam+undIJONvigAz8KR5GWblsFTEfQs0WODsjbSXWlm+JHEv8Gr6Tfdbg==", - "dev": true, - "dependencies": { - "jsonparse": "^1.3.1", - "minipass": "^3.0.0" - } - }, - "node_modules/minipass-json-stream/node_modules/minipass": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", - "dev": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/minipass-json-stream/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/minipass-pipeline": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz", @@ -11755,26 +10581,6 @@ "node": ">=10" } }, - "node_modules/mqtt/node_modules/ws": { - "version": "7.5.10", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz", - "integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==", - "engines": { - "node": ">=8.3.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": "^5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, "node_modules/mqtt/node_modules/yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", @@ -11790,9 +10596,40 @@ } }, "node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "node_modules/msgpackr": { + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/msgpackr/-/msgpackr-1.11.0.tgz", + "integrity": "sha512-I8qXuuALqJe5laEBYoFykChhSXLikZmUhccjGsPuSJ/7uPip2TJ7lwdIQwWSAi0jGZDXv4WOP8Qg65QZRuXxXw==", + "dev": true, + "optionalDependencies": { + "msgpackr-extract": "^3.0.2" + } + }, + "node_modules/msgpackr-extract": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/msgpackr-extract/-/msgpackr-extract-3.0.3.tgz", + "integrity": "sha512-P0efT1C9jIdVRefqjzOQ9Xml57zpOXnIuS+csaB4MdZbTdmGDLo8XhzBG1N7aO11gKDDkJvBLULeFTo46wwreA==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "dependencies": { + "node-gyp-build-optional-packages": "5.2.2" + }, + "bin": { + "download-msgpackr-prebuilds": "bin/download-prebuilds.js" + }, + "optionalDependencies": { + "@msgpackr-extract/msgpackr-extract-darwin-arm64": "3.0.3", + "@msgpackr-extract/msgpackr-extract-darwin-x64": "3.0.3", + "@msgpackr-extract/msgpackr-extract-linux-arm": "3.0.3", + "@msgpackr-extract/msgpackr-extract-linux-arm64": "3.0.3", + "@msgpackr-extract/msgpackr-extract-linux-x64": "3.0.3", + "@msgpackr-extract/msgpackr-extract-win32-x64": "3.0.3" + } }, "node_modules/multicast-dns": { "version": "7.2.5", @@ -11927,13 +10764,19 @@ "node-gyp-build": "^4.2.2" } }, - "node_modules/node-addon-api": { + "node_modules/nice-napi/node_modules/node-addon-api": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-3.2.1.tgz", "integrity": "sha512-mmcei9JghVNDYydghQmeDX8KoAm0FAiYyIcUt/N4nhyAipB17pllZQDOJD2fotxABnt4Mdz+dKTO7eftLg4d0A==", "dev": true, "optional": true }, + "node_modules/node-addon-api": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-6.1.0.tgz", + "integrity": "sha512-+eawOlIgy680F0kBzPUNFhMZGtJ1YmqM6l4+Crf4IkImjYrO/mqPwRMh352g23uIaQKFItcQ64I7KMaJxHgAVA==", + "dev": true + }, "node_modules/node-forge": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz", @@ -11944,9 +10787,9 @@ } }, "node_modules/node-gyp": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-10.1.0.tgz", - "integrity": "sha512-B4J5M1cABxPc5PwfjhbV5hoy2DP9p8lFXASnEN6hugXOa61416tnTZ29x9sSwAd0o99XNIcpvDDy1swAExsVKA==", + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-10.2.0.tgz", + "integrity": "sha512-sp3FonBAaFe4aYTcFdZUn2NYkbP7xroPGYvQmP4Nl5PxamznItBnNCgjrVTKrEfQynInMsJvZrdmqUnysCJ8rw==", "dev": true, "dependencies": { "env-paths": "^2.2.0", @@ -11955,9 +10798,9 @@ "graceful-fs": "^4.2.6", "make-fetch-happen": "^13.0.0", "nopt": "^7.0.0", - "proc-log": "^3.0.0", + "proc-log": "^4.1.0", "semver": "^7.3.5", - "tar": "^6.1.2", + "tar": "^6.2.1", "which": "^4.0.0" }, "bin": { @@ -11968,9 +10811,9 @@ } }, "node_modules/node-gyp-build": { - "version": "4.8.1", - "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.1.tgz", - "integrity": "sha512-OSs33Z9yWr148JZcbZd5WiAXhh/n9z8TxQcdMhIOlpN9AhWpLfvVFO73+m77bBABQMaY9XSvIa+qk0jlI7Gcaw==", + "version": "4.8.2", + "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.2.tgz", + "integrity": "sha512-IRUxE4BVsHWXkV/SFOut4qTlagw2aM8T5/vnTsmrHJvVoKueJHRc/JaFND7QDDc61kLYUJ6qlZM3sqTSyx2dTw==", "dev": true, "optional": true, "bin": { @@ -11979,24 +10822,36 @@ "node-gyp-build-test": "build-test.js" } }, + "node_modules/node-gyp-build-optional-packages": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/node-gyp-build-optional-packages/-/node-gyp-build-optional-packages-5.2.2.tgz", + "integrity": "sha512-s+w+rBWnpTMwSFbaE0UXsRlg7hU4FjekKU4eyAih5T8nJuNZT1nNsskXpxmeqSK9UzkBl6UgRlnKc8hz8IEqOw==", + "dev": true, + "dependencies": { + "detect-libc": "^2.0.1" + }, + "bin": { + "node-gyp-build-optional-packages": "bin.js", + "node-gyp-build-optional-packages-optional": "optional.js", + "node-gyp-build-optional-packages-test": "build-test.js" + } + }, "node_modules/node-gyp/node_modules/glob": { - "version": "10.3.12", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.12.tgz", - "integrity": "sha512-TCNv8vJ+xz4QiqTpfOJA7HvYv+tNIRHKfUWw/q+v2jdgN4ebz+KY9tGx5J4rHP0o84mNP+ApH66HRX8us3Khqg==", + "version": "10.4.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", + "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", "dev": true, "dependencies": { "foreground-child": "^3.1.0", - "jackspeak": "^2.3.6", - "minimatch": "^9.0.1", - "minipass": "^7.0.4", - "path-scurry": "^1.10.2" + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" }, "bin": { "glob": "dist/esm/bin.mjs" }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, "funding": { "url": "https://github.com/sponsors/isaacs" } @@ -12025,16 +10880,10 @@ "node": "^16.13.0 || >=18.0.0" } }, - "node_modules/node-machine-id": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/node-machine-id/-/node-machine-id-1.1.12.tgz", - "integrity": "sha512-QNABxbrPa3qEIfrE6GOJ7BYIuignnJw7iQ2YPbc3Nla1HzRJjXzZOiikfF8m7eAMfichLt3M4VgLOetqgDmgGQ==", - "dev": true - }, "node_modules/node-releases": { - "version": "2.0.14", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz", - "integrity": "sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==", + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.18.tgz", + "integrity": "sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==", "dev": true }, "node_modules/nopt": { @@ -12053,13 +10902,12 @@ } }, "node_modules/normalize-package-data": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-6.0.1.tgz", - "integrity": "sha512-6rvCfeRW+OEZagAB4lMLSNuTNYZWLVtKccK79VSTf//yTY5VOCgcpH80O+bZK8Neps7pUnd5G+QlMg1yV/2iZQ==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-6.0.2.tgz", + "integrity": "sha512-V6gygoYb/5EmNI+MEGrWkC+e6+Rr7mTmfHrxDbLzxQogBkgzo76rkok0Am6thgSF7Mv2nLOajAJj5vDJZEFn7g==", "dev": true, "dependencies": { "hosted-git-info": "^7.0.0", - "is-core-module": "^2.8.1", "semver": "^7.3.5", "validate-npm-package-license": "^3.0.4" }, @@ -12073,1204 +10921,1476 @@ "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", "dev": true, "engines": { - "node": ">=0.10.0" + "node": ">=0.10.0" + } + }, + "node_modules/normalize-range": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz", + "integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/npm-bundled": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-3.0.1.tgz", + "integrity": "sha512-+AvaheE/ww1JEwRHOrn4WHNzOxGtVp+adrg2AeZS/7KuxGUYFuBta98wYpfHBbJp6Tg6j1NKSEVHNcfZzJHQwQ==", + "dev": true, + "dependencies": { + "npm-normalize-package-bin": "^3.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm-install-checks": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/npm-install-checks/-/npm-install-checks-6.3.0.tgz", + "integrity": "sha512-W29RiK/xtpCGqn6f3ixfRYGk+zRyr+Ew9F2E20BfXxT5/euLdA/Nm7fO7OeTGuAmTs30cpgInyJ0cYe708YTZw==", + "dev": true, + "dependencies": { + "semver": "^7.1.1" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm-normalize-package-bin": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-3.0.1.tgz", + "integrity": "sha512-dMxCf+zZ+3zeQZXKxmyuCKlIDPGuv8EF940xbkC4kQVDTtqoh6rJFO+JTKSA6/Rwi0getWmtuy4Itup0AMcaDQ==", + "dev": true, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm-package-arg": { + "version": "11.0.3", + "resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-11.0.3.tgz", + "integrity": "sha512-sHGJy8sOC1YraBywpzQlIKBE4pBbGbiF95U6Auspzyem956E0+FtDtsx1ZxlOJkQCZ1AFXAY/yuvtFYrOxF+Bw==", + "dev": true, + "dependencies": { + "hosted-git-info": "^7.0.0", + "proc-log": "^4.0.0", + "semver": "^7.3.5", + "validate-npm-package-name": "^5.0.0" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm-packlist": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-8.0.2.tgz", + "integrity": "sha512-shYrPFIS/JLP4oQmAwDyk5HcyysKW8/JLTEA32S0Z5TzvpaeeX2yMFfoK1fjEBnCBvVyIB/Jj/GBFdm0wsgzbA==", + "dev": true, + "dependencies": { + "ignore-walk": "^6.0.4" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm-pick-manifest": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/npm-pick-manifest/-/npm-pick-manifest-9.1.0.tgz", + "integrity": "sha512-nkc+3pIIhqHVQr085X9d2JzPzLyjzQS96zbruppqC9aZRm/x8xx6xhI98gHtsfELP2bE+loHq8ZaHFHhe+NauA==", + "dev": true, + "dependencies": { + "npm-install-checks": "^6.0.0", + "npm-normalize-package-bin": "^3.0.0", + "npm-package-arg": "^11.0.0", + "semver": "^7.3.5" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm-registry-fetch": { + "version": "17.1.0", + "resolved": "https://registry.npmjs.org/npm-registry-fetch/-/npm-registry-fetch-17.1.0.tgz", + "integrity": "sha512-5+bKQRH0J1xG1uZ1zMNvxW0VEyoNWgJpY9UDuluPFLKDfJ9u2JmmjmTJV1srBGQOROfdBMiVvnH2Zvpbm+xkVA==", + "dev": true, + "dependencies": { + "@npmcli/redact": "^2.0.0", + "jsonparse": "^1.3.1", + "make-fetch-happen": "^13.0.0", + "minipass": "^7.0.2", + "minipass-fetch": "^3.0.0", + "minizlib": "^2.1.2", + "npm-package-arg": "^11.0.0", + "proc-log": "^4.0.0" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dev": true, + "dependencies": { + "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/nth-check": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", + "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", + "dev": true, + "dependencies": { + "boolbase": "^1.0.0" + }, + "funding": { + "url": "https://github.com/fb55/nth-check?sponsor=1" + } + }, + "node_modules/number-allocator": { + "version": "1.0.14", + "resolved": "https://registry.npmjs.org/number-allocator/-/number-allocator-1.0.14.tgz", + "integrity": "sha512-OrL44UTVAvkKdOdRQZIJpLkAdjXGTRda052sN4sO77bKEzYYqWKMBjQvrJFzqygI99gL6Z4u2xctPW1tB8ErvA==", + "dependencies": { + "debug": "^4.3.1", + "js-sdsl": "4.3.0" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.2.tgz", + "integrity": "sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/obuf": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/obuf/-/obuf-1.1.2.tgz", + "integrity": "sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==", + "dev": true + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "dev": true, + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/on-headers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", + "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/onetime": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-7.0.0.tgz", + "integrity": "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==", + "dev": true, + "dependencies": { + "mimic-function": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/open": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/open/-/open-10.1.0.tgz", + "integrity": "sha512-mnkeQ1qP5Ue2wd+aivTD3NHd/lZ96Lu0jgf0pwktLPtx6cTZiH7tyeGRRHs0zX0rbrahXPnXlUnbeXyaBBuIaw==", + "dev": true, + "dependencies": { + "default-browser": "^5.2.1", + "define-lazy-prop": "^3.0.0", + "is-inside-container": "^1.0.0", + "is-wsl": "^3.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/ora": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz", + "integrity": "sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==", + "dev": true, + "dependencies": { + "bl": "^4.1.0", + "chalk": "^4.1.0", + "cli-cursor": "^3.1.0", + "cli-spinners": "^2.5.0", + "is-interactive": "^1.0.0", + "is-unicode-supported": "^0.1.0", + "log-symbols": "^4.1.0", + "strip-ansi": "^6.0.0", + "wcwidth": "^1.0.1" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/normalize-range": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz", - "integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==", + "node_modules/ora/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, "engines": { - "node": ">=0.10.0" + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/npm-bundled": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-3.0.1.tgz", - "integrity": "sha512-+AvaheE/ww1JEwRHOrn4WHNzOxGtVp+adrg2AeZS/7KuxGUYFuBta98wYpfHBbJp6Tg6j1NKSEVHNcfZzJHQwQ==", + "node_modules/ora/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, "dependencies": { - "npm-normalize-package-bin": "^3.0.0" + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/npm-install-checks": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/npm-install-checks/-/npm-install-checks-6.3.0.tgz", - "integrity": "sha512-W29RiK/xtpCGqn6f3ixfRYGk+zRyr+Ew9F2E20BfXxT5/euLdA/Nm7fO7OeTGuAmTs30cpgInyJ0cYe708YTZw==", + "node_modules/ora/node_modules/cli-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", + "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", "dev": true, "dependencies": { - "semver": "^7.1.1" + "restore-cursor": "^3.1.0" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": ">=8" } }, - "node_modules/npm-normalize-package-bin": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-3.0.1.tgz", - "integrity": "sha512-dMxCf+zZ+3zeQZXKxmyuCKlIDPGuv8EF940xbkC4kQVDTtqoh6rJFO+JTKSA6/Rwi0getWmtuy4Itup0AMcaDQ==", + "node_modules/ora/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": ">=7.0.0" } }, - "node_modules/npm-package-arg": { - "version": "11.0.1", - "resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-11.0.1.tgz", - "integrity": "sha512-M7s1BD4NxdAvBKUPqqRW957Xwcl/4Zvo8Aj+ANrzvIPzGJZElrH7Z//rSaec2ORcND6FHHLnZeY8qgTpXDMFQQ==", + "node_modules/ora/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/ora/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true, - "dependencies": { - "hosted-git-info": "^7.0.0", - "proc-log": "^3.0.0", - "semver": "^7.3.5", - "validate-npm-package-name": "^5.0.0" - }, "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": ">=8" } }, - "node_modules/npm-packlist": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-8.0.2.tgz", - "integrity": "sha512-shYrPFIS/JLP4oQmAwDyk5HcyysKW8/JLTEA32S0Z5TzvpaeeX2yMFfoK1fjEBnCBvVyIB/Jj/GBFdm0wsgzbA==", + "node_modules/ora/node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", "dev": true, "dependencies": { - "ignore-walk": "^6.0.4" + "mimic-fn": "^2.1.0" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/npm-pick-manifest": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/npm-pick-manifest/-/npm-pick-manifest-9.0.0.tgz", - "integrity": "sha512-VfvRSs/b6n9ol4Qb+bDwNGUXutpy76x6MARw/XssevE0TnctIKcmklJZM5Z7nqs5z5aW+0S63pgCNbpkUNNXBg==", + "node_modules/ora/node_modules/restore-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", + "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", "dev": true, "dependencies": { - "npm-install-checks": "^6.0.0", - "npm-normalize-package-bin": "^3.0.0", - "npm-package-arg": "^11.0.0", - "semver": "^7.3.5" + "onetime": "^5.1.0", + "signal-exit": "^3.0.2" }, "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": ">=8" } }, - "node_modules/npm-registry-fetch": { - "version": "16.2.1", - "resolved": "https://registry.npmjs.org/npm-registry-fetch/-/npm-registry-fetch-16.2.1.tgz", - "integrity": "sha512-8l+7jxhim55S85fjiDGJ1rZXBWGtRLi1OSb4Z3BPLObPuIaeKRlPRiYMSHU4/81ck3t71Z+UwDDl47gcpmfQQA==", + "node_modules/ora/node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true + }, + "node_modules/ora/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, "dependencies": { - "@npmcli/redact": "^1.1.0", - "make-fetch-happen": "^13.0.0", - "minipass": "^7.0.2", - "minipass-fetch": "^3.0.0", - "minipass-json-stream": "^1.0.1", - "minizlib": "^2.1.2", - "npm-package-arg": "^11.0.0", - "proc-log": "^4.0.0" + "has-flag": "^4.0.0" }, "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": ">=8" } }, - "node_modules/npm-registry-fetch/node_modules/proc-log": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-4.2.0.tgz", - "integrity": "sha512-g8+OnU/L2v+wyiVK+D5fA34J7EH8jZ8DDlvwhRCMxmMj7UCBvxiO1mGeN+36JXIKF4zevU4kRBd8lVgG9vLelA==", + "node_modules/ordered-binary": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/ordered-binary/-/ordered-binary-1.5.2.tgz", + "integrity": "sha512-JTo+4+4Fw7FreyAvlSLjb1BBVaxEQAacmjD3jjuyPZclpbEghTvQZbXBb2qPd2LeIMxiHwXBZUcpmG2Gl/mDEA==", + "dev": true + }, + "node_modules/os-tmpdir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "integrity": "sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==", "dev": true, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": ">=0.10.0" } }, - "node_modules/npm-run-path": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", - "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", "dev": true, "dependencies": { - "path-key": "^3.0.0" + "yocto-queue": "^0.1.0" }, "engines": { - "node": ">=8" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/nth-check": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", - "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", "dev": true, "dependencies": { - "boolbase": "^1.0.0" + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" }, "funding": { - "url": "https://github.com/fb55/nth-check?sponsor=1" - } - }, - "node_modules/number-allocator": { - "version": "1.0.14", - "resolved": "https://registry.npmjs.org/number-allocator/-/number-allocator-1.0.14.tgz", - "integrity": "sha512-OrL44UTVAvkKdOdRQZIJpLkAdjXGTRda052sN4sO77bKEzYYqWKMBjQvrJFzqygI99gL6Z4u2xctPW1tB8ErvA==", - "dependencies": { - "debug": "^4.3.1", - "js-sdsl": "4.3.0" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/nx": { - "version": "17.2.8", - "resolved": "https://registry.npmjs.org/nx/-/nx-17.2.8.tgz", - "integrity": "sha512-rM5zXbuXLEuqQqcjVjClyvHwRJwt+NVImR2A6KFNG40Z60HP6X12wAxxeLHF5kXXTDRU0PFhf/yACibrpbPrAw==", + "node_modules/p-map": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", + "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", "dev": true, - "hasInstallScript": true, "dependencies": { - "@nrwl/tao": "17.2.8", - "@yarnpkg/lockfile": "^1.1.0", - "@yarnpkg/parsers": "3.0.0-rc.46", - "@zkochan/js-yaml": "0.0.6", - "axios": "^1.5.1", - "chalk": "^4.1.0", - "cli-cursor": "3.1.0", - "cli-spinners": "2.6.1", - "cliui": "^8.0.1", - "dotenv": "~16.3.1", - "dotenv-expand": "~10.0.0", - "enquirer": "~2.3.6", - "figures": "3.2.0", - "flat": "^5.0.2", - "fs-extra": "^11.1.0", - "glob": "7.1.4", - "ignore": "^5.0.4", - "jest-diff": "^29.4.1", - "js-yaml": "4.1.0", - "jsonc-parser": "3.2.0", - "lines-and-columns": "~2.0.3", - "minimatch": "3.0.5", - "node-machine-id": "1.1.12", - "npm-run-path": "^4.0.1", - "open": "^8.4.0", - "semver": "7.5.3", - "string-width": "^4.2.3", - "strong-log-transformer": "^2.1.0", - "tar-stream": "~2.2.0", - "tmp": "~0.2.1", - "tsconfig-paths": "^4.1.2", - "tslib": "^2.3.0", - "yargs": "^17.6.2", - "yargs-parser": "21.1.1" - }, - "bin": { - "nx": "bin/nx.js", - "nx-cloud": "bin/nx-cloud.js" - }, - "optionalDependencies": { - "@nx/nx-darwin-arm64": "17.2.8", - "@nx/nx-darwin-x64": "17.2.8", - "@nx/nx-freebsd-x64": "17.2.8", - "@nx/nx-linux-arm-gnueabihf": "17.2.8", - "@nx/nx-linux-arm64-gnu": "17.2.8", - "@nx/nx-linux-arm64-musl": "17.2.8", - "@nx/nx-linux-x64-gnu": "17.2.8", - "@nx/nx-linux-x64-musl": "17.2.8", - "@nx/nx-win32-arm64-msvc": "17.2.8", - "@nx/nx-win32-x64-msvc": "17.2.8" + "aggregate-error": "^3.0.0" }, - "peerDependencies": { - "@swc-node/register": "^1.6.7", - "@swc/core": "^1.3.85" + "engines": { + "node": ">=10" }, - "peerDependenciesMeta": { - "@swc-node/register": { - "optional": true - }, - "@swc/core": { - "optional": true - } + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/nx/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "node_modules/p-retry": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-6.2.0.tgz", + "integrity": "sha512-JA6nkq6hKyWLLasXQXUrO4z8BUZGUt/LjlJxx8Gb2+2ntodU/SS63YZ8b0LUTbQ8ZB9iwOfhEPhg4ykKnn2KsA==", "dev": true, "dependencies": { - "color-convert": "^2.0.1" + "@types/retry": "0.12.2", + "is-network-error": "^1.0.0", + "retry": "^0.13.1" }, "engines": { - "node": ">=8" + "node": ">=16.17" }, "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/nx/node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true - }, - "node_modules/nx/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "node_modules/p-retry/node_modules/retry": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", + "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==", "dev": true, - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" + "engines": { + "node": ">= 4" } }, - "node_modules/nx/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "dev": true + }, + "node_modules/pacote": { + "version": "18.0.6", + "resolved": "https://registry.npmjs.org/pacote/-/pacote-18.0.6.tgz", + "integrity": "sha512-+eK3G27SMwsB8kLIuj4h1FUhHtwiEUo21Tw8wNjmvdlpOEr613edv+8FUsTj/4F/VN5ywGE19X18N7CC2EJk6A==", "dev": true, "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" + "@npmcli/git": "^5.0.0", + "@npmcli/installed-package-contents": "^2.0.1", + "@npmcli/package-json": "^5.1.0", + "@npmcli/promise-spawn": "^7.0.0", + "@npmcli/run-script": "^8.0.0", + "cacache": "^18.0.0", + "fs-minipass": "^3.0.0", + "minipass": "^7.0.2", + "npm-package-arg": "^11.0.0", + "npm-packlist": "^8.0.0", + "npm-pick-manifest": "^9.0.0", + "npm-registry-fetch": "^17.0.0", + "proc-log": "^4.0.0", + "promise-retry": "^2.0.1", + "sigstore": "^2.2.0", + "ssri": "^10.0.0", + "tar": "^6.1.11" + }, + "bin": { + "pacote": "bin/index.js" }, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "node": "^16.14.0 || >=18.0.0" } }, - "node_modules/nx/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", "dev": true, "dependencies": { - "color-name": "~1.1.4" + "callsites": "^3.0.0" }, "engines": { - "node": ">=7.0.0" + "node": ">=6" } }, - "node_modules/nx/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/nx/node_modules/glob": { - "version": "7.1.4", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.4.tgz", - "integrity": "sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==", + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", "dev": true, "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" }, "engines": { - "node": "*" + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/nx/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "node_modules/parse-json/node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true + }, + "node_modules/parse-node-version": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parse-node-version/-/parse-node-version-1.0.1.tgz", + "integrity": "sha512-3YHlOa/JgH6Mnpr05jP9eDG254US9ek25LyIxZlDItp2iJtwyaXQb57lBYLdT3MowkUFYEV2XXNAYIPlESvJlA==", "dev": true, "engines": { - "node": ">=8" + "node": ">= 0.10" } }, - "node_modules/nx/node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dev": true, + "node_modules/parse5": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.1.2.tgz", + "integrity": "sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==", + "devOptional": true, "dependencies": { - "argparse": "^2.0.1" + "entities": "^4.4.0" }, - "bin": { - "js-yaml": "bin/js-yaml.js" + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" } }, - "node_modules/nx/node_modules/jsonc-parser": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.0.tgz", - "integrity": "sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==", - "dev": true - }, - "node_modules/nx/node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "node_modules/parse5-html-rewriting-stream": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/parse5-html-rewriting-stream/-/parse5-html-rewriting-stream-7.0.0.tgz", + "integrity": "sha512-mazCyGWkmCRWDI15Zp+UiCqMp/0dgEmkZRvhlsqqKYr4SsVm/TvnSpD9fCvqCA2zoWJcfRym846ejWBBHRiYEg==", "dev": true, "dependencies": { - "yallist": "^4.0.0" + "entities": "^4.3.0", + "parse5": "^7.0.0", + "parse5-sax-parser": "^7.0.0" }, - "engines": { - "node": ">=10" + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" } }, - "node_modules/nx/node_modules/minimatch": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.5.tgz", - "integrity": "sha512-tUpxzX0VAzJHjLu0xUfFv1gwVp9ba3IOuRAVH2EGuRW8a5emA2FlACLqiT/lDVtS1W+TGNwqz3sWaNyLgDJWuw==", + "node_modules/parse5-sax-parser": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/parse5-sax-parser/-/parse5-sax-parser-7.0.0.tgz", + "integrity": "sha512-5A+v2SNsq8T6/mG3ahcz8ZtQ0OUFTatxPbeidoMB7tkJSGDY3tdfl4MHovtLQHkEn5CGxijNWRQHhRQ6IRpXKg==", "dev": true, "dependencies": { - "brace-expansion": "^1.1.7" + "parse5": "^7.0.0" }, - "engines": { - "node": "*" + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" } }, - "node_modules/nx/node_modules/semver": { - "version": "7.5.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.3.tgz", - "integrity": "sha512-QBlUtyVk/5EeHbi7X0fw6liDZc7BBmEaSYn01fMU1OUYbf6GPsbTtd8WmnqbI20SeycoHSeiybkE/q1Q+qlThQ==", + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, "engines": { - "node": ">=10" + "node": ">= 0.8" } }, - "node_modules/nx/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, "engines": { "node": ">=8" } }, - "node_modules/nx/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/object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", - "dev": true, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", "engines": { "node": ">=0.10.0" } }, - "node_modules/object-inspect": { - "version": "1.13.1", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz", - "integrity": "sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==", + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", "dev": true, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "engines": { + "node": ">=8" } }, - "node_modules/obuf": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/obuf/-/obuf-1.1.2.tgz", - "integrity": "sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==", + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", "dev": true }, - "node_modules/on-finished": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", - "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", "dev": true, "dependencies": { - "ee-first": "1.1.1" + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" }, "engines": { - "node": ">= 0.8" + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/on-headers": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", - "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==", - "dev": true, - "engines": { - "node": ">= 0.8" - } + "node_modules/path-scurry/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true }, - "node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dependencies": { - "wrappy": "1" - } + "node_modules/path-to-regexp": { + "version": "0.1.10", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.10.tgz", + "integrity": "sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w==", + "dev": true }, - "node_modules/onetime": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", - "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "node_modules/path-type": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-5.0.0.tgz", + "integrity": "sha512-5HviZNaZcfqP95rwpv+1HDgUamezbqdSYTyzjTvwtJSnIH+3vnbmWsItli8OFEndS984VT55M3jduxZbX351gg==", "dev": true, - "dependencies": { - "mimic-fn": "^2.1.0" - }, "engines": { - "node": ">=6" + "node": ">=12" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/open": { - "version": "8.4.2", - "resolved": "https://registry.npmjs.org/open/-/open-8.4.2.tgz", - "integrity": "sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==", + "node_modules/picocolors": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.0.tgz", + "integrity": "sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw==", + "dev": true + }, + "node_modules/picomatch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", + "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", "dev": true, - "dependencies": { - "define-lazy-prop": "^2.0.0", - "is-docker": "^2.1.1", - "is-wsl": "^2.2.0" - }, "engines": { "node": ">=12" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/optionator": { - "version": "0.9.4", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", - "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "node_modules/pify": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", "dev": true, - "dependencies": { - "deep-is": "^0.1.3", - "fast-levenshtein": "^2.0.6", - "levn": "^0.4.1", - "prelude-ls": "^1.2.1", - "type-check": "^0.4.0", - "word-wrap": "^1.2.5" - }, + "optional": true, "engines": { - "node": ">= 0.8.0" + "node": ">=6" } }, - "node_modules/ora": { - "version": "5.4.1", - "resolved": "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz", - "integrity": "sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==", + "node_modules/piscina": { + "version": "4.6.1", + "resolved": "https://registry.npmjs.org/piscina/-/piscina-4.6.1.tgz", + "integrity": "sha512-z30AwWGtQE+Apr+2WBZensP2lIvwoaMcOPkQlIEmSGMJNUvaYACylPYrQM6wSdUNJlnDVMSpLv7xTMJqlVshOA==", "dev": true, - "dependencies": { - "bl": "^4.1.0", - "chalk": "^4.1.0", - "cli-cursor": "^3.1.0", - "cli-spinners": "^2.5.0", - "is-interactive": "^1.0.0", - "is-unicode-supported": "^0.1.0", - "log-symbols": "^4.1.0", - "strip-ansi": "^6.0.0", - "wcwidth": "^1.0.1" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "optionalDependencies": { + "nice-napi": "^1.0.2" } }, - "node_modules/ora/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "node_modules/pkg-dir": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-7.0.0.tgz", + "integrity": "sha512-Ie9z/WINcxxLp27BKOCHGde4ITq9UklYKDzVo1nhk5sqGEXU3FpkwP5GM2voTGJkGd9B3Otl+Q4uwSOeSUtOBA==", "dev": true, "dependencies": { - "color-convert": "^2.0.1" + "find-up": "^6.3.0" }, "engines": { - "node": ">=8" + "node": ">=14.16" }, "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/ora/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "node_modules/pkg-dir/node_modules/find-up": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-6.3.0.tgz", + "integrity": "sha512-v2ZsoEuVHYy8ZIlYqwPe/39Cy+cFDzp4dXPaxNvkEuouymu+2Jbz0PxpKarJHYJTmv2HWT3O382qY8l4jMWthw==", "dev": true, "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" + "locate-path": "^7.1.0", + "path-exists": "^5.0.0" }, "engines": { - "node": ">=10" + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" }, "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/ora/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "node_modules/pkg-dir/node_modules/locate-path": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-7.2.0.tgz", + "integrity": "sha512-gvVijfZvn7R+2qyPX8mAuKcFGDf6Nc61GdvGafQsHL0sBIxfKzA+usWn4GFC/bk+QdwPUD4kWFJLhElipq+0VA==", "dev": true, "dependencies": { - "color-name": "~1.1.4" + "p-locate": "^6.0.0" }, "engines": { - "node": ">=7.0.0" + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/ora/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/ora/node_modules/has-flag": { + "node_modules/pkg-dir/node_modules/p-limit": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-4.0.0.tgz", + "integrity": "sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==", "dev": true, + "dependencies": { + "yocto-queue": "^1.0.0" + }, "engines": { - "node": ">=8" + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/ora/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "node_modules/pkg-dir/node_modules/p-locate": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-6.0.0.tgz", + "integrity": "sha512-wPrq66Llhl7/4AGC6I+cqxT07LhXvWL08LNXz1fENOw0Ap4sRZZ/gZpTTJ5jpurzzzfS2W/Ge9BY3LgLjCShcw==", "dev": true, "dependencies": { - "has-flag": "^4.0.0" + "p-limit": "^4.0.0" }, "engines": { - "node": ">=8" + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/os-tmpdir": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", - "integrity": "sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==", + "node_modules/pkg-dir/node_modules/path-exists": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-5.0.0.tgz", + "integrity": "sha512-RjhtfwJOxzcFmNOi6ltcbcu4Iu+FL3zEj83dk4kAS+fVpTxXLO1b38RvJgT/0QwvV/L3aY9TAnyv0EOqW4GoMQ==", "dev": true, "engines": { - "node": ">=0.10.0" + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" } }, - "node_modules/p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "node_modules/pkg-dir/node_modules/yocto-queue": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.1.1.tgz", + "integrity": "sha512-b4JR1PFR10y1mKjhHY9LaGo6tmrgjit7hxVIeAmyMw3jegXR4dhYqLaQF5zMXZxY7tLpMyJeLjr1C4rLmkVe8g==", "dev": true, - "dependencies": { - "p-try": "^2.0.0" - }, "engines": { - "node": ">=6" + "node": ">=12.20" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "node_modules/postcss": { + "version": "8.4.41", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.41.tgz", + "integrity": "sha512-TesUflQ0WKZqAvg52PWL6kHgLKP6xB6heTOdoYM0Wt2UHyxNa4K25EZZMgKns3BH1RLVbZCREPpLY0rhnNoHVQ==", "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], "dependencies": { - "p-limit": "^2.2.0" + "nanoid": "^3.3.7", + "picocolors": "^1.0.1", + "source-map-js": "^1.2.0" }, "engines": { - "node": ">=8" + "node": "^10 || ^12 || >=14" } }, - "node_modules/p-map": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", - "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", + "node_modules/postcss-loader": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/postcss-loader/-/postcss-loader-8.1.1.tgz", + "integrity": "sha512-0IeqyAsG6tYiDRCYKQJLAmgQr47DX6N7sFSWvQxt6AcupX8DIdmykuk/o/tx0Lze3ErGHJEp5OSRxrelC6+NdQ==", "dev": true, "dependencies": { - "aggregate-error": "^3.0.0" + "cosmiconfig": "^9.0.0", + "jiti": "^1.20.0", + "semver": "^7.5.4" }, "engines": { - "node": ">=10" + "node": ">= 18.12.0" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-retry": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-4.6.2.tgz", - "integrity": "sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ==", - "dev": true, - "dependencies": { - "@types/retry": "0.12.0", - "retry": "^0.13.1" + "type": "opencollective", + "url": "https://opencollective.com/webpack" }, - "engines": { - "node": ">=8" + "peerDependencies": { + "@rspack/core": "0.x || 1.x", + "postcss": "^7.0.0 || ^8.0.1", + "webpack": "^5.0.0" + }, + "peerDependenciesMeta": { + "@rspack/core": { + "optional": true + }, + "webpack": { + "optional": true + } } }, - "node_modules/p-retry/node_modules/retry": { - "version": "0.13.1", - "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", - "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==", - "dev": true, - "engines": { - "node": ">= 4" - } + "node_modules/postcss-media-query-parser": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/postcss-media-query-parser/-/postcss-media-query-parser-0.2.3.tgz", + "integrity": "sha512-3sOlxmbKcSHMjlUXQZKQ06jOswE7oVkXPxmZdoB1r5l0q6gTFTQSHxNxOrCccElbW7dxNytifNEo8qidX2Vsig==", + "dev": true }, - "node_modules/p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "node_modules/postcss-modules-extract-imports": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.1.0.tgz", + "integrity": "sha512-k3kNe0aNFQDAZGbin48pL2VNidTF0w4/eASDsxlyspobzU3wZQLOGj7L9gfRe0Jo9/4uud09DsjFNH7winGv8Q==", "dev": true, "engines": { - "node": ">=6" - } - }, - "node_modules/pacote": { - "version": "17.0.4", - "resolved": "https://registry.npmjs.org/pacote/-/pacote-17.0.4.tgz", - "integrity": "sha512-eGdLHrV/g5b5MtD5cTPyss+JxOlaOloSMG3UwPMAvL8ywaLJ6beONPF40K4KKl/UI6q5hTKCJq5rCu8tkF+7Dg==", - "dev": true, - "dependencies": { - "@npmcli/git": "^5.0.0", - "@npmcli/installed-package-contents": "^2.0.1", - "@npmcli/promise-spawn": "^7.0.0", - "@npmcli/run-script": "^7.0.0", - "cacache": "^18.0.0", - "fs-minipass": "^3.0.0", - "minipass": "^7.0.2", - "npm-package-arg": "^11.0.0", - "npm-packlist": "^8.0.0", - "npm-pick-manifest": "^9.0.0", - "npm-registry-fetch": "^16.0.0", - "proc-log": "^3.0.0", - "promise-retry": "^2.0.1", - "read-package-json": "^7.0.0", - "read-package-json-fast": "^3.0.0", - "sigstore": "^2.0.0", - "ssri": "^10.0.0", - "tar": "^6.1.11" - }, - "bin": { - "pacote": "lib/bin.js" + "node": "^10 || ^12 || >= 14" }, - "engines": { - "node": "^16.14.0 || >=18.0.0" + "peerDependencies": { + "postcss": "^8.1.0" } }, - "node_modules/parent-module": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", - "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "node_modules/postcss-modules-local-by-default": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.5.tgz", + "integrity": "sha512-6MieY7sIfTK0hYfafw1OMEG+2bg8Q1ocHCpoWLqOKj3JXlKu4G7btkmM/B7lFubYkYWmRSPLZi5chid63ZaZYw==", "dev": true, "dependencies": { - "callsites": "^3.0.0" + "icss-utils": "^5.0.0", + "postcss-selector-parser": "^6.0.2", + "postcss-value-parser": "^4.1.0" }, "engines": { - "node": ">=6" + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" } }, - "node_modules/parse-json": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", - "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "node_modules/postcss-modules-scope": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-3.2.0.tgz", + "integrity": "sha512-oq+g1ssrsZOsx9M96c5w8laRmvEu9C3adDSjI8oTcbfkrTE8hx/zfyobUoWIxaKPO8bt6S62kxpw5GqypEw1QQ==", "dev": true, "dependencies": { - "@babel/code-frame": "^7.0.0", - "error-ex": "^1.3.1", - "json-parse-even-better-errors": "^2.3.0", - "lines-and-columns": "^1.1.6" + "postcss-selector-parser": "^6.0.4" }, "engines": { - "node": ">=8" + "node": "^10 || ^12 || >= 14" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "peerDependencies": { + "postcss": "^8.1.0" } }, - "node_modules/parse-json/node_modules/json-parse-even-better-errors": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", - "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", - "dev": true - }, - "node_modules/parse-json/node_modules/lines-and-columns": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", - "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", - "dev": true - }, - "node_modules/parse-node-version": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parse-node-version/-/parse-node-version-1.0.1.tgz", - "integrity": "sha512-3YHlOa/JgH6Mnpr05jP9eDG254US9ek25LyIxZlDItp2iJtwyaXQb57lBYLdT3MowkUFYEV2XXNAYIPlESvJlA==", + "node_modules/postcss-modules-values": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/postcss-modules-values/-/postcss-modules-values-4.0.0.tgz", + "integrity": "sha512-RDxHkAiEGI78gS2ofyvCsu7iycRv7oqw5xMWn9iMoR0N/7mf9D50ecQqUo5BZ9Zh2vH4bCUR/ktCqbB9m8vJjQ==", "dev": true, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/parse5": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.1.2.tgz", - "integrity": "sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==", - "devOptional": true, "dependencies": { - "entities": "^4.4.0" + "icss-utils": "^5.0.0" }, - "funding": { - "url": "https://github.com/inikulin/parse5?sponsor=1" + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" } }, - "node_modules/parse5-html-rewriting-stream": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/parse5-html-rewriting-stream/-/parse5-html-rewriting-stream-7.0.0.tgz", - "integrity": "sha512-mazCyGWkmCRWDI15Zp+UiCqMp/0dgEmkZRvhlsqqKYr4SsVm/TvnSpD9fCvqCA2zoWJcfRym846ejWBBHRiYEg==", + "node_modules/postcss-selector-parser": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz", + "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==", "dev": true, "dependencies": { - "entities": "^4.3.0", - "parse5": "^7.0.0", - "parse5-sax-parser": "^7.0.0" + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" }, - "funding": { - "url": "https://github.com/inikulin/parse5?sponsor=1" + "engines": { + "node": ">=4" } }, - "node_modules/parse5-sax-parser": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/parse5-sax-parser/-/parse5-sax-parser-7.0.0.tgz", - "integrity": "sha512-5A+v2SNsq8T6/mG3ahcz8ZtQ0OUFTatxPbeidoMB7tkJSGDY3tdfl4MHovtLQHkEn5CGxijNWRQHhRQ6IRpXKg==", + "node_modules/postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", + "dev": true + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", "dev": true, - "dependencies": { - "parse5": "^7.0.0" + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/prettier": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.3.3.tgz", + "integrity": "sha512-i2tDNA0O5IrMO757lfrdQZCc2jPNDVntV0m/+4whiDfWaTKfMNgR7Qz0NAeGz/nRqF4m5/6CLzbP4/liHt12Ew==", + "dev": true, + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" }, "funding": { - "url": "https://github.com/inikulin/parse5?sponsor=1" + "url": "https://github.com/prettier/prettier?sponsor=1" } }, - "node_modules/parseurl": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", - "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "node_modules/prettier-eslint": { + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/prettier-eslint/-/prettier-eslint-13.0.0.tgz", + "integrity": "sha512-P5K31qWgUOQCtJL/3tpvEe28KfP49qbr6MTVEXC7I2k7ci55bP3YDr+glhyCdhIzxGCVp2f8eobfQ5so52RIIA==", "dev": true, + "dependencies": { + "@typescript-eslint/parser": "^3.0.0", + "common-tags": "^1.4.0", + "dlv": "^1.1.0", + "eslint": "^7.9.0", + "indent-string": "^4.0.0", + "lodash.merge": "^4.6.0", + "loglevel-colored-level-prefix": "^1.0.0", + "prettier": "^2.0.0", + "pretty-format": "^23.0.1", + "require-relative": "^0.8.7", + "typescript": "^3.9.3", + "vue-eslint-parser": "~7.1.0" + }, "engines": { - "node": ">= 0.8" + "node": ">=10.0.0" } }, - "node_modules/path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "node_modules/prettier-eslint/node_modules/@babel/code-frame": { + "version": "7.12.11", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.11.tgz", + "integrity": "sha512-Zt1yodBx1UcyiePMSkWnU4hPqhwq7hGi2nFL1LeA3EUl+q2LQx16MISgJ0+z7dnmgvP9QtIleuETGOiOH1RcIw==", "dev": true, - "engines": { - "node": ">=8" + "dependencies": { + "@babel/highlight": "^7.10.4" } }, - "node_modules/path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "node_modules/prettier-eslint/node_modules/@eslint/eslintrc": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.4.3.tgz", + "integrity": "sha512-J6KFFz5QCYUJq3pf0mjEcCJVERbzv71PUIDczuh9JkwGEzced6CO5ADLHB1rbf/+oPBtoPfMYNOpGDzCANlbXw==", + "dev": true, + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.1.1", + "espree": "^7.3.0", + "globals": "^13.9.0", + "ignore": "^4.0.6", + "import-fresh": "^3.2.1", + "js-yaml": "^3.13.1", + "minimatch": "^3.0.4", + "strip-json-comments": "^3.1.1" + }, "engines": { - "node": ">=0.10.0" + "node": "^10.12.0 || >=12.0.0" } }, - "node_modules/path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "node_modules/prettier-eslint/node_modules/@humanwhocodes/config-array": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.5.0.tgz", + "integrity": "sha512-FagtKFz74XrTl7y6HCzQpwDfXP0yhxe9lHLD1UZxjvZIcbyRz8zTFF/yYNfSfzU414eDwZ1SrO0Qvtyf+wFMQg==", + "deprecated": "Use @eslint/config-array instead", "dev": true, + "dependencies": { + "@humanwhocodes/object-schema": "^1.2.0", + "debug": "^4.1.1", + "minimatch": "^3.0.4" + }, "engines": { - "node": ">=8" + "node": ">=10.10.0" } }, - "node_modules/path-parse": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "node_modules/prettier-eslint/node_modules/@humanwhocodes/object-schema": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", + "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", + "deprecated": "Use @eslint/object-schema instead", "dev": true }, - "node_modules/path-scurry": { - "version": "1.10.2", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.10.2.tgz", - "integrity": "sha512-7xTavNy5RQXnsjANvVvMkEjvloOinkAjv/Z6Ildz9v2RinZ4SBKTWFOVRbaF8p0vpHnyjV/UwNDdKuUv6M5qcA==", + "node_modules/prettier-eslint/node_modules/@typescript-eslint/parser": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-3.10.1.tgz", + "integrity": "sha512-Ug1RcWcrJP02hmtaXVS3axPPTTPnZjupqhgj+NnZ6BCkwSImWk/283347+x9wN+lqOdK9Eo3vsyiyDHgsmiEJw==", "dev": true, "dependencies": { - "lru-cache": "^10.2.0", - "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + "@types/eslint-visitor-keys": "^1.0.0", + "@typescript-eslint/experimental-utils": "3.10.1", + "@typescript-eslint/types": "3.10.1", + "@typescript-eslint/typescript-estree": "3.10.1", + "eslint-visitor-keys": "^1.1.0" }, "engines": { - "node": ">=16 || 14 >=14.17" + "node": "^10.12.0 || >=12.0.0" }, "funding": { - "url": "https://github.com/sponsors/isaacs" + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^5.0.0 || ^6.0.0 || ^7.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } } }, - "node_modules/path-scurry/node_modules/lru-cache": { - "version": "10.2.2", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.2.2.tgz", - "integrity": "sha512-9hp3Vp2/hFQUiIwKo8XCeFVnrg8Pk3TYNPIR7tJADKi5YfcF7vEaK7avFHTlSy3kOKYaJQaalfEo6YuXdceBOQ==", + "node_modules/prettier-eslint/node_modules/@typescript-eslint/types": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-3.10.1.tgz", + "integrity": "sha512-+3+FCUJIahE9q0lDi1WleYzjCwJs5hIsbugIgnbB+dSCYUxl8L6PwmsyOPFZde2hc1DlTo/xnkOgiTLSyAbHiQ==", "dev": true, "engines": { - "node": "14 || >=16.14" + "node": "^8.10.0 || ^10.13.0 || >=11.10.1" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" } }, - "node_modules/path-to-regexp": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", - "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==", - "dev": true - }, - "node_modules/path-type": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", - "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "node_modules/prettier-eslint/node_modules/@typescript-eslint/typescript-estree": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-3.10.1.tgz", + "integrity": "sha512-QbcXOuq6WYvnB3XPsZpIwztBoquEYLXh2MtwVU+kO8jgYCiv4G5xrSP/1wg4tkvrEE+esZVquIPX/dxPlePk1w==", "dev": true, + "dependencies": { + "@typescript-eslint/types": "3.10.1", + "@typescript-eslint/visitor-keys": "3.10.1", + "debug": "^4.1.1", + "glob": "^7.1.6", + "is-glob": "^4.0.1", + "lodash": "^4.17.15", + "semver": "^7.3.2", + "tsutils": "^3.17.1" + }, "engines": { - "node": ">=8" + "node": "^10.12.0 || >=12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } } }, - "node_modules/picocolors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", - "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", - "dev": true - }, - "node_modules/picomatch": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.1.tgz", - "integrity": "sha512-xUXwsxNjwTQ8K3GnT4pCJm+xq3RUPQbmkYJTP5aFIfNIvbcc/4MUxgBaaRSZJ6yGJZiGSyYlM6MzwTsRk8SYCg==", + "node_modules/prettier-eslint/node_modules/@typescript-eslint/visitor-keys": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-3.10.1.tgz", + "integrity": "sha512-9JgC82AaQeglebjZMgYR5wgmfUdUc+EitGUUMW8u2nDckaeimzW+VsoLV6FoimPv2id3VQzfjwBxEMVz08ameQ==", "dev": true, + "dependencies": { + "eslint-visitor-keys": "^1.1.0" + }, "engines": { - "node": ">=12" + "node": "^8.10.0 || ^10.13.0 || >=11.10.1" }, "funding": { - "url": "https://github.com/sponsors/jonschlinkert" + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" } }, - "node_modules/pify": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", - "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", + "node_modules/prettier-eslint/node_modules/acorn": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", + "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", "dev": true, - "optional": true, + "bin": { + "acorn": "bin/acorn" + }, "engines": { - "node": ">=6" + "node": ">=0.4.0" } }, - "node_modules/piscina": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/piscina/-/piscina-4.4.0.tgz", - "integrity": "sha512-+AQduEJefrOApE4bV7KRmp3N2JnnyErlVqq4P/jmko4FPz9Z877BCccl/iB3FdrWSUkvbGV9Kan/KllJgat3Vg==", + "node_modules/prettier-eslint/node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "dev": true, - "optionalDependencies": { - "nice-napi": "^1.0.2" + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" } }, - "node_modules/pkg-dir": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-7.0.0.tgz", - "integrity": "sha512-Ie9z/WINcxxLp27BKOCHGde4ITq9UklYKDzVo1nhk5sqGEXU3FpkwP5GM2voTGJkGd9B3Otl+Q4uwSOeSUtOBA==", + "node_modules/prettier-eslint/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, "dependencies": { - "find-up": "^6.3.0" + "color-convert": "^2.0.1" }, "engines": { - "node": ">=14.16" + "node": ">=8" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/pkg-dir/node_modules/find-up": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-6.3.0.tgz", - "integrity": "sha512-v2ZsoEuVHYy8ZIlYqwPe/39Cy+cFDzp4dXPaxNvkEuouymu+2Jbz0PxpKarJHYJTmv2HWT3O382qY8l4jMWthw==", + "node_modules/prettier-eslint/node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", "dev": true, "dependencies": { - "locate-path": "^7.1.0", - "path-exists": "^5.0.0" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "sprintf-js": "~1.0.2" } }, - "node_modules/pkg-dir/node_modules/locate-path": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-7.2.0.tgz", - "integrity": "sha512-gvVijfZvn7R+2qyPX8mAuKcFGDf6Nc61GdvGafQsHL0sBIxfKzA+usWn4GFC/bk+QdwPUD4kWFJLhElipq+0VA==", + "node_modules/prettier-eslint/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "dev": true, "dependencies": { - "p-locate": "^6.0.0" + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/prettier-eslint/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" }, "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + "node": ">=10" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/pkg-dir/node_modules/p-limit": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-4.0.0.tgz", - "integrity": "sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==", + "node_modules/prettier-eslint/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, "dependencies": { - "yocto-queue": "^1.0.0" + "color-name": "~1.1.4" }, "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + "node": ">=7.0.0" + } + }, + "node_modules/prettier-eslint/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/prettier-eslint/node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "engines": { + "node": ">=10" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/pkg-dir/node_modules/p-locate": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-6.0.0.tgz", - "integrity": "sha512-wPrq66Llhl7/4AGC6I+cqxT07LhXvWL08LNXz1fENOw0Ap4sRZZ/gZpTTJ5jpurzzzfS2W/Ge9BY3LgLjCShcw==", + "node_modules/prettier-eslint/node_modules/eslint": { + "version": "7.32.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.32.0.tgz", + "integrity": "sha512-VHZ8gX+EDfz+97jGcgyGCyRia/dPOd6Xh9yPv8Bl1+SoaIwD+a/vlrOmGRUyOYu7MwUhc7CxqeaDZU13S4+EpA==", "dev": true, "dependencies": { - "p-limit": "^4.0.0" + "@babel/code-frame": "7.12.11", + "@eslint/eslintrc": "^0.4.3", + "@humanwhocodes/config-array": "^0.5.0", + "ajv": "^6.10.0", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.0.1", + "doctrine": "^3.0.0", + "enquirer": "^2.3.5", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^5.1.1", + "eslint-utils": "^2.1.0", + "eslint-visitor-keys": "^2.0.0", + "espree": "^7.3.1", + "esquery": "^1.4.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "functional-red-black-tree": "^1.0.1", + "glob-parent": "^5.1.2", + "globals": "^13.6.0", + "ignore": "^4.0.6", + "import-fresh": "^3.0.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "js-yaml": "^3.13.1", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.0.4", + "natural-compare": "^1.4.0", + "optionator": "^0.9.1", + "progress": "^2.0.0", + "regexpp": "^3.1.0", + "semver": "^7.2.1", + "strip-ansi": "^6.0.0", + "strip-json-comments": "^3.1.0", + "table": "^6.0.9", + "text-table": "^0.2.0", + "v8-compile-cache": "^2.0.3" + }, + "bin": { + "eslint": "bin/eslint.js" }, "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + "node": "^10.12.0 || >=12.0.0" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://opencollective.com/eslint" } }, - "node_modules/pkg-dir/node_modules/path-exists": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-5.0.0.tgz", - "integrity": "sha512-RjhtfwJOxzcFmNOi6ltcbcu4Iu+FL3zEj83dk4kAS+fVpTxXLO1b38RvJgT/0QwvV/L3aY9TAnyv0EOqW4GoMQ==", + "node_modules/prettier-eslint/node_modules/eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + }, "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + "node": ">=8.0.0" } }, - "node_modules/pkg-dir/node_modules/yocto-queue": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.0.0.tgz", - "integrity": "sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==", + "node_modules/prettier-eslint/node_modules/eslint-visitor-keys": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", + "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", "dev": true, "engines": { - "node": ">=12.20" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=4" } }, - "node_modules/postcss": { - "version": "8.4.35", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.35.tgz", - "integrity": "sha512-u5U8qYpBCpN13BsiEB0CbR1Hhh4Gc0zLFuedrHJKMctHCHAGrMdG0PRM/KErzAL3CU6/eckEtmHNB3x6e3c0vA==", + "node_modules/prettier-eslint/node_modules/eslint/node_modules/eslint-visitor-keys": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", + "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/postcss" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "dependencies": { - "nanoid": "^3.3.7", - "picocolors": "^1.0.0", - "source-map-js": "^1.0.2" - }, "engines": { - "node": "^10 || ^12 || >=14" + "node": ">=10" } }, - "node_modules/postcss-loader": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/postcss-loader/-/postcss-loader-8.1.1.tgz", - "integrity": "sha512-0IeqyAsG6tYiDRCYKQJLAmgQr47DX6N7sFSWvQxt6AcupX8DIdmykuk/o/tx0Lze3ErGHJEp5OSRxrelC6+NdQ==", + "node_modules/prettier-eslint/node_modules/espree": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-7.3.1.tgz", + "integrity": "sha512-v3JCNCE64umkFpmkFGqzVKsOT0tN1Zr+ueqLZfpV1Ob8e+CEgPWa+OxCoGH3tnhimMKIaBm4m/vaRpJ/krRz2g==", "dev": true, "dependencies": { - "cosmiconfig": "^9.0.0", - "jiti": "^1.20.0", - "semver": "^7.5.4" + "acorn": "^7.4.0", + "acorn-jsx": "^5.3.1", + "eslint-visitor-keys": "^1.3.0" }, "engines": { - "node": ">= 18.12.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "@rspack/core": "0.x || 1.x", - "postcss": "^7.0.0 || ^8.0.1", - "webpack": "^5.0.0" - }, - "peerDependenciesMeta": { - "@rspack/core": { - "optional": true - }, - "webpack": { - "optional": true - } + "node": "^10.12.0 || >=12.0.0" } }, - "node_modules/postcss-media-query-parser": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/postcss-media-query-parser/-/postcss-media-query-parser-0.2.3.tgz", - "integrity": "sha512-3sOlxmbKcSHMjlUXQZKQ06jOswE7oVkXPxmZdoB1r5l0q6gTFTQSHxNxOrCccElbW7dxNytifNEo8qidX2Vsig==", - "dev": true - }, - "node_modules/postcss-modules-extract-imports": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.1.0.tgz", - "integrity": "sha512-k3kNe0aNFQDAZGbin48pL2VNidTF0w4/eASDsxlyspobzU3wZQLOGj7L9gfRe0Jo9/4uud09DsjFNH7winGv8Q==", + "node_modules/prettier-eslint/node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", "dev": true, "engines": { - "node": "^10 || ^12 || >= 14" - }, - "peerDependencies": { - "postcss": "^8.1.0" + "node": ">=4.0" } }, - "node_modules/postcss-modules-local-by-default": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.5.tgz", - "integrity": "sha512-6MieY7sIfTK0hYfafw1OMEG+2bg8Q1ocHCpoWLqOKj3JXlKu4G7btkmM/B7lFubYkYWmRSPLZi5chid63ZaZYw==", + "node_modules/prettier-eslint/node_modules/globals": { + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", "dev": true, "dependencies": { - "icss-utils": "^5.0.0", - "postcss-selector-parser": "^6.0.2", - "postcss-value-parser": "^4.1.0" + "type-fest": "^0.20.2" }, "engines": { - "node": "^10 || ^12 || >= 14" + "node": ">=8" }, - "peerDependencies": { - "postcss": "^8.1.0" + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/postcss-modules-scope": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-3.2.0.tgz", - "integrity": "sha512-oq+g1ssrsZOsx9M96c5w8laRmvEu9C3adDSjI8oTcbfkrTE8hx/zfyobUoWIxaKPO8bt6S62kxpw5GqypEw1QQ==", + "node_modules/prettier-eslint/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true, - "dependencies": { - "postcss-selector-parser": "^6.0.4" - }, "engines": { - "node": "^10 || ^12 || >= 14" - }, - "peerDependencies": { - "postcss": "^8.1.0" + "node": ">=8" } }, - "node_modules/postcss-modules-values": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/postcss-modules-values/-/postcss-modules-values-4.0.0.tgz", - "integrity": "sha512-RDxHkAiEGI78gS2ofyvCsu7iycRv7oqw5xMWn9iMoR0N/7mf9D50ecQqUo5BZ9Zh2vH4bCUR/ktCqbB9m8vJjQ==", + "node_modules/prettier-eslint/node_modules/ignore": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", + "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", "dev": true, - "dependencies": { - "icss-utils": "^5.0.0" - }, "engines": { - "node": "^10 || ^12 || >= 14" - }, - "peerDependencies": { - "postcss": "^8.1.0" + "node": ">= 4" } }, - "node_modules/postcss-selector-parser": { - "version": "6.0.16", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.16.tgz", - "integrity": "sha512-A0RVJrX+IUkVZbW3ClroRWurercFhieevHB38sr2+l9eUClMqome3LmEmnhlNy+5Mr2EYN6B2Kaw9wYdd+VHiw==", + "node_modules/prettier-eslint/node_modules/js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", "dev": true, "dependencies": { - "cssesc": "^3.0.0", - "util-deprecate": "^1.0.2" + "argparse": "^1.0.7", + "esprima": "^4.0.0" }, - "engines": { - "node": ">=4" + "bin": { + "js-yaml": "bin/js-yaml.js" } }, - "node_modules/postcss-value-parser": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", - "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", + "node_modules/prettier-eslint/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", "dev": true }, - "node_modules/prelude-ls": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", - "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "node_modules/prettier-eslint/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, "engines": { - "node": ">= 0.8.0" + "node": "*" } }, - "node_modules/prettier": { - "version": "3.2.5", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.2.5.tgz", - "integrity": "sha512-3/GWa9aOC0YeD7LUfvOG2NiDyhOWRvt1k+rcKhOuYnMY24iiCphgneUfJDyFXd6rZCAnuLBv6UeAULtrhT/F4A==", + "node_modules/prettier-eslint/node_modules/prettier": { + "version": "2.8.8", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz", + "integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==", "dev": true, "bin": { - "prettier": "bin/prettier.cjs" + "prettier": "bin-prettier.js" }, "engines": { - "node": ">=14" + "node": ">=10.13.0" }, "funding": { "url": "https://github.com/prettier/prettier?sponsor=1" } }, - "node_modules/prettier-eslint": { - "version": "16.3.0", - "resolved": "https://registry.npmjs.org/prettier-eslint/-/prettier-eslint-16.3.0.tgz", - "integrity": "sha512-Lh102TIFCr11PJKUMQ2kwNmxGhTsv/KzUg9QYF2Gkw259g/kPgndZDWavk7/ycbRvj2oz4BPZ1gCU8bhfZH/Xg==", + "node_modules/prettier-eslint/node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "dev": true + }, + "node_modules/prettier-eslint/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, "dependencies": { - "@typescript-eslint/parser": "^6.7.5", - "common-tags": "^1.4.0", - "dlv": "^1.1.0", - "eslint": "^8.7.0", - "indent-string": "^4.0.0", - "lodash.merge": "^4.6.0", - "loglevel-colored-level-prefix": "^1.0.0", - "prettier": "^3.0.1", - "pretty-format": "^29.7.0", - "require-relative": "^0.8.7", - "typescript": "^5.2.2", - "vue-eslint-parser": "^9.1.0" + "has-flag": "^4.0.0" }, "engines": { - "node": ">=16.10.0" + "node": ">=8" + } + }, + "node_modules/prettier-eslint/node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "engines": { + "node": ">=10" }, - "peerDependencies": { - "prettier-plugin-svelte": "^3.0.0", - "svelte-eslint-parser": "*" + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/prettier-eslint/node_modules/typescript": { + "version": "3.9.10", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.9.10.tgz", + "integrity": "sha512-w6fIxVE/H1PkLKcCPsFqKE7Kv7QUwhU8qQY2MueZXWx5cPZdwFupLgKK3vntcK98BtNHZtAF4LA/yl2a7k8R6Q==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" }, - "peerDependenciesMeta": { - "prettier-plugin-svelte": { - "optional": true - }, - "svelte-eslint-parser": { - "optional": true - } + "engines": { + "node": ">=4.2.0" } }, "node_modules/prettier-linter-helpers": { @@ -13284,37 +12404,21 @@ "engines": { "node": ">=6.0.0" } - }, - "node_modules/pretty-format": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", - "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", - "dev": true, - "dependencies": { - "@jest/schemas": "^29.6.3", - "ansi-styles": "^5.0.0", - "react-is": "^18.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/pretty-format/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + }, + "node_modules/pretty-format": { + "version": "23.6.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-23.6.0.tgz", + "integrity": "sha512-zf9NV1NSlDLDjycnwm6hpFATCGl/K1lt0R/GdkAK2O5LN/rwJoB+Mh93gGJjut4YbmecbfgLWVGSTCr0Ewvvbw==", + "dev": true, + "dependencies": { + "ansi-regex": "^3.0.0", + "ansi-styles": "^3.2.0" } }, "node_modules/proc-log": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-3.0.0.tgz", - "integrity": "sha512-++Vn7NS4Xf9NacaU9Xq3URUuqZETPsf8L4j5/ckhaRYsfPeRyzGw+iDjFhV/Jr3uNmTvvddEJFWh5R1gRgUH8A==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-4.2.0.tgz", + "integrity": "sha512-g8+OnU/L2v+wyiVK+D5fA34J7EH8jZ8DDlvwhRCMxmMj7UCBvxiO1mGeN+36JXIKF4zevU4kRBd8lVgG9vLelA==", "dev": true, "engines": { "node": "^14.17.0 || ^16.13.0 || >=18.0.0" @@ -13325,6 +12429,15 @@ "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" }, + "node_modules/progress": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/promise-inflight": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz", @@ -13366,12 +12479,6 @@ "node": ">= 0.10" } }, - "node_modules/proxy-from-env": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", - "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", - "dev": true - }, "node_modules/prr": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz", @@ -13380,22 +12487,19 @@ "optional": true }, "node_modules/pump": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", - "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.2.tgz", + "integrity": "sha512-tUPXtzlGM8FE3P0ZL6DVs/3P58k9nk8/jZeQCurTJylQA8qFYzHFfhBJkuqyE0FifOsQ0uKWekiZ5g8wtr28cw==", "dependencies": { "end-of-stream": "^1.1.0", "once": "^1.3.1" } }, "node_modules/punycode": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", - "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", - "dev": true, - "engines": { - "node": ">=6" - } + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", + "integrity": "sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ==", + "dev": true }, "node_modules/qjobs": { "version": "1.2.0", @@ -13407,12 +12511,12 @@ } }, "node_modules/qs": { - "version": "6.11.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", - "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", + "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", "dev": true, "dependencies": { - "side-channel": "^1.0.4" + "side-channel": "^1.0.6" }, "engines": { "node": ">=0.6" @@ -13474,62 +12578,6 @@ "node": ">= 0.8" } }, - "node_modules/react-is": { - "version": "18.3.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", - "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", - "dev": true - }, - "node_modules/read-package-json": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/read-package-json/-/read-package-json-7.0.1.tgz", - "integrity": "sha512-8PcDiZ8DXUjLf687Ol4BR8Bpm2umR7vhoZOzNRt+uxD9GpBh/K+CAAALVIiYFknmvlmyg7hM7BSNUXPaCCqd0Q==", - "dev": true, - "dependencies": { - "glob": "^10.2.2", - "json-parse-even-better-errors": "^3.0.0", - "normalize-package-data": "^6.0.0", - "npm-normalize-package-bin": "^3.0.0" - }, - "engines": { - "node": "^16.14.0 || >=18.0.0" - } - }, - "node_modules/read-package-json-fast": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/read-package-json-fast/-/read-package-json-fast-3.0.2.tgz", - "integrity": "sha512-0J+Msgym3vrLOUB3hzQCuZHII0xkNGCtz/HJH9xZshwv9DbDwkw1KaE3gx/e2J5rpEY5rtOy6cyhKOPrkP7FZw==", - "dev": true, - "dependencies": { - "json-parse-even-better-errors": "^3.0.0", - "npm-normalize-package-bin": "^3.0.0" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "node_modules/read-package-json/node_modules/glob": { - "version": "10.3.12", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.12.tgz", - "integrity": "sha512-TCNv8vJ+xz4QiqTpfOJA7HvYv+tNIRHKfUWw/q+v2jdgN4ebz+KY9tGx5J4rHP0o84mNP+ApH66HRX8us3Khqg==", - "dev": true, - "dependencies": { - "foreground-child": "^3.1.0", - "jackspeak": "^2.3.6", - "minimatch": "^9.0.1", - "minipass": "^7.0.4", - "path-scurry": "^1.10.2" - }, - "bin": { - "glob": "dist/esm/bin.mjs" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/readable-stream": { "version": "3.6.2", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", @@ -13580,9 +12628,9 @@ "dev": true }, "node_modules/regenerate-unicode-properties": { - "version": "10.1.1", - "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.1.1.tgz", - "integrity": "sha512-X007RyZLsCJVVrjgEFVpLUTZwyOZk3oiL75ZcuYjlIWd6rNJtOjkBwQc5AsRrpbKVkxN6sklw/k/9m2jJYOf8Q==", + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.2.0.tgz", + "integrity": "sha512-DqHn3DwbmmPVzeKj9woBadqmXxLvQoQIwu7nopMc72ztvxVmVk2SBhSnx67zuye5TP+lJsb/TBQsjLKhnDf3MA==", "dev": true, "dependencies": { "regenerate": "^1.4.2" @@ -13612,6 +12660,18 @@ "integrity": "sha512-TVILVSz2jY5D47F4mA4MppkBrafEaiUWJO/TcZHEIuI13AqoZMkK1WMA4Om1YkYbTx+9Ki1/tSUXbceyr9saRg==", "dev": true }, + "node_modules/regexpp": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", + "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + } + }, "node_modules/regexpu-core": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-5.3.2.tgz", @@ -13703,12 +12763,12 @@ } }, "node_modules/resolve-from": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", - "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", "dev": true, "engines": { - "node": ">=8" + "node": ">=4" } }, "node_modules/resolve-url-loader": { @@ -13751,16 +12811,19 @@ } }, "node_modules/restore-cursor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", - "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-5.1.0.tgz", + "integrity": "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==", "dev": true, "dependencies": { - "onetime": "^5.1.0", - "signal-exit": "^3.0.2" + "onetime": "^7.0.0", + "signal-exit": "^4.1.0" }, "engines": { - "node": ">=8" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/retry": { @@ -13783,14 +12846,15 @@ } }, "node_modules/rfdc": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.3.1.tgz", - "integrity": "sha512-r5a3l5HzYlIC68TpmYKlxWjmOP6wiPJ1vWv2HeLhNsRZMrCkxeqxiHlQ21oXmQ4F3SiryXBHhAD7JZqvOJjFmg==" + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz", + "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==" }, "node_modules/rimraf": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", "dev": true, "dependencies": { "glob": "^7.1.3" @@ -13803,9 +12867,9 @@ } }, "node_modules/rollup": { - "version": "4.17.2", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.17.2.tgz", - "integrity": "sha512-/9ClTJPByC0U4zNLowV1tMBe8yMEAxewtR3cUNX5BoEpGH3dQEWpJLr6CLp0fPdYRF/fzVOgvDb1zXuakwF5kQ==", + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.22.4.tgz", + "integrity": "sha512-vD8HJ5raRcWOyymsR6Z3o6+RzfEPCnVLMFJ6vRslO1jt4LO6dUo5Qnpg7y4RkZFM2DMe3WUirkI5c16onjrc6A==", "dev": true, "dependencies": { "@types/estree": "1.0.5" @@ -13818,32 +12882,35 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.17.2", - "@rollup/rollup-android-arm64": "4.17.2", - "@rollup/rollup-darwin-arm64": "4.17.2", - "@rollup/rollup-darwin-x64": "4.17.2", - "@rollup/rollup-linux-arm-gnueabihf": "4.17.2", - "@rollup/rollup-linux-arm-musleabihf": "4.17.2", - "@rollup/rollup-linux-arm64-gnu": "4.17.2", - "@rollup/rollup-linux-arm64-musl": "4.17.2", - "@rollup/rollup-linux-powerpc64le-gnu": "4.17.2", - "@rollup/rollup-linux-riscv64-gnu": "4.17.2", - "@rollup/rollup-linux-s390x-gnu": "4.17.2", - "@rollup/rollup-linux-x64-gnu": "4.17.2", - "@rollup/rollup-linux-x64-musl": "4.17.2", - "@rollup/rollup-win32-arm64-msvc": "4.17.2", - "@rollup/rollup-win32-ia32-msvc": "4.17.2", - "@rollup/rollup-win32-x64-msvc": "4.17.2", + "@rollup/rollup-android-arm-eabi": "4.22.4", + "@rollup/rollup-android-arm64": "4.22.4", + "@rollup/rollup-darwin-arm64": "4.22.4", + "@rollup/rollup-darwin-x64": "4.22.4", + "@rollup/rollup-linux-arm-gnueabihf": "4.22.4", + "@rollup/rollup-linux-arm-musleabihf": "4.22.4", + "@rollup/rollup-linux-arm64-gnu": "4.22.4", + "@rollup/rollup-linux-arm64-musl": "4.22.4", + "@rollup/rollup-linux-powerpc64le-gnu": "4.22.4", + "@rollup/rollup-linux-riscv64-gnu": "4.22.4", + "@rollup/rollup-linux-s390x-gnu": "4.22.4", + "@rollup/rollup-linux-x64-gnu": "4.22.4", + "@rollup/rollup-linux-x64-musl": "4.22.4", + "@rollup/rollup-win32-arm64-msvc": "4.22.4", + "@rollup/rollup-win32-ia32-msvc": "4.22.4", + "@rollup/rollup-win32-x64-msvc": "4.22.4", "fsevents": "~2.3.2" } }, - "node_modules/run-async": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/run-async/-/run-async-3.0.0.tgz", - "integrity": "sha512-540WwVDOMxA6dN6We19EcT9sc3hkXPw5mzRNGM3FkdN/vtE9NFvj5lFAPNwUDmJjXidm3v7TC1cTE7t17Ulm1Q==", + "node_modules/run-applescript": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/run-applescript/-/run-applescript-7.0.0.tgz", + "integrity": "sha512-9by4Ij99JUr/MCFBUkDKLWK3G9HVXmabKz9U5MlIAIuvuzkiOicRYs8XJLxX+xahD+mLiiCYDqF9dKAgtzKP1A==", "dev": true, "engines": { - "node": ">=0.12.0" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/run-parallel": { @@ -13902,15 +12969,10 @@ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", "dev": true }, - "node_modules/safevalues": { - "version": "0.3.4", - "resolved": "https://registry.npmjs.org/safevalues/-/safevalues-0.3.4.tgz", - "integrity": "sha512-LRneZZRXNgjzwG4bDQdOTSbze3fHm1EAKN/8bePxnlEZiBmkYEDggaHbuvHI9/hoqHbGfsEA7tWS9GhYHZBBsw==" - }, "node_modules/sass": { - "version": "1.71.1", - "resolved": "https://registry.npmjs.org/sass/-/sass-1.71.1.tgz", - "integrity": "sha512-wovtnV2PxzteLlfNzbgm1tFXPLoZILYAMJtvoXXkD7/+1uP41eKkIt1ypWq5/q2uT94qHjXehEYfmjKOvjL9sg==", + "version": "1.77.6", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.77.6.tgz", + "integrity": "sha512-ByXE1oLD79GVq9Ht1PeHWCPMPB8XHpBuz1r85oByKHjZY6qV6rWnQovQzXJXuQ/XyE1Oj3iPk3lo28uzaRA2/Q==", "dev": true, "dependencies": { "chokidar": ">=3.0.0 <4.0.0", @@ -13925,9 +12987,9 @@ } }, "node_modules/sass-loader": { - "version": "14.1.1", - "resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-14.1.1.tgz", - "integrity": "sha512-QX8AasDg75monlybel38BZ49JP5Z+uSKfKwF2rO7S74BywaRmGQMUBw9dtkS+ekyM/QnP+NOrRYq8ABMZ9G8jw==", + "version": "16.0.0", + "resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-16.0.0.tgz", + "integrity": "sha512-n13Z+3rU9A177dk4888czcVFiC8CL9dii4qpXWUg3YIIgZEvi9TCFKjOQcbK0kJM7DJu9VucrZFddvNfYCPwtw==", "dev": true, "dependencies": { "neo-async": "^2.6.2" @@ -13965,9 +13027,9 @@ } }, "node_modules/sax": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/sax/-/sax-1.3.0.tgz", - "integrity": "sha512-0s+oAmw9zLl1V1cS9BtZN7JAd0cW5e0QH4W3LWEK6a4LaLEA2OTpGYWDY+6XasBLtz6wkm3u1xRw95mRuJ59WA==", + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.4.1.tgz", + "integrity": "sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==", "dev": true, "optional": true }, @@ -13990,6 +13052,23 @@ "url": "https://opencollective.com/webpack" } }, + "node_modules/schema-utils/node_modules/ajv-formats": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", + "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", + "dev": true, + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, "node_modules/select-hose": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz", @@ -14010,13 +13089,10 @@ } }, "node_modules/semver": { - "version": "7.6.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", - "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, "bin": { "semver": "bin/semver.js" }, @@ -14024,28 +13100,10 @@ "node": ">=10" } }, - "node_modules/semver/node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/semver/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/send": { - "version": "0.18.0", - "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", - "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", + "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", "dev": true, "dependencies": { "debug": "2.6.9", @@ -14093,12 +13151,6 @@ "node": ">=4" } }, - "node_modules/send/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true - }, "node_modules/send/node_modules/statuses": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", @@ -14187,20 +13239,29 @@ "dev": true }, "node_modules/serve-static": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", - "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", + "version": "1.16.2", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", + "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", "dev": true, "dependencies": { - "encodeurl": "~1.0.2", + "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "parseurl": "~1.3.3", - "send": "0.18.0" + "send": "0.19.0" }, "engines": { "node": ">= 0.8.0" } }, + "node_modules/serve-static/node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/set-function-length": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", @@ -14285,35 +13346,72 @@ } }, "node_modules/signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "dev": true + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } }, "node_modules/sigstore": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/sigstore/-/sigstore-2.3.0.tgz", - "integrity": "sha512-q+o8L2ebiWD1AxD17eglf1pFrl9jtW7FHa0ygqY6EKvibK8JHyq9Z26v9MZXeDiw+RbfOJ9j2v70M10Hd6E06A==", + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/sigstore/-/sigstore-2.3.1.tgz", + "integrity": "sha512-8G+/XDU8wNsJOQS5ysDVO0Etg9/2uA5gR9l4ZwijjlwxBcrU6RPfwi2+jJmbP+Ap1Hlp/nVAaEO4Fj22/SL2gQ==", "dev": true, "dependencies": { - "@sigstore/bundle": "^2.3.1", + "@sigstore/bundle": "^2.3.2", "@sigstore/core": "^1.0.0", - "@sigstore/protobuf-specs": "^0.3.1", - "@sigstore/sign": "^2.3.0", - "@sigstore/tuf": "^2.3.1", - "@sigstore/verify": "^1.2.0" + "@sigstore/protobuf-specs": "^0.3.2", + "@sigstore/sign": "^2.3.2", + "@sigstore/tuf": "^2.3.4", + "@sigstore/verify": "^1.2.1" }, "engines": { "node": "^16.14.0 || >=18.0.0" } }, "node_modules/slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-5.1.0.tgz", + "integrity": "sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg==", "dev": true, "engines": { - "node": ">=8" + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/slice-ansi": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-5.0.0.tgz", + "integrity": "sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^6.0.0", + "is-fullwidth-code-point": "^4.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, + "node_modules/slice-ansi/node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, "node_modules/smart-buffer": { @@ -14327,16 +13425,16 @@ } }, "node_modules/socket.io": { - "version": "4.7.5", - "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.7.5.tgz", - "integrity": "sha512-DmeAkF6cwM9jSfmp6Dr/5/mfMwb5Z5qRrSXLpo3Fq5SqyU8CMF15jIN4ZhfSwu35ksM1qmHZDQ/DK5XTccSTvA==", + "version": "4.8.0", + "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.8.0.tgz", + "integrity": "sha512-8U6BEgGjQOfGz3HHTYaC/L1GaxDCJ/KM0XTkJly0EhZ5U/du9uNEZy4ZgYzEzIqlx2CMm25CrCqr1ck899eLNA==", "dev": true, "dependencies": { "accepts": "~1.3.4", "base64id": "~2.0.0", "cors": "~2.8.5", "debug": "~4.3.2", - "engine.io": "~6.5.2", + "engine.io": "~6.6.0", "socket.io-adapter": "~2.5.2", "socket.io-parser": "~4.2.4" }, @@ -14354,6 +13452,27 @@ "ws": "~8.17.1" } }, + "node_modules/socket.io-adapter/node_modules/ws": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", + "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", + "dev": true, + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, "node_modules/socket.io-parser": { "version": "4.2.4", "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.4.tgz", @@ -14393,14 +13512,14 @@ } }, "node_modules/socks-proxy-agent": { - "version": "8.0.3", - "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.3.tgz", - "integrity": "sha512-VNegTZKhuGq5vSD6XNKlbqWhyt/40CgoEw8XxD6dhnm8Jq9IEa3nIa4HwnM8XOqU0CdB0BwWVXusqiFXfHB3+A==", + "version": "8.0.4", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.4.tgz", + "integrity": "sha512-GNAq/eg8Udq2x0eNiFkr9gRg5bA7PXEWagQdeRX4cPSG+X/8V38v637gim9bjFptMk1QWsCTr0ttrJEiXbNnRw==", "dev": true, "dependencies": { "agent-base": "^7.1.1", "debug": "^4.3.4", - "socks": "^2.7.1" + "socks": "^2.8.3" }, "engines": { "node": ">= 14" @@ -14416,9 +13535,9 @@ } }, "node_modules/source-map-js": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz", - "integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", "dev": true, "engines": { "node": ">=0.10.0" @@ -14502,9 +13621,9 @@ } }, "node_modules/spdx-license-ids": { - "version": "3.0.17", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.17.tgz", - "integrity": "sha512-sh8PWc/ftMqAAdFiBu6Fy6JUOYjqDJBJvIhpfDMyHrr0Rbp5liZqd4TjtQ/RgfLjKFZb+LMx5hpml5qOWy0qvg==", + "version": "3.0.20", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.20.tgz", + "integrity": "sha512-jg25NiDV/1fLtSgEgyvVyDunvaNHbuwF9lfNV17gSmPFAlYzdfNBlLtLzXTevwkPj7DhGbmN9VnmJIgLnhvaBw==", "dev": true }, "node_modules/spdy": { @@ -14546,9 +13665,9 @@ } }, "node_modules/sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz", + "integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==", "dev": true }, "node_modules/ssri": { @@ -14580,47 +13699,15 @@ "node_modules/streamroller": { "version": "3.1.5", "resolved": "https://registry.npmjs.org/streamroller/-/streamroller-3.1.5.tgz", - "integrity": "sha512-KFxaM7XT+irxvdqSP1LGLgNWbYN7ay5owZ3r/8t77p+EtSUAfUgtl7be3xtqtOmGUl9K9YPO2ca8133RlTjvKw==", - "dev": true, - "dependencies": { - "date-format": "^4.0.14", - "debug": "^4.3.4", - "fs-extra": "^8.1.0" - }, - "engines": { - "node": ">=8.0" - } - }, - "node_modules/streamroller/node_modules/fs-extra": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", - "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", - "dev": true, - "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^4.0.0", - "universalify": "^0.1.0" - }, - "engines": { - "node": ">=6 <7 || >=8" - } - }, - "node_modules/streamroller/node_modules/jsonfile": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", - "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", - "dev": true, - "optionalDependencies": { - "graceful-fs": "^4.1.6" - } - }, - "node_modules/streamroller/node_modules/universalify": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", - "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", + "integrity": "sha512-KFxaM7XT+irxvdqSP1LGLgNWbYN7ay5owZ3r/8t77p+EtSUAfUgtl7be3xtqtOmGUl9K9YPO2ca8133RlTjvKw==", "dev": true, + "dependencies": { + "date-format": "^4.0.14", + "debug": "^4.3.4", + "fs-extra": "^8.1.0" + }, "engines": { - "node": ">= 4.0.0" + "node": ">=8.0" } }, "node_modules/string_decoder": { @@ -14632,17 +13719,20 @@ } }, "node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", "dev": true, "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" }, "engines": { - "node": ">=8" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/string-width-cjs": { @@ -14660,6 +13750,48 @@ "node": ">=8" } }, + "node_modules/string-width-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/string-width-cjs/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width/node_modules/ansi-regex": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", + "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/string-width/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, "node_modules/strip-ansi": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", @@ -14685,13 +13817,22 @@ "node": ">=8" } }, - "node_modules/strip-bom": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", + "node_modules/strip-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", "dev": true, "engines": { - "node": ">=4" + "node": ">=8" + } + }, + "node_modules/strip-ansi/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" } }, "node_modules/strip-final-newline": { @@ -14715,23 +13856,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/strong-log-transformer": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/strong-log-transformer/-/strong-log-transformer-2.1.0.tgz", - "integrity": "sha512-B3Hgul+z0L9a236FAUC9iZsL+nVHgoCJnqCbN588DjYxvGXaXaaFbfmQ/JhvKjZwsOukuR72XbHv71Qkug0HxA==", - "dev": true, - "dependencies": { - "duplexer": "^0.1.1", - "minimist": "^1.2.0", - "through": "^2.3.4" - }, - "bin": { - "sl-log-transformer": "bin/sl-log-transformer.js" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/supports-color": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", @@ -14766,9 +13890,9 @@ } }, "node_modules/synckit": { - "version": "0.8.8", - "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.8.8.tgz", - "integrity": "sha512-HwOKAP7Wc5aRGYdKH+dw0PRRpbO841v2DENBtjnR5HFWoiNByAl7vrx3p0G/rCyYXQsrxqtX48TImFtPcIHSpQ==", + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.9.1.tgz", + "integrity": "sha512-7gr8p9TQP6RAHusBOSLs46F4564ZrjV8xFmw5zCmgmhGUcw2hxsShhJ6CEiHQMgPDwAQ1fWHPM0ypc4RMAig4A==", "dev": true, "dependencies": { "@pkgr/core": "^0.1.0", @@ -14781,6 +13905,101 @@ "url": "https://opencollective.com/unts" } }, + "node_modules/table": { + "version": "6.8.2", + "resolved": "https://registry.npmjs.org/table/-/table-6.8.2.tgz", + "integrity": "sha512-w2sfv80nrAh2VCbqR5AK27wswXhqcck2AhfnNW76beQXskGZ1V12GwS//yYVa3d3fcvAip2OUnbDAjW2k3v9fA==", + "dev": true, + "dependencies": { + "ajv": "^8.0.1", + "lodash.truncate": "^4.4.2", + "slice-ansi": "^4.0.0", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/table/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/table/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/table/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/table/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/table/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/table/node_modules/slice-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", + "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "astral-regex": "^2.0.0", + "is-fullwidth-code-point": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, + "node_modules/table/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/tapable": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", @@ -14807,22 +14026,6 @@ "node": ">=10" } }, - "node_modules/tar-stream": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", - "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", - "dev": true, - "dependencies": { - "bl": "^4.0.3", - "end-of-stream": "^1.4.1", - "fs-constants": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^3.1.1" - }, - "engines": { - "node": ">=6" - } - }, "node_modules/tar/node_modules/fs-minipass": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", @@ -14875,9 +14078,9 @@ "dev": true }, "node_modules/terser": { - "version": "5.29.1", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.29.1.tgz", - "integrity": "sha512-lZQ/fyaIGxsbGxApKmoPTODIzELy3++mXhS5hOqaAWZjQtpq/hFHAc+rm29NND1rYRxRWKcjuARNwULNXa5RtQ==", + "version": "5.31.6", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.31.6.tgz", + "integrity": "sha512-PQ4DAriWzKj+qgehQ7LK5bQqCFNMmlhjR2PFFLuqGCpuCAauxemVBWwWOxo3UIwWQx8+Pr61Df++r76wDmkQBg==", "dev": true, "dependencies": { "@jridgewell/source-map": "^0.3.3", @@ -14975,53 +14178,23 @@ "url": "https://opencollective.com/webpack" } }, - "node_modules/test-exclude": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", - "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", - "dev": true, - "dependencies": { - "@istanbuljs/schema": "^0.1.2", - "glob": "^7.1.4", - "minimatch": "^3.0.4" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/test-exclude/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/test-exclude/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, "node_modules/text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", "dev": true }, - "node_modules/through": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", - "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==", - "dev": true + "node_modules/thingies": { + "version": "1.21.0", + "resolved": "https://registry.npmjs.org/thingies/-/thingies-1.21.0.tgz", + "integrity": "sha512-hsqsJsFMsV+aD4s3CWKk85ep/3I9XzYV/IXaSouJMYIoDlgyi11cBhsqYe9/geRfB0YIikBQg6raRaM+nIMP9g==", + "dev": true, + "engines": { + "node": ">=10.18" + }, + "peerDependencies": { + "tslib": "^2" + } }, "node_modules/thunky": { "version": "1.1.0", @@ -15030,15 +14203,15 @@ "dev": true }, "node_modules/tmp": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz", - "integrity": "sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==", + "version": "0.0.33", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", + "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", "dev": true, "dependencies": { - "rimraf": "^3.0.0" + "os-tmpdir": "~1.0.2" }, "engines": { - "node": ">=8.17.0" + "node": ">=0.6.0" } }, "node_modules/to-fast-properties": { @@ -15071,6 +14244,22 @@ "node": ">=0.6" } }, + "node_modules/tree-dump": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/tree-dump/-/tree-dump-1.0.2.tgz", + "integrity": "sha512-dpev9ABuLWdEubk+cIaI9cHwRNNDjkBBLXTwI4UCUFdQ5xXKqNXoK4FEciw/vxf+NQ7Cb7sGUyeUtORvHIdRXQ==", + "dev": true, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, "node_modules/tree-kill": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", @@ -15092,24 +14281,31 @@ "typescript": ">=4.2.0" } }, - "node_modules/tsconfig-paths": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-4.2.0.tgz", - "integrity": "sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg==", + "node_modules/tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + }, + "node_modules/tsutils": { + "version": "3.21.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", + "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", "dev": true, "dependencies": { - "json5": "^2.2.2", - "minimist": "^1.2.6", - "strip-bom": "^3.0.0" + "tslib": "^1.8.1" }, "engines": { - "node": ">=6" + "node": ">= 6" + }, + "peerDependencies": { + "typescript": ">=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta" } }, - "node_modules/tslib": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + "node_modules/tsutils/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true }, "node_modules/tuf-js": { "version": "2.2.1", @@ -15174,9 +14370,9 @@ "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==" }, "node_modules/typescript": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz", - "integrity": "sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==", + "version": "5.5.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.4.tgz", + "integrity": "sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q==", "dev": true, "bin": { "tsc": "bin/tsc", @@ -15187,9 +14383,9 @@ } }, "node_modules/ua-parser-js": { - "version": "0.7.37", - "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.37.tgz", - "integrity": "sha512-xV8kqRKM+jhMvcHWUKthV9fNebIzrNy//2O9ZwWcfiBFR5f25XVZPLlEajk/sf3Ra15V92isyQqnIEXRDaZWEA==", + "version": "0.7.39", + "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.39.tgz", + "integrity": "sha512-IZ6acm6RhQHNibSt7+c09hhvsKy9WUr4DVbeq9U8o71qxyYtJpQeDxQnMrVqnIFMLcQjHO0I9wgfO2vIahht4w==", "dev": true, "funding": [ { @@ -15205,29 +14401,23 @@ "url": "https://github.com/sponsors/faisalman" } ], + "bin": { + "ua-parser-js": "script/cli.js" + }, "engines": { "node": "*" } }, - "node_modules/undici": { - "version": "6.11.1", - "resolved": "https://registry.npmjs.org/undici/-/undici-6.11.1.tgz", - "integrity": "sha512-KyhzaLJnV1qa3BSHdj4AZ2ndqI0QWPxYzaIOio0WzcEJB9gvuysprJSLtpvc2D9mhR9jPDUk7xlJlZbH2KR5iw==", - "dev": true, - "engines": { - "node": ">=18.0" - } - }, "node_modules/undici-types": { - "version": "5.26.5", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", - "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", + "version": "6.19.8", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", + "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==", "dev": true }, "node_modules/unicode-canonical-property-names-ecmascript": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz", - "integrity": "sha512-yY5PpDlfVIU5+y/BSCxAJRBIS1Zc2dDG3Ujq+sR0U+JjUevW2JhocOF+soROYDSaAezOzOKuyyixhD6mBknSmQ==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.1.tgz", + "integrity": "sha512-dA8WbNeb2a6oQzAQ55YlT5vQAWGV9WXOsi3SskE3bcCdM0P4SDd+24zS/OCacdRq5BkdsRj9q3Pg6YyQoxIGqg==", "dev": true, "engines": { "node": ">=4" @@ -15247,9 +14437,9 @@ } }, "node_modules/unicode-match-property-value-ecmascript": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.1.0.tgz", - "integrity": "sha512-qxkjQt6qjg/mYscYMC0XKRn3Rh0wFPlfxB0xkt9CfyTvpX1Ra0+rAmdX2QyAobptSEvuy4RtpPRui6XkV+8wjA==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.2.0.tgz", + "integrity": "sha512-4IehN3V/+kkr5YeSSDDQG8QLqO26XpL2XP3GQtqwlT/QYSECAwFztxVHjlbh0+gjJ3XmNLS0zDsbgs9jWKExLg==", "dev": true, "engines": { "node": ">=4" @@ -15264,6 +14454,18 @@ "node": ">=4" } }, + "node_modules/unicorn-magic": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.1.0.tgz", + "integrity": "sha512-lRfVq8fE8gz6QMBuDM6a+LO3IAzTi05H6gCVaUpir2E1Rwpo4ZUog45KpNXKC/Mn3Yb9UDuHumeFTo9iV/D9FQ==", + "dev": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/unique-filename": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-3.0.0.tgz", @@ -15289,12 +14491,12 @@ } }, "node_modules/universalify": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", - "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", "dev": true, "engines": { - "node": ">= 10.0.0" + "node": ">= 4.0.0" } }, "node_modules/unpipe": { @@ -15307,9 +14509,9 @@ } }, "node_modules/update-browserslist-db": { - "version": "1.0.15", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.15.tgz", - "integrity": "sha512-K9HWH62x3/EalU1U6sjSZiylm9C8tgq2mSvshZpqc7QE69RaA2qjhkW2HlNA0tFpEbtyFz7HTqbSdN4MSwUodA==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.1.tgz", + "integrity": "sha512-R8UzCaa9Az+38REPiJ1tXlImTJXlVfgHZsglwBD/k6nj76ctsH1E3q4doGrukiLQd3sGQYu56r5+lo5r94l29A==", "dev": true, "funding": [ { @@ -15326,8 +14528,8 @@ } ], "dependencies": { - "escalade": "^3.1.2", - "picocolors": "^1.0.0" + "escalade": "^3.2.0", + "picocolors": "^1.1.0" }, "bin": { "update-browserslist-db": "cli.js" @@ -15345,6 +14547,15 @@ "punycode": "^2.1.0" } }, + "node_modules/uri-js/node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", @@ -15368,6 +14579,12 @@ "uuid": "dist/bin/uuid" } }, + "node_modules/v8-compile-cache": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.4.0.tgz", + "integrity": "sha512-ocyWc3bAHBB/guyqJQVI5o4BZkPhznPYUG2ea80Gond/BgNWpap8TOmLSeeQG7bnh2KMISxskdADG59j7zruhw==", + "dev": true + }, "node_modules/validate-npm-package-license": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", @@ -15397,14 +14614,14 @@ } }, "node_modules/vite": { - "version": "5.1.7", - "resolved": "https://registry.npmjs.org/vite/-/vite-5.1.7.tgz", - "integrity": "sha512-sgnEEFTZYMui/sTlH1/XEnVNHMujOahPLGMxn1+5sIT45Xjng1Ec1K78jRP15dSmVgg5WBin9yO81j3o9OxofA==", + "version": "5.4.6", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.6.tgz", + "integrity": "sha512-IeL5f8OO5nylsgzd9tq4qD2QqI0k2CQLGrWD0rCN0EQJZpBK5vJAx0I+GDkMOXxQX/OfFHMuLIx6ddAxGX/k+Q==", "dev": true, "dependencies": { - "esbuild": "^0.19.3", - "postcss": "^8.4.35", - "rollup": "^4.2.0" + "esbuild": "^0.21.3", + "postcss": "^8.4.43", + "rollup": "^4.20.0" }, "bin": { "vite": "bin/vite.js" @@ -15423,6 +14640,7 @@ "less": "*", "lightningcss": "^1.21.0", "sass": "*", + "sass-embedded": "*", "stylus": "*", "sugarss": "*", "terser": "^5.4.0" @@ -15440,6 +14658,9 @@ "sass": { "optional": true }, + "sass-embedded": { + "optional": true + }, "stylus": { "optional": true }, @@ -15452,9 +14673,9 @@ } }, "node_modules/vite/node_modules/@esbuild/aix-ppc64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.19.12.tgz", - "integrity": "sha512-bmoCYyWdEL3wDQIVbcyzRyeKLgk2WtWLTWz1ZIAZF/EGbNOwSA6ew3PftJ1PqMiOOGu0OyFMzG53L0zqIpPeNA==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", + "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", "cpu": [ "ppc64" ], @@ -15468,9 +14689,9 @@ } }, "node_modules/vite/node_modules/@esbuild/android-arm": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.19.12.tgz", - "integrity": "sha512-qg/Lj1mu3CdQlDEEiWrlC4eaPZ1KztwGJ9B6J+/6G+/4ewxJg7gqj8eVYWvao1bXrqGiW2rsBZFSX3q2lcW05w==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", + "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", "cpu": [ "arm" ], @@ -15484,9 +14705,9 @@ } }, "node_modules/vite/node_modules/@esbuild/android-arm64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.19.12.tgz", - "integrity": "sha512-P0UVNGIienjZv3f5zq0DP3Nt2IE/3plFzuaS96vihvD0Hd6H/q4WXUGpCxD/E8YrSXfNyRPbpTq+T8ZQioSuPA==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", + "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", "cpu": [ "arm64" ], @@ -15500,9 +14721,9 @@ } }, "node_modules/vite/node_modules/@esbuild/android-x64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.19.12.tgz", - "integrity": "sha512-3k7ZoUW6Q6YqhdhIaq/WZ7HwBpnFBlW905Fa4s4qWJyiNOgT1dOqDiVAQFwBH7gBRZr17gLrlFCRzF6jFh7Kew==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", + "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", "cpu": [ "x64" ], @@ -15516,9 +14737,9 @@ } }, "node_modules/vite/node_modules/@esbuild/darwin-arm64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.19.12.tgz", - "integrity": "sha512-B6IeSgZgtEzGC42jsI+YYu9Z3HKRxp8ZT3cqhvliEHovq8HSX2YX8lNocDn79gCKJXOSaEot9MVYky7AKjCs8g==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", + "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", "cpu": [ "arm64" ], @@ -15532,9 +14753,9 @@ } }, "node_modules/vite/node_modules/@esbuild/darwin-x64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.19.12.tgz", - "integrity": "sha512-hKoVkKzFiToTgn+41qGhsUJXFlIjxI/jSYeZf3ugemDYZldIXIxhvwN6erJGlX4t5h417iFuheZ7l+YVn05N3A==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", + "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", "cpu": [ "x64" ], @@ -15548,9 +14769,9 @@ } }, "node_modules/vite/node_modules/@esbuild/freebsd-arm64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.19.12.tgz", - "integrity": "sha512-4aRvFIXmwAcDBw9AueDQ2YnGmz5L6obe5kmPT8Vd+/+x/JMVKCgdcRwH6APrbpNXsPz+K653Qg8HB/oXvXVukA==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", + "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", "cpu": [ "arm64" ], @@ -15564,9 +14785,9 @@ } }, "node_modules/vite/node_modules/@esbuild/freebsd-x64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.19.12.tgz", - "integrity": "sha512-EYoXZ4d8xtBoVN7CEwWY2IN4ho76xjYXqSXMNccFSx2lgqOG/1TBPW0yPx1bJZk94qu3tX0fycJeeQsKovA8gg==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", + "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", "cpu": [ "x64" ], @@ -15580,9 +14801,9 @@ } }, "node_modules/vite/node_modules/@esbuild/linux-arm": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.19.12.tgz", - "integrity": "sha512-J5jPms//KhSNv+LO1S1TX1UWp1ucM6N6XuL6ITdKWElCu8wXP72l9MM0zDTzzeikVyqFE6U8YAV9/tFyj0ti+w==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", + "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", "cpu": [ "arm" ], @@ -15596,9 +14817,9 @@ } }, "node_modules/vite/node_modules/@esbuild/linux-arm64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.19.12.tgz", - "integrity": "sha512-EoTjyYyLuVPfdPLsGVVVC8a0p1BFFvtpQDB/YLEhaXyf/5bczaGeN15QkR+O4S5LeJ92Tqotve7i1jn35qwvdA==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", + "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", "cpu": [ "arm64" ], @@ -15612,9 +14833,9 @@ } }, "node_modules/vite/node_modules/@esbuild/linux-ia32": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.19.12.tgz", - "integrity": "sha512-Thsa42rrP1+UIGaWz47uydHSBOgTUnwBwNq59khgIwktK6x60Hivfbux9iNR0eHCHzOLjLMLfUMLCypBkZXMHA==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", + "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", "cpu": [ "ia32" ], @@ -15628,9 +14849,9 @@ } }, "node_modules/vite/node_modules/@esbuild/linux-loong64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.19.12.tgz", - "integrity": "sha512-LiXdXA0s3IqRRjm6rV6XaWATScKAXjI4R4LoDlvO7+yQqFdlr1Bax62sRwkVvRIrwXxvtYEHHI4dm50jAXkuAA==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", + "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", "cpu": [ "loong64" ], @@ -15644,9 +14865,9 @@ } }, "node_modules/vite/node_modules/@esbuild/linux-mips64el": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.19.12.tgz", - "integrity": "sha512-fEnAuj5VGTanfJ07ff0gOA6IPsvrVHLVb6Lyd1g2/ed67oU1eFzL0r9WL7ZzscD+/N6i3dWumGE1Un4f7Amf+w==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", + "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", "cpu": [ "mips64el" ], @@ -15660,9 +14881,9 @@ } }, "node_modules/vite/node_modules/@esbuild/linux-ppc64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.19.12.tgz", - "integrity": "sha512-nYJA2/QPimDQOh1rKWedNOe3Gfc8PabU7HT3iXWtNUbRzXS9+vgB0Fjaqr//XNbd82mCxHzik2qotuI89cfixg==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", + "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", "cpu": [ "ppc64" ], @@ -15676,9 +14897,9 @@ } }, "node_modules/vite/node_modules/@esbuild/linux-riscv64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.19.12.tgz", - "integrity": "sha512-2MueBrlPQCw5dVJJpQdUYgeqIzDQgw3QtiAHUC4RBz9FXPrskyyU3VI1hw7C0BSKB9OduwSJ79FTCqtGMWqJHg==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", + "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", "cpu": [ "riscv64" ], @@ -15692,9 +14913,9 @@ } }, "node_modules/vite/node_modules/@esbuild/linux-s390x": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.19.12.tgz", - "integrity": "sha512-+Pil1Nv3Umes4m3AZKqA2anfhJiVmNCYkPchwFJNEJN5QxmTs1uzyy4TvmDrCRNT2ApwSari7ZIgrPeUx4UZDg==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", + "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", "cpu": [ "s390x" ], @@ -15708,9 +14929,9 @@ } }, "node_modules/vite/node_modules/@esbuild/linux-x64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.19.12.tgz", - "integrity": "sha512-B71g1QpxfwBvNrfyJdVDexenDIt1CiDN1TIXLbhOw0KhJzE78KIFGX6OJ9MrtC0oOqMWf+0xop4qEU8JrJTwCg==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", + "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", "cpu": [ "x64" ], @@ -15724,9 +14945,9 @@ } }, "node_modules/vite/node_modules/@esbuild/netbsd-x64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.19.12.tgz", - "integrity": "sha512-3ltjQ7n1owJgFbuC61Oj++XhtzmymoCihNFgT84UAmJnxJfm4sYCiSLTXZtE00VWYpPMYc+ZQmB6xbSdVh0JWA==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", + "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", "cpu": [ "x64" ], @@ -15740,9 +14961,9 @@ } }, "node_modules/vite/node_modules/@esbuild/openbsd-x64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.19.12.tgz", - "integrity": "sha512-RbrfTB9SWsr0kWmb9srfF+L933uMDdu9BIzdA7os2t0TXhCRjrQyCeOt6wVxr79CKD4c+p+YhCj31HBkYcXebw==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", + "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", "cpu": [ "x64" ], @@ -15756,9 +14977,9 @@ } }, "node_modules/vite/node_modules/@esbuild/sunos-x64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.19.12.tgz", - "integrity": "sha512-HKjJwRrW8uWtCQnQOz9qcU3mUZhTUQvi56Q8DPTLLB+DawoiQdjsYq+j+D3s9I8VFtDr+F9CjgXKKC4ss89IeA==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", + "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", "cpu": [ "x64" ], @@ -15772,9 +14993,9 @@ } }, "node_modules/vite/node_modules/@esbuild/win32-arm64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.19.12.tgz", - "integrity": "sha512-URgtR1dJnmGvX864pn1B2YUYNzjmXkuJOIqG2HdU62MVS4EHpU2946OZoTMnRUHklGtJdJZ33QfzdjGACXhn1A==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", + "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", "cpu": [ "arm64" ], @@ -15788,9 +15009,9 @@ } }, "node_modules/vite/node_modules/@esbuild/win32-ia32": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.19.12.tgz", - "integrity": "sha512-+ZOE6pUkMOJfmxmBZElNOx72NKpIa/HFOMGzu8fqzQJ5kgf6aTGrcJaFsNiVMH4JKpMipyK+7k0n2UXN7a8YKQ==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", + "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", "cpu": [ "ia32" ], @@ -15804,9 +15025,9 @@ } }, "node_modules/vite/node_modules/@esbuild/win32-x64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.19.12.tgz", - "integrity": "sha512-T1QyPSDCyMXaO3pzBkF96E8xMkiRYbUEZADd29SyPGabqxMViNoii+NcK7eWJAEoU6RZyEm5lVSIjTmcdoB9HA==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", + "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", "cpu": [ "x64" ], @@ -15820,9 +15041,9 @@ } }, "node_modules/vite/node_modules/esbuild": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.19.12.tgz", - "integrity": "sha512-aARqgq8roFBj054KvQr5f1sFu0D65G+miZRCuJyJ0G13Zwx7vRar5Zhn2tkQNzIXcBrNVsv/8stehpj+GAjgbg==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", + "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", "dev": true, "hasInstallScript": true, "bin": { @@ -15832,29 +15053,57 @@ "node": ">=12" }, "optionalDependencies": { - "@esbuild/aix-ppc64": "0.19.12", - "@esbuild/android-arm": "0.19.12", - "@esbuild/android-arm64": "0.19.12", - "@esbuild/android-x64": "0.19.12", - "@esbuild/darwin-arm64": "0.19.12", - "@esbuild/darwin-x64": "0.19.12", - "@esbuild/freebsd-arm64": "0.19.12", - "@esbuild/freebsd-x64": "0.19.12", - "@esbuild/linux-arm": "0.19.12", - "@esbuild/linux-arm64": "0.19.12", - "@esbuild/linux-ia32": "0.19.12", - "@esbuild/linux-loong64": "0.19.12", - "@esbuild/linux-mips64el": "0.19.12", - "@esbuild/linux-ppc64": "0.19.12", - "@esbuild/linux-riscv64": "0.19.12", - "@esbuild/linux-s390x": "0.19.12", - "@esbuild/linux-x64": "0.19.12", - "@esbuild/netbsd-x64": "0.19.12", - "@esbuild/openbsd-x64": "0.19.12", - "@esbuild/sunos-x64": "0.19.12", - "@esbuild/win32-arm64": "0.19.12", - "@esbuild/win32-ia32": "0.19.12", - "@esbuild/win32-x64": "0.19.12" + "@esbuild/aix-ppc64": "0.21.5", + "@esbuild/android-arm": "0.21.5", + "@esbuild/android-arm64": "0.21.5", + "@esbuild/android-x64": "0.21.5", + "@esbuild/darwin-arm64": "0.21.5", + "@esbuild/darwin-x64": "0.21.5", + "@esbuild/freebsd-arm64": "0.21.5", + "@esbuild/freebsd-x64": "0.21.5", + "@esbuild/linux-arm": "0.21.5", + "@esbuild/linux-arm64": "0.21.5", + "@esbuild/linux-ia32": "0.21.5", + "@esbuild/linux-loong64": "0.21.5", + "@esbuild/linux-mips64el": "0.21.5", + "@esbuild/linux-ppc64": "0.21.5", + "@esbuild/linux-riscv64": "0.21.5", + "@esbuild/linux-s390x": "0.21.5", + "@esbuild/linux-x64": "0.21.5", + "@esbuild/netbsd-x64": "0.21.5", + "@esbuild/openbsd-x64": "0.21.5", + "@esbuild/sunos-x64": "0.21.5", + "@esbuild/win32-arm64": "0.21.5", + "@esbuild/win32-ia32": "0.21.5", + "@esbuild/win32-x64": "0.21.5" + } + }, + "node_modules/vite/node_modules/postcss": { + "version": "8.4.47", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.47.tgz", + "integrity": "sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "nanoid": "^3.3.7", + "picocolors": "^1.1.0", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" } }, "node_modules/void-elements": { @@ -15867,49 +15116,89 @@ } }, "node_modules/vue-eslint-parser": { - "version": "9.4.2", - "resolved": "https://registry.npmjs.org/vue-eslint-parser/-/vue-eslint-parser-9.4.2.tgz", - "integrity": "sha512-Ry9oiGmCAK91HrKMtCrKFWmSFWvYkpGglCeFAIqDdr9zdXmMMpJOmUJS7WWsW7fX81h6mwHmUZCQQ1E0PkSwYQ==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/vue-eslint-parser/-/vue-eslint-parser-7.1.1.tgz", + "integrity": "sha512-8FdXi0gieEwh1IprIBafpiJWcApwrU+l2FEj8c1HtHFdNXMd0+2jUSjBVmcQYohf/E72irwAXEXLga6TQcB3FA==", "dev": true, "dependencies": { - "debug": "^4.3.4", - "eslint-scope": "^7.1.1", - "eslint-visitor-keys": "^3.3.0", - "espree": "^9.3.1", - "esquery": "^1.4.0", - "lodash": "^4.17.21", - "semver": "^7.3.6" + "debug": "^4.1.1", + "eslint-scope": "^5.0.0", + "eslint-visitor-keys": "^1.1.0", + "espree": "^6.2.1", + "esquery": "^1.0.1", + "lodash": "^4.17.15" }, "engines": { - "node": "^14.17.0 || >=16.0.0" + "node": ">=8.10" }, "funding": { "url": "https://github.com/sponsors/mysticatea" }, "peerDependencies": { - "eslint": ">=6.0.0" + "eslint": ">=5.0.0" + } + }, + "node_modules/vue-eslint-parser/node_modules/acorn": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", + "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" } }, "node_modules/vue-eslint-parser/node_modules/eslint-scope": { - "version": "7.2.2", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", - "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", "dev": true, "dependencies": { "esrecurse": "^4.3.0", - "estraverse": "^5.2.0" + "estraverse": "^4.1.1" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": ">=8.0.0" + } + }, + "node_modules/vue-eslint-parser/node_modules/eslint-visitor-keys": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", + "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/vue-eslint-parser/node_modules/espree": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-6.2.1.tgz", + "integrity": "sha512-ysCxRQY3WaXJz9tdbWOwuWr5Y/XrPTGX9Kiz3yoUXwW0VZ4w30HTkQLaGx/+ttFjF8i+ACbArnB4ce68a9m5hw==", + "dev": true, + "dependencies": { + "acorn": "^7.1.1", + "acorn-jsx": "^5.2.0", + "eslint-visitor-keys": "^1.1.0" }, - "funding": { - "url": "https://opencollective.com/eslint" + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/vue-eslint-parser/node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true, + "engines": { + "node": ">=4.0" } }, "node_modules/watchpack": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz", - "integrity": "sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg==", + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.1.tgz", + "integrity": "sha512-8wrBCMtVhqcXP2Sup1ctSkga6uc2Bx0IIvKyT7yTFier5AXHooSI+QyQQAtTb7+E0IUCCKyTFmXqdqgum2XWGg==", "dev": true, "dependencies": { "glob-to-regexp": "^0.4.1", @@ -15937,27 +15226,32 @@ "defaults": "^1.0.3" } }, + "node_modules/weak-lru-cache": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/weak-lru-cache/-/weak-lru-cache-1.2.2.tgz", + "integrity": "sha512-DEAoo25RfSYMuTGc9vPJzZcZullwIqRDSI9LOy+fkCJPi6hykCnfKaXTuPBDuXAUcqHXyOgFtHNp/kB2FjYHbw==", + "dev": true + }, "node_modules/webpack": { - "version": "5.90.3", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.90.3.tgz", - "integrity": "sha512-h6uDYlWCctQRuXBs1oYpVe6sFcWedl0dpcVaTf/YF67J9bKvwJajFulMVSYKHrksMB3I/pIagRzDxwxkebuzKA==", + "version": "5.94.0", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.94.0.tgz", + "integrity": "sha512-KcsGn50VT+06JH/iunZJedYGUJS5FGjow8wb9c0v5n1Om8O1g4L6LjtfxwlXIATopoQu+vOXXa7gYisWxCoPyg==", "dev": true, "dependencies": { - "@types/eslint-scope": "^3.7.3", "@types/estree": "^1.0.5", - "@webassemblyjs/ast": "^1.11.5", - "@webassemblyjs/wasm-edit": "^1.11.5", - "@webassemblyjs/wasm-parser": "^1.11.5", + "@webassemblyjs/ast": "^1.12.1", + "@webassemblyjs/wasm-edit": "^1.12.1", + "@webassemblyjs/wasm-parser": "^1.12.1", "acorn": "^8.7.1", - "acorn-import-assertions": "^1.9.0", + "acorn-import-attributes": "^1.9.5", "browserslist": "^4.21.10", "chrome-trace-event": "^1.0.2", - "enhanced-resolve": "^5.15.0", + "enhanced-resolve": "^5.17.1", "es-module-lexer": "^1.2.1", "eslint-scope": "5.1.1", "events": "^3.2.0", "glob-to-regexp": "^0.4.1", - "graceful-fs": "^4.2.9", + "graceful-fs": "^4.2.11", "json-parse-even-better-errors": "^2.3.1", "loader-runner": "^4.2.0", "mime-types": "^2.1.27", @@ -15965,7 +15259,7 @@ "schema-utils": "^3.2.0", "tapable": "^2.1.1", "terser-webpack-plugin": "^5.3.10", - "watchpack": "^2.4.0", + "watchpack": "^2.4.1", "webpack-sources": "^3.2.3" }, "bin": { @@ -15985,19 +15279,20 @@ } }, "node_modules/webpack-dev-middleware": { - "version": "6.1.2", - "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-6.1.2.tgz", - "integrity": "sha512-Wu+EHmX326YPYUpQLKmKbTyZZJIB8/n6R09pTmB03kJmnMsVPTo9COzHZFr01txwaCAuZvfBJE4ZCHRcKs5JaQ==", + "version": "7.4.2", + "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-7.4.2.tgz", + "integrity": "sha512-xOO8n6eggxnwYpy1NlzUKpvrjfJTvae5/D6WOK0S2LSo7vjmo5gCM1DbLUmFqrMTJP+W/0YZNctm7jasWvLuBA==", "dev": true, "dependencies": { "colorette": "^2.0.10", - "memfs": "^3.4.12", + "memfs": "^4.6.0", "mime-types": "^2.1.31", + "on-finished": "^2.4.1", "range-parser": "^1.2.1", "schema-utils": "^4.0.0" }, "engines": { - "node": ">= 14.15.0" + "node": ">= 18.12.0" }, "funding": { "type": "opencollective", @@ -16013,54 +15308,54 @@ } }, "node_modules/webpack-dev-server": { - "version": "4.15.1", - "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-4.15.1.tgz", - "integrity": "sha512-5hbAst3h3C3L8w6W4P96L5vaV0PxSmJhxZvWKYIdgxOQm8pNZ5dEOmmSLBVpP85ReeyRt6AS1QJNyo/oFFPeVA==", - "dev": true, - "dependencies": { - "@types/bonjour": "^3.5.9", - "@types/connect-history-api-fallback": "^1.3.5", - "@types/express": "^4.17.13", - "@types/serve-index": "^1.9.1", - "@types/serve-static": "^1.13.10", - "@types/sockjs": "^0.3.33", - "@types/ws": "^8.5.5", + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-5.0.4.tgz", + "integrity": "sha512-dljXhUgx3HqKP2d8J/fUMvhxGhzjeNVarDLcbO/EWMSgRizDkxHQDZQaLFL5VJY9tRBj2Gz+rvCEYYvhbqPHNA==", + "dev": true, + "dependencies": { + "@types/bonjour": "^3.5.13", + "@types/connect-history-api-fallback": "^1.5.4", + "@types/express": "^4.17.21", + "@types/serve-index": "^1.9.4", + "@types/serve-static": "^1.15.5", + "@types/sockjs": "^0.3.36", + "@types/ws": "^8.5.10", "ansi-html-community": "^0.0.8", - "bonjour-service": "^1.0.11", - "chokidar": "^3.5.3", + "bonjour-service": "^1.2.1", + "chokidar": "^3.6.0", "colorette": "^2.0.10", "compression": "^1.7.4", "connect-history-api-fallback": "^2.0.0", "default-gateway": "^6.0.3", "express": "^4.17.3", "graceful-fs": "^4.2.6", - "html-entities": "^2.3.2", + "html-entities": "^2.4.0", "http-proxy-middleware": "^2.0.3", - "ipaddr.js": "^2.0.1", - "launch-editor": "^2.6.0", - "open": "^8.0.9", - "p-retry": "^4.5.0", - "rimraf": "^3.0.2", - "schema-utils": "^4.0.0", - "selfsigned": "^2.1.1", + "ipaddr.js": "^2.1.0", + "launch-editor": "^2.6.1", + "open": "^10.0.3", + "p-retry": "^6.2.0", + "rimraf": "^5.0.5", + "schema-utils": "^4.2.0", + "selfsigned": "^2.4.1", "serve-index": "^1.9.1", "sockjs": "^0.3.24", "spdy": "^4.0.2", - "webpack-dev-middleware": "^5.3.1", - "ws": "^8.13.0" + "webpack-dev-middleware": "^7.1.0", + "ws": "^8.16.0" }, "bin": { "webpack-dev-server": "bin/webpack-dev-server.js" }, "engines": { - "node": ">= 12.13.0" + "node": ">= 18.12.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/webpack" }, "peerDependencies": { - "webpack": "^4.37.0 || ^5.0.0" + "webpack": "^5.0.0" }, "peerDependenciesMeta": { "webpack": { @@ -16071,41 +15366,98 @@ } } }, - "node_modules/webpack-dev-server/node_modules/webpack-dev-middleware": { - "version": "5.3.4", - "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-5.3.4.tgz", - "integrity": "sha512-BVdTqhhs+0IfoeAf7EoH5WE+exCmqGerHfDM0IL096Px60Tq2Mn9MAbnaGUe6HiMa41KMCYF19gyzZmBcq/o4Q==", + "node_modules/webpack-dev-server/node_modules/glob": { + "version": "10.4.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", + "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", "dev": true, "dependencies": { - "colorette": "^2.0.10", - "memfs": "^3.4.3", - "mime-types": "^2.1.31", - "range-parser": "^1.2.1", - "schema-utils": "^4.0.0" + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/webpack-dev-server/node_modules/http-proxy-middleware": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.6.tgz", + "integrity": "sha512-ya/UeJ6HVBYxrgYotAZo1KvPWlgB48kUJLDePFeneHsVujFaW5WNj2NgWCAE//B1Dl02BIfYlpNgBy8Kf8Rjmw==", + "dev": true, + "dependencies": { + "@types/http-proxy": "^1.17.8", + "http-proxy": "^1.18.1", + "is-glob": "^4.0.1", + "is-plain-obj": "^3.0.0", + "micromatch": "^4.0.2" }, "engines": { - "node": ">= 12.13.0" + "node": ">=12.0.0" + }, + "peerDependencies": { + "@types/express": "^4.17.13" + }, + "peerDependenciesMeta": { + "@types/express": { + "optional": true + } + } + }, + "node_modules/webpack-dev-server/node_modules/rimraf": { + "version": "5.0.10", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-5.0.10.tgz", + "integrity": "sha512-l0OE8wL34P4nJH/H2ffoaniAokM2qSmrtXHmlpvYr5AVVX8msAyW0l8NVJFDxlSK4u3Uh/f41cQheDVdnYijwQ==", + "dev": true, + "dependencies": { + "glob": "^10.3.7" + }, + "bin": { + "rimraf": "dist/esm/bin.mjs" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/webpack-dev-server/node_modules/ws": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", + "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", + "dev": true, + "engines": { + "node": ">=10.0.0" }, "peerDependencies": { - "webpack": "^4.0.0 || ^5.0.0" + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } } }, "node_modules/webpack-merge": { - "version": "5.10.0", - "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-5.10.0.tgz", - "integrity": "sha512-+4zXKdx7UnO+1jaN4l2lHVD+mFvnlZQP/6ljaJVb4SZiwIKeUnrT5l0gkT8z+n4hKpC+jpOv6O9R+gLtag7pSA==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-6.0.1.tgz", + "integrity": "sha512-hXXvrjtx2PLYx4qruKl+kyRSLc52V+cCvMxRjmKwoA+CBbbF5GfIBtR6kCvl0fYGqTUPKB+1ktVmTHqMOzgCBg==", "dev": true, "dependencies": { "clone-deep": "^4.0.1", "flat": "^5.0.2", - "wildcard": "^2.0.0" + "wildcard": "^2.0.1" }, "engines": { - "node": ">=10.0.0" + "node": ">=18.0.0" } }, "node_modules/webpack-sources": { @@ -16333,6 +15685,35 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, + "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/wrap-ansi-cjs/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/wrap-ansi/node_modules/ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", @@ -16366,22 +15747,50 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, + "node_modules/wrap-ansi/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/wrap-ansi/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" }, "node_modules/ws": { - "version": "8.17.1", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", - "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", - "dev": true, + "version": "7.5.10", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz", + "integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==", "engines": { - "node": ">=10.0.0" + "node": ">=8.3.0" }, "peerDependencies": { "bufferutil": "^4.0.1", - "utf-8-validate": ">=5.0.2" + "utf-8-validate": "^5.0.2" }, "peerDependenciesMeta": { "bufferutil": { @@ -16442,6 +15851,35 @@ "node": ">=12" } }, + "node_modules/yargs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/yargs/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/yargs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", @@ -16454,13 +15892,22 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/zone.js": { - "version": "0.14.5", - "resolved": "https://registry.npmjs.org/zone.js/-/zone.js-0.14.5.tgz", - "integrity": "sha512-9XYWZzY6PhHOSdkYryNcMm7L8EK7a4q+GbTvxbIA2a9lMdRUpGuyaYvLDcg8D6bdn+JomSsbPcilVKg6SmUx6w==", - "dependencies": { - "tslib": "^2.3.0" + "node_modules/yoctocolors-cjs": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yoctocolors-cjs/-/yoctocolors-cjs-2.1.2.tgz", + "integrity": "sha512-cYVsTjKl8b+FrnidjibDWskAv7UKOfcwaVZdp/it9n1s9fU3IkgDbhdIRKCW4JDsAlECJY0ytoVPT3sK6kideA==", + "dev": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } + }, + "node_modules/zone.js": { + "version": "0.14.10", + "resolved": "https://registry.npmjs.org/zone.js/-/zone.js-0.14.10.tgz", + "integrity": "sha512-YGAhaO7J5ywOXW6InXNlLmfU194F8lVgu7bRntUF3TiG8Y3nBK0x1UJJuHUP/e8IyihkjCYqhCScpSwnlaSRkQ==" } } } diff --git a/modules/ui/package.json b/modules/ui/package.json index aceb9c389..b8f777d64 100644 --- a/modules/ui/package.json +++ b/modules/ui/package.json @@ -17,37 +17,37 @@ }, "private": true, "dependencies": { - "@angular/animations": "^17.0.8", - "@angular/cdk": "^17.0.4", - "@angular/common": "^17.0.8", - "@angular/compiler": "^17.0.8", - "@angular/core": "^17.0.8", - "@angular/forms": "^17.3.1", - "@angular/material": "^17.3.1", - "@angular/platform-browser": "^17.0.8", - "@angular/platform-browser-dynamic": "^17.3.1", - "@angular/router": "^17.3.1", - "@ngrx/component-store": "^17.1.1", - "@ngrx/effects": "^17.1.1", - "@ngrx/store": "^17.0.1", + "@angular/animations": "^18.2.4", + "@angular/cdk": "^18.2.0", + "@angular/common": "^18.2.4", + "@angular/compiler": "^18.2.4", + "@angular/core": "^18.2.4", + "@angular/forms": "^18.2.4", + "@angular/material": "^18.2.0", + "@angular/platform-browser": "^18.2.4", + "@angular/platform-browser-dynamic": "^18.2.4", + "@angular/router": "^18.2.4", + "@ngrx/component-store": "^18.0.2", + "@ngrx/effects": "^18.0.2", + "@ngrx/store": "^18.0.2", "ngx-mask": "^16.4.2", "ngx-mqtt": "^17.0.0", "rxjs": "~7.8.0", "tslib": "^2.6.2", - "zone.js": "^0.14.4" + "zone.js": "^0.14.10" }, "devDependencies": { - "@angular-devkit/build-angular": "^17.3.2", - "@angular-eslint/builder": "17.2.0", - "@angular-eslint/eslint-plugin": "17.2.0", - "@angular-eslint/eslint-plugin-template": "17.2.0", - "@angular-eslint/schematics": "17.2.0", - "@angular-eslint/template-parser": "17.2.0", - "@angular/cli": "~17.0.9", - "@angular/compiler-cli": "^17.3.1", + "@angular-devkit/build-angular": "^18.1.4", + "@angular-eslint/builder": "18.3.0", + "@angular-eslint/eslint-plugin": "^18.3.0", + "@angular-eslint/eslint-plugin-template": "^18.3.0", + "@angular-eslint/schematics": "^18.3.0", + "@angular-eslint/template-parser": "18.3.0", + "@angular/cli": "~18.2.4", + "@angular/compiler-cli": "^18.2.4", "@types/jasmine": "~4.3.6", - "@typescript-eslint/eslint-plugin": "^6.21.0", - "@typescript-eslint/parser": "^6.21.0", + "@typescript-eslint/eslint-plugin": "^8.2.0", + "@typescript-eslint/parser": "^8.2.0", "eslint": "^8.57.0", "eslint-config-prettier": "^9.1.0", "eslint-plugin-prettier": "^5.1.3", @@ -58,7 +58,7 @@ "karma-jasmine": "~5.1.0", "karma-jasmine-html-reporter": "~2.1.0", "prettier": "^3.2.5", - "prettier-eslint": "^16.3.0", - "typescript": "~5.2.2" + "prettier-eslint": "^13.0.0", + "typescript": "~5.5.4" } } diff --git a/modules/ui/src/app/app.component.html b/modules/ui/src/app/app.component.html index b1341a58d..912e40c32 100644 --- a/modules/ui/src/app/app.component.html +++ b/modules/ui/src/app/app.component.html @@ -29,7 +29,7 @@ route: Routes.Testing, svgIcon: 'testrun_logo_small', label: 'Testing', - name: 'testrun' + name: 'testrun', } "> @@ -40,7 +40,7 @@ route: Routes.Devices, svgIcon: 'devices', label: 'Devices', - name: 'devices' + name: 'devices', } "> @@ -51,7 +51,7 @@ route: Routes.Reports, svgIcon: 'reports', label: 'Reports', - name: 'reports' + name: 'reports', } "> @@ -62,17 +62,13 @@ route: Routes.RiskAssessment, svgIcon: 'risk_assessment', label: 'Risk Assessment', - name: 'risk-assessment' + name: 'risk-assessment', } "> + (consentShownEvent)="consentShown()">
@@ -128,19 +124,70 @@

Testrun

- - - - No ports detected. Please connect and configure network and device - connections in the +
+ + + + No ports detected. Please connect and configure network and + device connections in the + + + Selected port is missing! Please define a valid one using + + System settings + panel. + + + + Further information is required in your device configurations. + Please update your + Devices + to continue testing. - - Selected port is missing! Please define a valid one using - + + + Step 1: To perform a device test, please, select ports in Testrun > panel. - - - Step 1: To perform a device test, please, select ports in - System settings - panel. - - - Step 2: To perform a device test please - Create a Device - first. - - - Step 3: Once device is created, you are able to - start testing. - - - The device is now being tested. Why not take the time to complete the - device - Risk Assessment questionnaire? - + + Step 2: To perform a device test please + Create a Device + first. + + + Step 3: Once device is created, you are able to + start testing. + + + The device is now being tested. Why not take the time to complete + the device + Risk Assessment questionnaire? + +
@@ -262,6 +300,10 @@

Testrun

+ .mat-icon, @@ -133,8 +133,8 @@ $nav-open-btn-width: 210px; } .app-sidebar-button-active { - border: 1px solid mat.get-color-from-palette($color-primary, 500); - background-color: mat.get-color-from-palette($color-primary, 500); + border: 1px solid mat.m2-get-color-from-palette($color-primary, 500); + background-color: mat.m2-get-color-from-palette($color-primary, 500); } .app-sidebar-button-active > .mat-icon { @@ -186,7 +186,7 @@ $nav-open-btn-width: 210px; .app-content-main { position: relative; display: grid; - grid-template-rows: 0 auto; + grid-template-rows: auto 0 1fr; overflow: hidden; } diff --git a/modules/ui/src/app/app.component.spec.ts b/modules/ui/src/app/app.component.spec.ts index df531c8b7..fcad77213 100644 --- a/modules/ui/src/app/app.component.spec.ts +++ b/modules/ui/src/app/app.component.spec.ts @@ -54,13 +54,17 @@ import { selectError, selectHasConnectionSettings, selectHasDevices, + selectHasExpiredDevices, selectHasRiskProfiles, selectInterfaces, selectInternetConnection, + selectIsAllDevicesOutdated, selectIsOpenStartTestrun, selectIsOpenWaitSnackBar, + selectIsTestingComplete, selectMenuOpened, selectReports, + selectRiskProfiles, selectStatus, selectSystemStatus, } from './store/selectors'; @@ -74,6 +78,8 @@ import { TestRunMqttService } from './services/test-run-mqtt.service'; import { MOCK_ADAPTERS } from './mocks/settings.mock'; import { WifiComponent } from './components/wifi/wifi.component'; import { MatTooltipModule } from '@angular/material/tooltip'; +import { Profile } from './model/profile'; +import { TestrunStatus } from './model/testrun-status'; const windowMock = { location: { @@ -168,9 +174,13 @@ describe('AppComponent', () => { { selector: selectError, value: null }, { selector: selectMenuOpened, value: false }, { selector: selectHasDevices, value: false }, + { selector: selectIsAllDevicesOutdated, value: false }, + { selector: selectHasExpiredDevices, value: false }, { selector: selectHasRiskProfiles, value: false }, { selector: selectStatus, value: null }, { selector: selectSystemStatus, value: null }, + { selector: selectIsTestingComplete, value: false }, + { selector: selectRiskProfiles, value: [] }, { selector: selectIsOpenStartTestrun, value: false }, { selector: selectIsOpenWaitSnackBar, value: false }, { selector: selectReports, value: [] }, @@ -185,6 +195,7 @@ describe('AppComponent', () => { FakeSpinnerComponent, FakeShutdownAppComponent, FakeVersionComponent, + FakeTestingCompleteComponent, ], }); @@ -452,6 +463,21 @@ describe('AppComponent', () => { expect(internet).toBeTruthy(); }); + describe('Testing complete', () => { + beforeEach(() => { + store.overrideSelector(selectIsTestingComplete, true); + fixture.detectChanges(); + }); + + it('should have testing complete component', () => { + const testingCompleteComp = compiled.querySelector( + 'app-testing-complete' + ); + + expect(testingCompleteComp).toBeTruthy(); + }); + }); + describe('Callout component visibility', () => { describe('with no connection settings', () => { beforeEach(() => { @@ -768,6 +794,31 @@ describe('AppComponent', () => { }); }); }); + + describe('with expired devices', () => { + beforeEach(() => { + store.overrideSelector(selectHasExpiredDevices, true); + fixture.detectChanges(); + }); + + it('should have callout component', () => { + const callouts = compiled.querySelectorAll('app-callout'); + let hasExpiredDeviceCallout = false; + callouts.forEach(callout => { + if ( + callout?.innerHTML + .trim() + .includes( + 'Further information is required in your device configurations.' + ) + ) { + hasExpiredDeviceCallout = true; + } + }); + + expect(hasExpiredDeviceCallout).toBeTrue(); + }); + }); }); it('should not call toggleSettingsBtn focus on closeSetting when device length is 0', async () => { @@ -803,6 +854,15 @@ describe('AppComponent', () => { expect(component.certDrawer.open).toHaveBeenCalledTimes(1); }); + + it('should set focus to first focusable elem when close callout', fakeAsync(() => { + component.calloutClosed('mockId'); + tick(100); + + expect( + mockFocusManagerService.focusFirstElementInContainer + ).toHaveBeenCalled(); + })); }); @Component({ @@ -836,7 +896,14 @@ class FakeShutdownAppComponent { }) class FakeVersionComponent { @Input() consentShown!: boolean; - @Input() hasRiskProfiles!: boolean; @Output() consentShownEvent = new EventEmitter(); - @Output() navigateToRiskAssessmentEvent = new EventEmitter(); +} + +@Component({ + selector: 'app-testing-complete', + template: '
', +}) +class FakeTestingCompleteComponent { + @Input() profiles: Profile[] = []; + @Input() data!: TestrunStatus | null; } diff --git a/modules/ui/src/app/app.component.ts b/modules/ui/src/app/app.component.ts index 2214b8927..7218459c4 100644 --- a/modules/ui/src/app/app.component.ts +++ b/modules/ui/src/app/app.component.ts @@ -13,7 +13,14 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { Component, ElementRef, ViewChild } from '@angular/core'; +import { + AfterViewInit, + Component, + ElementRef, + QueryList, + ViewChild, + ViewChildren, +} from '@angular/core'; import { MatIconRegistry } from '@angular/material/icon'; import { DomSanitizer } from '@angular/platform-browser'; import { MatDrawer } from '@angular/material/sidenav'; @@ -35,6 +42,7 @@ import { AppStore } from './app.store'; import { TestRunService } from './services/test-run.service'; import { LiveAnnouncer } from '@angular/cdk/a11y'; import { filter, take } from 'rxjs/operators'; +import { skip, timer } from 'rxjs'; const DEVICES_LOGO_URL = '/assets/icons/devices.svg'; const DEVICES_RUN_URL = '/assets/icons/device_run.svg'; @@ -44,6 +52,8 @@ const TESTRUN_LOGO_URL = '/assets/icons/testrun_logo_small.svg'; const TESTRUN_LOGO_COLOR_URL = '/assets/icons/testrun_logo_color.svg'; const CLOSE_URL = '/assets/icons/close.svg'; const DRAFT_URL = '/assets/icons/draft.svg'; +const PILOT_URL = '/assets/icons/pilot.svg'; +const QUALIFICATION_URL = '/assets/icons/qualification.svg'; @Component({ selector: 'app-root', @@ -51,7 +61,7 @@ const DRAFT_URL = '/assets/icons/draft.svg'; styleUrls: ['./app.component.scss'], providers: [AppStore], }) -export class AppComponent { +export class AppComponent implements AfterViewInit { public readonly CalloutType = CalloutType; public readonly StatusOfTestrun = StatusOfTestrun; public readonly Routes = Routes; @@ -64,6 +74,8 @@ export class AppComponent { public toggleCertificatesBtn!: HTMLButtonElement; @ViewChild('navigation') public navigation!: ElementRef; @ViewChild('settings') public settings!: SettingsComponent; + @ViewChildren('riskAssessmentLink') + riskAssessmentLink!: QueryList; viewModel$ = this.appStore.viewModel$; constructor( @@ -115,14 +127,49 @@ export class AppComponent { 'draft', this.domSanitizer.bypassSecurityTrustResourceUrl(DRAFT_URL) ); + this.matIconRegistry.addSvgIcon( + 'pilot', + this.domSanitizer.bypassSecurityTrustResourceUrl(PILOT_URL) + ); + this.matIconRegistry.addSvgIcon( + 'qualification', + this.domSanitizer.bypassSecurityTrustResourceUrl(QUALIFICATION_URL) + ); + } + + ngAfterViewInit() { + this.viewModel$ + .pipe( + filter(({ isStatusLoaded }) => isStatusLoaded === true), + take(1) + ) + .subscribe(({ systemStatus }) => { + let skipCount = 0; + if (systemStatus === StatusOfTestrun.InProgress) { + // link should not be focused after page is just loaded + skipCount = 1; + } + this.riskAssessmentLink.changes.pipe(skip(skipCount)).subscribe(() => { + if (this.riskAssessmentLink.length > 0) { + this.riskAssessmentLink.first.nativeElement.focus(); + } + }); + }); } get isRiskAssessmentRoute(): boolean { return this.route.url === Routes.RiskAssessment; } + get isDevicesRoute(): boolean { + return this.route.url === Routes.Devices; + } + navigateToDeviceRepository(): void { this.route.navigate([Routes.Devices]); + } + navigateToAddDevice(): void { + this.route.navigate([Routes.Devices]); this.store.dispatch(setIsOpenAddDevice({ isOpenAddDevice: true })); } @@ -132,7 +179,9 @@ export class AppComponent { } navigateToRiskAssessment(): void { - this.route.navigate([Routes.RiskAssessment]); + this.route.navigate([Routes.RiskAssessment]).then(() => { + this.appStore.setFocusOnPage(); + }); } async closeCertificates(): Promise { @@ -207,4 +256,13 @@ export class AppComponent { this.appStore.setFocusOnPage(); }); } + + calloutClosed(id: string | null) { + if (id) { + this.appStore.setCloseCallout(id); + timer(100).subscribe(() => { + this.focusManagerService.focusFirstElementInContainer(); + }); + } + } } diff --git a/modules/ui/src/app/app.module.ts b/modules/ui/src/app/app.module.ts index 795d4e0d8..4ab788cbc 100644 --- a/modules/ui/src/app/app.module.ts +++ b/modules/ui/src/app/app.module.ts @@ -14,7 +14,7 @@ * limitations under the License. */ import { HTTP_INTERCEPTORS, HttpClientModule } from '@angular/common/http'; -import { NgModule } from '@angular/core'; +import { importProvidersFrom, NgModule } from '@angular/core'; import { MatButtonModule } from '@angular/material/button'; import { MatButtonToggleModule } from '@angular/material/button-toggle'; import { MatIconModule } from '@angular/material/icon'; @@ -50,11 +50,13 @@ import { WindowProvider } from './providers/window.provider'; import { CertificatesComponent } from './pages/certificates/certificates.component'; import { LOADER_TIMEOUT_CONFIG_TOKEN } from './services/loaderConfig'; import { WifiComponent } from './components/wifi/wifi.component'; +import { TestingCompleteComponent } from './components/testing-complete/testing-complete.component'; import { MqttModule, IMqttServiceOptions } from 'ngx-mqtt'; +import { MatNativeDateModule } from '@angular/material/core'; export const MQTT_SERVICE_OPTIONS: IMqttServiceOptions = { - hostname: 'localhost', + hostname: window.location.hostname, port: 9001, }; @@ -89,6 +91,7 @@ export const MQTT_SERVICE_OPTIONS: IMqttServiceOptions = { CertificatesComponent, MqttModule.forRoot(MQTT_SERVICE_OPTIONS), WifiComponent, + TestingCompleteComponent, ], providers: [ WindowProvider, @@ -103,6 +106,7 @@ export const MQTT_SERVICE_OPTIONS: IMqttServiceOptions = { multi: true, }, { provide: LOADER_TIMEOUT_CONFIG_TOKEN, useValue: 1000 }, + importProvidersFrom(MatNativeDateModule), ], bootstrap: [AppComponent], }) diff --git a/modules/ui/src/app/app.store.spec.ts b/modules/ui/src/app/app.store.spec.ts index e26db7eb3..4236b24fd 100644 --- a/modules/ui/src/app/app.store.spec.ts +++ b/modules/ui/src/app/app.store.spec.ts @@ -15,20 +15,25 @@ */ import { fakeAsync, TestBed, tick } from '@angular/core/testing'; import { of, skip, take } from 'rxjs'; -import { AppStore, CONSENT_SHOWN_KEY } from './app.store'; +import { AppStore, CALLOUT_STATE_KEY, CONSENT_SHOWN_KEY } from './app.store'; import { MockStore, provideMockStore } from '@ngrx/store/testing'; import { AppState } from './store/state'; import { selectError, selectHasConnectionSettings, selectHasDevices, + selectHasExpiredDevices, selectHasRiskProfiles, selectInterfaces, selectInternetConnection, + selectIsAllDevicesOutdated, selectIsOpenWaitSnackBar, + selectIsTestingComplete, selectMenuOpened, selectReports, + selectRiskProfiles, selectStatus, + selectSystemStatus, selectTestModules, } from './store/selectors'; import { TestRunService } from './services/test-run.service'; @@ -58,6 +63,12 @@ const mock = (() => { setItem: (key: string, value: string) => { store[key] = value + ''; }, + getObject: (key: string) => { + return store[key] || null; + }, + setObject: (key: string, value: object) => { + store[key] = JSON.stringify(value); + }, clear: () => { store = {}; }, @@ -97,6 +108,10 @@ describe('AppStore', () => { { selector: selectIsOpenWaitSnackBar, value: false }, { selector: selectTestModules, value: MOCK_TEST_MODULES }, { selector: selectInternetConnection, value: false }, + { selector: selectIsAllDevicesOutdated, value: false }, + { selector: selectSystemStatus, value: null }, + { selector: selectIsTestingComplete, value: false }, + { selector: selectRiskProfiles, value: [] }, ], }), { provide: TestRunService, useValue: mockService }, @@ -111,6 +126,7 @@ describe('AppStore', () => { appStore = TestBed.inject(AppStore); store.overrideSelector(selectHasDevices, true); + store.overrideSelector(selectHasExpiredDevices, true); store.overrideSelector(selectHasRiskProfiles, false); store.overrideSelector(selectReports, []); store.overrideSelector(selectHasConnectionSettings, true); @@ -156,14 +172,20 @@ describe('AppStore', () => { expect(store).toEqual({ consentShown: false, hasDevices: true, + hasExpiredDevices: true, + isAllDevicesOutdated: false, hasRiskProfiles: false, reports: [], isStatusLoaded: false, systemStatus: null, + testrunStatus: null, + isTestingComplete: false, + riskProfiles: [], hasConnectionSettings: true, isMenuOpen: true, interfaces: {}, settingMissedError: null, + calloutState: new Map(), hasInternetConnection: false, }); done(); @@ -306,5 +328,22 @@ describe('AppStore', () => { ); }); }); + + describe('setCloseCallout', () => { + it('should update store', done => { + appStore.viewModel$.pipe(skip(1), take(1)).subscribe(store => { + expect(store.calloutState.get('test')).toEqual(true); + done(); + }); + + appStore.setCloseCallout('test'); + }); + + it('should update storage', () => { + appStore.setCloseCallout('test'); + + expect(mock.getObject(CALLOUT_STATE_KEY)).toBeTruthy(); + }); + }); }); }); diff --git a/modules/ui/src/app/app.store.ts b/modules/ui/src/app/app.store.ts index 6e338968f..91280724c 100644 --- a/modules/ui/src/app/app.store.ts +++ b/modules/ui/src/app/app.store.ts @@ -21,12 +21,17 @@ import { selectError, selectHasConnectionSettings, selectHasDevices, + selectHasExpiredDevices, selectHasRiskProfiles, selectInterfaces, selectInternetConnection, + selectIsAllDevicesOutdated, + selectIsTestingComplete, selectMenuOpened, selectReports, + selectRiskProfiles, selectStatus, + selectSystemStatus, } from './store/selectors'; import { Store } from '@ngrx/store'; import { AppState } from './store/state'; @@ -51,19 +56,25 @@ import { import { FocusManagerService } from './services/focus-manager.service'; import { TestRunMqttService } from './services/test-run-mqtt.service'; import { NotificationService } from './services/notification.service'; +import { Profile } from './model/profile'; export const CONSENT_SHOWN_KEY = 'CONSENT_SHOWN'; +export const CALLOUT_STATE_KEY = 'CALLOUT_STATE'; export interface AppComponentState { consentShown: boolean; isStatusLoaded: boolean; systemStatus: TestrunStatus | null; + calloutState: Map; } @Injectable() export class AppStore extends ComponentStore { private consentShown$ = this.select(state => state.consentShown); + private calloutState$ = this.select(state => state.calloutState); private isStatusLoaded$ = this.select(state => state.isStatusLoaded); private hasInternetConnection$ = this.store.select(selectInternetConnection); private hasDevices$ = this.store.select(selectHasDevices); + private isAllDevicesOutdated$ = this.store.select(selectIsAllDevicesOutdated); + private hasExpiredDevices$ = this.store.select(selectHasExpiredDevices); private hasRiskProfiles$ = this.store.select(selectHasRiskProfiles); private reports$ = this.store.select(selectReports); private hasConnectionSetting$ = this.store.select( @@ -75,18 +86,30 @@ export class AppStore extends ComponentStore { private settingMissedError$: Observable = this.store.select(selectError); systemStatus$: Observable = this.store.select(selectStatus); + testrunStatus$: Observable = + this.store.select(selectSystemStatus); + isTestingComplete$: Observable = this.store.select( + selectIsTestingComplete + ); + riskProfiles$: Observable = this.store.select(selectRiskProfiles); viewModel$ = this.select({ consentShown: this.consentShown$, hasDevices: this.hasDevices$, + isAllDevicesOutdated: this.isAllDevicesOutdated$, + hasExpiredDevices: this.hasExpiredDevices$, hasRiskProfiles: this.hasRiskProfiles$, reports: this.reports$, isStatusLoaded: this.isStatusLoaded$, systemStatus: this.systemStatus$, + testrunStatus: this.testrunStatus$, + isTestingComplete: this.isTestingComplete$, + riskProfiles: this.riskProfiles$, hasConnectionSettings: this.hasConnectionSetting$, isMenuOpen: this.isMenuOpen$, interfaces: this.interfaces$, settingMissedError: this.settingMissedError$, + calloutState: this.calloutState$, hasInternetConnection: this.hasInternetConnection$, }); @@ -95,6 +118,17 @@ export class AppStore extends ComponentStore { consentShown, })); + updateCalloutState = this.updater((state, callout: string) => { + const calloutState = state.calloutState; + calloutState.set(callout, true); + // @ts-expect-error property is defined in index.html + sessionStorage.setObject(CALLOUT_STATE_KEY, calloutState); + return { + ...state, + calloutState: new Map(calloutState), + }; + }); + updateIsStatusLoaded = this.updater((state, isStatusLoaded: boolean) => ({ ...state, isStatusLoaded, @@ -217,6 +251,14 @@ export class AppStore extends ComponentStore { ); }); + setCloseCallout = this.effect(trigger$ => { + return trigger$.pipe( + tap((id: string) => { + this.updateCalloutState(id); + }) + ); + }); + constructor( private store: Store, private testRunService: TestRunService, @@ -224,10 +266,16 @@ export class AppStore extends ComponentStore { private focusManagerService: FocusManagerService, private notificationService: NotificationService ) { + // @ts-expect-error get object is defined in index.html + const calloutState = sessionStorage.getObject(CALLOUT_STATE_KEY); + super({ consentShown: sessionStorage.getItem(CONSENT_SHOWN_KEY) !== null, isStatusLoaded: false, systemStatus: null, + calloutState: calloutState + ? new Map(Object.entries(calloutState)) + : new Map(), }); } } diff --git a/modules/ui/src/app/components/callout/callout.component.html b/modules/ui/src/app/components/callout/callout.component.html index a3fb7930c..36f0c981f 100644 --- a/modules/ui/src/app/components/callout/callout.component.html +++ b/modules/ui/src/app/components/callout/callout.component.html @@ -15,12 +15,23 @@ -->
{{ type }} + +

+
diff --git a/modules/ui/src/app/components/callout/callout.component.scss b/modules/ui/src/app/components/callout/callout.component.scss index 8a9d77125..bd1008638 100644 --- a/modules/ui/src/app/components/callout/callout.component.scss +++ b/modules/ui/src/app/components/callout/callout.component.scss @@ -21,25 +21,13 @@ width: 100%; } -:host:has(.callout-container.info), -:host:has(.callout-container.error), -:host:has(.callout-container.check_circle) { - position: absolute; -} - -:host + ::ng-deep app-callout { - top: 60px; -} - -@media (width < 742px) { - :host + ::ng-deep app-callout { - top: 80px; - } -} +:host .info-pilot ::ng-deep app-program-type-icon { + padding-right: 1px; -@media (width < 490px) { - :host + ::ng-deep app-callout { - top: 100px; + .icon { + width: 18px; + height: 18px; + line-height: 18px; } } @@ -61,10 +49,16 @@ .callout-container.info { margin: 24px 32px; - background-color: mat.get-color-from-palette($color-primary, 50); + background-color: mat.m2-get-color-from-palette($color-primary, 50); .callout-icon { - color: mat.get-color-from-palette($color-primary, 700); + color: mat.m2-get-color-from-palette($color-primary, 700); + } + + .info-pilot { + width: 24px; + display: flex; + justify-content: center; } } @@ -117,3 +111,9 @@ line-height: 20px; letter-spacing: 0.2px; } + +.callout-close-button { + margin-left: auto; + margin-right: -20px; + color: $warn; +} diff --git a/modules/ui/src/app/components/callout/callout.component.spec.ts b/modules/ui/src/app/components/callout/callout.component.spec.ts index 28215ec7c..fab52c949 100644 --- a/modules/ui/src/app/components/callout/callout.component.spec.ts +++ b/modules/ui/src/app/components/callout/callout.component.spec.ts @@ -16,6 +16,7 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; import { CalloutComponent } from './callout.component'; +import { MatIconTestingModule } from '@angular/material/icon/testing'; describe('CalloutComponent', () => { let component: CalloutComponent; @@ -24,7 +25,7 @@ describe('CalloutComponent', () => { beforeEach(() => { TestBed.configureTestingModule({ - imports: [CalloutComponent], + imports: [CalloutComponent, MatIconTestingModule], }).compileComponents(); fixture = TestBed.createComponent(CalloutComponent); component = fixture.componentInstance; @@ -42,4 +43,27 @@ describe('CalloutComponent', () => { expect(calloutContainerdEl?.classList).toContain('mockValue'); }); + + describe('closeable', () => { + beforeEach(() => { + fixture.componentRef.setInput('closable', true); + fixture.detectChanges(); + }); + + it('should have close button', () => { + const closeButton = compiled.querySelector('.callout-close-button'); + + expect(closeButton).toBeTruthy(); + }); + + it('should emit event', () => { + const calloutClosedSpy = spyOn(component.calloutClosed, 'emit'); + const closeButton = compiled.querySelector( + '.callout-close-button' + ) as HTMLButtonElement; + closeButton?.click(); + + expect(calloutClosedSpy).toHaveBeenCalled(); + }); + }); }); diff --git a/modules/ui/src/app/components/callout/callout.component.ts b/modules/ui/src/app/components/callout/callout.component.ts index bfb6ea9bf..8fa46d9f0 100644 --- a/modules/ui/src/app/components/callout/callout.component.ts +++ b/modules/ui/src/app/components/callout/callout.component.ts @@ -13,18 +13,38 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { ChangeDetectionStrategy, Component, Input } from '@angular/core'; +import { + ChangeDetectionStrategy, + Component, + EventEmitter, + Input, + Output, +} from '@angular/core'; import { CommonModule } from '@angular/common'; import { MatIconModule } from '@angular/material/icon'; +import { MatButtonModule } from '@angular/material/button'; +import { CalloutType } from '../../model/callout-type'; +import { ProgramType } from '../../model/program-type'; +import { ProgramTypeIconComponent } from '../program-type-icon/program-type-icon.component'; @Component({ + changeDetection: ChangeDetectionStrategy.OnPush, + imports: [ + CommonModule, + MatIconModule, + MatButtonModule, + ProgramTypeIconComponent, + ], selector: 'app-callout', standalone: true, - imports: [CommonModule, MatIconModule], - templateUrl: './callout.component.html', styleUrls: ['./callout.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush, + templateUrl: './callout.component.html', }) export class CalloutComponent { + readonly CalloutType = CalloutType; + readonly ProgramType = ProgramType; + @Input() id: string | null = null; @Input() type = ''; + @Input() closable = false; + @Output() calloutClosed = new EventEmitter(); } diff --git a/modules/ui/src/app/components/component-with-announcement.ts b/modules/ui/src/app/components/component-with-announcement.ts new file mode 100644 index 000000000..0f555e15a --- /dev/null +++ b/modules/ui/src/app/components/component-with-announcement.ts @@ -0,0 +1,35 @@ +import { LiveAnnouncer } from '@angular/cdk/a11y'; +import { FocusManagerService } from '../services/focus-manager.service'; +import { timer } from 'rxjs'; + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +type Constructor = new (...args: any[]) => T; + +export function ComponentWithAnnouncement(base: T) { + return class extends base { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + constructor(...args: any[]) { + const [dialogRef, title, liveAnnouncer, focusService] = args; + super(...args); + + this.focusService = focusService; + + dialogRef.afterOpened().subscribe(() => { + (liveAnnouncer as LiveAnnouncer).clear(); + (liveAnnouncer as LiveAnnouncer) + .announce(title, 'assertive') + .then(() => { + timer(200).subscribe(() => { + this.focusService.focusFirstElementInContainer(); + }); + }); + }); + + dialogRef.beforeClosed().subscribe(() => { + (liveAnnouncer as LiveAnnouncer).clear(); + }); + } + + focusService!: FocusManagerService; + }; +} 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 8f85b6edf..f1586f9a0 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 @@ -14,26 +14,46 @@ limitations under the License. --> + +
+ +
+ + +

+ {{ device.test_pack }} + + + {{ device.manufacturer }} +

+

{{ device.model }} -

-
+

+

{{ device.mac_addr }} -

- +

+
- + + + + +
+ + +
diff --git a/modules/ui/src/app/components/download-zip-modal/download-zip-modal.component.scss b/modules/ui/src/app/components/download-zip-modal/download-zip-modal.component.scss index 0a92617c1..dd937047a 100644 --- a/modules/ui/src/app/components/download-zip-modal/download-zip-modal.component.scss +++ b/modules/ui/src/app/components/download-zip-modal/download-zip-modal.component.scss @@ -13,7 +13,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +@use '@angular/material' as mat; @import '../../../theming/colors'; +@import '../../../theming/variables'; :host { display: grid; @@ -31,6 +33,7 @@ } .risk-profile-select-form-content { + margin: 14px 0 6px; font-family: Roboto, sans-serif; font-size: 14px; line-height: 20px; @@ -49,8 +52,14 @@ } .risk-profile-select-form-actions { + justify-content: flex-end; min-height: 30px; padding: 16px 0 0; + gap: 8px; + + &:has(app-download-report) { + justify-content: space-between; + } } .profile-select { @@ -84,3 +93,81 @@ align-self: center; margin-right: 16px; } +.testing-result-heading { + margin: 16px 0; +} +.testing-result-title { + margin: 0; + font-size: 32px; + line-height: 40px; + text-align: center; + color: $grey-900; +} + +.testing-result-subtitle { + margin: 0; + font-family: $font-secondary; + font-size: 14px; + line-height: 20px; + letter-spacing: 0.2px; + text-align: center; + color: $grey-800; +} + +.testing-result { + display: flex; + height: auto; + min-height: 176px; + align-items: center; + gap: 8px; + margin: 6px 0 10px; + border-radius: 8px; +} + +.testing-result-status { + display: flex; + justify-content: center; + align-items: center; + flex: 1 0 0; + min-width: 208px; + width: fit-content; + height: 100%; + min-height: 176px; + box-sizing: border-box; + margin: 0; + padding: 16px; + border-radius: 8px; + color: $white; + font-size: 24px; + line-height: 32px; + background: red; +} + +.testing-result-description { + display: flex; + justify-content: center; + box-sizing: border-box; + margin: 0; + padding: 8px 24px; + color: $grey-800; + font-family: $font-secondary; + font-size: 14px; + line-height: 20px; + letter-spacing: 0.2px; +} + +.failed-result { + background: $red-50; + + .testing-result-status { + background: $red-800; + } +} + +.success-result { + background: $green-50; + + .testing-result-status { + background: mat.m2-get-color-from-palette($color-accent, 700); + } +} diff --git a/modules/ui/src/app/components/download-zip-modal/download-zip-modal.component.ts b/modules/ui/src/app/components/download-zip-modal/download-zip-modal.component.ts index b042bdabf..a703edb6d 100644 --- a/modules/ui/src/app/components/download-zip-modal/download-zip-modal.component.ts +++ b/modules/ui/src/app/components/download-zip-modal/download-zip-modal.component.ts @@ -19,10 +19,13 @@ import { MatOptionModule } from '@angular/material/core'; import { TestRunService } from '../../services/test-run.service'; import { Routes } from '../../model/routes'; import { RouterLink } from '@angular/router'; -import { MatTooltip, MatTooltipModule } from '@angular/material/tooltip'; +import { TestrunStatus, StatusOfTestrun } from '../../model/testrun-status'; +import { DownloadReportComponent } from '../download-report/download-report.component'; interface DialogData { profiles: Profile[]; + testrunStatus?: TestrunStatus; + isTestingComplete?: boolean; } @Component({ @@ -39,8 +42,7 @@ interface DialogData { MatSelectModule, MatOptionModule, RouterLink, - MatTooltip, - MatTooltipModule, + DownloadReportComponent, ], templateUrl: './download-zip-modal.component.html', styleUrl: './download-zip-modal.component.scss', @@ -52,6 +54,7 @@ export class DownloadZipModalComponent extends EscapableDialogComponent { questions: [], } as Profile; public readonly Routes = Routes; + public readonly StatusOfTestrun = StatusOfTestrun; profiles: Profile[] = []; selectedProfile: Profile; constructor( @@ -75,6 +78,7 @@ export class DownloadZipModalComponent extends EscapableDialogComponent { cancel(profile?: Profile | null) { if (profile === null) { this.dialogRef.close(null); + return; } let value = profile?.name; if (profile && profile?.name === this.NO_PROFILE.name) { diff --git a/modules/ui/src/app/components/dynamic-form/dynamic-form.component.html b/modules/ui/src/app/components/dynamic-form/dynamic-form.component.html new file mode 100644 index 000000000..0ea7d3df4 --- /dev/null +++ b/modules/ui/src/app/components/dynamic-form/dynamic-form.component.html @@ -0,0 +1,268 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {{ description }} + + Please, check. “ and \ are not allowed. + + + The field is required + + + The field must be a maximum of + {{ getControl(formControlName).getError('maxlength').requiredLength }} + characters. + + + + + + + + {{ description }} + + Please, check. “ and \ are not allowed. + + + The field is required + + + The field must be a maximum of + {{ getControl(formControlName).getError('maxlength').requiredLength }} + characters. + + + + + + + + {{ description }} + + The field is required + + + Please, check the email address. Valid e-mail can contain only latin + letters, numbers, @ and . (dot). + + + The field must be a maximum of + {{ getControl(formControlName).getError('maxlength').requiredLength }} + characters. + + + + + +
+

+ + {{ getOptionValue(option) }} + +

+ {{ + description + }} +
+
+ + + + + + {{ getOptionValue(option) }} + + + {{ + description + }} + + + The field is required + + + + diff --git a/modules/ui/src/app/components/dynamic-form/dynamic-form.component.scss b/modules/ui/src/app/components/dynamic-form/dynamic-form.component.scss new file mode 100644 index 000000000..14f7086f0 --- /dev/null +++ b/modules/ui/src/app/components/dynamic-form/dynamic-form.component.scss @@ -0,0 +1,67 @@ +/** + * Copyright 2023 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +@use '@angular/material' as mat; +@import 'src/theming/colors'; +@import 'src/theming/variables'; + +::ng-deep .field-label { + margin: 0; + color: $grey-800; + font-size: 18px; + line-height: 24px; + padding-top: 24px; + padding-bottom: 16px; + display: inline-block; + &:has(+ .field-select-multiple.ng-invalid.ng-dirty) { + color: mat.m2-get-color-from-palette($color-warn, 700); + } +} +mat-form-field { + width: 100%; +} +.field-hint { + font-family: $font-secondary; + font-size: 12px; + font-weight: 400; + line-height: 16px; + text-align: left; + padding-top: 8px; +} + +.form-field { + width: 100%; +} + +.form-field ::ng-deep .mat-mdc-form-field-textarea-control { + display: inherit; +} + +.field-select-multiple { + .field-select-checkbox { + &:has(::ng-deep .mat-mdc-checkbox-checked) { + background: mat.m2-get-color-from-palette($color-primary, 50); + } + ::ng-deep .mdc-checkbox__ripple { + display: none; + } + &:first-of-type { + margin-top: 0; + } + &:last-of-type { + margin-bottom: 8px; + } + } +} diff --git a/modules/ui/src/app/components/dynamic-form/dynamic-form.component.spec.ts b/modules/ui/src/app/components/dynamic-form/dynamic-form.component.spec.ts new file mode 100644 index 000000000..ef63d45d7 --- /dev/null +++ b/modules/ui/src/app/components/dynamic-form/dynamic-form.component.spec.ts @@ -0,0 +1,235 @@ +/** + * Copyright 2023 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { DynamicFormComponent } from './dynamic-form.component'; +import { Component, ViewChild, ViewEncapsulation } from '@angular/core'; +import { + FormBuilder, + FormGroup, + FormsModule, + ReactiveFormsModule, +} from '@angular/forms'; +import { PROFILE_FORM } from '../../mocks/profile.mock'; +import { FormControlType } from '../../model/question'; +import { NoopAnimationsModule } from '@angular/platform-browser/animations'; + +@Component({ + template: + '
', +}) +class DummyComponent { + @ViewChild('dynamicForm') public dynamicForm!: DynamicFormComponent; + public testForm!: FormGroup; + public format = PROFILE_FORM; + constructor(private readonly fb: FormBuilder) { + this.testForm = this.fb.group({ + test: [''], + }); + } +} + +describe('DynamicFormComponent', () => { + let dummy: DummyComponent; + let fixture: ComponentFixture; + let compiled: HTMLElement; + let component: DynamicFormComponent; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [DummyComponent], + imports: [ + DynamicFormComponent, + ReactiveFormsModule, + FormsModule, + NoopAnimationsModule, + ], + }) + .overrideComponent(DummyComponent, { + set: { encapsulation: ViewEncapsulation.None }, + }) + .compileComponents(); + + fixture = TestBed.createComponent(DummyComponent); + + dummy = fixture.componentInstance; + compiled = fixture.nativeElement as HTMLElement; + fixture.detectChanges(); + component = dummy.dynamicForm; + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); + + PROFILE_FORM.forEach((item, index) => { + it(`should have form field with specific type"`, () => { + const fields = compiled.querySelectorAll('.form-field'); + + if (item.type === FormControlType.SELECT) { + const select = fields[index].querySelector('mat-select'); + expect(select).toBeTruthy(); + } else if (item.type === FormControlType.SELECT_MULTIPLE) { + const select = fields[index].querySelector('mat-checkbox'); + expect(select).toBeTruthy(); + } else if (item.type === FormControlType.TEXTAREA) { + const input = fields[index]?.querySelector('textarea'); + expect(input).toBeTruthy(); + } else { + const input = fields[index]?.querySelector('input'); + expect(input).toBeTruthy(); + } + }); + + it('should have label', () => { + const labels = compiled.querySelectorAll('.field-label'); + + const label = item.question; + expect(labels[index].textContent?.trim()).toEqual(label); + }); + + it('should have hint', () => { + const fields = compiled.querySelectorAll('.form-field'); + const hint = fields[index].querySelector('mat-hint'); + + if (item.description) { + expect(hint?.textContent?.trim()).toEqual(item.description); + } else { + expect(hint).toBeNull(); + } + }); + + if (item.type === FormControlType.SELECT) { + describe('select', () => { + it(`should have default value if provided`, () => { + const fields = compiled.querySelectorAll('.form-field'); + const select = fields[index].querySelector('mat-select'); + expect(select?.textContent?.trim()).toEqual(item.default || ''); + }); + + it('should have "required" error when field is not filled', () => { + const fields = compiled.querySelectorAll('.form-field'); + + component.getControl(index).setValue(''); + component.getControl(index).markAsTouched(); + + fixture.detectChanges(); + + const error = fields[index].querySelector('mat-error')?.innerHTML; + + expect(error).toContain('The field is required'); + }); + }); + } + + if (item.type === FormControlType.SELECT_MULTIPLE) { + describe('select multiple', () => { + it(`should mark form group as dirty while tab navigation`, () => { + const fields = compiled.querySelectorAll('.form-field'); + const checkbox = fields[index].querySelector( + '.field-select-checkbox:last-of-type mat-checkbox' + ); + checkbox?.dispatchEvent(new KeyboardEvent('keydown', { key: 'Tab' })); + fixture.detectChanges(); + + expect(component.getControl(index).dirty).toBeTrue(); + }); + }); + } + + if ( + item.type === FormControlType.TEXT || + item.type === FormControlType.TEXTAREA || + item.type === FormControlType.EMAIL_MULTIPLE + ) { + describe('text or text-long or email-multiple', () => { + if (item.validation?.required) { + it('should have "required" error when field is not filled', () => { + const fields = compiled.querySelectorAll('.form-field'); + const input = fields[index].querySelector( + '.mat-mdc-input-element' + ) as HTMLInputElement; + ['', ' '].forEach(value => { + input.value = value; + input.dispatchEvent(new Event('input')); + component.getControl(index).markAsTouched(); + fixture.detectChanges(); + const errors = fields[index].querySelectorAll('mat-error'); + let hasError = false; + errors.forEach(error => { + if (error.textContent === 'The field is required') { + hasError = true; + } + }); + + expect(hasError).toBeTrue(); + }); + }); + } + + it('should have "invalid_format" error when field does not satisfy validation rules', () => { + const fields = compiled.querySelectorAll('.form-field'); + const input: HTMLInputElement = fields[index].querySelector( + '.mat-mdc-input-element' + ) as HTMLInputElement; + input.value = 'as\\\\\\\\\\""""""""'; + input.dispatchEvent(new Event('input')); + component.getControl(index).markAsTouched(); + fixture.detectChanges(); + const result = + item.type === FormControlType.EMAIL_MULTIPLE + ? 'Please, check the email address. Valid e-mail can contain only latin letters, numbers, @ and . (dot).' + : 'Please, check. “ and \\ are not allowed.'; + const errors = fields[index].querySelectorAll('mat-error'); + let hasError = false; + errors.forEach(error => { + if (error.textContent === result) { + hasError = true; + } + }); + + expect(hasError).toBeTrue(); + }); + + if (item.validation?.max) { + it('should have "maxlength" error when field is exceeding max length', () => { + const fields = compiled.querySelectorAll('.form-field'); + const input: HTMLInputElement = fields[index].querySelector( + '.mat-mdc-input-element' + ) as HTMLInputElement; + input.value = + 'very long value very long value very long value very long value very long value very long value very long value very long value very long value very long value'; + input.dispatchEvent(new Event('input')); + component.getControl(index).markAsTouched(); + fixture.detectChanges(); + + const errors = fields[index].querySelectorAll('mat-error'); + let hasError = false; + errors.forEach(error => { + if ( + error.textContent === + `The field must be a maximum of ${item.validation?.max} characters.` + ) { + hasError = true; + } + }); + expect(hasError).toBeTrue(); + }); + } + }); + } + }); +}); diff --git a/modules/ui/src/app/components/dynamic-form/dynamic-form.component.ts b/modules/ui/src/app/components/dynamic-form/dynamic-form.component.ts new file mode 100644 index 000000000..3d6b13016 --- /dev/null +++ b/modules/ui/src/app/components/dynamic-form/dynamic-form.component.ts @@ -0,0 +1,171 @@ +/** + * Copyright 2023 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { Component, inject, Input, OnInit } from '@angular/core'; +import { + FormControlType, + OptionType, + QuestionFormat, + Validation, +} from '../../model/question'; +import { + AbstractControl, + ControlContainer, + FormBuilder, + FormControl, + FormGroup, + ReactiveFormsModule, + ValidatorFn, + Validators, +} from '@angular/forms'; +import { + MatError, + MatFormField, + MatOption, + MatSelectModule, +} from '@angular/material/select'; +import { MatButtonModule } from '@angular/material/button'; +import { CommonModule } from '@angular/common'; +import { MatInputModule } from '@angular/material/input'; +import { MatFormFieldModule } from '@angular/material/form-field'; +import { MatCheckboxModule } from '@angular/material/checkbox'; +import { TextFieldModule } from '@angular/cdk/text-field'; +import { DeviceValidators } from '../../pages/devices/components/device-form/device.validators'; +import { ProfileValidators } from '../../pages/risk-assessment/profile-form/profile.validators'; +@Component({ + selector: 'app-dynamic-form', + standalone: true, + imports: [ + MatFormField, + MatOption, + MatButtonModule, + CommonModule, + ReactiveFormsModule, + MatInputModule, + MatError, + MatFormFieldModule, + MatSelectModule, + MatCheckboxModule, + TextFieldModule, + ], + viewProviders: [ + { + provide: ControlContainer, + useFactory: () => inject(ControlContainer, { skipSelf: true }), + }, + ], + templateUrl: './dynamic-form.component.html', + styleUrl: './dynamic-form.component.scss', +}) +export class DynamicFormComponent implements OnInit { + public readonly FormControlType = FormControlType; + + @Input() format: QuestionFormat[] = []; + @Input() optionKey: string | undefined; + + parentContainer = inject(ControlContainer); + get formGroup() { + return this.parentContainer.control as FormGroup; + } + + constructor( + private fb: FormBuilder, + private deviceValidators: DeviceValidators, + private profileValidators: ProfileValidators + ) {} + getControl(name: string | number) { + return this.formGroup.get(name.toString()) as AbstractControl; + } + + getFormGroup(name: string | number): FormGroup { + return this.formGroup?.controls[name] as FormGroup; + } + + public markSectionAsDirty( + optionIndex: number, + optionLength: number, + formControlName: string + ) { + if (optionIndex === optionLength - 1) { + this.getControl(formControlName).markAsDirty({ + onlySelf: true, + }); + } + } + + ngOnInit() { + this.createProfileForm(this.format); + } + + createProfileForm(questions: QuestionFormat[]) { + questions.forEach((question, index) => { + if (question.type === FormControlType.SELECT_MULTIPLE) { + this.formGroup.addControl( + index.toString(), + this.getMultiSelectGroup(question) + ); + } else { + const validators = this.getValidators( + question.type, + question.validation + ); + this.formGroup.addControl( + index.toString(), + new FormControl(question.default || '', validators) + ); + } + }); + } + + getValidators(type: FormControlType, validation?: Validation): ValidatorFn[] { + const validators: ValidatorFn[] = []; + if (validation) { + if (validation.required) { + validators.push(this.profileValidators.textRequired()); + } + if (validation.max) { + validators.push(Validators.maxLength(Number(validation.max))); + } + if (type === FormControlType.EMAIL_MULTIPLE) { + validators.push(this.profileValidators.emailStringFormat()); + } + if (type === FormControlType.TEXT || type === FormControlType.TEXTAREA) { + validators.push(this.profileValidators.textFormat()); + } + } + return validators; + } + + getMultiSelectGroup(question: QuestionFormat): FormGroup { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const group: any = {}; + question.options?.forEach((option, index) => { + group[index] = false; + }); + return this.fb.group(group, { + validators: question.validation?.required + ? [this.profileValidators.multiSelectRequired] + : [], + }); + } + + getOptionValue(option: OptionType) { + if (this.optionKey && typeof option === 'object') { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + return (option as any)[this.optionKey]; + } + return option; + } +} diff --git a/modules/ui/src/app/components/program-type-icon/program-type-con.component.spec.ts b/modules/ui/src/app/components/program-type-icon/program-type-con.component.spec.ts new file mode 100644 index 000000000..7a984eff0 --- /dev/null +++ b/modules/ui/src/app/components/program-type-icon/program-type-con.component.spec.ts @@ -0,0 +1,46 @@ +/** + * Copyright 2023 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { ProgramTypeIconComponent } from './program-type-icon.component'; +import { MatIconTestingModule } from '@angular/material/icon/testing'; + +describe('ProgramTypeIconComponent', () => { + let component: ProgramTypeIconComponent; + let fixture: ComponentFixture; + let compiled: HTMLElement; + + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [ProgramTypeIconComponent, MatIconTestingModule], + }).compileComponents(); + fixture = TestBed.createComponent(ProgramTypeIconComponent); + component = fixture.componentInstance; + component.type = 'pilot'; + compiled = fixture.nativeElement as HTMLElement; + fixture.detectChanges(); + }); + + it('should create component', () => { + expect(component).toBeTruthy(); + }); + + it('should have svgIcon provided from type', () => { + const iconEl = compiled.querySelector('.icon'); + + expect(iconEl?.getAttribute('ng-reflect-svg-icon')).toEqual('pilot'); + }); +}); diff --git a/modules/ui/src/app/components/program-type-icon/program-type-icon.component.ts b/modules/ui/src/app/components/program-type-icon/program-type-icon.component.ts new file mode 100644 index 000000000..ccf5b3335 --- /dev/null +++ b/modules/ui/src/app/components/program-type-icon/program-type-icon.component.ts @@ -0,0 +1,39 @@ +/** + * Copyright 2023 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { Component, Input } from '@angular/core'; +import { MatIcon } from '@angular/material/icon'; + +@Component({ + selector: 'app-program-type-icon', + standalone: true, + imports: [MatIcon], + template: ` `, + styles: ` + :host { + display: inline-flex; + align-items: center; + padding-right: 4px; + } + .icon { + width: 16px; + height: 16px; + line-height: 16px; + } + `, +}) +export class ProgramTypeIconComponent { + @Input() type = ''; +} diff --git a/modules/ui/src/app/components/report-action/report-action.component.ts b/modules/ui/src/app/components/report-action/report-action.component.ts index 62dc39d58..debf132fa 100644 --- a/modules/ui/src/app/components/report-action/report-action.component.ts +++ b/modules/ui/src/app/components/report-action/report-action.component.ts @@ -15,6 +15,9 @@ export class ReportActionComponent { constructor(private datePipe: DatePipe) {} getTestRunId(data: TestrunStatus) { + if (!data.device) { + return ''; + } return `${data.device.manufacturer} ${data.device.model} ${ data.device.firmware } ${this.getFormattedDateString(data.started)}`; diff --git a/modules/ui/src/app/components/simple-dialog/simple-dialog.component.html b/modules/ui/src/app/components/simple-dialog/simple-dialog.component.html index 6d5f768d6..975773924 100644 --- a/modules/ui/src/app/components/simple-dialog/simple-dialog.component.html +++ b/modules/ui/src/app/components/simple-dialog/simple-dialog.component.html @@ -20,6 +20,8 @@ +
+
+
+ + + diff --git a/modules/ui/src/app/components/stepper/stepper.component.scss b/modules/ui/src/app/components/stepper/stepper.component.scss new file mode 100644 index 000000000..d5be19c55 --- /dev/null +++ b/modules/ui/src/app/components/stepper/stepper.component.scss @@ -0,0 +1,91 @@ +/** + * Copyright 2023 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +@import '../../../theming/colors'; +@import '../../../theming/variables'; + +.form-container { + height: 100%; + display: flex; + flex-direction: column; +} + +.form-header { + padding: 24px; +} + +.form-content { + display: grid; + padding: 24px 8px; + gap: 10px; + overflow: hidden; + height: 100%; +} + +.form-footer { + margin-top: auto; + display: inline-block; + text-align: center; + height: 24px; + padding: 0 24px 24px 24px; +} + +.form-steps { + display: inline-flex; + text-align: center; + align-items: center; + height: 100%; +} + +.form-step { + border: 2px solid $lighter-grey; + width: 4px; + height: 4px; + display: inline-block; + border-radius: 100%; + margin: 0 8px; + &.step-active { + border-color: $secondary; + background: $secondary; + } +} + +.form-button-back { + float: left; +} + +.form-button-forward { + float: right; +} + +.form-button-back, +.form-button-forward { + height: $icon-size; + width: $icon-size; + min-width: $icon-size; + margin: 0; + padding: 0; + & mat-icon { + color: $secondary; + width: $icon-size; + height: $icon-size; + font-size: $icon-size; + margin: 0; + } + + &.hidden { + visibility: hidden; + } +} diff --git a/modules/ui/src/app/components/stepper/stepper.component.spec.ts b/modules/ui/src/app/components/stepper/stepper.component.spec.ts new file mode 100644 index 000000000..25b5f9740 --- /dev/null +++ b/modules/ui/src/app/components/stepper/stepper.component.spec.ts @@ -0,0 +1,101 @@ +/** + * Copyright 2023 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { StepperComponent } from './stepper.component'; +import { Component, ViewChild, ViewEncapsulation } from '@angular/core'; +import { FormBuilder, ReactiveFormsModule, Validators } from '@angular/forms'; +import { CdkStep } from '@angular/cdk/stepper'; +import { MatFormField, MatFormFieldModule } from '@angular/material/form-field'; +import { CommonModule } from '@angular/common'; +import { MatInputModule } from '@angular/material/input'; +import { NoopAnimationsModule } from '@angular/platform-browser/animations'; + +@Component({ + selector: 'app-stepper-bypass', + standalone: true, + imports: [ + CdkStep, + StepperComponent, + MatFormField, + CommonModule, + ReactiveFormsModule, + MatInputModule, + MatFormFieldModule, + ], + templateUrl: './stepper-test.component.html', +}) +class TestStepperComponent { + @ViewChild('stepper') public stepper!: StepperComponent; + testForm; + firstStep; + secondStep; + constructor(private fb: FormBuilder) { + this.firstStep = this.fb.group({ + firstControl: ['', [Validators.required]], + }); + this.secondStep = this.fb.group({ + secondControl: ['', [Validators.required]], + }); + this.testForm = this.fb.group({ + steps: this.fb.array([this.firstStep, this.secondStep]), + }); + } +} + +describe('StepperComponent', () => { + let component: TestStepperComponent; + let fixture: ComponentFixture; + let compiled: HTMLElement; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [StepperComponent, TestStepperComponent, NoopAnimationsModule], + }) + .overrideComponent(TestStepperComponent, { + set: { encapsulation: ViewEncapsulation.None }, + }) + .compileComponents(); + + fixture = TestBed.createComponent(TestStepperComponent); + component = fixture.componentInstance; + compiled = fixture.nativeElement as HTMLElement; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); + + it('should have title', () => { + expect(fixture.nativeElement.querySelector('.form-header')).toBeTruthy(); + }); + + it('should not mark selected step touched if not interacted', () => { + component.stepper.nextClick(); + + expect(component.firstStep.touched).toBeFalse(); + }); + + it('should mark selected step touched if interacted', () => { + const button = compiled.querySelector( + '.form-button-forward' + ) as HTMLButtonElement; + button.click(); + + expect(component.firstStep.touched).toBeTrue(); + }); +}); diff --git a/modules/ui/src/app/components/stepper/stepper.component.ts b/modules/ui/src/app/components/stepper/stepper.component.ts new file mode 100644 index 000000000..ee7f1c5ab --- /dev/null +++ b/modules/ui/src/app/components/stepper/stepper.component.ts @@ -0,0 +1,72 @@ +/** + * Copyright 2023 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { Component, Input, TemplateRef } from '@angular/core'; +import { CdkStepper, CdkStepperModule } from '@angular/cdk/stepper'; +import { NgForOf, NgIf, NgTemplateOutlet } from '@angular/common'; +import { MatIcon } from '@angular/material/icon'; +import { MatButton, MatIconButton } from '@angular/material/button'; +import { FormGroup } from '@angular/forms'; +import { MatTooltipModule } from '@angular/material/tooltip'; + +@Component({ + selector: 'app-stepper', + standalone: true, + imports: [ + NgForOf, + NgTemplateOutlet, + CdkStepperModule, + NgIf, + MatIcon, + MatIconButton, + MatButton, + MatTooltipModule, + ], + templateUrl: './stepper.component.html', + styleUrl: './stepper.component.scss', + providers: [{ provide: CdkStepper, useExisting: StepperComponent }], +}) +export class StepperComponent extends CdkStepper { + @Input() header: TemplateRef | undefined; + @Input() title = ''; + @Input() activeClass = 'active'; + + forwardButtonHidden() { + return this.selectedIndex === this.steps.length - 1; + } + + backButtonHidden() { + return this.selectedIndex === 0; + } + + nextClick() { + if ( + this.selected?.interacted && + !(this.selected?.stepControl as FormGroup)?.valid + ) { + this.selected?.stepControl?.markAllAsTouched(); + } + } + + getStepLabel(isActive: boolean) { + return isActive + ? `Step #${this.selectedIndex + 1} out of ${this.steps.length}` + : ''; + } + + getNavigationLabel(index: number) { + return `Go to step #${index} out of ${this.title}`; + } +} diff --git a/modules/ui/src/app/components/testing-complete/testing-complete.component.spec.ts b/modules/ui/src/app/components/testing-complete/testing-complete.component.spec.ts new file mode 100644 index 000000000..30c3108e6 --- /dev/null +++ b/modules/ui/src/app/components/testing-complete/testing-complete.component.spec.ts @@ -0,0 +1,91 @@ +import { + ComponentFixture, + fakeAsync, + TestBed, + tick, +} from '@angular/core/testing'; + +import { TestingCompleteComponent } from './testing-complete.component'; +import { TestRunService } from '../../services/test-run.service'; +import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; +import { Router } from '@angular/router'; +import { RouterTestingModule } from '@angular/router/testing'; +import { Component } from '@angular/core'; +import { MOCK_PROGRESS_DATA_COMPLIANT } from '../../mocks/testrun.mock'; +import { of } from 'rxjs'; +import { MatDialogRef } from '@angular/material/dialog'; +import { DownloadZipModalComponent } from '../download-zip-modal/download-zip-modal.component'; +import { Routes } from '../../model/routes'; + +describe('TestingCompleteComponent', () => { + let component: TestingCompleteComponent; + let fixture: ComponentFixture; + let router: Router; + + const testrunServiceMock: jasmine.SpyObj = + jasmine.createSpyObj('testrunServiceMock', ['downloadZip']); + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [ + RouterTestingModule.withRoutes([ + { path: 'risk-assessment', component: FakeRiskAssessmentComponent }, + ]), + TestingCompleteComponent, + BrowserAnimationsModule, + ], + providers: [{ provide: TestRunService, useValue: testrunServiceMock }], + }).compileComponents(); + + fixture = TestBed.createComponent(TestingCompleteComponent); + component = fixture.componentInstance; + router = TestBed.get(Router); + component.data = MOCK_PROGRESS_DATA_COMPLIANT; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); + + describe('#onInit', () => { + beforeEach(() => { + testrunServiceMock.downloadZip.calls.reset(); + }); + + it('should call downloadZip on service if profile is a string', fakeAsync(() => { + const openSpy = spyOn(component.dialog, 'open').and.returnValue({ + afterClosed: () => of(''), + } as MatDialogRef); + + component.ngOnInit(); + tick(1000); + + expect(openSpy).toHaveBeenCalledWith(DownloadZipModalComponent, { + ariaLabel: 'Testing complete', + data: { + profiles: [], + testrunStatus: MOCK_PROGRESS_DATA_COMPLIANT, + isTestingComplete: true, + }, + autoFocus: 'first-tabbable', + ariaDescribedBy: 'testing-result-main-info', + hasBackdrop: true, + disableClose: true, + panelClass: 'initiate-test-run-dialog', + }); + + tick(); + + expect(testrunServiceMock.downloadZip).toHaveBeenCalled(); + expect(router.url).not.toBe(Routes.RiskAssessment); + openSpy.calls.reset(); + })); + }); +}); + +@Component({ + selector: 'app-fake-risk-assessment-component', + template: '', +}) +class FakeRiskAssessmentComponent {} diff --git a/modules/ui/src/app/components/testing-complete/testing-complete.component.ts b/modules/ui/src/app/components/testing-complete/testing-complete.component.ts new file mode 100644 index 000000000..28da57bee --- /dev/null +++ b/modules/ui/src/app/components/testing-complete/testing-complete.component.ts @@ -0,0 +1,99 @@ +import { + ChangeDetectionStrategy, + Component, + Input, + OnDestroy, + OnInit, +} from '@angular/core'; +import { Subject, takeUntil, timer } from 'rxjs'; +import { MatDialog } from '@angular/material/dialog'; +import { TestRunService } from '../../services/test-run.service'; +import { Router } from '@angular/router'; +import { DownloadZipModalComponent } from '../download-zip-modal/download-zip-modal.component'; +import { Routes } from '../../model/routes'; +import { Profile } from '../../model/profile'; +import { TestrunStatus } from '../../model/testrun-status'; +import { FocusManagerService } from '../../services/focus-manager.service'; + +@Component({ + selector: 'app-testing-complete', + standalone: true, + imports: [], + template: '', + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class TestingCompleteComponent implements OnDestroy, OnInit { + @Input() profiles: Profile[] = []; + @Input() data!: TestrunStatus | null; + private destroy$: Subject = new Subject(); + + constructor( + public dialog: MatDialog, + private testrunService: TestRunService, + private route: Router, + private focusManagerService: FocusManagerService + ) {} + + ngOnInit() { + timer(1000).subscribe(() => { + this.openTestingCompleteModal(); + }); + } + ngOnDestroy() { + this.destroy$.next(true); + this.destroy$.unsubscribe(); + } + + private openTestingCompleteModal(): void { + const dialogRef = this.dialog.open(DownloadZipModalComponent, { + ariaLabel: 'Testing complete', + data: { + profiles: this.profiles, + testrunStatus: this.data, + isTestingComplete: true, + }, + autoFocus: 'first-tabbable', + ariaDescribedBy: 'testing-result-main-info', + hasBackdrop: true, + disableClose: true, + panelClass: 'initiate-test-run-dialog', + }); + + dialogRef + ?.afterClosed() + .pipe(takeUntil(this.destroy$)) + .subscribe(profile => { + if (profile === undefined) { + // close modal + this.focusFirstElement(); + return; + } + if (profile === null) { + this.navigateToRiskAssessment(); + } else if (this.data?.report != null) { + this.testrunService.downloadZip( + this.getZipLink(this.data?.report), + profile + ); + } + }); + } + + private navigateToRiskAssessment(): void { + this.route.navigate([Routes.RiskAssessment]).then(() => { + this.focusFirstElement(); + }); + } + + private focusFirstElement() { + timer(1000) + .pipe(takeUntil(this.destroy$)) + .subscribe(() => { + this.focusManagerService.focusFirstElementInContainer(); + }); + } + + private getZipLink(reportURL: string): string { + return reportURL.replace('report', 'export'); + } +} diff --git a/modules/ui/src/app/components/version/consent-dialog/consent-dialog.component.html b/modules/ui/src/app/components/version/consent-dialog/consent-dialog.component.html index 516e72b49..6867dd445 100644 --- a/modules/ui/src/app/components/version/consent-dialog/consent-dialog.component.html +++ b/modules/ui/src/app/components/version/consent-dialog/consent-dialog.component.html @@ -81,24 +81,12 @@

Welcome to Testrun!

-
- - Risk Assessment feature added! -

- Now you can answer a short questionnaire, create a security profile and - attach it to the Testrun result to complete a device verification. Also, - it will speed up the process a lot! -

-

- +

+ + Pilot Assessment +

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

diff --git a/modules/ui/src/app/components/version/consent-dialog/consent-dialog.component.scss b/modules/ui/src/app/components/version/consent-dialog/consent-dialog.component.scss index 9eb334496..c32f47a41 100644 --- a/modules/ui/src/app/components/version/consent-dialog/consent-dialog.component.scss +++ b/modules/ui/src/app/components/version/consent-dialog/consent-dialog.component.scss @@ -81,6 +81,17 @@ } } +.section-container-pilot { + .section-title { + font-weight: 500; + letter-spacing: 0.25px; + } + .section-content { + margin: 0; + padding-top: 9px; + } +} + .consent-actions { border-top: 1px solid $lighter-grey; margin: 0 -16px; diff --git a/modules/ui/src/app/components/version/consent-dialog/consent-dialog.component.spec.ts b/modules/ui/src/app/components/version/consent-dialog/consent-dialog.component.spec.ts index c538554f2..d6925c131 100644 --- a/modules/ui/src/app/components/version/consent-dialog/consent-dialog.component.spec.ts +++ b/modules/ui/src/app/components/version/consent-dialog/consent-dialog.component.spec.ts @@ -13,7 +13,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { + ComponentFixture, + fakeAsync, + TestBed, + tick, +} from '@angular/core/testing'; import { ConsentDialogComponent } from './consent-dialog.component'; import { @@ -24,15 +29,28 @@ import { import { MatButtonModule } from '@angular/material/button'; import { of } from 'rxjs'; import { NEW_VERSION, VERSION } from '../../../mocks/version.mock'; +import { MatIconTestingModule } from '@angular/material/icon/testing'; +import { FocusManagerService } from '../../../services/focus-manager.service'; +import SpyObj = jasmine.SpyObj; describe('ConsentDialogComponent', () => { let component: ConsentDialogComponent; let fixture: ComponentFixture; let compiled: HTMLElement; + let mockFocusManagerService: SpyObj; beforeEach(() => { + mockFocusManagerService = jasmine.createSpyObj('mockFocusManagerService', [ + 'focusFirstElementInContainer', + ]); + TestBed.configureTestingModule({ - imports: [ConsentDialogComponent, MatDialogModule, MatButtonModule], + imports: [ + ConsentDialogComponent, + MatDialogModule, + MatButtonModule, + MatIconTestingModule, + ], providers: [ { provide: MatDialogRef, @@ -42,11 +60,12 @@ describe('ConsentDialogComponent', () => { }, }, { provide: MAT_DIALOG_DATA, useValue: {} }, + { provide: FocusManagerService, useValue: mockFocusManagerService }, ], }); fixture = TestBed.createComponent(ConsentDialogComponent); component = fixture.componentInstance; - component.data = { version: NEW_VERSION, hasRiskProfiles: false }; + component.data = { version: NEW_VERSION }; component.optOut = false; fixture.detectChanges(); compiled = fixture.nativeElement as HTMLElement; @@ -61,7 +80,7 @@ describe('ConsentDialogComponent', () => { const confirmButton = compiled.querySelector( '.confirm-button' ) as HTMLButtonElement; - const dialogRes = { grant: true, isNavigateToRiskAssessment: undefined }; + const dialogRes = { grant: true }; confirmButton?.click(); @@ -77,7 +96,7 @@ describe('ConsentDialogComponent', () => { const confirmButton = compiled.querySelector( '.confirm-button' ) as HTMLButtonElement; - const dialogRes = { grant: false, isNavigateToRiskAssessment: undefined }; + const dialogRes = { grant: false }; confirmButton?.click(); @@ -97,9 +116,18 @@ describe('ConsentDialogComponent', () => { expect(closeSpy).toHaveBeenCalledTimes(0); }); + it('should set focus to first focusable elem when close dialog', fakeAsync(() => { + component.confirm(true); + tick(100); + + expect( + mockFocusManagerService.focusFirstElementInContainer + ).toHaveBeenCalled(); + })); + describe('with new version available', () => { beforeEach(() => { - component.data = { version: NEW_VERSION, hasRiskProfiles: false }; + component.data = { version: NEW_VERSION }; fixture.detectChanges(); }); @@ -122,7 +150,7 @@ describe('ConsentDialogComponent', () => { describe('with no new version available', () => { beforeEach(() => { - component.data = { version: VERSION, hasRiskProfiles: false }; + component.data = { version: VERSION }; fixture.detectChanges(); }); @@ -134,51 +162,4 @@ describe('ConsentDialogComponent', () => { expect(content).toBeNull(); }); }); - - describe('with no risk assessment profiles', () => { - beforeEach(() => { - component.data = { version: VERSION, hasRiskProfiles: false }; - fixture.detectChanges(); - }); - - it('should has risk-assessment content', () => { - const content = compiled.querySelector( - '.section-content.risk-assessment' - ) as HTMLElement; - - const innerContent = content.innerHTML.trim(); - expect(innerContent).toContain( - 'Now you can answer a short questionnaire' - ); - }); - - it('should close dialog with isNavigateToRiskAssessment as true when click "confirm"', () => { - const closeSpy = spyOn(component.dialogRef, 'close'); - const riskAssessmentBtn = compiled.querySelector( - '.risk-assessment-button' - ) as HTMLButtonElement; - const dialogRes = { grant: true, isNavigateToRiskAssessment: true }; - - riskAssessmentBtn?.click(); - - expect(closeSpy).toHaveBeenCalledWith(dialogRes); - - closeSpy.calls.reset(); - }); - }); - - describe('with risk assessment profiles', () => { - beforeEach(() => { - component.data = { version: VERSION, hasRiskProfiles: true }; - fixture.detectChanges(); - }); - - it('should not has risk-assessment content', () => { - const content = compiled.querySelector( - '.section-content.risk-assessment' - ) as HTMLElement; - - expect(content).toBeNull(); - }); - }); }); diff --git a/modules/ui/src/app/components/version/consent-dialog/consent-dialog.component.ts b/modules/ui/src/app/components/version/consent-dialog/consent-dialog.component.ts index 8b27a8961..3becacf89 100644 --- a/modules/ui/src/app/components/version/consent-dialog/consent-dialog.component.ts +++ b/modules/ui/src/app/components/version/consent-dialog/consent-dialog.component.ts @@ -26,10 +26,11 @@ import { CalloutType } from '../../../model/callout-type'; import { NgIf } from '@angular/common'; import { MatCheckbox } from '@angular/material/checkbox'; import { FormsModule } from '@angular/forms'; +import { FocusManagerService } from '../../../services/focus-manager.service'; +import { timer } from 'rxjs'; type DialogData = { version: Version; - hasRiskProfiles: boolean; }; @Component({ @@ -50,16 +51,19 @@ export class ConsentDialogComponent { public readonly CalloutType = CalloutType; optOut = false; constructor( + private readonly focusManagerService: FocusManagerService, public dialogRef: MatDialogRef, @Inject(MAT_DIALOG_DATA) public data: DialogData ) {} - confirm(optOut: boolean, isNavigateToRiskAssessment?: boolean) { + confirm(optOut: boolean) { // dialog should be closed with opposite value to grant or deny access to GA const dialogResult: ConsentDialogResult = { grant: !optOut, - isNavigateToRiskAssessment, }; this.dialogRef.close(dialogResult); + timer(100).subscribe(() => { + this.focusManagerService.focusFirstElementInContainer(); + }); } } diff --git a/modules/ui/src/app/components/version/version.component.html b/modules/ui/src/app/components/version/version.component.html index d45ea69da..2068f6043 100644 --- a/modules/ui/src/app/components/version/version.component.html +++ b/modules/ui/src/app/components/version/version.component.html @@ -18,7 +18,7 @@ mat-button class="version-content" [class.version-content-update]="version.update_available" - [attr.aria-label]="getVersionButtonLabel(version.installed_version)" + [attr.aria-label]="getVersionButtonLabel(version)" (click)="openConsentDialog(version)"> {{ version?.installed_version }} { let component: VersionComponent; @@ -43,7 +48,7 @@ describe('VersionComponent', () => { mockService = jasmine.createSpyObj(['getVersion', 'fetchVersion']); mockService.getVersion.and.returnValue(versionBehaviorSubject$); TestBed.configureTestingModule({ - imports: [VersionComponent], + imports: [VersionComponent, MatIconTestingModule], providers: [{ provide: TestRunService, useValue: mockService }], }); fixture = TestBed.createComponent(VersionComponent); @@ -56,31 +61,32 @@ describe('VersionComponent', () => { }); it('should get correct aria label for version button', () => { - const labelUnavailableVersion = component.getVersionButtonLabel( - UNAVAILABLE_VERSION.installed_version - ); + const labelUnavailableVersion = + component.getVersionButtonLabel(UNAVAILABLE_VERSION); - const labelAvailableVersion = component.getVersionButtonLabel( - VERSION.installed_version - ); + const labelAvailableVersion = component.getVersionButtonLabel(NEW_VERSION); + + const labelVersion = component.getVersionButtonLabel(VERSION); expect(labelUnavailableVersion).toContain( 'Version temporarily unavailable.' ); expect(labelAvailableVersion).toContain('New version is available.'); + expect(labelVersion).toEqual('v1. Click to open the Welcome modal'); }); - it('should open consent window on start', () => { + it('should open consent window on start', fakeAsync(() => { const openSpy = spyOn(component.dialog, 'open').and.returnValue({ - afterClosed: () => of(true), - } as MatDialogRef); + afterClosed: () => of({ grant: null }), + } as MatDialogRef); versionBehaviorSubject$.next(VERSION); mockService.getVersion.and.returnValue(versionBehaviorSubject$); fixture.detectChanges(); component.ngOnInit(); + tick(2000); expect(openSpy).toHaveBeenCalled(); - }); + })); it('should open consent window when button clicked', () => { const openSpy = spyOn(component.dialog, 'open').and.returnValue({ diff --git a/modules/ui/src/app/components/version/version.component.ts b/modules/ui/src/app/components/version/version.component.ts index 186c3bcf6..ad8fcafa0 100644 --- a/modules/ui/src/app/components/version/version.component.ts +++ b/modules/ui/src/app/components/version/version.component.ts @@ -34,10 +34,10 @@ 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'; -import { filter } from 'rxjs'; +import { filter, timer } from 'rxjs'; import { ConsentDialogComponent } from './consent-dialog/consent-dialog.component'; -// eslint-disable-next-line @typescript-eslint/ban-types +// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type declare const gtag: Function; @Component({ selector: 'app-version', @@ -48,9 +48,7 @@ declare const gtag: Function; }) export class VersionComponent implements OnInit, OnDestroy { @Input() consentShown!: boolean; - @Input() hasRiskProfiles!: boolean; @Output() consentShownEvent = new EventEmitter(); - @Output() navigateToRiskAssessmentEvent = new EventEmitter(); version$!: Observable; private destroy$: Subject = new Subject(); @@ -66,9 +64,10 @@ export class VersionComponent implements OnInit, OnDestroy { filter(version => version !== null), tap(version => { if (!this.consentShown) { - // @ts-expect-error null is filtered - this.openConsentDialog(version); - this.consentShownEvent.emit(); + timer(2000).subscribe(() => { + this.openConsentDialog(version); + this.consentShownEvent.emit(); + }); } // @ts-expect-error data layer is not null window.dataLayer.push({ @@ -79,14 +78,21 @@ export class VersionComponent implements OnInit, OnDestroy { ); } - getVersionButtonLabel(installedVersion: string): string { - return installedVersion === UNAVAILABLE_VERSION.installed_version - ? 'Version temporarily unavailable. Click to open the Welcome modal' - : `${installedVersion} New version is available. Click to update`; + getVersionButtonLabel(installedVersion: Version): string { + if ( + installedVersion.installed_version === + UNAVAILABLE_VERSION.installed_version + ) { + return 'Version temporarily unavailable. Click to open the Welcome modal'; + } + if (installedVersion.update_available) { + return `${installedVersion.installed_version} New version is available. Click to update`; + } + return `${installedVersion.installed_version}. Click to open the Welcome modal`; } openConsentDialog(version: Version) { - const dialogData = { version, hasRiskProfiles: this.hasRiskProfiles }; + const dialogData = { version }; const dialogRef = this.dialog.open(ConsentDialogComponent, { ariaLabel: 'Welcome to Testrun modal window', data: dialogData, @@ -106,10 +112,6 @@ export class VersionComponent implements OnInit, OnDestroy { gtag('consent', 'update', { analytics_storage: dialogResult.grant ? 'granted' : 'denied', }); - - if (dialogResult.isNavigateToRiskAssessment) { - this.navigateToRiskAssessmentEvent.emit(); - } }); } diff --git a/modules/ui/src/app/guards/can-deactivate.guard.spec.ts b/modules/ui/src/app/guards/can-deactivate.guard.spec.ts new file mode 100644 index 000000000..5571654bc --- /dev/null +++ b/modules/ui/src/app/guards/can-deactivate.guard.spec.ts @@ -0,0 +1,31 @@ +/** + * Copyright 2023 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { TestBed } from '@angular/core/testing'; + +import { CanDeactivateGuard } from './can-deactivate.guard'; + +describe('CanDeactivateGuard', () => { + let guard: CanDeactivateGuard; + + beforeEach(() => { + TestBed.configureTestingModule({}); + guard = TestBed.inject(CanDeactivateGuard); + }); + + it('should be created', () => { + expect(guard).toBeTruthy(); + }); +}); diff --git a/modules/ui/src/app/guards/can-deactivate.guard.ts b/modules/ui/src/app/guards/can-deactivate.guard.ts new file mode 100644 index 000000000..68fb2147c --- /dev/null +++ b/modules/ui/src/app/guards/can-deactivate.guard.ts @@ -0,0 +1,38 @@ +/** + * Copyright 2023 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { Injectable } from '@angular/core'; +import { CanDeactivate, UrlTree } from '@angular/router'; +import { Observable } from 'rxjs'; + +type CanDeactivateType = + | Observable + | Promise + | boolean + | UrlTree; + +export interface CanComponentDeactivate { + canDeactivate: () => CanDeactivateType; +} +@Injectable({ + providedIn: 'root', +}) +export class CanDeactivateGuard + implements CanDeactivate +{ + canDeactivate(component: CanComponentDeactivate): CanDeactivateType { + return component.canDeactivate ? component.canDeactivate() : true; + } +} diff --git a/modules/ui/src/app/mocks/device.mock.ts b/modules/ui/src/app/mocks/device.mock.ts index 8bbfb56ea..86ef4ffd7 100644 --- a/modules/ui/src/app/mocks/device.mock.ts +++ b/modules/ui/src/app/mocks/device.mock.ts @@ -13,9 +13,28 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { Device } from '../model/device'; +import { + Device, + DeviceStatus, + DeviceQuestionnaireSection, +} from '../model/device'; +import { ProfileRisk } from '../model/profile'; +import { FormControlType } from '../model/question'; export const device = { + status: DeviceStatus.VALID, + manufacturer: 'Delta', + model: 'O3-DIN-CPU', + mac_addr: '00:1e:42:35:73:c4', + test_modules: { + dns: { + enabled: true, + }, + }, +} as Device; + +export const expired_device = { + status: DeviceStatus.INVALID, manufacturer: 'Delta', model: 'O3-DIN-CPU', mac_addr: '00:1e:42:35:73:c4', @@ -26,6 +45,7 @@ export const device = { }, } as Device; export const updated_device = { + status: DeviceStatus.VALID, manufacturer: 'Alpha', model: 'O3-XYZ-CPU', mac_addr: '00:1e:42:35:73:11', @@ -45,8 +65,61 @@ export const MOCK_TEST_MODULES = [ { displayName: 'Udmi', name: 'udmi', - enabled: false, + enabled: true, }, ]; export const MOCK_MODULES = ['Connection', 'Udmi']; + +export const DEVICES_FORM: DeviceQuestionnaireSection[] = [ + { + step: 1, + title: 'Step 1 title', + description: 'Step 1 description', + questions: [ + { + id: 1, + question: 'What type of device is this?', + type: FormControlType.SELECT, + options: [ + { + text: 'Building Automation Gateway', + risk: ProfileRisk.HIGH, + id: 1, + }, + { + text: 'IoT Gateway', + risk: ProfileRisk.LIMITED, + id: 2, + }, + ], + }, + { + id: 2, + question: 'Does your device process any sensitive information? ', + type: FormControlType.SELECT, + options: [ + { + id: 1, + text: 'Yes', + risk: ProfileRisk.LIMITED, + }, + { + id: 2, + text: 'No', + risk: ProfileRisk.HIGH, + }, + ], + }, + { + id: 3, + question: 'Please select the technology this device falls into', + type: FormControlType.SELECT, + options: [ + { text: 'Hardware - Access Control' }, + { text: 'Hardware - Air quality' }, + ], + }, + ], + }, +]; diff --git a/modules/ui/src/app/mocks/profile.mock.ts b/modules/ui/src/app/mocks/profile.mock.ts index d53703809..3082d39d2 100644 --- a/modules/ui/src/app/mocks/profile.mock.ts +++ b/modules/ui/src/app/mocks/profile.mock.ts @@ -14,12 +14,8 @@ * limitations under the License. */ -import { - FormControlType, - Profile, - ProfileFormat, - ProfileStatus, -} from '../model/profile'; +import { Profile, ProfileFormat, ProfileStatus } from '../model/profile'; +import { FormControlType } from '../model/question'; export const PROFILE_MOCK: Profile = { name: 'Primary profile', diff --git a/modules/ui/src/app/mocks/reports.mock.ts b/modules/ui/src/app/mocks/reports.mock.ts index e1422a36c..2889b0571 100644 --- a/modules/ui/src/app/mocks/reports.mock.ts +++ b/modules/ui/src/app/mocks/reports.mock.ts @@ -1,16 +1,20 @@ import { HistoryTestrun, TestrunStatus } from '../model/testrun-status'; import { MatTableDataSource } from '@angular/material/table'; +import { DeviceStatus, TestingType } from '../model/device'; export const HISTORY = [ { mac_addr: '01:02:03:04:05:06', status: 'compliant', device: { + status: DeviceStatus.VALID, manufacturer: 'Delta', model: '03-DIN-SRC', mac_addr: '01:02:03:04:05:06', firmware: '1.2.2', + test_pack: TestingType.Qualification, }, + tags: [], report: 'https://api.testrun.io/report.pdf', started: '2023-06-23T10:11:00.123Z', finished: '2023-06-23T10:17:10.123Z', @@ -19,11 +23,14 @@ export const HISTORY = [ status: 'compliant', mac_addr: '01:02:03:04:05:07', device: { + status: DeviceStatus.VALID, manufacturer: 'Delta', model: '03-DIN-SRC', mac_addr: '01:02:03:04:05:07', firmware: '1.2.3', + test_pack: TestingType.Qualification, }, + tags: [], report: 'https://api.testrun.io/report.pdf', started: '2023-07-23T10:11:00.123Z', finished: '2023-07-23T10:17:10.123Z', @@ -32,11 +39,14 @@ export const HISTORY = [ mac_addr: null, status: 'compliant', device: { + status: DeviceStatus.VALID, manufacturer: 'Delta', model: '03-DIN-SRC', mac_addr: '01:02:03:04:05:08', firmware: '1.2.2', + test_pack: TestingType.Qualification, }, + tags: [], report: 'https://api.testrun.io/report.pdf', started: '2023-06-23T10:11:00.123Z', finished: '2023-06-23T10:17:10.123Z', @@ -48,11 +58,14 @@ export const HISTORY_AFTER_REMOVE = [ mac_addr: '01:02:03:04:05:06', status: 'compliant', device: { + status: DeviceStatus.VALID, manufacturer: 'Delta', model: '03-DIN-SRC', mac_addr: '01:02:03:04:05:06', firmware: '1.2.2', + test_pack: TestingType.Qualification, }, + tags: [], report: 'https://api.testrun.io/report.pdf', started: '2023-06-23T10:11:00.123Z', finished: '2023-06-23T10:17:10.123Z', @@ -61,11 +74,14 @@ export const HISTORY_AFTER_REMOVE = [ mac_addr: null, status: 'compliant', device: { + status: DeviceStatus.VALID, manufacturer: 'Delta', model: '03-DIN-SRC', mac_addr: '01:02:03:04:05:08', firmware: '1.2.2', + test_pack: TestingType.Qualification, }, + tags: [], report: 'https://api.testrun.io/report.pdf', started: '2023-06-23T10:11:00.123Z', finished: '2023-06-23T10:17:10.123Z', @@ -77,49 +93,61 @@ export const FORMATTED_HISTORY = [ status: 'compliant', mac_addr: '01:02:03:04:05:06', device: { + status: DeviceStatus.VALID, manufacturer: 'Delta', model: '03-DIN-SRC', mac_addr: '01:02:03:04:05:06', firmware: '1.2.2', + test_pack: TestingType.Qualification, }, + tags: [], report: 'https://api.testrun.io/report.pdf', started: '2023-06-23T10:11:00.123Z', finished: '2023-06-23T10:17:10.123Z', deviceFirmware: '1.2.2', deviceInfo: 'Delta 03-DIN-SRC', duration: '06m 10s', + program: 'Device Qualification', }, { status: 'compliant', mac_addr: '01:02:03:04:05:07', device: { + status: DeviceStatus.VALID, manufacturer: 'Delta', model: '03-DIN-SRC', mac_addr: '01:02:03:04:05:07', firmware: '1.2.3', + test_pack: TestingType.Qualification, }, + tags: [], report: 'https://api.testrun.io/report.pdf', started: '2023-07-23T10:11:00.123Z', finished: '2023-07-23T10:17:10.123Z', deviceFirmware: '1.2.3', deviceInfo: 'Delta 03-DIN-SRC', duration: '06m 10s', + program: 'Device Qualification', }, { mac_addr: null, status: 'compliant', device: { + status: DeviceStatus.VALID, manufacturer: 'Delta', model: '03-DIN-SRC', mac_addr: '01:02:03:04:05:08', firmware: '1.2.2', + test_pack: TestingType.Qualification, }, + tags: [], report: 'https://api.testrun.io/report.pdf', started: '2023-06-23T10:11:00.123Z', finished: '2023-06-23T10:17:10.123Z', deviceFirmware: '1.2.2', deviceInfo: 'Delta 03-DIN-SRC', duration: '06m 10s', + program: 'Device Qualification', }, ]; diff --git a/modules/ui/src/app/mocks/testrun.mock.ts b/modules/ui/src/app/mocks/testrun.mock.ts index bb588634c..c90927cd3 100644 --- a/modules/ui/src/app/mocks/testrun.mock.ts +++ b/modules/ui/src/app/mocks/testrun.mock.ts @@ -19,6 +19,7 @@ import { TestrunStatus, TestsData, } from '../model/testrun-status'; +import { DeviceStatus } from '../model/device'; export const TEST_DATA_RESULT: IResult[] = [ { @@ -32,6 +33,11 @@ export const TEST_DATA_RESULT: IResult[] = [ 'The device should use the DNS server provided by the DHCP server', result: 'Non-Compliant', }, + { + name: 'dns.mdns', + description: 'Does the device has MDNS (or any kind of IP multicast)', + result: 'Not Started', + }, ]; export const TEST_DATA_RESULT_WITH_RECOMMENDATIONS: IResult[] = [ @@ -47,9 +53,28 @@ export const TEST_DATA_RESULT_WITH_RECOMMENDATIONS: IResult[] = [ }, ]; +export const TEST_DATA_RESULT_WITH_ERROR: IResult[] = [ + { + name: 'dns.network.hostname_resolution', + description: 'The device should resolve hostnames', + result: 'Compliant', + }, + { + name: 'dns.network.from_dhcp', + description: + 'The device should use the DNS server provided by the DHCP server', + result: 'Error', + }, + { + name: 'dns.mdns', + description: 'Does the device has MDNS (or any kind of IP multicast)', + result: 'Not Started', + }, +]; + export const TEST_DATA_TABLE_RESULT: IResult[] = [ ...TEST_DATA_RESULT, - ...new Array(24).fill(null).map(() => ({}) as IResult), + ...new Array(23).fill(null).map(() => ({}) as IResult), ]; export const EMPTY_RESULT = new Array(100) @@ -71,6 +96,7 @@ const PROGRESS_DATA_RESPONSE = ( status, mac_addr: '01:02:03:04:05:06', device: { + status: DeviceStatus.VALID, manufacturer: 'Delta', model: '03-DIN-CPU', mac_addr: '01:02:03:04:05:06', @@ -80,6 +106,7 @@ const PROGRESS_DATA_RESPONSE = ( finished, tests, report, + tags: ['VSA', 'Other tag', 'And one more'], }; }; @@ -131,3 +158,10 @@ export const MOCK_PROGRESS_DATA_WAITING_FOR_DEVICE: TestrunStatus = { status: StatusOfTestrun.WaitingForDevice, started: null, }; + +export const MOCK_PROGRESS_DATA_WITH_ERROR: TestrunStatus = + PROGRESS_DATA_RESPONSE(StatusOfTestrun.InProgress, null, { + ...TEST_DATA, + total: 3, + results: TEST_DATA_RESULT_WITH_ERROR, + }); diff --git a/modules/ui/src/app/model/callout-type.ts b/modules/ui/src/app/model/callout-type.ts index c784b46f6..c8f86d1c4 100644 --- a/modules/ui/src/app/model/callout-type.ts +++ b/modules/ui/src/app/model/callout-type.ts @@ -15,6 +15,7 @@ */ export enum CalloutType { Info = 'info', + InfoPilot = 'info pilot', Check = 'check_circle', Warning = 'warning_amber', Error = 'error', diff --git a/modules/ui/src/app/model/device.ts b/modules/ui/src/app/model/device.ts index ba526e661..55d383401 100644 --- a/modules/ui/src/app/model/device.ts +++ b/modules/ui/src/app/model/device.ts @@ -13,12 +13,25 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +import { QuestionFormat } from './question'; +import { Question } from './profile'; + export interface Device { manufacturer: string; model: string; mac_addr: string; test_modules?: TestModules; firmware?: string; + status?: DeviceStatus; + type?: string; + technology?: string; + test_pack?: TestingType; + additional_info?: Question[]; +} + +export enum DeviceStatus { + VALID = 'Valid', + INVALID = 'Invalid', } /** @@ -43,3 +56,19 @@ export enum DeviceView { Basic = 'basic', WithActions = 'with actions', } + +export interface DeviceQuestionnaireSection { + step: number; + title?: string; + description?: string; + questions: QuestionnaireFormat[]; +} + +export interface QuestionnaireFormat extends QuestionFormat { + id: number; +} + +export enum TestingType { + Pilot = 'Pilot Assessment', + Qualification = 'Device Qualification', +} diff --git a/modules/ui/src/app/model/profile.ts b/modules/ui/src/app/model/profile.ts index 059b3cafe..37ee31b21 100644 --- a/modules/ui/src/app/model/profile.ts +++ b/modules/ui/src/app/model/profile.ts @@ -13,6 +13,8 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + +import { QuestionFormat } from './question'; export interface Profile { name: string; risk?: string; @@ -22,27 +24,7 @@ export interface Profile { created?: string; } -export enum FormControlType { - SELECT = 'select', - TEXTAREA = 'text-long', - EMAIL_MULTIPLE = 'email-multiple', - SELECT_MULTIPLE = 'select-multiple', - TEXT = 'text', -} - -export interface Validation { - required?: boolean; - max?: string; -} - -export interface ProfileFormat { - question: string; - type: FormControlType; - description?: string; - options?: string[]; - default?: string; - validation?: Validation; -} +export type ProfileFormat = QuestionFormat; export interface Question { question?: string; diff --git a/modules/ui/src/app/model/program-type.ts b/modules/ui/src/app/model/program-type.ts new file mode 100644 index 000000000..6c6d8b9f5 --- /dev/null +++ b/modules/ui/src/app/model/program-type.ts @@ -0,0 +1,19 @@ +/** + * Copyright 2023 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +export enum ProgramType { + Pilot = 'pilot', + Qualification = 'qualification', +} diff --git a/modules/ui/src/app/model/question.ts b/modules/ui/src/app/model/question.ts new file mode 100644 index 000000000..284fcdd3d --- /dev/null +++ b/modules/ui/src/app/model/question.ts @@ -0,0 +1,38 @@ +/* + * Copyright 2023 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +export interface Validation { + required: boolean | undefined; + max?: string; +} + +export enum FormControlType { + SELECT = 'select', + TEXTAREA = 'text-long', + EMAIL_MULTIPLE = 'email-multiple', + SELECT_MULTIPLE = 'select-multiple', + TEXT = 'text', +} + +export interface QuestionFormat { + question: string; + type: FormControlType; + description?: string; + options?: OptionType[]; + default?: string; + validation?: Validation; +} + +export type OptionType = string | object; diff --git a/modules/ui/src/app/model/setting.ts b/modules/ui/src/app/model/setting.ts index 708dcfc94..02c3ec0ce 100644 --- a/modules/ui/src/app/model/setting.ts +++ b/modules/ui/src/app/model/setting.ts @@ -20,6 +20,7 @@ export interface SystemConfig { } | null; log_level?: string; monitor_period?: number; + single_intf?: boolean; } export interface InterfacesValidation { diff --git a/modules/ui/src/app/model/testrun-status.ts b/modules/ui/src/app/model/testrun-status.ts index 3bc63804c..faa707f92 100644 --- a/modules/ui/src/app/model/testrun-status.ts +++ b/modules/ui/src/app/model/testrun-status.ts @@ -18,16 +18,19 @@ import { Device } from './device'; export interface TestrunStatus { mac_addr: string | null; status: string; + description?: string; device: IDevice; started: string | null; finished: string | null; tests?: TestsResponse; report: string; + tags: string[] | null; } export interface HistoryTestrun extends TestrunStatus { deviceFirmware: string; deviceInfo: string; + program: string; duration: string; } @@ -84,6 +87,7 @@ export interface StatusResultClassName { green: boolean; red: boolean; blue: boolean; + cyan: boolean; grey: boolean; } @@ -98,6 +102,7 @@ export const IDLE_STATUS = { total: 0, results: [], }, + tags: [], } as TestrunStatus; export type TestrunStatusKey = keyof typeof StatusOfTestrun; diff --git a/modules/ui/src/app/model/version.ts b/modules/ui/src/app/model/version.ts index 21ec6ee38..5eb946e42 100644 --- a/modules/ui/src/app/model/version.ts +++ b/modules/ui/src/app/model/version.ts @@ -23,5 +23,4 @@ export interface Version { export interface ConsentDialogResult { grant: boolean; - isNavigateToRiskAssessment?: boolean; } diff --git a/modules/ui/src/app/pages/certificates/certificate.validator.ts b/modules/ui/src/app/pages/certificates/certificate.validator.ts index 9143824e8..72e197af1 100644 --- a/modules/ui/src/app/pages/certificates/certificate.validator.ts +++ b/modules/ui/src/app/pages/certificates/certificate.validator.ts @@ -9,7 +9,6 @@ export const getValidationErrors = (file: File): string[] => { errors.push(validateExtension(file.name)); errors.push(validateFileNameLength(file.name)); errors.push(validateSize(file.size)); - // @ts-expect-error null values are filtered return errors.filter(error => error !== null); }; const validateFileName = (name: string): string | null => { diff --git a/modules/ui/src/app/pages/certificates/certificates.component.html b/modules/ui/src/app/pages/certificates/certificates.component.html index d75f115b4..bcc5173b6 100644 --- a/modules/ui/src/app/pages/certificates/certificates.component.html +++ b/modules/ui/src/app/pages/certificates/certificates.component.html @@ -39,14 +39,5 @@

Certificates

deleteCertificate($event) ">
-
diff --git a/modules/ui/src/app/pages/certificates/certificates.component.spec.ts b/modules/ui/src/app/pages/certificates/certificates.component.spec.ts index c8f0e91b6..1deda3d21 100644 --- a/modules/ui/src/app/pages/certificates/certificates.component.spec.ts +++ b/modules/ui/src/app/pages/certificates/certificates.component.spec.ts @@ -89,20 +89,6 @@ describe('CertificatesComponent', () => { expect(component.closeCertificatedEvent.emit).toHaveBeenCalled(); }); - it('should emit closeSettingEvent when close button clicked', () => { - const headerCloseButton = fixture.nativeElement.querySelector( - '.close-button' - ) as HTMLButtonElement; - spyOn(component.closeCertificatedEvent, 'emit'); - - headerCloseButton.click(); - - expect(mockLiveAnnouncer.announce).toHaveBeenCalledWith( - 'The certificates panel is closed.' - ); - expect(component.closeCertificatedEvent.emit).toHaveBeenCalled(); - }); - it('should have upload file button', () => { const uploadCertificatesButton = fixture.nativeElement.querySelector( '.browse-files-button' @@ -166,9 +152,9 @@ describe('CertificatesComponent', () => { flush(); })); - it('should focus navigation button if next active element does not exist', fakeAsync(() => { + it('should focus navigation close button if next active element does not exist', fakeAsync(() => { const nextButton = window.document.querySelector( - '.certificates-drawer-content .close-button' + '.certificates-drawer-header .certificates-drawer-header-button' ) as HTMLButtonElement; const buttonFocusSpy = spyOn(nextButton, 'focus'); diff --git a/modules/ui/src/app/pages/certificates/certificates.component.ts b/modules/ui/src/app/pages/certificates/certificates.component.ts index bf6cf19fa..3b8cccccc 100644 --- a/modules/ui/src/app/pages/certificates/certificates.component.ts +++ b/modules/ui/src/app/pages/certificates/certificates.component.ts @@ -104,7 +104,7 @@ export class CertificatesComponent implements OnDestroy { } else { // If next interactive element doest not exist, close button will be focused const menuButton = window.document.querySelector( - '.certificates-drawer-content .close-button' + '.certificates-drawer-header .certificates-drawer-header-button' ) as HTMLButtonElement; menuButton?.focus(); } diff --git a/modules/ui/src/app/pages/devices/components/device-form/device-form.component.html b/modules/ui/src/app/pages/devices/components/device-form/device-form.component.html deleted file mode 100644 index 5978f6d22..000000000 --- a/modules/ui/src/app/pages/devices/components/device-form/device-form.component.html +++ /dev/null @@ -1,130 +0,0 @@ - -
- {{ data.title }} - - Device Manufacturer - - Please enter device manufacturer name - - Please, check. The manufacturer name must be a maximum of 28 - characters. Only letters, numbers, and accented letters are - permitted. - - - Device Manufacturer is required - - - - Device Model - - Please enter device name - - Please, check. The device model name must be a maximum of 28 - characters. Only letters, numbers, and accented letters are - permitted. - - - Device Model is required - - - - MAC address - - Please enter MAC address - - MAC address is required - - - Please, check. A MAC address consists of 12 hexadecimal digits (0 to 9, - a to f, or A to F). - - - This MAC address is already used for another device in the - repository. - - - - - - - {{ error$ | async }} - - - - - - - - - -
diff --git a/modules/ui/src/app/pages/devices/components/device-form/device-form.component.scss b/modules/ui/src/app/pages/devices/components/device-form/device-form.component.scss deleted file mode 100644 index 831f9907a..000000000 --- a/modules/ui/src/app/pages/devices/components/device-form/device-form.component.scss +++ /dev/null @@ -1,76 +0,0 @@ -/** - * Copyright 2023 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -@import 'src/theming/colors'; - -$device-form-max-width: 580px; -$device-form-min-width: 285px; - -:host { - display: grid; - grid-template-rows: 1fr; - overflow: auto; - grid-template-columns: minmax(285px, $device-form-max-width); -} - -.device-form { - display: grid; - padding: 24px; - max-width: $device-form-max-width; - min-width: $device-form-min-width; - gap: 10px; - overflow: auto; -} - -.manufacturer-field, -.model-field { - &::ng-deep.mat-mdc-form-field-subscript-wrapper:has(mat-error) { - height: 40px; - } -} - -.device-form-title { - color: $grey-800; - font-size: 22px; - line-height: 28px; - padding-bottom: 14px; -} - -.device-form-test-modules { - overflow: auto; - min-height: 78px; -} - -.device-form-actions { - padding: 0; - min-height: 30px; -} - -.close-button { - color: $primary; -} - -.device-form-mac-address-error { - white-space: nowrap; -} - -.delete-button { - color: $primary; - margin-right: auto; -} - -.hidden { - display: none; -} diff --git a/modules/ui/src/app/pages/devices/components/device-form/device-form.component.spec.ts b/modules/ui/src/app/pages/devices/components/device-form/device-form.component.spec.ts deleted file mode 100644 index 6ea72aa52..000000000 --- a/modules/ui/src/app/pages/devices/components/device-form/device-form.component.spec.ts +++ /dev/null @@ -1,459 +0,0 @@ -/** - * Copyright 2023 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import { - ComponentFixture, - fakeAsync, - flush, - TestBed, -} from '@angular/core/testing'; - -import { DeviceFormComponent, FormAction } from './device-form.component'; -import { MatButtonModule } from '@angular/material/button'; -import { FormControl, ReactiveFormsModule } from '@angular/forms'; -import { MatCheckboxModule } from '@angular/material/checkbox'; -import { MatInputModule } from '@angular/material/input'; -import { - MAT_DIALOG_DATA, - MatDialogModule, - MatDialogRef, -} from '@angular/material/dialog'; -import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; -import { Device } from '../../../../model/device'; -import { of } from 'rxjs'; -import { DeviceTestsComponent } from '../../../../components/device-tests/device-tests.component'; -import { SpinnerComponent } from '../../../../components/spinner/spinner.component'; -import { NgxMaskDirective, NgxMaskPipe, provideNgxMask } from 'ngx-mask'; -import { DevicesStore } from '../../devices.store'; -import { device, MOCK_TEST_MODULES } from '../../../../mocks/device.mock'; -import SpyObj = jasmine.SpyObj; - -describe('DeviceFormComponent', () => { - let component: DeviceFormComponent; - let fixture: ComponentFixture; - let mockDevicesStore: SpyObj; - let compiled: HTMLElement; - - beforeEach(() => { - mockDevicesStore = jasmine.createSpyObj('DevicesStore', [ - 'editDevice', - 'saveDevice', - ]); - - TestBed.configureTestingModule({ - declarations: [DeviceFormComponent], - providers: [ - { provide: DevicesStore, useValue: mockDevicesStore }, - { - provide: MatDialogRef, - useValue: { - keydownEvents: () => of(new KeyboardEvent('keydown', { code: '' })), - close: () => ({}), - }, - }, - { provide: MAT_DIALOG_DATA, useValue: {} }, - provideNgxMask(), - ], - imports: [ - MatButtonModule, - ReactiveFormsModule, - MatCheckboxModule, - MatInputModule, - MatDialogModule, - BrowserAnimationsModule, - DeviceTestsComponent, - SpinnerComponent, - NgxMaskDirective, - NgxMaskPipe, - ], - }); - TestBed.overrideProvider(DevicesStore, { useValue: mockDevicesStore }); - - fixture = TestBed.createComponent(DeviceFormComponent); - component = fixture.componentInstance; - compiled = fixture.nativeElement as HTMLElement; - component.data = { - testModules: MOCK_TEST_MODULES, - devices: [], - }; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); - - it('should contain device form', () => { - const form = compiled.querySelector('.device-form'); - - expect(form).toBeTruthy(); - }); - - it('should close dialog on "cancel" click', () => { - const closeSpy = spyOn(component.dialogRef, 'close'); - const closeButton = compiled.querySelector( - '.close-button' - ) as HTMLButtonElement; - - closeButton?.click(); - - expect(closeSpy).toHaveBeenCalledWith(); - - closeSpy.calls.reset(); - }); - - it('should not save data when fields are empty', () => { - const saveButton = compiled.querySelector( - '.save-button' - ) as HTMLButtonElement; - const model: HTMLInputElement = compiled.querySelector( - '.device-form-model' - ) as HTMLInputElement; - const manufacturer: HTMLInputElement = compiled.querySelector( - '.device-form-manufacturer' - ) as HTMLInputElement; - const macAddress: HTMLInputElement = compiled.querySelector( - '.device-form-mac-address' - ) as HTMLInputElement; - - ['', ' '].forEach(value => { - model.value = value; - model.dispatchEvent(new Event('input')); - manufacturer.value = value; - manufacturer.dispatchEvent(new Event('input')); - macAddress.value = value; - macAddress.dispatchEvent(new Event('input')); - saveButton?.click(); - fixture.detectChanges(); - - const requiredErrors = compiled.querySelectorAll('mat-error'); - expect(requiredErrors?.length).toEqual(3); - - requiredErrors.forEach(error => { - expect(error?.innerHTML).toContain('required'); - }); - }); - }); - - it('should not save data if no test selected', fakeAsync(() => { - component.model.setValue('model'); - component.manufacturer.setValue('manufacturer'); - component.mac_addr.setValue('07:07:07:07:07:07'); - component.test_modules.setValue([false, false]); - - component.saveDevice(); - fixture.detectChanges(); - - const error = compiled.querySelector('mat-error'); - expect(error?.innerHTML).toContain( - 'At least one test has to be selected to save a Device.' - ); - - flush(); - })); - - it('should save data when form is valid', () => { - const device: Device = { - manufacturer: 'manufacturer', - model: 'model', - mac_addr: '07:07:07:07:07:07', - test_modules: { - connection: { - enabled: true, - }, - udmi: { - enabled: false, - }, - }, - }; - component.model.setValue('model'); - component.manufacturer.setValue('manufacturer'); - component.mac_addr.setValue('07:07:07:07:07:07'); - - component.saveDevice(); - - const args = mockDevicesStore.saveDevice.calls.argsFor(0); - // @ts-expect-error config is in object - expect(args[0].device).toEqual(device); - expect(mockDevicesStore.saveDevice).toHaveBeenCalled(); - }); - - describe('test modules', () => { - it('should be present', () => { - const test = compiled.querySelectorAll('mat-checkbox'); - - expect(test.length).toEqual(2); - }); - - it('should be enabled', () => { - const tests = compiled.querySelectorAll('.device-form-test-modules p'); - - expect(tests[0].classList.contains('disabled')).toEqual(false); - }); - }); - - describe('device model', () => { - it('should not contain errors when input is correct', () => { - const model: HTMLInputElement = compiled.querySelector( - '.device-form-model' - ) as HTMLInputElement; - ['model', 'Gebäude', 'jardín'].forEach(value => { - model.value = value; - model.dispatchEvent(new Event('input')); - - const errors = component.model.errors; - const uiValue = model.value; - const formValue = component.model.value; - - expect(uiValue).toEqual(formValue); - expect(errors).toBeNull(); - }); - }); - - it('should have "invalid_format" error when field does not satisfy validation rules', () => { - [ - 'very long value very long value very long value very long value very long value very long value very long value', - 'as&@3$', - ].forEach(value => { - const model: HTMLInputElement = compiled.querySelector( - '.device-form-model' - ) as HTMLInputElement; - model.value = value; - model.dispatchEvent(new Event('input')); - component.model.markAsTouched(); - fixture.detectChanges(); - - const modelError = compiled.querySelector('mat-error')?.innerHTML; - const error = component.model.hasError('invalid_format'); - - expect(error).toBeTruthy(); - expect(modelError).toContain( - 'The device model name must be a maximum of 28 characters. Only letters, numbers, and accented letters are permitted.' - ); - }); - }); - }); - - describe('device manufacturer', () => { - it('should not contain errors when input is correct', () => { - const manufacturer: HTMLInputElement = compiled.querySelector( - '.device-form-manufacturer' - ) as HTMLInputElement; - ['manufacturer', 'Gebäude', 'jardín'].forEach(value => { - manufacturer.value = value; - manufacturer.dispatchEvent(new Event('input')); - - const errors = component.manufacturer.errors; - const uiValue = manufacturer.value; - const formValue = component.manufacturer.value; - - expect(uiValue).toEqual(formValue); - expect(errors).toBeNull(); - }); - }); - - it('should have "invalid_format" error when field does not satisfy validation', () => { - [ - 'very long value very long value very long value very long value very long value very long value very long value', - 'as&@3$', - ].forEach(value => { - const manufacturer: HTMLInputElement = compiled.querySelector( - '.device-form-manufacturer' - ) as HTMLInputElement; - manufacturer.value = value; - manufacturer.dispatchEvent(new Event('input')); - component.manufacturer.markAsTouched(); - fixture.detectChanges(); - - const manufacturerError = - compiled.querySelector('mat-error')?.innerHTML; - const error = component.manufacturer.hasError('invalid_format'); - - expect(error).toBeTruthy(); - expect(manufacturerError).toContain( - 'The manufacturer name must be a maximum of 28 characters. Only letters, numbers, and accented letters are permitted.' - ); - }); - }); - }); - - describe('mac address', () => { - it('should not be disabled', () => { - expect(component.mac_addr.disabled).toBeFalse(); - }); - - it('should not contain errors when input is correct', () => { - const macAddress: HTMLInputElement = compiled.querySelector( - '.device-form-mac-address' - ) as HTMLInputElement; - ['07:07:07:07:07:07', ' 07:07:07:07:07:07 '].forEach(value => { - macAddress.value = value; - macAddress.dispatchEvent(new Event('input')); - - const errors = component.mac_addr.errors; - const formValue = component.mac_addr.value; - - expect(macAddress.value).toEqual(formValue); - expect(errors).toBeNull(); - }); - }); - - it('should have "pattern" error when field does not satisfy pattern', () => { - ['value', 'q01e423573c4'].forEach(value => { - const macAddress: HTMLInputElement = compiled.querySelector( - '.device-form-mac-address' - ) as HTMLInputElement; - macAddress.value = value; - macAddress.dispatchEvent(new Event('input')); - component.mac_addr.markAsTouched(); - fixture.detectChanges(); - - const macAddressError = compiled.querySelector('mat-error')?.innerHTML; - const error = component.mac_addr.hasError('pattern'); - - expect(error).toBeTruthy(); - expect(macAddressError).toContain( - 'Please, check. A MAC address consists of 12 hexadecimal digits (0 to 9, a to f, or A to F).' - ); - }); - }); - - it('should have "has_same_mac_address" error when MAC address is already used', () => { - component.data = { - testModules: MOCK_TEST_MODULES, - devices: [device], - }; - component.ngOnInit(); - fixture.detectChanges(); - - const macAddress: HTMLInputElement = compiled.querySelector( - '.device-form-mac-address' - ) as HTMLInputElement; - macAddress.value = '00:1e:42:35:73:c4'; - macAddress.dispatchEvent(new Event('input')); - component.mac_addr.markAsTouched(); - fixture.detectChanges(); - - const macAddressError = compiled.querySelector('mat-error')?.innerHTML; - const error = component.mac_addr.hasError('has_same_mac_address'); - - expect(error).toBeTruthy(); - expect(macAddressError).toContain( - 'This MAC address is already used for another device in the repository.' - ); - }); - }); - - it('should have hidden delete device button', () => { - const deleteButton = compiled.querySelector( - '.delete-button' - ) as HTMLButtonElement; - - expect(deleteButton.classList.contains('hidden')).toBeTrue(); - }); - - describe('when device is present', () => { - beforeEach(() => { - component.data = { - devices: [device], - testModules: MOCK_TEST_MODULES, - device: { - manufacturer: 'Delta', - model: 'O3-DIN-CPU', - mac_addr: '00:1e:42:35:73:c4', - test_modules: { - udmi: { - enabled: true, - }, - }, - }, - }; - component.ngOnInit(); - fixture.detectChanges(); - }); - - it('should fill form values with device values', () => { - const model: HTMLInputElement = compiled.querySelector( - '.device-form-model' - ) as HTMLInputElement; - const manufacturer: HTMLInputElement = compiled.querySelector( - '.device-form-manufacturer' - ) as HTMLInputElement; - - expect(model.value).toEqual('O3-DIN-CPU'); - expect(manufacturer.value).toEqual('Delta'); - }); - - it('should save data even mac address already exist', fakeAsync(() => { - // fill the test controls - component.test_modules.push(new FormControl(false)); - component.test_modules.push(new FormControl(true)); - component.saveDevice(); - fixture.detectChanges(); - - fixture.whenStable().then(() => { - const error = compiled.querySelector('mat-error'); - expect(error).toBeFalse(); - }); - - const args = mockDevicesStore.editDevice.calls.argsFor(0); - // @ts-expect-error config is in object - expect(args[0].device).toEqual({ - manufacturer: 'Delta', - model: 'O3-DIN-CPU', - mac_addr: '00:1e:42:35:73:c4', - test_modules: { - connection: { - enabled: false, - }, - udmi: { - enabled: true, - }, - }, - }); - expect(mockDevicesStore.editDevice).toHaveBeenCalled(); - - flush(); - })); - - it('should have delete device button', () => { - const deleteButton = compiled.querySelector( - '.delete-button' - ) as HTMLButtonElement; - - expect(deleteButton.classList.contains('hidden')).toBeFalse(); - expect(deleteButton).toBeTruthy(); - }); - - it('should close dialog with delete action on "delete" click', () => { - const closeSpy = spyOn(component.dialogRef, 'close'); - const closeButton = compiled.querySelector( - '.delete-button' - ) as HTMLButtonElement; - - closeButton?.click(); - - expect(closeSpy).toHaveBeenCalledWith({ action: FormAction.Delete }); - - closeSpy.calls.reset(); - }); - }); - - it('should has loader element', () => { - const spinner = compiled.querySelector('app-spinner'); - - expect(spinner).toBeTruthy(); - }); -}); diff --git a/modules/ui/src/app/pages/devices/components/device-form/device-form.component.ts b/modules/ui/src/app/pages/devices/components/device-form/device-form.component.ts deleted file mode 100644 index 38ab05ffa..000000000 --- a/modules/ui/src/app/pages/devices/components/device-form/device-form.component.ts +++ /dev/null @@ -1,211 +0,0 @@ -/** - * Copyright 2023 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import { Component, Inject, OnDestroy, OnInit } from '@angular/core'; -import { - AbstractControl, - FormArray, - FormBuilder, - FormGroup, - Validators, -} from '@angular/forms'; -import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; - -import { Device, TestModule } from '../../../../model/device'; -import { DeviceValidators } from './device.validators'; -import { Subject } from 'rxjs'; -import { BehaviorSubject } from 'rxjs/internal/BehaviorSubject'; -import { EscapableDialogComponent } from '../../../../components/escapable-dialog/escapable-dialog.component'; -import { DevicesStore } from '../../devices.store'; - -const MAC_ADDRESS_PATTERN = - '^[\\s]*[a-fA-F0-9]{2}(?:[:][a-fA-F0-9]{2}){5}[\\s]*$'; - -interface DialogData { - title?: string; - device?: Device; - devices: Device[]; - testModules: TestModule[]; -} - -export enum FormAction { - Delete = 'Delete', - Save = 'Save', -} - -export interface FormResponse { - device?: Device; - action: FormAction; -} - -@Component({ - selector: 'app-device-form', - templateUrl: './device-form.component.html', - styleUrls: ['./device-form.component.scss'], - providers: [DevicesStore], -}) -export class DeviceFormComponent - extends EscapableDialogComponent - implements OnInit, OnDestroy -{ - deviceForm!: FormGroup; - testModules: TestModule[] = []; - error$: BehaviorSubject = new BehaviorSubject( - null - ); - private destroy$: Subject = new Subject(); - - constructor( - public override dialogRef: MatDialogRef, - @Inject(MAT_DIALOG_DATA) public data: DialogData, - private fb: FormBuilder, - private deviceValidators: DeviceValidators, - private devicesStore: DevicesStore - ) { - super(dialogRef); - } - - get model() { - return this.deviceForm.get('model') as AbstractControl; - } - - get manufacturer() { - return this.deviceForm.get('manufacturer') as AbstractControl; - } - - get mac_addr() { - return this.deviceForm.get('mac_addr') as AbstractControl; - } - - get test_modules() { - return this.deviceForm.controls['test_modules'] as FormArray; - } - - ngOnInit() { - this.createDeviceForm(); - this.testModules = this.data.testModules; - if (this.data.device) { - this.model.setValue(this.data.device.model); - this.manufacturer.setValue(this.data.device.manufacturer); - this.mac_addr.setValue(this.data.device.mac_addr); - } - } - - ngOnDestroy() { - this.destroy$.next(true); - this.destroy$.unsubscribe(); - } - - delete(): void { - this.dialogRef.close({ action: FormAction.Delete } as FormResponse); - } - - cancel(): void { - this.dialogRef.close(); - } - - saveDevice() { - this.checkMandatoryFields(); - if (this.deviceForm.invalid) { - this.deviceForm.markAllAsTouched(); - return; - } - - if (this.isAllTestsDisabled()) { - this.error$.next( - 'At least one test has to be selected to save a Device.' - ); - return; - } - - const device = this.createDeviceFromForm(); - - this.updateDevice(device, () => { - this.dialogRef.close({ - action: FormAction.Save, - device, - } as FormResponse); - }); - } - - private updateDevice(device: Device, callback: () => void) { - if (this.data.device) { - this.devicesStore.editDevice({ - device, - mac_addr: this.data.device.mac_addr, - onSuccess: callback, - }); - } else { - this.devicesStore.saveDevice({ device, onSuccess: callback }); - } - } - - private isAllTestsDisabled(): boolean { - return this.deviceForm.value.test_modules.every((enabled: boolean) => { - return !enabled; - }); - } - - private createDeviceFromForm(): Device { - const testModules: { [key: string]: { enabled: boolean } } = {}; - this.deviceForm.value.test_modules.forEach( - (enabled: boolean, i: number) => { - testModules[this.testModules[i]?.name] = { - enabled: enabled, - }; - } - ); - return { - model: this.model.value.trim(), - manufacturer: this.manufacturer.value.trim(), - mac_addr: this.mac_addr.value.trim(), - test_modules: testModules, - } as Device; - } - - /** - * Model, manufacturer, MAC address are mandatory. - * It should be checked on submit. Other validation happens on blur. - */ - private checkMandatoryFields() { - this.setRequiredErrorIfEmpty(this.model); - this.setRequiredErrorIfEmpty(this.manufacturer); - this.setRequiredErrorIfEmpty(this.mac_addr); - } - - private setRequiredErrorIfEmpty(control: AbstractControl) { - if (!control.value.trim()) { - control.setErrors({ required: true }); - } - } - - private createDeviceForm() { - this.deviceForm = this.fb.group({ - model: ['', [this.deviceValidators.deviceStringFormat()]], - manufacturer: ['', [this.deviceValidators.deviceStringFormat()]], - mac_addr: [ - '', - [ - Validators.pattern(MAC_ADDRESS_PATTERN), - this.deviceValidators.differentMACAddress( - this.data.devices, - this.data.device - ), - ], - ], - test_modules: new FormArray([]), - }); - } -} diff --git a/modules/ui/src/app/pages/devices/components/device-form/device.validators.ts b/modules/ui/src/app/pages/devices/components/device-form/device.validators.ts index 60ceb5d48..3ffa45802 100644 --- a/modules/ui/src/app/pages/devices/components/device-form/device.validators.ts +++ b/modules/ui/src/app/pages/devices/components/device-form/device.validators.ts @@ -54,6 +54,16 @@ export class DeviceValidators { }; } + public testModulesRequired(): ValidatorFn { + return (control: AbstractControl): ValidationErrors | null => { + const value = control.value; + if (value.every((module: boolean) => !module)) { + return { required: true }; + } + return null; + }; + } + public differentMACAddress(devices: Device[], device?: Device): ValidatorFn { return (control: AbstractControl): ValidationErrors | null => { const value = control.value?.trim(); diff --git a/modules/ui/src/app/pages/devices/components/device-qualification-from/device-qualification-from.component.html b/modules/ui/src/app/pages/devices/components/device-qualification-from/device-qualification-from.component.html new file mode 100644 index 000000000..4bf0e13a5 --- /dev/null +++ b/modules/ui/src/app/pages/devices/components/device-qualification-from/device-qualification-from.component.html @@ -0,0 +1,347 @@ + +
+ +
+ +

{{ data.title }}

+
+
+ + + +

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

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

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

+
+

+ {{ step.title }} +

+

+ {{ step.description }} +

+ +
+
+
+ + +

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

+

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

+
+ + +
diff --git a/modules/ui/src/app/pages/devices/components/device-qualification-from/device-qualification-from.component.scss b/modules/ui/src/app/pages/devices/components/device-qualification-from/device-qualification-from.component.scss new file mode 100644 index 000000000..3ce6ee85b --- /dev/null +++ b/modules/ui/src/app/pages/devices/components/device-qualification-from/device-qualification-from.component.scss @@ -0,0 +1,359 @@ +/** + * Copyright 2023 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +@use '@angular/material' as mat; +@import 'src/theming/colors'; +@import 'src/theming/variables'; + +$form-min-width: 732px; + +:host { + container-type: size; + container-name: qualification-form; + + display: grid; + grid-template-rows: 1fr; + overflow: auto; + grid-template-columns: minmax(285px, $form-max-width); + height: 100vh; + max-height: 978px; +} + +.device-qualification-form { + overflow: hidden; +} + +::ng-deep .device-form-test-modules { + overflow: auto; + min-height: 78px; + display: grid; + grid-template-columns: repeat(2, 1fr); + grid-template-rows: repeat(4, 1fr); + grid-auto-flow: column; + padding-top: 16px; + p { + margin: 8px 0; + } +} + +.close-button { + color: $primary; +} + +.device-form-mac-address-error { + white-space: nowrap; +} + +.hidden { + display: none; +} + +.device-qualification-form-header { + position: relative; + padding-top: 24px; + &-title { + margin: 0; + font-size: 32px; + font-style: normal; + font-weight: 400; + line-height: 40px; + color: $grey-800; + text-align: center; + padding: 38px 0; + background-image: url(/assets/icons/create_device_header.svg); + } + + &-close-button { + position: absolute; + right: 0; + top: 0; + min-width: 24px; + width: 24px; + height: 24px; + box-sizing: content-box; + line-height: normal !important; + padding: 0; + margin: 0; + + .close-button-icon { + width: 24px; + height: 24px; + margin: 0; + } + + ::ng-deep * { + line-height: inherit !important; + } + } +} + +.device-qualification-form-journey-label { + font-family: $font-secondary; + font-style: normal; + font-weight: 400; + font-size: 16px; + line-height: 24px; + letter-spacing: 0.1px; + color: $grey-800; + margin: 24px 16px 0 16px; +} + +.device-qualification-form-journey-button { + padding: 0 18px 0 24px; +} + +.device-qualification-form-journey-button-info { + display: flex; +} + +.device-qualification-form-journey-button-label { + font-family: $font-secondary; + font-style: normal; + font-weight: 500; + font-size: 14px; + line-height: 20px; + letter-spacing: 0.2px; + color: $grey-800; +} + +.device-qualification-form-test-modules-container { + padding: 0 24px; +} + +.device-qualification-form-step-title { + margin: 0; + font-style: normal; + font-weight: 500; + font-size: 22px; + line-height: 28px; + text-align: center; + color: $grey-900; + padding: 0 24px; + display: inline-block; + height: 28px; +} + +.device-qualification-form-step-description { + font-family: $font-secondary; + text-align: center; + color: $grey-800; + margin: 0; + padding: 8px 16px 0 16px; +} + +.step-link { + color: $primary; + text-decoration: underline; + cursor: pointer; +} + +.device-qualification-form-step-content { + padding: 0 16px; + overflow: scroll; +} + +.device-qualification-form-page { + padding-top: 10px; + margin-top: -10px; + display: grid; + gap: 8px; + height: 100%; + overflow: hidden; + align-content: start; + &:has(.device-qualification-form-summary-container) { + grid-template-rows: min-content min-content 1fr min-content; + } +} + +.device-qualification-form-summary-container { + display: grid; + align-items: center; + justify-content: center; + overflow: scroll; + ::ng-deep { + .device-item, + .device-qualification-form-summary-info { + width: $device-item-width; + } + } +} + +.device-qualification-form-summary { + border-radius: 12px; + background: mat.m2-get-color-from-palette($color-primary, 50); + padding: 24px; + width: max-content; + margin-left: auto; + margin-right: auto; + margin-top: 24px; + &-error { + background: $red-50; + } +} + +.device-qualification-form-actions { + width: $device-item-width; + text-align: center; + padding: 8px 24px; + justify-self: center; + align-self: end; + &:has(.delete-button) { + text-align: right; + } + .close-button, + .delete-button { + border: 1px solid $lighter-grey; + } + .delete-button { + color: $primary; + float: left; + } + .close-button { + padding: 0 16px; + } + .save-button { + margin-left: 16px; + } +} + +.device-qualification-form-summary-info { + margin-top: 16px; + border-radius: 12px; + padding: 16px 24px; + background: #fff; + box-sizing: border-box; + &-title, + &-title-error { + font-size: 18px; + font-weight: 400; + line-height: 24px; + text-align: center; + color: $grey-900; + } + &-description { + font-family: $font-secondary; + font-size: 16px; + font-weight: 400; + line-height: 24px; + text-align: center; + color: $grey-800; + } + .info-label { + display: block; + font-family: $font-secondary; + font-size: 14px; + font-weight: 400; + line-height: 20px; + text-align: left; + color: $secondary; + } + .info-value { + display: block; + color: $grey-800; + font-family: $font-secondary; + font-size: 16px; + font-weight: 400; + line-height: 24px; + text-align: left; + } +} + +.device-qualification-form-summary-info-title-error { + display: flex; + align-items: center; + height: 48px; + margin: auto; + justify-content: center; + color: $red-800; + gap: 14px; + ::ng-deep mat-icon { + color: $red-800; + } +} + +.device-qualification-form-instructions { + margin-top: auto; + padding-top: 8px; + color: $grey-800; + text-align: center; + font-family: $font-secondary; + font-size: 16px; + width: 510px; + ul { + margin-bottom: 0; + text-align: left; + padding-left: 26px; + } + li { + line-height: 24px; + } +} + +::ng-deep mat-error { + background: $white; +} + +:host mat-form-field { + &::ng-deep.mat-mdc-form-field-subscript-wrapper:has( + mat-error.error-multiline + ) { + height: 46px; + } +} + +.device-qualification-form-test-modules-container-error + ::ng-deep + .device-tests-title { + color: $red-800; +} + +.device-qualification-form-test-modules-error { + padding: 0 24px; +} + +.form-content-summary { + display: grid; + grid-template-rows: 1fr auto; + grid-row-gap: 16px; + overflow: hidden; +} + +@container qualification-form (height < 870px) { + .device-qualification-form-page { + overflow: scroll; + ::ng-deep app-device-tests { + overflow: visible; + } + } +} + +@container qualification-form (height < 580px) { + .device-qualification-form-page { + overflow: scroll; + .device-qualification-form-step-content, + .device-qualification-form-summary-container { + overflow: visible; + } + } +} + +@container qualification-form (width < 360px) { + .manufacturer-field ::ng-deep mat-hint { + white-space: nowrap; + } + ::ng-deep .device-form-test-modules { + display: block; + } +} diff --git a/modules/ui/src/app/pages/devices/components/device-qualification-from/device-qualification-from.component.spec.ts b/modules/ui/src/app/pages/devices/components/device-qualification-from/device-qualification-from.component.spec.ts new file mode 100644 index 000000000..e1b59025b --- /dev/null +++ b/modules/ui/src/app/pages/devices/components/device-qualification-from/device-qualification-from.component.spec.ts @@ -0,0 +1,760 @@ +/** + * Copyright 2023 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { + ComponentFixture, + discardPeriodicTasks, + fakeAsync, + TestBed, + tick, +} from '@angular/core/testing'; + +import { DeviceQualificationFromComponent } from './device-qualification-from.component'; +import { + MAT_DIALOG_DATA, + MatDialogModule, + MatDialogRef, +} from '@angular/material/dialog'; +import { of } from 'rxjs'; +import { NgxMaskDirective, NgxMaskPipe, provideNgxMask } from 'ngx-mask'; +import { MatButtonModule } from '@angular/material/button'; +import { FormArray, ReactiveFormsModule } from '@angular/forms'; +import { MatCheckboxModule } from '@angular/material/checkbox'; +import { MatInputModule } from '@angular/material/input'; +import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; +import { DeviceTestsComponent } from '../../../../components/device-tests/device-tests.component'; +import { SpinnerComponent } from '../../../../components/spinner/spinner.component'; +import { + device, + DEVICES_FORM, + MOCK_TEST_MODULES, +} from '../../../../mocks/device.mock'; +import { MatIconTestingModule } from '@angular/material/icon/testing'; +import { TestRunService } from '../../../../services/test-run.service'; +import { DevicesStore } from '../../devices.store'; +import { provideMockStore } from '@ngrx/store/testing'; +import { FormAction } from '../../devices.component'; +import { DeviceStatus, TestingType } from '../../../../model/device'; +import { Component, Input } from '@angular/core'; +import { QuestionFormat } from '../../../../model/question'; +import { BehaviorSubject } from 'rxjs/internal/BehaviorSubject'; + +describe('DeviceQualificationFromComponent', () => { + let component: DeviceQualificationFromComponent; + let fixture: ComponentFixture; + let compiled: HTMLElement; + const testrunServiceMock: jasmine.SpyObj = + jasmine.createSpyObj('testrunServiceMock', ['fetchQuestionnaireFormat']); + const keyboardEvent = new BehaviorSubject( + new KeyboardEvent('keydown', { code: '' }) + ); + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [FakeDynamicFormComponent], + imports: [ + DeviceQualificationFromComponent, + MatButtonModule, + ReactiveFormsModule, + MatCheckboxModule, + MatInputModule, + MatDialogModule, + BrowserAnimationsModule, + DeviceTestsComponent, + SpinnerComponent, + NgxMaskDirective, + NgxMaskPipe, + MatIconTestingModule, + ], + providers: [ + DevicesStore, + { + provide: MatDialogRef, + useValue: { + keydownEvents: () => keyboardEvent.asObservable(), + close: () => ({}), + }, + }, + { provide: MAT_DIALOG_DATA, useValue: {} }, + { provide: TestRunService, useValue: testrunServiceMock }, + provideNgxMask(), + provideMockStore({}), + ], + }).compileComponents(); + + fixture = TestBed.createComponent(DeviceQualificationFromComponent); + component = fixture.componentInstance; + compiled = fixture.nativeElement as HTMLElement; + + component.data = { + testModules: MOCK_TEST_MODULES, + devices: [], + index: 0, + isCreate: true, + }; + testrunServiceMock.fetchQuestionnaireFormat.and.returnValue( + of(DEVICES_FORM) + ); + }); + + it('should create', () => { + fixture.detectChanges(); + expect(component).toBeTruthy(); + }); + + it('should contain device form', () => { + fixture.detectChanges(); + const form = compiled.querySelector('.device-qualification-form'); + + expect(form).toBeTruthy(); + }); + + it('should fetch devices format', () => { + fixture.detectChanges(); + const getQuestionnaireFormatSpy = spyOn( + component.devicesStore, + 'getQuestionnaireFormat' + ); + component.ngOnInit(); + fixture.detectChanges(); + + expect(getQuestionnaireFormatSpy).toHaveBeenCalled(); + }); + + it('should close dialog on "cancel" click with do data if form has no changes', () => { + fixture.detectChanges(); + const closeSpy = spyOn(component.dialogRef, 'close'); + const closeButton = compiled.querySelector( + '.device-qualification-form-header-close-button' + ) as HTMLButtonElement; + + closeButton?.click(); + + expect(closeSpy).toHaveBeenCalledWith(); + + closeSpy.calls.reset(); + }); + + it('should close dialog on escape', fakeAsync(() => { + const closeSpy = spyOn(component.dialogRef, 'close'); + fixture.detectChanges(); + + keyboardEvent.next(new KeyboardEvent('keydown', { code: 'Escape' })); + + tick(); + + expect(closeSpy).toHaveBeenCalledWith(); + + closeSpy.calls.reset(); + })); + + it('should close dialog on "cancel" click', () => { + fixture.detectChanges(); + component.manufacturer.setValue('test'); + ( + component.deviceQualificationForm.get('steps') as FormArray + ).controls.forEach(control => control.markAsDirty()); + fixture.detectChanges(); + const closeSpy = spyOn(component.dialogRef, 'close'); + const closeButton = compiled.querySelector( + '.device-qualification-form-header-close-button' + ) as HTMLButtonElement; + + closeButton?.click(); + + expect(closeSpy).toHaveBeenCalledWith({ + action: FormAction.Close, + index: 0, + device: { + status: DeviceStatus.VALID, + manufacturer: 'test', + model: '', + mac_addr: '', + test_pack: 'Device Qualification', + type: '', + technology: '', + test_modules: { + udmi: { + enabled: true, + }, + connection: { + enabled: true, + }, + }, + additional_info: [ + { question: 'What type of device is this?', answer: '' }, + { + question: 'Does your device process any sensitive information? ', + answer: '', + }, + { + question: 'Please select the technology this device falls into', + answer: '', + }, + ], + }, + }); + + closeSpy.calls.reset(); + }); + + describe('test modules', () => { + beforeEach(() => { + fixture.detectChanges(); + }); + + it('should be present', () => { + const test = compiled.querySelectorAll('mat-checkbox'); + + expect(test.length).toEqual(2); + }); + + it('should be enabled', () => { + const tests = compiled.querySelectorAll('.device-form-test-modules p'); + + expect(tests[0].classList.contains('disabled')).toEqual(false); + }); + + it('should have error when no modules selected', () => { + component.test_modules.setValue([false, false]); + component.test_modules.markAsTouched(); + fixture.detectChanges(); + const modules = compiled.querySelector( + '.device-qualification-form-test-modules-container-error' + ); + const error = compiled.querySelector( + '.device-qualification-form-test-modules-error' + ); + + expect(modules).toBeTruthy(); + expect(error?.innerHTML.trim()).toEqual( + 'At least one test has to be selected to save a Device.' + ); + }); + }); + + describe('device model', () => { + beforeEach(() => { + fixture.detectChanges(); + }); + + it('should not contain errors when input is correct', () => { + const model: HTMLInputElement = compiled.querySelector( + '.device-qualification-form-model' + ) as HTMLInputElement; + ['model', 'Gebäude', 'jardín'].forEach(value => { + model.value = value; + model.dispatchEvent(new Event('input')); + + const errors = component.model.errors; + const uiValue = model.value; + const formValue = component.model.value; + + expect(uiValue).toEqual(formValue); + expect(errors).toBeNull(); + }); + }); + + it('should have "invalid_format" error when field does not satisfy validation rules', () => { + [ + 'very long value very long value very long value very long value very long value very long value very long value', + 'as&@3$', + ].forEach(value => { + const model: HTMLInputElement = compiled.querySelector( + '.device-qualification-form-model' + ) as HTMLInputElement; + model.value = value; + model.dispatchEvent(new Event('input')); + component.model.markAsTouched(); + fixture.detectChanges(); + + const modelError = compiled.querySelector('mat-error')?.innerHTML; + const error = component.model.hasError('invalid_format'); + + expect(error).toBeTruthy(); + expect(modelError).toContain( + 'The device model name must be a maximum of 28 characters. Only letters, numbers, and accented letters are permitted.' + ); + }); + }); + }); + + describe('device manufacturer', () => { + beforeEach(() => { + fixture.detectChanges(); + }); + + it('should not contain errors when input is correct', () => { + const manufacturer: HTMLInputElement = compiled.querySelector( + '.device-qualification-form-manufacturer' + ) as HTMLInputElement; + ['manufacturer', 'Gebäude', 'jardín'].forEach(value => { + manufacturer.value = value; + manufacturer.dispatchEvent(new Event('input')); + + const errors = component.manufacturer.errors; + const uiValue = manufacturer.value; + const formValue = component.manufacturer.value; + + expect(uiValue).toEqual(formValue); + expect(errors).toBeNull(); + }); + }); + + it('should have "invalid_format" error when field does not satisfy validation', () => { + [ + 'very long value very long value very long value very long value very long value very long value very long value', + 'as&@3$', + ].forEach(value => { + const manufacturer: HTMLInputElement = compiled.querySelector( + '.device-qualification-form-manufacturer' + ) as HTMLInputElement; + manufacturer.value = value; + manufacturer.dispatchEvent(new Event('input')); + component.manufacturer.markAsTouched(); + fixture.detectChanges(); + + const manufacturerError = + compiled.querySelector('mat-error')?.innerHTML; + const error = component.manufacturer.hasError('invalid_format'); + + expect(error).toBeTruthy(); + expect(manufacturerError).toContain( + 'The manufacturer name must be a maximum of 28 characters. Only letters, numbers, and accented letters are permitted.' + ); + }); + }); + }); + + describe('mac address', () => { + beforeEach(() => { + fixture.detectChanges(); + }); + + it('should not be disabled', () => { + expect(component.mac_addr.disabled).toBeFalse(); + }); + + it('should not contain errors when input is correct', () => { + const macAddress: HTMLInputElement = compiled.querySelector( + '.device-qualification-form-mac-address' + ) as HTMLInputElement; + ['07:07:07:07:07:07', ' 07:07:07:07:07:07 '].forEach(value => { + macAddress.value = value; + macAddress.dispatchEvent(new Event('input')); + + const errors = component.mac_addr.errors; + const formValue = component.mac_addr.value; + + expect(macAddress.value).toEqual(formValue); + expect(errors).toBeNull(); + }); + }); + + it('should have "pattern" error when field does not satisfy pattern', () => { + ['value', 'q01e423573c4'].forEach(value => { + const macAddress: HTMLInputElement = compiled.querySelector( + '.device-qualification-form-mac-address' + ) as HTMLInputElement; + macAddress.value = value; + macAddress.dispatchEvent(new Event('input')); + component.mac_addr.markAsTouched(); + fixture.detectChanges(); + + const macAddressError = compiled.querySelector('mat-error')?.innerHTML; + const error = component.mac_addr.hasError('pattern'); + + expect(error).toBeTruthy(); + expect(macAddressError).toContain( + 'Please, check. A MAC address consists of 12 hexadecimal digits (0 to 9, a to f, or A to F).' + ); + }); + }); + + it('should have "has_same_mac_address" error when MAC address is already used', () => { + component.data = { + testModules: MOCK_TEST_MODULES, + devices: [device], + index: 0, + isCreate: true, + }; + component.ngOnInit(); + fixture.detectChanges(); + + const macAddress: HTMLInputElement = compiled.querySelector( + '.device-qualification-form-mac-address' + ) as HTMLInputElement; + macAddress.value = '00:1e:42:35:73:c4'; + macAddress.dispatchEvent(new Event('input')); + component.mac_addr.markAsTouched(); + fixture.detectChanges(); + + const macAddressError = compiled.querySelector('mat-error')?.innerHTML; + const error = component.mac_addr.hasError('has_same_mac_address'); + + expect(error).toBeTruthy(); + expect(macAddressError).toContain( + 'This MAC address is already used for another device in the repository.' + ); + }); + }); + + describe('when device is present', () => { + beforeEach(() => { + component.data = { + devices: [device], + testModules: MOCK_TEST_MODULES, + device: { + status: DeviceStatus.VALID, + manufacturer: 'Delta', + model: 'O3-DIN-CPU', + mac_addr: '00:1e:42:35:73:c4', + test_modules: { + udmi: { + enabled: true, + }, + }, + }, + isCreate: false, + index: 0, + }; + }); + + it('should fill form values with device values', fakeAsync(() => { + fixture.detectChanges(); + + testrunServiceMock.fetchQuestionnaireFormat.and.returnValue( + of(DEVICES_FORM) + ); + + tick(1); + + const model: HTMLInputElement = compiled.querySelector( + '.device-qualification-form-model' + ) as HTMLInputElement; + const manufacturer: HTMLInputElement = compiled.querySelector( + '.device-qualification-form-manufacturer' + ) as HTMLInputElement; + + expect(model.value).toEqual('O3-DIN-CPU'); + expect(manufacturer.value).toEqual('Delta'); + + discardPeriodicTasks(); + })); + }); + + describe('steps', () => { + beforeEach(() => { + fixture.detectChanges(); + }); + + describe('with questionnaire', () => { + it('should have steps', () => { + expect( + (component.deviceQualificationForm.get('steps') as FormArray).controls + .length + ).toEqual(3); + }); + }); + + it('should not save data when fields are empty', () => { + const forwardButton = compiled.querySelector( + '.form-button-forward' + ) as HTMLButtonElement; + const model: HTMLInputElement = compiled.querySelector( + '.device-qualification-form-model' + ) as HTMLInputElement; + const manufacturer: HTMLInputElement = compiled.querySelector( + '.device-qualification-form-manufacturer' + ) as HTMLInputElement; + const macAddress: HTMLInputElement = compiled.querySelector( + '.device-qualification-form-mac-address' + ) as HTMLInputElement; + + ['', ' '].forEach(value => { + model.value = value; + model.dispatchEvent(new Event('input')); + manufacturer.value = value; + manufacturer.dispatchEvent(new Event('input')); + macAddress.value = value; + macAddress.dispatchEvent(new Event('input')); + forwardButton?.click(); + fixture.detectChanges(); + + const requiredErrors = compiled.querySelectorAll('mat-error'); + expect(requiredErrors?.length).toEqual(3); + + requiredErrors.forEach(error => { + expect(error?.innerHTML).toContain('required'); + }); + }); + }); + + describe('happy flow', () => { + beforeEach(() => { + component.model.setValue('model'); + component.manufacturer.setValue('manufacturer'); + component.mac_addr.setValue('07:07:07:07:07:07'); + component.test_modules.setValue([true, true]); + }); + + it('should save device when step is changed', () => { + const forwardButton = compiled.querySelector( + '.form-button-forward' + ) as HTMLButtonElement; + forwardButton.click(); + + expect(component.device).toEqual({ + status: DeviceStatus.VALID, + manufacturer: 'manufacturer', + model: 'model', + mac_addr: '07:07:07:07:07:07', + test_pack: TestingType.Qualification, + type: '', + technology: '', + test_modules: { + udmi: { + enabled: true, + }, + connection: { + enabled: true, + }, + }, + additional_info: [ + { question: 'What type of device is this?', answer: '' }, + { + question: 'Does your device process any sensitive information? ', + answer: '', + }, + { + question: 'Please select the technology this device falls into', + answer: '', + }, + ], + }); + }); + + describe('summary', () => { + beforeEach(() => { + const forwardButton = compiled.querySelector( + '.form-button-forward' + ) as HTMLButtonElement; + forwardButton.click(); // will redirect to 2 step + fixture.detectChanges(); + + const nextForwardButton = compiled.querySelector( + '.form-button-forward' + ) as HTMLButtonElement; + nextForwardButton.click(); //will redirect to summary + + fixture.detectChanges(); + }); + + it('should have device item', () => { + const item = compiled.querySelector('app-device-item'); + expect(item).toBeTruthy(); + }); + + it('should have instructions', () => { + const instructions = compiled.querySelector( + '.device-qualification-form-instructions' + ); + expect(instructions).toBeTruthy(); + }); + + it('should not have instructions when device is editing', () => { + component.data = { + devices: [device], + testModules: MOCK_TEST_MODULES, + device: { + status: DeviceStatus.VALID, + manufacturer: 'Delta', + model: 'O3-DIN-CPU', + mac_addr: '00:1e:42:35:73:c4', + test_modules: { + udmi: { + enabled: true, + }, + }, + }, + isCreate: false, + index: 0, + }; + fixture.detectChanges(); + + const instructions = compiled.querySelector( + '.device-qualification-form-instructions' + ); + expect(instructions).toBeNull(); + }); + + it('should save device', () => { + const saveSpy = spyOn(component.devicesStore, 'saveDevice'); + + component.submit(); + + const args = saveSpy.calls.argsFor(0); + // @ts-expect-error config is in object + expect(args[0].device).toEqual({ + status: DeviceStatus.VALID, + manufacturer: 'manufacturer', + model: 'model', + mac_addr: '07:07:07:07:07:07', + test_pack: 'Device Qualification', + type: '', + technology: '', + test_modules: { + connection: { + enabled: true, + }, + udmi: { + enabled: true, + }, + }, + additional_info: [ + { question: 'What type of device is this?', answer: '' }, + { + question: + 'Does your device process any sensitive information? ', + answer: '', + }, + { + question: 'Please select the technology this device falls into', + answer: '', + }, + ], + }); + expect(saveSpy).toHaveBeenCalled(); + }); + + it('should edit device', () => { + component.data = { + devices: [device], + testModules: MOCK_TEST_MODULES, + device: { + status: DeviceStatus.VALID, + manufacturer: 'Delta', + model: 'O3-DIN-CPU', + mac_addr: '00:1e:42:35:73:c4', + test_modules: { + udmi: { + enabled: true, + }, + }, + }, + isCreate: false, + index: 0, + }; + fixture.detectChanges(); + const editSpy = spyOn(component.devicesStore, 'editDevice'); + + component.submit(); + + const args = editSpy.calls.argsFor(0); + // @ts-expect-error config is in object + expect(args[0].device).toEqual({ + status: DeviceStatus.VALID, + manufacturer: 'manufacturer', + model: 'model', + mac_addr: '07:07:07:07:07:07', + test_pack: 'Device Qualification', + type: '', + technology: '', + test_modules: { + connection: { + enabled: true, + }, + udmi: { + enabled: true, + }, + }, + additional_info: [ + { question: 'What type of device is this?', answer: '' }, + { + question: + 'Does your device process any sensitive information? ', + answer: '', + }, + { + question: 'Please select the technology this device falls into', + answer: '', + }, + ], + }); + expect(editSpy).toHaveBeenCalled(); + }); + }); + }); + + describe('with errors', () => { + beforeEach(() => { + component.data = { + devices: [device], + testModules: MOCK_TEST_MODULES, + device: { + status: DeviceStatus.VALID, + manufacturer: 'Delta', + model: 'O3-DIN-CPU', + mac_addr: '00:1e:42:35:73:c4', + test_modules: { + udmi: { + enabled: true, + }, + }, + }, + isCreate: false, + index: 0, + }; + component.model.setValue(''); + + fixture.detectChanges(); + }); + + describe('summary', () => { + beforeEach(() => { + const forwardButton = compiled.querySelector( + '.form-button-forward' + ) as HTMLButtonElement; + forwardButton.click(); // will redirect to 2 step + fixture.detectChanges(); + + const nextForwardButton = compiled.querySelector( + '.form-button-forward' + ) as HTMLButtonElement; + nextForwardButton.click(); //will redirect to summary + fixture.detectChanges(); + }); + + it('should have error message', () => { + const error = compiled.querySelector( + '.device-qualification-form-summary-info-description' + ); + expect(error?.textContent?.trim()).toEqual( + 'Please go back and correct the errors on Step 1.' + ); + }); + }); + }); + }); +}); + +@Component({ + selector: 'app-dynamic-form', + template: '
', +}) +class FakeDynamicFormComponent { + @Input() format: QuestionFormat[] = []; + @Input() optionKey: string | undefined; +} diff --git a/modules/ui/src/app/pages/devices/components/device-qualification-from/device-qualification-from.component.ts b/modules/ui/src/app/pages/devices/components/device-qualification-from/device-qualification-from.component.ts new file mode 100644 index 000000000..67eb39d4c --- /dev/null +++ b/modules/ui/src/app/pages/devices/components/device-qualification-from/device-qualification-from.component.ts @@ -0,0 +1,574 @@ +/** + * Copyright 2023 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { + AfterViewInit, + Component, + ElementRef, + HostListener, + Inject, + OnDestroy, + OnInit, + ViewChild, +} from '@angular/core'; +import { + AbstractControl, + FormArray, + FormBuilder, + FormGroup, + ReactiveFormsModule, + Validators, +} from '@angular/forms'; +import { DeviceValidators } from '../device-form/device.validators'; +import { + Device, + DeviceQuestionnaireSection, + DeviceStatus, + DeviceView, + TestingType, + TestModule, +} from '../../../../model/device'; +import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; +import { CommonModule } from '@angular/common'; +import { CdkStep, StepperSelectionEvent } from '@angular/cdk/stepper'; +import { StepperComponent } from '../../../../components/stepper/stepper.component'; +import { + MatError, + MatFormField, + MatFormFieldModule, +} from '@angular/material/form-field'; +import { DeviceTestsComponent } from '../../../../components/device-tests/device-tests.component'; +import { MatButtonModule } from '@angular/material/button'; +import { MatInputModule } from '@angular/material/input'; +import { MatSelectModule } from '@angular/material/select'; +import { MatCheckboxModule } from '@angular/material/checkbox'; +import { TextFieldModule } from '@angular/cdk/text-field'; +import { NgxMaskDirective, NgxMaskPipe, provideNgxMask } from 'ngx-mask'; +import { MatIcon } from '@angular/material/icon'; +import { MatRadioButton, MatRadioGroup } from '@angular/material/radio'; +import { ProfileValidators } from '../../../risk-assessment/profile-form/profile.validators'; +import { DevicesStore } from '../../devices.store'; +import { DynamicFormComponent } from '../../../../components/dynamic-form/dynamic-form.component'; +import { filter, skip, Subject, takeUntil, timer } from 'rxjs'; +import { FormAction, FormResponse } from '../../devices.component'; +import { DeviceItemComponent } from '../../../../components/device-item/device-item.component'; +import { ProgramTypeIconComponent } from '../../../../components/program-type-icon/program-type-icon.component'; +import { Question } from '../../../../model/profile'; +import { FormControlType } from '../../../../model/question'; +import { ProgramType } from '../../../../model/program-type'; +import { FocusManagerService } from '../../../../services/focus-manager.service'; + +const MAC_ADDRESS_PATTERN = + '^[\\s]*[a-fA-F0-9]{2}(?:[:][a-fA-F0-9]{2}){5}[\\s]*$'; + +interface DialogData { + title?: string; + device?: Device; + initialDevice?: Device; + devices: Device[]; + testModules: TestModule[]; + index: number; + isCreate: boolean; +} + +@Component({ + selector: 'app-device-qualification-from', + standalone: true, + imports: [ + CdkStep, + StepperComponent, + MatFormField, + DeviceTestsComponent, + MatButtonModule, + CommonModule, + ReactiveFormsModule, + MatInputModule, + MatError, + MatFormFieldModule, + MatSelectModule, + MatCheckboxModule, + TextFieldModule, + NgxMaskDirective, + NgxMaskPipe, + MatIcon, + MatRadioGroup, + MatRadioButton, + DynamicFormComponent, + DeviceItemComponent, + ProgramTypeIconComponent, + ], + providers: [provideNgxMask(), DevicesStore], + templateUrl: './device-qualification-from.component.html', + styleUrl: './device-qualification-from.component.scss', +}) +export class DeviceQualificationFromComponent + implements OnInit, AfterViewInit, OnDestroy +{ + readonly FORM_HEIGHT = 993; + readonly TestingType = TestingType; + readonly DeviceView = DeviceView; + readonly ProgramType = ProgramType; + @ViewChild('stepper') public stepper!: StepperComponent; + testModules: TestModule[] = []; + deviceQualificationForm: FormGroup = this.fb.group({}); + device: Device | undefined; + format: DeviceQuestionnaireSection[] = []; + selectedIndex: number = 0; + typeStep = 1; + typeQuestion = 0; + technologyStep = 1; + technologyQuestion = 2; + + private destroy$: Subject = new Subject(); + + get model() { + return this.getStep(0).get('model') as AbstractControl; + } + + get manufacturer() { + return this.getStep(0).get('manufacturer') as AbstractControl; + } + + get mac_addr() { + return this.getStep(0).get('mac_addr') as AbstractControl; + } + + get test_pack() { + return this.getStep(0).get('test_pack') as AbstractControl; + } + + get type() { + return this.getStep(this.typeStep)?.get( + this.typeQuestion.toString() + ) as AbstractControl; + } + + get technology() { + return this.getStep(this.technologyStep)?.get( + this.technologyQuestion.toString() + ) as AbstractControl; + } + + get test_modules() { + return this.getStep(0).controls['test_modules'] as FormArray; + } + + get formValid() { + return ( + this.deviceQualificationForm.get('steps') as FormArray + ).controls.every(control => (control as FormGroup).valid); + } + + deviceHasNoChanges(device1: Device | undefined, device2: Device | undefined) { + return device1 && device2 && this.compareDevices(device1, device2); + } + + @HostListener('window:resize', ['$event']) + onResize() { + this.setDialogHeight(); + } + + constructor( + private fb: FormBuilder, + private deviceValidators: DeviceValidators, + private profileValidators: ProfileValidators, + public dialogRef: MatDialogRef, + @Inject(MAT_DIALOG_DATA) public data: DialogData, + public devicesStore: DevicesStore, + private element: ElementRef, + private focusService: FocusManagerService + ) { + this.device = data.device; + } + + ngOnInit(): void { + this.createBasicStep(); + this.testModules = this.data.testModules; + + this.devicesStore.questionnaireFormat$.pipe(skip(1)).subscribe(format => { + this.createDeviceForm(format); + this.format = format; + + format.forEach(step => { + step.questions.forEach((question, index) => { + // need to define the step and index of type and technology + if (question.question.toLowerCase().includes('type')) { + this.typeStep = step.step; + this.typeQuestion = index; + } else if (question.question.toLowerCase().includes('technology')) { + this.technologyStep = step.step; + this.technologyQuestion = index; + } + }); + }); + + timer(0) + .pipe(takeUntil(this.destroy$)) + .subscribe(() => { + if (this.data.device) { + this.fillDeviceForm(this.format, this.data.device!); + } + if (this.data.index) { + // previous steps should be marked as interacted + for (let i = 0; i <= this.data.index; i++) { + this.goToStep(i); + } + } + this.dialogRef + .keydownEvents() + .pipe(filter((e: KeyboardEvent) => e.code === 'Escape')) + .subscribe(() => { + this.closeForm(); + }); + }); + }); + + this.devicesStore.getQuestionnaireFormat(); + } + + ngAfterViewInit() { + //set static height for better UX + this.element.nativeElement.style.height = + this.element.nativeElement.offsetHeight + 'px'; + } + + ngOnDestroy() { + this.destroy$.next(true); + this.destroy$.unsubscribe(); + } + + submit(): void { + this.updateDevice(this.device!, () => { + this.dialogRef.close({ + action: FormAction.Save, + device: this.device, + } as FormResponse); + }); + } + + delete(): void { + this.dialogRef.close({ + action: FormAction.Delete, + device: this.createDeviceFromForm(), + index: this.stepper.selectedIndex, + } as FormResponse); + } + + closeForm(): void { + const device1 = this.data.initialDevice; + const device2 = this.createDeviceFromForm(); + if ( + (device1 && device2 && this.compareDevices(device1, device2)) || + (!device1 && this.deviceIsEmpty(device2)) + ) { + this.dialogRef.close(); + } else { + this.dialogRef.close({ + action: FormAction.Close, + device: this.createDeviceFromForm(), + index: this.stepper.selectedIndex, + } as FormResponse); + } + } + + getStep(step: number) { + return (this.deviceQualificationForm.get('steps') as FormArray).controls[ + step + ] as FormGroup; + } + + onStepChange(event: StepperSelectionEvent) { + this.focusService.focusFirstElementInContainer(); + if (event.previouslySelectedStep.completed) { + this.device = this.createDeviceFromForm(); + } + } + + getErrorSteps(): number[] { + const steps: number[] = []; + (this.deviceQualificationForm.get('steps') as FormArray).controls.forEach( + (control, index) => { + if (!control.valid) steps.push(index); + } + ); + return steps; + } + + goToStep(index: number, event?: Event) { + event?.preventDefault(); + this.stepper.selectedIndex = index; + } + + private fillDeviceForm( + format: DeviceQuestionnaireSection[], + device: Device + ): void { + format.forEach(step => { + step.questions.forEach((question, index) => { + const answer = device.additional_info?.find( + answers => answers.question === question.question + )?.answer; + if (answer !== undefined && answer !== null && answer !== '') { + if (question.type === FormControlType.SELECT_MULTIPLE) { + question.options?.forEach((item, idx) => { + if ((answer as number[])?.includes(idx)) { + ( + this.getStep(step.step).get(index.toString()) as FormGroup + ).controls[idx].setValue(true); + } else { + ( + this.getStep(step.step).get(index.toString()) as FormGroup + ).controls[idx].setValue(false); + } + }); + } else { + ( + this.getStep(step.step).get(index.toString()) as AbstractControl + ).setValue(answer || ''); + } + } else { + ( + this.getStep(step.step)?.get(index.toString()) as AbstractControl + )?.markAsTouched(); + } + }); + }); + this.model.setValue(device.model); + this.manufacturer.setValue(device.manufacturer); + this.mac_addr.setValue(device.mac_addr); + + if ( + device.test_pack && + (device.test_pack === TestingType.Qualification || + device.test_pack === TestingType.Pilot) + ) { + this.test_pack.setValue(device.test_pack); + } else { + this.test_pack.setValue(TestingType.Qualification); + } + + this.type?.setValue(device.type); + this.technology?.setValue(device.technology); + } + + private updateDevice(device: Device, callback: () => void) { + if (!this.data.isCreate && this.data.device) { + this.devicesStore.editDevice({ + device, + mac_addr: this.data.device.mac_addr, + onSuccess: callback, + }); + } else { + this.devicesStore.saveDevice({ device, onSuccess: callback }); + } + } + + private createDeviceFromForm(): Device { + const testModules: { [key: string]: { enabled: boolean } } = {}; + this.getStep(0).value.test_modules.forEach( + (enabled: boolean, i: number) => { + testModules[this.testModules[i]?.name] = { + enabled: enabled, + }; + } + ); + + const additionalInfo: Question[] = []; + + this.format.forEach(step => { + step.questions.forEach((question, index) => { + const response: Question = {}; + response.question = question.question; + + if (question.type === FormControlType.SELECT_MULTIPLE) { + const answer: number[] = []; + question.options?.forEach((_, idx) => { + const value = this.getStep(step.step).value[index][idx]; + if (value) { + answer.push(idx); + } + }); + response.answer = answer; + } else { + response.answer = this.getStep(step.step).value[index]?.trim(); + } + additionalInfo.push(response); + }); + }); + + return { + status: DeviceStatus.VALID, + model: this.model.value.trim(), + manufacturer: this.manufacturer.value.trim(), + mac_addr: this.mac_addr.value.trim(), + test_pack: this.test_pack.value, + test_modules: testModules, + type: this.type.value, + technology: this.technology.value, + additional_info: additionalInfo, + } as Device; + } + + private createBasicStep() { + const firstStep = this.fb.group({ + model: [ + '', + [ + this.profileValidators.textRequired(), + this.deviceValidators.deviceStringFormat(), + ], + ], + manufacturer: [ + '', + [ + this.profileValidators.textRequired(), + this.deviceValidators.deviceStringFormat(), + ], + ], + mac_addr: [ + '', + [ + this.profileValidators.textRequired(), + Validators.pattern(MAC_ADDRESS_PATTERN), + this.deviceValidators.differentMACAddress( + this.data.devices, + this.data.device + ), + ], + ], + test_modules: new FormArray( + [], + this.deviceValidators.testModulesRequired() + ), + test_pack: [TestingType.Qualification], + }); + + this.deviceQualificationForm = this.fb.group({ + steps: this.fb.array([firstStep]), + }); + } + + private createDeviceForm(format: DeviceQuestionnaireSection[]) { + format.forEach(() => { + (this.deviceQualificationForm.get('steps') as FormArray).controls.push( + this.createStep() + ); + }); + + // summary step + (this.deviceQualificationForm.get('steps') as FormArray).controls.push( + this.fb.group({}) + ); + } + + private createStep() { + return new FormGroup({}); + } + + private compareDevices(device1: Device, device2: Device) { + if (device1.manufacturer !== device2.manufacturer) { + return false; + } + if (device1.model !== device2.model) { + return false; + } + if (device1.mac_addr !== device2.mac_addr) { + return false; + } + if (device1.type !== device2.type) { + return false; + } + if (device1.technology !== device2.technology) { + return false; + } + if (device1.test_pack !== device2.test_pack) { + return false; + } + const keys1 = Object.keys(device1.test_modules!); + + for (const key of keys1) { + const val1 = device1.test_modules![key]; + const val2 = device2.test_modules![key]; + if (val1.enabled !== val2.enabled) { + return false; + } + } + + if (device1.additional_info) { + for (const question of device1.additional_info) { + if ( + question.answer !== + device2.additional_info?.find( + question2 => question2.question === question.question + )?.answer + ) { + return false; + } + } + } else { + return false; + } + return true; + } + + private deviceIsEmpty(device: Device) { + if (device.manufacturer !== '') { + return false; + } + if (device.model !== '') { + return false; + } + if (device.mac_addr !== '') { + return false; + } + if (device.type !== '') { + return false; + } + if (device.technology !== '') { + return false; + } + if (device.test_pack !== TestingType.Qualification) { + return false; + } + const keys1 = Object.keys(device.test_modules!); + + for (const key of keys1) { + const val1 = device.test_modules![key]; + if (!val1.enabled) { + return false; + } + } + + if (device.additional_info) { + for (const question of device.additional_info) { + if (question.answer !== '') { + return false; + } + } + } else { + return false; + } + return true; + } + + private setDialogHeight(): void { + const windowHeight = window.innerHeight; + if (windowHeight < this.FORM_HEIGHT) { + this.element.nativeElement.style.height = '100vh'; + } else { + this.element.nativeElement.style.height = this.FORM_HEIGHT + 'px'; + } + } +} diff --git a/modules/ui/src/app/pages/devices/devices-routing.module.ts b/modules/ui/src/app/pages/devices/devices-routing.module.ts index 19acb07c9..53ed9ef0a 100644 --- a/modules/ui/src/app/pages/devices/devices-routing.module.ts +++ b/modules/ui/src/app/pages/devices/devices-routing.module.ts @@ -16,8 +16,15 @@ import { NgModule } from '@angular/core'; import { RouterModule, Routes } from '@angular/router'; import { DevicesComponent } from './devices.component'; +import { CanDeactivateGuard } from '../../guards/can-deactivate.guard'; -const routes: Routes = [{ path: '', component: DevicesComponent }]; +const routes: Routes = [ + { + path: '', + component: DevicesComponent, + canDeactivate: [CanDeactivateGuard], + }, +]; @NgModule({ imports: [RouterModule.forChild(routes)], diff --git a/modules/ui/src/app/pages/devices/devices.component.html b/modules/ui/src/app/pages/devices/devices.component.html index aef3730c5..f6567b49e 100644 --- a/modules/ui/src/app/pages/devices/devices.component.html +++ b/modules/ui/src/app/pages/devices/devices.component.html @@ -20,14 +20,15 @@

Devices

- + { const openSpy = spyOn(component.dialog, 'open').and.returnValue({ afterClosed: () => of(true), - } as MatDialogRef); + beforeClosed: () => of(true), + } as MatDialogRef); fixture.detectChanges(); const button = compiled.querySelector( '.device-add-button' @@ -158,15 +150,18 @@ describe('DevicesComponent', () => { expect(button).toBeTruthy(); expect(openSpy).toHaveBeenCalled(); - expect(openSpy).toHaveBeenCalledWith(DeviceFormComponent, { - ariaLabel: 'Create device', + expect(openSpy).toHaveBeenCalledWith(DeviceQualificationFromComponent, { + ariaLabel: 'Create Device', data: { device: null, - title: 'Create device', + initialDevice: undefined, + title: 'Create Device', testModules: [], devices: [device, device, device], + index: 0, + isCreate: true, }, - autoFocus: true, + autoFocus: 'first-tabbable', hasBackdrop: true, disableClose: true, panelClass: 'device-form-dialog', @@ -178,47 +173,25 @@ describe('DevicesComponent', () => { describe('#openDialog', () => { it('should open device dialog on item click', () => { const openSpy = spyOn(component.dialog, 'open').and.returnValue({ - afterClosed: () => of(true), - } as MatDialogRef); + beforeClosed: () => of(true), + } as MatDialogRef); fixture.detectChanges(); - component.openDialog([device], MOCK_TEST_MODULES, device); + component.openDialog([device], MOCK_TEST_MODULES, device, device, true); expect(openSpy).toHaveBeenCalled(); - expect(openSpy).toHaveBeenCalledWith(DeviceFormComponent, { - ariaLabel: 'Edit device', - data: { - device: device, - title: 'Edit device', - devices: [device], - testModules: MOCK_TEST_MODULES, - }, - autoFocus: true, - hasBackdrop: true, - disableClose: true, - panelClass: 'device-form-dialog', - }); - - openSpy.calls.reset(); - }); - - it('should open device dialog with delete-button focus element', () => { - const openSpy = spyOn(component.dialog, 'open').and.returnValue({ - afterClosed: () => of(true), - } as MatDialogRef); - fixture.detectChanges(); - - component.openDialog([device], MOCK_TEST_MODULES, device, true); - - expect(openSpy).toHaveBeenCalledWith(DeviceFormComponent, { + expect(openSpy).toHaveBeenCalledWith(DeviceQualificationFromComponent, { ariaLabel: 'Edit device', data: { device: device, + initialDevice: device, title: 'Edit device', devices: [device], testModules: MOCK_TEST_MODULES, + index: 0, + isCreate: false, }, - autoFocus: '.delete-button', + autoFocus: 'first-tabbable', hasBackdrop: true, disableClose: true, panelClass: 'device-form-dialog', @@ -237,22 +210,67 @@ describe('DevicesComponent', () => { it('should call setIsOpenAddDevice if dialog closes with null', () => { spyOn(component.dialog, 'open').and.returnValue({ - afterClosed: () => of(null), - } as MatDialogRef); + beforeClosed: () => of(null), + } as MatDialogRef); component.openDialog([], MOCK_TEST_MODULES); expect(mockDevicesStore.setIsOpenAddDevice).toHaveBeenCalled(); }); + describe('close dialog', () => { + beforeEach(() => { + component.viewModel$ = of({ + devices: [device, device, device], + selectedDevice: device, + deviceInProgress: null, + testModules: [], + }); + fixture.detectChanges(); + }); + + it('should show device item', fakeAsync(() => { + const item = compiled.querySelectorAll('app-device-item'); + + expect(item.length).toEqual(3); + })); + + it('should open device dialog when dialog return null', () => { + const openDeviceDialogSpy = spyOn(component, 'openDialog'); + spyOn(component.dialog, 'open').and.returnValue({ + beforeClosed: () => of(null), + } as MatDialogRef); + + component.openCloseDialog( + [device], + MOCK_TEST_MODULES, + device, + undefined, + false, + 0, + 0 + ); + + expect(openDeviceDialogSpy).toHaveBeenCalledWith( + [device], + MOCK_TEST_MODULES, + device, + undefined, + false, + 0, + 0 + ); + }); + }); + it('should delete device if dialog closes with object, action delete and selected device', () => { spyOn(component.dialog, 'open').and.returnValue({ - afterClosed: () => + beforeClosed: () => of({ device, action: FormAction.Delete, }), - } as MatDialogRef); + } as MatDialogRef); component.openDialog([device], MOCK_TEST_MODULES, device); @@ -270,18 +288,20 @@ describe('DevicesComponent', () => { fixture.detectChanges(); }); - it('should show device item', fakeAsync(() => { - const item = compiled.querySelectorAll('app-device-item'); - - expect(item.length).toEqual(3); - })); - it('should delete device when dialog return true', () => { spyOn(component.dialog, 'open').and.returnValue({ - afterClosed: () => of(true), + beforeClosed: () => of(true), } as MatDialogRef); - component.openDeleteDialog([device], MOCK_TEST_MODULES, device); + component.openDeleteDialog( + [device], + MOCK_TEST_MODULES, + device, + device, + false, + 0, + 0 + ); const args = mockDevicesStore.deleteDevice.calls.argsFor(0); // @ts-expect-error config is in object @@ -292,16 +312,27 @@ describe('DevicesComponent', () => { it('should open device dialog when dialog return null', () => { const openDeviceDialogSpy = spyOn(component, 'openDialog'); spyOn(component.dialog, 'open').and.returnValue({ - afterClosed: () => of(null), + beforeClosed: () => of(null), } as MatDialogRef); - component.openDeleteDialog([device], MOCK_TEST_MODULES, device); + component.openDeleteDialog( + [device], + MOCK_TEST_MODULES, + device, + device, + false, + 0, + 0 + ); expect(openDeviceDialogSpy).toHaveBeenCalledWith( [device], MOCK_TEST_MODULES, device, - true + device, + false, + 0, + 0 ); }); }); @@ -322,17 +353,21 @@ describe('DevicesComponent', () => { device: device, testModules: MOCK_TEST_MODULES, }, - autoFocus: true, + autoFocus: 'dialog', hasBackdrop: true, disableClose: true, panelClass: 'initiate-test-run-dialog', }); - tick(); + tick(100); + expect(router.url).toBe(Routes.Testing); expect(mockDevicesStore.setStatus).toHaveBeenCalledWith( MOCK_PROGRESS_DATA_IN_PROGRESS ); + expect( + stateServiceMock.focusFirstElementInContainer + ).toHaveBeenCalled(); openSpy.calls.reset(); }); diff --git a/modules/ui/src/app/pages/devices/devices.component.ts b/modules/ui/src/app/pages/devices/devices.component.ts index be172aed3..6bf24431b 100644 --- a/modules/ui/src/app/pages/devices/devices.component.ts +++ b/modules/ui/src/app/pages/devices/devices.component.ts @@ -20,22 +20,36 @@ import { OnInit, ChangeDetectorRef, } from '@angular/core'; -import { MatDialog } from '@angular/material/dialog'; -import { Device, DeviceView, TestModule } from '../../model/device'; +import { MatDialog, MatDialogRef } from '@angular/material/dialog'; import { - DeviceFormComponent, - FormAction, - FormResponse, -} from './components/device-form/device-form.component'; -import { Subject, takeUntil } from 'rxjs'; + Device, + DeviceStatus, + DeviceView, + TestModule, +} from '../../model/device'; +import { map, Subject, takeUntil, timer } from 'rxjs'; import { SimpleDialogComponent } from '../../components/simple-dialog/simple-dialog.component'; import { combineLatest } from 'rxjs/internal/observable/combineLatest'; import { FocusManagerService } from '../../services/focus-manager.service'; import { Routes } from '../../model/routes'; import { Router } from '@angular/router'; -import { timer } from 'rxjs/internal/observable/timer'; import { TestrunInitiateFormComponent } from '../testrun/components/testrun-initiate-form/testrun-initiate-form.component'; import { DevicesStore } from './devices.store'; +import { DeviceQualificationFromComponent } from './components/device-qualification-from/device-qualification-from.component'; +import { Observable } from 'rxjs/internal/Observable'; +import { CanComponentDeactivate } from '../../guards/can-deactivate.guard'; + +export enum FormAction { + Delete = 'Delete', + Close = 'Close', + Save = 'Save', +} + +export interface FormResponse { + device?: Device; + action: FormAction; + index: number; +} @Component({ selector: 'app-device-repository', @@ -43,10 +57,13 @@ import { DevicesStore } from './devices.store'; styleUrls: ['./devices.component.scss'], providers: [DevicesStore], }) -export class DevicesComponent implements OnInit, OnDestroy { +export class DevicesComponent + implements OnInit, OnDestroy, CanComponentDeactivate +{ readonly DeviceView = DeviceView; private destroy$: Subject = new Subject(); viewModel$ = this.devicesStore.viewModel$; + deviceDialog: MatDialogRef | undefined; constructor( private readonly focusManagerService: FocusManagerService, @@ -65,7 +82,11 @@ export class DevicesComponent implements OnInit, OnDestroy { ]) .pipe(takeUntil(this.destroy$)) .subscribe(([devices, isOpenAddDevice, testModules]) => { - if (!devices?.length && isOpenAddDevice) { + if ( + !devices?.filter(device => device.status === DeviceStatus.VALID) + .length && + isOpenAddDevice + ) { this.openDialog(devices, testModules); } }); @@ -76,6 +97,15 @@ export class DevicesComponent implements OnInit, OnDestroy { this.destroy$.unsubscribe(); } + canDeactivate(): Observable { + this.deviceDialog?.componentInstance?.closeForm(); + return this.dialog.afterAllClosed.pipe( + map(() => { + return true; + }) + ); + } + openStartTestrun( selectedDevice: Device, devices: Device[], @@ -88,7 +118,7 @@ export class DevicesComponent implements OnInit, OnDestroy { device: selectedDevice, testModules, }, - autoFocus: true, + autoFocus: 'dialog', hasBackdrop: true, disableClose: true, panelClass: 'initiate-test-run-dialog', @@ -104,7 +134,11 @@ export class DevicesComponent implements OnInit, OnDestroy { window.dataLayer.push({ event: 'successful_testrun_initiation', }); - this.route.navigate([Routes.Testing]); + this.route.navigate([Routes.Testing]).then(() => + timer(100).subscribe(() => { + this.focusManagerService.focusFirstElementInContainer(); + }) + ); } }); } @@ -112,61 +146,125 @@ export class DevicesComponent implements OnInit, OnDestroy { openDialog( devices: Device[] = [], testModules: TestModule[], + initialDevice?: Device, selectedDevice?: Device, - focusDeleteButton = false + isEditDevice = false, + index = 0, + deviceIndex?: number ): void { - const dialogRef = this.dialog.open(DeviceFormComponent, { - ariaLabel: selectedDevice ? 'Edit device' : 'Create device', + this.deviceDialog = this.dialog.open(DeviceQualificationFromComponent, { + ariaLabel: isEditDevice ? 'Edit device' : 'Create Device', data: { device: selectedDevice || null, - title: selectedDevice ? 'Edit device' : 'Create device', + initialDevice, + title: isEditDevice ? 'Edit device' : 'Create Device', testModules: testModules, devices, + index, + isCreate: !isEditDevice, }, - autoFocus: focusDeleteButton ? '.delete-button' : true, + autoFocus: 'first-tabbable', hasBackdrop: true, disableClose: true, panelClass: 'device-form-dialog', }); - - dialogRef - ?.afterClosed() - .pipe(takeUntil(this.destroy$)) - .subscribe((response: FormResponse) => { - this.devicesStore.selectDevice(null); - if (!response) { - this.devicesStore.setIsOpenAddDevice(false); - return; - } - if ( - response.action === FormAction.Save && - response.device && - !selectedDevice - ) { - timer(10) - .pipe(takeUntil(this.destroy$)) - .subscribe(() => { + this.deviceDialog?.beforeClosed().subscribe((response: FormResponse) => { + if (!response) { + this.devicesStore.setIsOpenAddDevice(false); + return; + } + if (response.action === FormAction.Close) { + this.openCloseDialog( + devices, + testModules, + initialDevice, + response.device, + isEditDevice, + response.index, + deviceIndex + ); + } else if (response.action === FormAction.Save && response.device) { + timer(10) + .pipe(takeUntil(this.destroy$)) + .subscribe(() => { + if (!initialDevice) { this.focusManagerService.focusFirstElementInContainer(); - }); - } - if (response.action === FormAction.Delete && selectedDevice) { - this.devicesStore.selectDevice(selectedDevice); - this.openDeleteDialog(devices, testModules, selectedDevice); + } else if (deviceIndex !== undefined) { + this.focusSelectedButton(deviceIndex); + } + }); + } + if (response.action === FormAction.Delete && initialDevice) { + if (response.device) { + this.openDeleteDialog( + devices, + testModules, + initialDevice, + response.device, + isEditDevice, + response.index, + deviceIndex! + ); } - }); + } + }); + } + + openCloseDialog( + devices: Device[], + testModules: TestModule[], + initialDevice?: Device, + device?: Device, + isEditDevice = false, + index = 0, + deviceIndex?: number + ) { + const dialogRef = this.dialog.open(SimpleDialogComponent, { + ariaLabel: 'Close the Device menu', + data: { + title: 'Are you sure?', + content: `By closing the device profile you will loose any new changes you have made to the device.`, + }, + autoFocus: true, + hasBackdrop: true, + disableClose: true, + panelClass: 'simple-dialog', + }); + + dialogRef?.beforeClosed().subscribe(close => { + if (!close) { + this.openDialog( + devices, + testModules, + initialDevice, + device, + isEditDevice, + index, + deviceIndex + ); + } else if (deviceIndex !== undefined) { + this.focusSelectedButton(deviceIndex); + } else { + this.focusManagerService.focusFirstElementInContainer(); + } + }); } openDeleteDialog( devices: Device[], testModules: TestModule[], - device: Device + initialDevice: Device, + device: Device, + isEditDevice = false, + index = 0, + deviceIndex: number ) { const dialogRef = this.dialog.open(SimpleDialogComponent, { ariaLabel: 'Delete device', data: { title: 'Delete device?', content: `You are about to delete ${ - device.manufacturer + ' ' + device.model + initialDevice.manufacturer + ' ' + initialDevice.model }. Are you sure?`, device: device, }, @@ -175,35 +273,45 @@ export class DevicesComponent implements OnInit, OnDestroy { disableClose: true, panelClass: 'simple-dialog', }); - - dialogRef - ?.afterClosed() - .pipe(takeUntil(this.destroy$)) - .subscribe(deleteDevice => { - if (deleteDevice) { - this.devicesStore.deleteDevice({ - device, - onDelete: () => { - this.focusNextButton(); - this.devicesStore.selectDevice(null); - }, - }); - } else { - this.openDialog(devices, testModules, device, true); - this.devicesStore.selectDevice(null); - } - }); + dialogRef?.beforeClosed().subscribe(deleteDevice => { + if (deleteDevice) { + this.devicesStore.deleteDevice({ + device: initialDevice, + onDelete: () => { + this.focusNextButton(deviceIndex); + }, + }); + } else { + this.openDialog( + devices, + testModules, + initialDevice, + device, + isEditDevice, + index, + deviceIndex + ); + } + }); } - private focusNextButton() { - // Try to focus next device item, if exitst - const next = this.element.nativeElement.querySelector( - '.device-item-selected + app-device-item button' - ); + private focusSelectedButton(index: number) { + const selected = this.element.nativeElement.querySelectorAll( + 'app-device-item .button-edit' + )[index]; + if (selected) { + selected.focus(); + } + } + private focusNextButton(index: number) { + this.changeDetectorRef.detectChanges(); + // Try to focus next device item, if exist + const next = this.element.nativeElement.querySelectorAll( + 'app-device-item .button-edit' + )[index]; if (next) { next.focus(); } else { - this.changeDetectorRef.detectChanges(); // If next device item doest not exist, add device button should be focused const addButton = this.element.nativeElement.querySelector('.device-add-button'); diff --git a/modules/ui/src/app/pages/devices/devices.module.ts b/modules/ui/src/app/pages/devices/devices.module.ts index e0110eca3..32f8a2865 100644 --- a/modules/ui/src/app/pages/devices/devices.module.ts +++ b/modules/ui/src/app/pages/devices/devices.module.ts @@ -24,7 +24,6 @@ import { MatDialogModule } from '@angular/material/dialog'; import { MatIconModule } from '@angular/material/icon'; import { MatInputModule } from '@angular/material/input'; import { MatToolbarModule } from '@angular/material/toolbar'; -import { DeviceFormComponent } from './components/device-form/device-form.component'; import { DevicesRoutingModule } from './devices-routing.module'; import { DevicesComponent } from './devices.component'; @@ -35,7 +34,7 @@ import { SimpleDialogComponent } from '../../components/simple-dialog/simple-dia import { NgxMaskDirective, NgxMaskPipe, provideNgxMask } from 'ngx-mask'; @NgModule({ - declarations: [DevicesComponent, DeviceFormComponent], + declarations: [DevicesComponent], imports: [ CommonModule, DevicesRoutingModule, diff --git a/modules/ui/src/app/pages/devices/devices.store.ts b/modules/ui/src/app/pages/devices/devices.store.ts index 19586a1c4..c9d5e9523 100644 --- a/modules/ui/src/app/pages/devices/devices.store.ts +++ b/modules/ui/src/app/pages/devices/devices.store.ts @@ -34,11 +34,13 @@ import { setIsOpenAddDevice, } from '../../store/actions'; import { TestrunStatus } from '../../model/testrun-status'; +import { DeviceQuestionnaireSection } from '../../model/device'; export interface DevicesComponentState { devices: Device[]; selectedDevice: Device | null; testModules: TestModule[]; + questionnaireFormat: DeviceQuestionnaireSection[]; } @Injectable() @@ -46,10 +48,10 @@ export class DevicesStore extends ComponentStore { devices$ = this.store.select(selectDevices); isOpenAddDevice$ = this.store.select(selectIsOpenAddDevice); testModules$ = this.store.select(selectTestModules); + questionnaireFormat$ = this.select(state => state.questionnaireFormat); private deviceInProgress$ = this.store.select(selectDeviceInProgress); private selectedDevice$ = this.select(state => state.selectedDevice); - //testModules = this.testRunService.getTestModules(); viewModel$ = this.select({ devices: this.devices$, selectedDevice: this.selectedDevice$, @@ -62,6 +64,12 @@ export class DevicesStore extends ComponentStore { selectedDevice: device, })); + updateQuestionnaireFormat = this.updater( + (state, questionnaireFormat: DeviceQuestionnaireSection[]) => ({ + ...state, + questionnaireFormat, + }) + ); deleteDevice = this.effect<{ device: Device; onDelete: () => void; @@ -139,6 +147,18 @@ export class DevicesStore extends ComponentStore { ); }); + getQuestionnaireFormat = this.effect(trigger$ => { + return trigger$.pipe( + exhaustMap(() => { + return this.testRunService.fetchQuestionnaireFormat().pipe( + tap((questionnaireFormat: DeviceQuestionnaireSection[]) => { + this.updateQuestionnaireFormat(questionnaireFormat); + }) + ); + }) + ); + }); + private addDevice(device: Device, devices: Device[]): void { this.updateDevices(devices.concat([device])); } @@ -179,6 +199,7 @@ export class DevicesStore extends ComponentStore { devices: [], selectedDevice: null, testModules: [], + questionnaireFormat: [], }); } } diff --git a/modules/ui/src/app/pages/reports/components/delete-report/delete-report.component.html b/modules/ui/src/app/pages/reports/components/delete-report/delete-report.component.html index 7b074ec54..8897dbc75 100644 --- a/modules/ui/src/app/pages/reports/components/delete-report/delete-report.component.html +++ b/modules/ui/src/app/pages/reports/components/delete-report/delete-report.component.html @@ -18,7 +18,9 @@ class="delete-report-button" href="#" matTooltip="Delete report for Testrun # {{ getTestRunId(data) }}" - (click)="deleteReport($event)"> + (click)="deleteReport($event)" + (keydown.enter)="deleteReport($event)" + (keydown.space)="deleteReport($event)"> diff --git a/modules/ui/src/app/pages/reports/components/filter-dialog/filter-dialog.component.scss b/modules/ui/src/app/pages/reports/components/filter-dialog/filter-dialog.component.scss index 6b65c0a82..3041ab183 100644 --- a/modules/ui/src/app/pages/reports/components/filter-dialog/filter-dialog.component.scss +++ b/modules/ui/src/app/pages/reports/components/filter-dialog/filter-dialog.component.scss @@ -40,7 +40,7 @@ min-width: 38px; margin: 0; padding: 0 8px; - color: mat.get-color-from-palette($color-primary, 600); + color: mat.m2-get-color-from-palette($color-primary, 600); font-weight: 500; line-height: 20px; letter-spacing: 0.25px; diff --git a/modules/ui/src/app/pages/reports/reports.component.html b/modules/ui/src/app/pages/reports/reports.component.html index 6fc7017a8..beb538149 100644 --- a/modules/ui/src/app/pages/reports/reports.component.html +++ b/modules/ui/src/app/pages/reports/reports.component.html @@ -44,7 +44,7 @@

Reports

context: { name: FilterName.Started, filterOpened: vm.filterOpened, - activeFilter: vm.activeFilter + activeFilter: vm.activeFilter, } ">
@@ -82,7 +82,7 @@

Reports

context: { name: FilterName.DeviceInfo, filterOpened: vm.filterOpened, - activeFilter: vm.activeFilter + activeFilter: vm.activeFilter, } "> @@ -104,7 +104,7 @@

Reports

context: { name: FilterName.DeviceFirmware, filterOpened: vm.filterOpened, - activeFilter: vm.activeFilter + activeFilter: vm.activeFilter, } "> @@ -112,6 +112,20 @@

Reports

{{ data.deviceFirmware }} + + + + Assessment type + + + {{ data.program }} + + + Reports context: { name: FilterName.Results, filterOpened: vm.filterOpened, - activeFilter: vm.activeFilter + activeFilter: vm.activeFilter, } "> @@ -204,7 +218,7 @@

Reports

context: { header: 'Sorry, there are no reports matching the search criteria.', - message: 'Please consider change or clear filters.' + message: 'Please consider change or clear filters.', } ">
@@ -216,7 +230,9 @@

Reports

mat-row #rowElement [class.report-selected]="rowElement === vm.selectedRow" - (click)="selectRow(rowElement)"> + (click)="selectRow(rowElement)" + (keydown.enter)="selectRow(rowElement)" + (keydown.space)="selectRow(rowElement)">
@@ -231,7 +247,7 @@

Reports

context: { header: 'Sorry, there are no reports yet!', message: - 'Reports will automatically generate following a test attempt completion.' + 'Reports will automatically generate following a test attempt completion.', } ">
diff --git a/modules/ui/src/app/pages/reports/reports.component.scss b/modules/ui/src/app/pages/reports/reports.component.scss index 47d76d880..bea1c4b00 100644 --- a/modules/ui/src/app/pages/reports/reports.component.scss +++ b/modules/ui/src/app/pages/reports/reports.component.scss @@ -94,7 +94,7 @@ } .filter-button.active .mat-icon { - color: mat.get-color-from-palette($color-primary, 600); + color: mat.m2-get-color-from-palette($color-primary, 600); } } @@ -124,10 +124,12 @@ display: flex; align-items: center; justify-content: center; - grid-row: 1/3; } .results-content-empty { + position: absolute; + top: 0; + width: 100%; height: 100%; } diff --git a/modules/ui/src/app/pages/reports/reports.component.spec.ts b/modules/ui/src/app/pages/reports/reports.component.spec.ts index c59cbedfb..4bcf2ebf3 100644 --- a/modules/ui/src/app/pages/reports/reports.component.spec.ts +++ b/modules/ui/src/app/pages/reports/reports.component.spec.ts @@ -13,7 +13,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { ComponentFixture, fakeAsync, TestBed } from '@angular/core/testing'; +import { + ComponentFixture, + fakeAsync, + TestBed, + tick, +} from '@angular/core/testing'; import { ReportsComponent } from './reports.component'; import { TestRunService } from '../../services/test-run.service'; @@ -247,7 +252,7 @@ describe('ReportsComponent', () => { fixture.detectChanges(); }); - it('should focus next active element if exist', () => { + it('should focus next active element if exist', fakeAsync(() => { const row = window.document.querySelector('tbody tr') as HTMLElement; row.classList.add('report-selected'); const nextButton = window.document.querySelector( @@ -257,10 +262,12 @@ describe('ReportsComponent', () => { component.focusNextButton(); + tick(50); + expect(buttonFocusSpy).toHaveBeenCalled(); - }); + })); - it('should focus navigation button if next active element does not exist', () => { + it('should focus navigation button if next active element does not exist', fakeAsync(() => { const button = document.createElement('BUTTON'); button.classList.add('app-sidebar-button-reports'); document.querySelector('body')?.appendChild(button); @@ -268,8 +275,10 @@ describe('ReportsComponent', () => { component.focusNextButton(); + tick(50); + expect(buttonFocusSpy).toHaveBeenCalled(); - }); + })); }); it('#removeDevice should call delete report', () => { @@ -323,6 +332,7 @@ describe('ReportsComponent', () => { green: false, red: true, blue: false, + cyan: false, grey: false, }); component.ngOnInit(); diff --git a/modules/ui/src/app/pages/reports/reports.component.ts b/modules/ui/src/app/pages/reports/reports.component.ts index 32c171ff6..f2cba227d 100644 --- a/modules/ui/src/app/pages/reports/reports.component.ts +++ b/modules/ui/src/app/pages/reports/reports.component.ts @@ -28,7 +28,7 @@ import { } from '../../model/testrun-status'; import { DatePipe } from '@angular/common'; import { MatSort, Sort } from '@angular/material/sort'; -import { Subject, takeUntil } from 'rxjs'; +import { Subject, takeUntil, timer } from 'rxjs'; import { MatRow } from '@angular/material/table'; import { FilterDialogComponent } from './components/filter-dialog/filter-dialog.component'; import { MatDialog } from '@angular/material/dialog'; @@ -152,13 +152,17 @@ export class ReportsComponent implements OnInit, OnDestroy { '.report-selected + tr a' ) as HTMLButtonElement; if (next) { - next.focus(); + timer(50).subscribe(() => { + next.focus(); + }); } else { // If next interactive element doest not exist, add menu reports button should be focused const menuButton = window.document.querySelector( '.app-sidebar-button-reports' ) as HTMLButtonElement; - menuButton?.focus(); + timer(50).subscribe(() => { + menuButton?.focus(); + }); } } diff --git a/modules/ui/src/app/pages/reports/reports.store.spec.ts b/modules/ui/src/app/pages/reports/reports.store.spec.ts index 6e6d1656a..e6b7ec2f3 100644 --- a/modules/ui/src/app/pages/reports/reports.store.spec.ts +++ b/modules/ui/src/app/pages/reports/reports.store.spec.ts @@ -136,6 +136,7 @@ describe('ReportsStore', () => { 'duration', 'deviceInfo', 'deviceFirmware', + 'program', 'status', 'report', ], diff --git a/modules/ui/src/app/pages/reports/reports.store.ts b/modules/ui/src/app/pages/reports/reports.store.ts index 39db74fd2..7b13bf5e9 100644 --- a/modules/ui/src/app/pages/reports/reports.store.ts +++ b/modules/ui/src/app/pages/reports/reports.store.ts @@ -253,6 +253,7 @@ export class ReportsStore extends ComponentStore { deviceFirmware: item.device.firmware, deviceInfo: item.device.manufacturer + ' ' + item.device.model, duration: this.getDuration(item.started, item.finished), + program: item.device.test_pack ?? '', }; }); } @@ -367,6 +368,7 @@ export class ReportsStore extends ComponentStore { 'duration', 'deviceInfo', 'deviceFirmware', + 'program', 'status', 'report', ], diff --git a/modules/ui/src/app/pages/reports/reportscomponent.ts b/modules/ui/src/app/pages/reports/reportscomponent.ts deleted file mode 100644 index 32c171ff6..000000000 --- a/modules/ui/src/app/pages/reports/reportscomponent.ts +++ /dev/null @@ -1,173 +0,0 @@ -/** - * Copyright 2023 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import { - Component, - ElementRef, - OnDestroy, - OnInit, - ViewChild, -} from '@angular/core'; -import { LiveAnnouncer } from '@angular/cdk/a11y'; -import { TestRunService } from '../../services/test-run.service'; -import { - StatusResultClassName, - TestrunStatus, -} from '../../model/testrun-status'; -import { DatePipe } from '@angular/common'; -import { MatSort, Sort } from '@angular/material/sort'; -import { Subject, takeUntil } from 'rxjs'; -import { MatRow } from '@angular/material/table'; -import { FilterDialogComponent } from './components/filter-dialog/filter-dialog.component'; -import { MatDialog } from '@angular/material/dialog'; -import { tap } from 'rxjs/internal/operators/tap'; -import { FilterName, Filters } from '../../model/filters'; -import { ReportsStore } from './reports.store'; - -@Component({ - selector: 'app-history', - templateUrl: './reports.component.html', - styleUrls: ['./reports.component.scss'], - providers: [ReportsStore], -}) -export class ReportsComponent implements OnInit, OnDestroy { - public readonly FilterName = FilterName; - private destroy$: Subject = new Subject(); - @ViewChild(MatSort, { static: false }) sort!: MatSort; - viewModel$ = this.store.viewModel$; - constructor( - private testRunService: TestRunService, - private datePipe: DatePipe, - private liveAnnouncer: LiveAnnouncer, - public dialog: MatDialog, - private store: ReportsStore - ) {} - - ngOnInit() { - this.store.getReports(); - this.store.updateSort(this.sort); - } - - getFormattedDateString(date: string | null) { - return date ? this.datePipe.transform(date, 'd MMM y H:mm') : ''; - } - sortData(sortState: Sort) { - this.store.updateSort(this.sort); - if (sortState.direction) { - this.liveAnnouncer.announce(`Sorted ${sortState.direction}ending`); - } else { - this.liveAnnouncer.announce('Sorting cleared'); - } - } - - public getResultClass(status: string): StatusResultClassName { - return this.testRunService.getResultClass(status); - } - - openFilter(event: Event, filter: string, filterOpened: boolean) { - event.stopPropagation(); - const target = new ElementRef(event.currentTarget); - - if (!filterOpened) { - this.openFilterDialog(target, filter); - } - } - - openFilterDialog(target: ElementRef, filter: string) { - this.store.setFilterOpened(true); - this.store.setActiveFiler(filter); - const dialogRef = this.dialog.open(FilterDialogComponent, { - ariaLabel: 'Filters', - data: { - filter, - trigger: target, - }, - autoFocus: true, - hasBackdrop: true, - disableClose: true, - panelClass: 'filter-form-dialog', - }); - - dialogRef - ?.afterClosed() - .pipe( - takeUntil(this.destroy$), - tap(() => { - this.store.setFilterOpened(false); - this.store.setActiveFiler(''); - }) - ) - .subscribe(filteredData => { - if (filteredData) { - if (filter === FilterName.Results) { - this.store.setFilteredValuesResults(filteredData.results); - } - if (filter === FilterName.DeviceInfo) { - this.store.setFilteredValuesDeviceInfo(filteredData.deviceInfo); - } - if (filter === FilterName.DeviceFirmware) { - this.store.setFilteredValuesDeviceFirmware( - filteredData.deviceFirmware - ); - } - if (filter === FilterName.Started) { - this.store.setFilteredValuesDateRange(filteredData.dateRange); - } - } - }); - } - - filterCleared(filters: Filters) { - this.store.setFilteredValues(filters); - } - - ngOnDestroy() { - this.destroy$.next(true); - this.destroy$.unsubscribe(); - } - - selectRow(row: MatRow) { - this.store.setSelectedRow(row); - } - - trackByStarted(index: number, item: TestrunStatus) { - return item.started; - } - - focusNextButton() { - // Try to focus next interactive element, if exists - const next = window.document.querySelector( - '.report-selected + tr a' - ) as HTMLButtonElement; - if (next) { - next.focus(); - } else { - // If next interactive element doest not exist, add menu reports button should be focused - const menuButton = window.document.querySelector( - '.app-sidebar-button-reports' - ) as HTMLButtonElement; - menuButton?.focus(); - } - } - - removeDevice(data: TestrunStatus) { - this.store.deleteReport({ - mac_addr: data.mac_addr, - deviceMacAddr: data.device.mac_addr, - started: data.started, - }); - this.focusNextButton(); - } -} diff --git a/modules/ui/src/app/pages/risk-assessment/components/success-dialog/success-dialog.component.html b/modules/ui/src/app/pages/risk-assessment/components/success-dialog/success-dialog.component.html new file mode 100644 index 000000000..debcfa36c --- /dev/null +++ b/modules/ui/src/app/pages/risk-assessment/components/success-dialog/success-dialog.component.html @@ -0,0 +1,45 @@ + +Risk Assessment Profile Completed +

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

+

+ The preliminary risk estimation based on your answers is + + {{ data.profile.risk }} risk + + +
{{ getRiskExplanation(data.profile.risk) }} The full report can be found + in the zip file. Please share with the lab to validate this profile and + determine next steps. +

+ + + diff --git a/modules/ui/src/app/pages/risk-assessment/components/success-dialog/success-dialog.component.scss b/modules/ui/src/app/pages/risk-assessment/components/success-dialog/success-dialog.component.scss new file mode 100644 index 000000000..23badf7a4 --- /dev/null +++ b/modules/ui/src/app/pages/risk-assessment/components/success-dialog/success-dialog.component.scss @@ -0,0 +1,73 @@ +/** + * Copyright 2023 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +@import '../../../../../theming/colors'; +@import '../../../../../theming/variables'; + +:host { + display: grid; + overflow: hidden; + width: 570px; + padding: 24px 0 8px 0; + > * { + padding: 0 16px 0 24px; + } +} + +.simple-dialog-title { + font-family: $font-primary; + font-size: 18px; + font-weight: 400; + line-height: 24px; + text-align: left; +} + +.simple-dialog-title + .simple-dialog-content { + margin-top: 0; + padding-top: 0; + border-bottom: 1px solid $lighter-grey; +} + +.simple-dialog-content { + font-family: Roboto, sans-serif; + font-size: 14px; + line-height: 20px; + letter-spacing: 0.2px; + color: $grey-800; + padding: 16px 16px 16px 24px; + margin: 0; +} + +.simple-dialog-actions { + padding: 0; + min-height: 30px; +} + +.simple-dialog-content-risk { + font-weight: bold; + display: inline-flex; + align-items: center; +} + +.profile-item-risk { + display: inline-flex; + align-items: center; + height: 20px; + margin-left: 2px; + font-family: $font-secondary; + font-size: 12px; + font-weight: 400; + letter-spacing: 0.3px; +} diff --git a/modules/ui/src/app/pages/risk-assessment/components/success-dialog/success-dialog.component.spec.ts b/modules/ui/src/app/pages/risk-assessment/components/success-dialog/success-dialog.component.spec.ts new file mode 100644 index 000000000..b3a40c1bc --- /dev/null +++ b/modules/ui/src/app/pages/risk-assessment/components/success-dialog/success-dialog.component.spec.ts @@ -0,0 +1,78 @@ +/** + * Copyright 2023 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { SuccessDialogComponent } from './success-dialog.component'; +import { TestRunService } from '../../../../services/test-run.service'; +import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; +import { of } from 'rxjs'; +import { PROFILE_MOCK } from '../../../../mocks/profile.mock'; +import { ProfileRisk } from '../../../../model/profile'; + +describe('SuccessDialogComponent', () => { + let component: SuccessDialogComponent; + let fixture: ComponentFixture; + const testRunServiceMock = jasmine.createSpyObj(['getRiskClass']); + let compiled: HTMLElement; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [SuccessDialogComponent], + providers: [ + { provide: TestRunService, useValue: testRunServiceMock }, + { + provide: MatDialogRef, + useValue: { + keydownEvents: () => of(new KeyboardEvent('keydown', { code: '' })), + close: () => ({}), + }, + }, + { provide: MAT_DIALOG_DATA, useValue: {} }, + ], + }).compileComponents(); + fixture = TestBed.createComponent(SuccessDialogComponent); + component = fixture.componentInstance; + component.data = { + profile: PROFILE_MOCK, + }; + compiled = fixture.nativeElement as HTMLElement; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); + + it('should close dialog on "cancel" click', () => { + const closeSpy = spyOn(component.dialogRef, 'close'); + const confirmButton = compiled.querySelector( + '.confirm-button' + ) as HTMLButtonElement; + + confirmButton?.click(); + + expect(closeSpy).toHaveBeenCalled(); + + closeSpy.calls.reset(); + }); + + it('should return proper text for risk', () => { + expect(component.getRiskExplanation(ProfileRisk.LIMITED)).toEqual(''); + expect(component.getRiskExplanation(ProfileRisk.HIGH)).toEqual( + 'An additional assessment may be required.' + ); + }); +}); diff --git a/modules/ui/src/app/pages/risk-assessment/components/success-dialog/success-dialog.component.ts b/modules/ui/src/app/pages/risk-assessment/components/success-dialog/success-dialog.component.ts new file mode 100644 index 000000000..e7ce23ff3 --- /dev/null +++ b/modules/ui/src/app/pages/risk-assessment/components/success-dialog/success-dialog.component.ts @@ -0,0 +1,65 @@ +/** + * Copyright 2023 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { Component, Inject } from '@angular/core'; +import { + MAT_DIALOG_DATA, + MatDialogModule, + MatDialogRef, +} from '@angular/material/dialog'; +import { MatButtonModule } from '@angular/material/button'; +import { EscapableDialogComponent } from '../../../../components/escapable-dialog/escapable-dialog.component'; +import { + Profile, + ProfileRisk, + RiskResultClassName, +} from '../../../../model/profile'; +import { TestRunService } from '../../../../services/test-run.service'; +import { CommonModule } from '@angular/common'; + +interface DialogData { + profile: Profile; +} + +@Component({ + selector: 'app-success-dialog', + templateUrl: './success-dialog.component.html', + styleUrls: ['./success-dialog.component.scss'], + standalone: true, + imports: [MatDialogModule, MatButtonModule, CommonModule], +}) +export class SuccessDialogComponent extends EscapableDialogComponent { + constructor( + private readonly testRunService: TestRunService, + public override dialogRef: MatDialogRef, + @Inject(MAT_DIALOG_DATA) public data: DialogData + ) { + super(dialogRef); + } + + confirm() { + this.dialogRef.close(); + } + + public getRiskClass(riskResult: string): RiskResultClassName { + return this.testRunService.getRiskClass(riskResult); + } + + getRiskExplanation(risk: string | undefined) { + return risk === ProfileRisk.HIGH + ? 'An additional assessment may be required.' + : ''; + } +} diff --git a/modules/ui/src/app/pages/risk-assessment/profile-form/profile-form.component.html b/modules/ui/src/app/pages/risk-assessment/profile-form/profile-form.component.html index 50f0bf3c7..dc1b0a330 100644 --- a/modules/ui/src/app/pages/risk-assessment/profile-form/profile-form.component.html +++ b/modules/ui/src/app/pages/risk-assessment/profile-form/profile-form.component.html @@ -15,11 +15,11 @@ -->
-

Profile name *

+

Profile name *

+ class="profile-form-field"> Specify risk assessment profile name Required for saving a profile @@ -43,21 +43,7 @@ - - - +
@@ -92,239 +78,3 @@ Close
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - {{ description }} - - Please, check. “ and \ are not allowed. - - - The field is required - - - The field must be a maximum of - {{ getControl(formControlName).getError('maxlength').requiredLength }} - characters. - - - - - - - - {{ description }} - - Please, check. “ and \ are not allowed. - - - The field is required - - - The field must be a maximum of - {{ getControl(formControlName).getError('maxlength').requiredLength }} - characters. - - - - - - - - {{ description }} - - The field is required - - - Please, check the email address. Valid e-mail can contain only latin - letters, numbers, @ and . (dot). - - - The field must be a maximum of - {{ getControl(formControlName).getError('maxlength').requiredLength }} - characters. - - - - - -
-

- - {{ option }} - -

- {{ - description - }} -
-
- - - - - - {{ option }} - - - {{ - description - }} - - The field is required - - - diff --git a/modules/ui/src/app/pages/risk-assessment/profile-form/profile-form.component.scss b/modules/ui/src/app/pages/risk-assessment/profile-form/profile-form.component.scss index 4fed0e420..1e4ad721b 100644 --- a/modules/ui/src/app/pages/risk-assessment/profile-form/profile-form.component.scss +++ b/modules/ui/src/app/pages/risk-assessment/profile-form/profile-form.component.scss @@ -25,41 +25,20 @@ .profile-form { overflow: scroll; + + .name-field-label { + padding-top: 0; + } .field-container { display: flex; flex-direction: column; align-items: flex-start; padding: 8px 16px 8px 24px; } - .field-label { - margin: 0; - color: $grey-800; - font-size: 18px; - line-height: 24px; - padding-top: 24px; - padding-bottom: 16px; - &:first-child { - padding-top: 0; - } - &:has(+ .field-select-multiple.ng-invalid.ng-dirty) { - color: mat.get-color-from-palette($color-warn, 700); - } - } - mat-form-field { + + .profile-form-field { width: 100%; } - .field-hint { - font-family: $font-secondary; - font-size: 12px; - font-weight: 400; - line-height: 16px; - text-align: left; - padding-top: 8px; - } -} - -.profile-form-field { - width: 100%; } .profile-form-field ::ng-deep .mat-mdc-form-field-textarea-control { @@ -77,19 +56,9 @@ color: $primary; } -.field-select-multiple { - .field-select-checkbox { - &:has(::ng-deep .mat-mdc-checkbox-checked) { - background: mat.get-color-from-palette($color-primary, 50); - } - ::ng-deep .mdc-checkbox__ripple { - display: none; - } - &:first-of-type { - margin-top: 0; - } - &:last-of-type { - margin-bottom: 8px; - } - } +.save-profile-button:not(.mat-mdc-button-disabled), +.save-draft-button:not(.mat-mdc-button-disabled), +.discard-button:not(.mat-mdc-button-disabled) { + cursor: pointer; + pointer-events: auto; } diff --git a/modules/ui/src/app/pages/risk-assessment/profile-form/profile-form.component.spec.ts b/modules/ui/src/app/pages/risk-assessment/profile-form/profile-form.component.spec.ts index 54a87e3b9..237f09160 100644 --- a/modules/ui/src/app/pages/risk-assessment/profile-form/profile-form.component.spec.ts +++ b/modules/ui/src/app/pages/risk-assessment/profile-form/profile-form.component.spec.ts @@ -28,7 +28,7 @@ import { PROFILE_MOCK_3, RENAME_PROFILE_MOCK, } from '../../../mocks/profile.mock'; -import { FormControlType, ProfileStatus } from '../../../model/profile'; +import { ProfileStatus } from '../../../model/profile'; describe('ProfileFormComponent', () => { let component: ProfileFormComponent; @@ -146,175 +146,6 @@ describe('ProfileFormComponent', () => { }); }); - PROFILE_FORM.forEach((item, index) => { - const uiIndex = index + 1; // as Profile name is at 0 position, the json items start from 1 i - - it(`should have form field with specific type"`, () => { - const fields = compiled.querySelectorAll('.profile-form-field'); - - if (item.type === FormControlType.SELECT) { - const select = fields[uiIndex].querySelector('mat-select'); - expect(select).toBeTruthy(); - } else if (item.type === FormControlType.SELECT_MULTIPLE) { - const select = fields[uiIndex].querySelector('mat-checkbox'); - expect(select).toBeTruthy(); - } else if (item.type === FormControlType.TEXTAREA) { - const input = fields[uiIndex]?.querySelector('textarea'); - expect(input).toBeTruthy(); - } else { - const input = fields[uiIndex]?.querySelector('input'); - expect(input).toBeTruthy(); - } - }); - - it('should have label', () => { - const labels = compiled.querySelectorAll('.field-label'); - const uiIndex = index + 1; // as Profile name is at 0 position, the json items start from 1 i - - const label = item?.validation?.required - ? item.question + ' *' - : item.question; - expect(labels[uiIndex].textContent?.trim()).toEqual(label); - }); - - it('should have hint', () => { - const fields = compiled.querySelectorAll('.profile-form-field'); - const uiIndex = index + 1; // as Profile name is at 0 position, the json items start from 1 i - const hint = fields[uiIndex].querySelector('mat-hint'); - - if (item.description) { - expect(hint?.textContent?.trim()).toEqual(item.description); - } else { - expect(hint).toBeNull(); - } - }); - - if (item.type === FormControlType.SELECT) { - describe('select', () => { - it(`should have default value if provided`, () => { - const fields = compiled.querySelectorAll('.profile-form-field'); - const select = fields[uiIndex].querySelector('mat-select'); - expect(select?.textContent?.trim()).toEqual(item.default || ''); - }); - - it('should have "required" error when field is not filled', () => { - const fields = compiled.querySelectorAll('.profile-form-field'); - - component.getControl(index).setValue(''); - component.getControl(index).markAsTouched(); - - fixture.detectChanges(); - - const error = fields[uiIndex].querySelector('mat-error')?.innerHTML; - - expect(error).toContain('The field is required'); - }); - }); - } - - if (item.type === FormControlType.SELECT_MULTIPLE) { - describe('select multiple', () => { - it(`should mark form group as dirty while tab navigation`, () => { - const fields = compiled.querySelectorAll('.profile-form-field'); - const checkbox = fields[uiIndex].querySelector( - '.field-select-checkbox:last-of-type mat-checkbox' - ); - checkbox?.dispatchEvent( - new KeyboardEvent('keydown', { key: 'Tab' }) - ); - fixture.detectChanges(); - - expect(component.getControl(index).dirty).toBeTrue(); - }); - }); - } - - if ( - item.type === FormControlType.TEXT || - item.type === FormControlType.TEXTAREA || - item.type === FormControlType.EMAIL_MULTIPLE - ) { - describe('text or text-long or email-multiple', () => { - if (item.validation?.required) { - it('should have "required" error when field is not filled', () => { - const fields = compiled.querySelectorAll('.profile-form-field'); - const uiIndex = index + 1; // as Profile name is at 0 position, the json items start from 1 i - const input = fields[uiIndex].querySelector( - '.mat-mdc-input-element' - ) as HTMLInputElement; - ['', ' '].forEach(value => { - input.value = value; - input.dispatchEvent(new Event('input')); - component.getControl(index).markAsTouched(); - fixture.detectChanges(); - const errors = fields[uiIndex].querySelectorAll('mat-error'); - let hasError = false; - errors.forEach(error => { - if (error.textContent === 'The field is required') { - hasError = true; - } - }); - - expect(hasError).toBeTrue(); - }); - }); - } - - it('should have "invalid_format" error when field does not satisfy validation rules', () => { - const fields = compiled.querySelectorAll('.profile-form-field'); - const uiIndex = index + 1; // as Profile name is at 0 position, the json items start from 1 i - const input: HTMLInputElement = fields[uiIndex].querySelector( - '.mat-mdc-input-element' - ) as HTMLInputElement; - input.value = 'as\\\\\\\\\\""""""""'; - input.dispatchEvent(new Event('input')); - component.getControl(index).markAsTouched(); - fixture.detectChanges(); - const result = - item.type === FormControlType.EMAIL_MULTIPLE - ? 'Please, check the email address. Valid e-mail can contain only latin letters, numbers, @ and . (dot).' - : 'Please, check. “ and \\ are not allowed.'; - const errors = fields[uiIndex].querySelectorAll('mat-error'); - let hasError = false; - errors.forEach(error => { - if (error.textContent === result) { - hasError = true; - } - }); - - expect(hasError).toBeTrue(); - }); - - if (item.validation?.max) { - it('should have "maxlength" error when field is exceeding max length', () => { - const fields = compiled.querySelectorAll('.profile-form-field'); - const uiIndex = index + 1; // as Profile name is at 0 position, the json items start from 1 i - const input: HTMLInputElement = fields[uiIndex].querySelector( - '.mat-mdc-input-element' - ) as HTMLInputElement; - input.value = - 'very long value very long value very long value very long value very long value very long value very long value very long value very long value very long value'; - input.dispatchEvent(new Event('input')); - component.getControl(index).markAsTouched(); - fixture.detectChanges(); - - const errors = fields[uiIndex].querySelectorAll('mat-error'); - let hasError = false; - errors.forEach(error => { - if ( - error.textContent === - `The field must be a maximum of ${item.validation?.max} characters.` - ) { - hasError = true; - } - }); - expect(hasError).toBeTrue(); - }); - } - }); - } - }); - describe('Draft button', () => { it('should be disabled when profile name is empty', () => { component.nameControl.setValue(''); diff --git a/modules/ui/src/app/pages/risk-assessment/profile-form/profile-form.component.ts b/modules/ui/src/app/pages/risk-assessment/profile-form/profile-form.component.ts index 567eb6c34..fe57d8867 100644 --- a/modules/ui/src/app/pages/risk-assessment/profile-form/profile-form.component.ts +++ b/modules/ui/src/app/pages/risk-assessment/profile-form/profile-form.component.ts @@ -16,6 +16,7 @@ import { CdkTextareaAutosize, TextFieldModule } from '@angular/cdk/text-field'; import { afterNextRender, + AfterViewInit, ChangeDetectionStrategy, Component, EventEmitter, @@ -39,19 +40,19 @@ import { FormGroup, ReactiveFormsModule, ValidatorFn, - Validators, } from '@angular/forms'; import { MatInputModule } from '@angular/material/input'; import { DeviceValidators } from '../../devices/components/device-form/device.validators'; import { - FormControlType, Profile, ProfileFormat, ProfileStatus, Question, - Validation, } from '../../../model/profile'; +import { FormControlType } from '../../../model/question'; import { ProfileValidators } from './profile.validators'; +import { DynamicFormComponent } from '../../../components/dynamic-form/dynamic-form.component'; +import { CdkTrapFocus } from '@angular/cdk/a11y'; @Component({ selector: 'app-profile-form', @@ -66,17 +67,18 @@ import { ProfileValidators } from './profile.validators'; MatSelectModule, MatCheckboxModule, TextFieldModule, + DynamicFormComponent, ], templateUrl: './profile-form.component.html', styleUrl: './profile-form.component.scss', + hostDirectives: [CdkTrapFocus], changeDetection: ChangeDetectionStrategy.OnPush, }) -export class ProfileFormComponent implements OnInit { +export class ProfileFormComponent implements OnInit, AfterViewInit { private profile: Profile | null = null; private profileList!: Profile[]; private injector = inject(Injector); private nameValidator!: ValidatorFn; - public readonly FormControlType = FormControlType; public readonly ProfileStatus = ProfileStatus; profileForm: FormGroup = this.fb.group({}); @ViewChildren(CdkTextareaAutosize) @@ -112,9 +114,12 @@ export class ProfileFormComponent implements OnInit { private fb: FormBuilder ) {} ngOnInit() { - this.profileForm = this.createProfileForm(this.profileFormat); + this.profileForm = this.createProfileForm(); + } + + ngAfterViewInit(): void { if (this.selectedProfile) { - this.fillProfileForm(this.profileFormat, this.selectedProfile); + this.fillProfileForm(this.profileFormat, this.selectedProfile!); } } @@ -139,7 +144,7 @@ export class ProfileFormComponent implements OnInit { return this.profileForm.get(name.toString()) as AbstractControl; } - createProfileForm(questions: ProfileFormat[]): FormGroup { + createProfileForm(): FormGroup { // eslint-disable-next-line @typescript-eslint/no-explicit-any const group: any = {}; @@ -154,52 +159,9 @@ export class ProfileFormComponent implements OnInit { this.nameValidator, ]); - questions.forEach((question, index) => { - if (question.type === FormControlType.SELECT_MULTIPLE) { - group[index] = this.getMultiSelectGroup(question); - } else { - const validators = this.getValidators( - question.type, - question.validation - ); - group[index] = new FormControl(question.default || '', validators); - } - }); return new FormGroup(group); } - getValidators(type: FormControlType, validation?: Validation): ValidatorFn[] { - const validators: ValidatorFn[] = []; - if (validation) { - if (validation.required) { - validators.push(this.profileValidators.textRequired()); - } - if (validation.max) { - validators.push(Validators.maxLength(Number(validation.max))); - } - if (type === FormControlType.EMAIL_MULTIPLE) { - validators.push(this.profileValidators.emailStringFormat()); - } - if (type === FormControlType.TEXT || type === FormControlType.TEXTAREA) { - validators.push(this.profileValidators.textFormat()); - } - } - return validators; - } - - getMultiSelectGroup(question: ProfileFormat): FormGroup { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const group: any = {}; - question.options?.forEach((option, index) => { - group[index] = false; - }); - return this.fb.group(group, { - validators: question.validation?.required - ? [this.profileValidators.multiSelectRequired] - : [], - }); - } - getFormGroup(name: string | number): FormGroup { return this.profileForm?.controls[name] as FormGroup; } @@ -236,16 +198,6 @@ export class ProfileFormComponent implements OnInit { this.saveProfile.emit(response); } - public markSectionAsDirty( - optionIndex: number, - optionLength: number, - formControlName: string - ) { - if (optionIndex === optionLength - 1) { - this.getControl(formControlName).markAsDirty(); - } - } - onDiscardClick() { this.discard.emit(); } diff --git a/modules/ui/src/app/pages/risk-assessment/profile-item/profile-item.component.html b/modules/ui/src/app/pages/risk-assessment/profile-item/profile-item.component.html index 31049cd93..41f90de38 100644 --- a/modules/ui/src/app/pages/risk-assessment/profile-item/profile-item.component.html +++ b/modules/ui/src/app/pages/risk-assessment/profile-item/profile-item.component.html @@ -28,8 +28,10 @@ ? EXPIRED_TOOLTIP : profile.status }}" + [attr.aria-label]="getProfileItemLabel(profile)" (click)="profileClicked.emit(profile)" - (keydown.enter)="enterProfileItem(profile)"> + (keydown.enter)="enterProfileItem(profile)" + (keydown.space)="enterProfileItem(profile)"> + (click)="copyProfileClicked.emit(profile)" + (keydown.enter)="copyProfileClicked.emit(profile)" + (keydown.space)="copyProfileClicked.emit(profile)"> content_copy diff --git a/modules/ui/src/app/pages/risk-assessment/profile-item/profile-item.component.ts b/modules/ui/src/app/pages/risk-assessment/profile-item/profile-item.component.ts index 514cbd46e..6ddbe06e9 100644 --- a/modules/ui/src/app/pages/risk-assessment/profile-item/profile-item.component.ts +++ b/modules/ui/src/app/pages/risk-assessment/profile-item/profile-item.component.ts @@ -29,7 +29,7 @@ import { } from '../../../model/profile'; import { MatIcon } from '@angular/material/icon'; import { MatButtonModule } from '@angular/material/button'; -import { CommonModule } from '@angular/common'; +import { CommonModule, DatePipe } from '@angular/common'; import { TestRunService } from '../../../services/test-run.service'; import { MatTooltip, MatTooltipModule } from '@angular/material/tooltip'; import { LiveAnnouncer } from '@angular/cdk/a11y'; @@ -38,7 +38,7 @@ import { LiveAnnouncer } from '@angular/cdk/a11y'; selector: 'app-profile-item', standalone: true, imports: [MatIcon, MatButtonModule, CommonModule, MatTooltipModule], - providers: [MatTooltip], + providers: [MatTooltip, DatePipe], templateUrl: './profile-item.component.html', styleUrl: './profile-item.component.scss', changeDetection: ChangeDetectionStrategy.OnPush, @@ -63,7 +63,8 @@ export class ProfileItemComponent { constructor( private readonly testRunService: TestRunService, - private liveAnnouncer: LiveAnnouncer + private liveAnnouncer: LiveAnnouncer, + private datePipe: DatePipe ) {} public getRiskClass(riskResult: string): RiskResultClassName { @@ -82,4 +83,13 @@ export class ProfileItemComponent { this.profileClicked.emit(profile); } } + + getProfileItemLabel(profile: Profile) { + return `${profile.status} ${profile.risk} risk ${profile.name} ${this.datePipe.transform(profile.created, 'dd MMM yyyy')}`; + } + + delete(event: Event, name: string) { + event.preventDefault(); + this.deleteButtonClicked.emit(name); + } } diff --git a/modules/ui/src/app/pages/risk-assessment/risk-assessment.component.scss b/modules/ui/src/app/pages/risk-assessment/risk-assessment.component.scss index c7241c6c4..90f8a3a41 100644 --- a/modules/ui/src/app/pages/risk-assessment/risk-assessment.component.scss +++ b/modules/ui/src/app/pages/risk-assessment/risk-assessment.component.scss @@ -22,6 +22,8 @@ } .risk-assessment-content-empty { + position: absolute; + top: 0; height: 100%; width: calc(100%); display: flex; diff --git a/modules/ui/src/app/pages/risk-assessment/risk-assessment.component.spec.ts b/modules/ui/src/app/pages/risk-assessment/risk-assessment.component.spec.ts index 8e792ff83..0b6d70210 100644 --- a/modules/ui/src/app/pages/risk-assessment/risk-assessment.component.spec.ts +++ b/modules/ui/src/app/pages/risk-assessment/risk-assessment.component.spec.ts @@ -48,6 +48,7 @@ describe('RiskAssessmentComponent', () => { const mockLiveAnnouncer: SpyObj = jasmine.createSpyObj([ 'announce', + 'clear', ]); let compiled: HTMLElement; @@ -171,15 +172,13 @@ describe('RiskAssessmentComponent', () => { tick(); expect(openSpy).toHaveBeenCalledWith(SimpleDialogComponent, { - ariaLabel: 'Delete risk profile', data: { title: 'Delete risk profile?', content: `You are about to delete ${PROFILE_MOCK.name}. Are you sure?`, }, - autoFocus: true, + autoFocus: 'dialog', hasBackdrop: true, disableClose: true, - panelClass: 'simple-dialog', }); openSpy.calls.reset(); @@ -236,10 +235,13 @@ describe('RiskAssessmentComponent', () => { }); it('should call store saveProfile when it is new profile', () => { - expect(mockRiskAssessmentStore.saveProfile).toHaveBeenCalledWith({ + const args = mockRiskAssessmentStore.saveProfile.calls.argsFor(0); + // @ts-expect-error config is in object + expect(args[0].profile).toEqual({ name: 'test', questions: [], }); + expect(mockRiskAssessmentStore.saveProfile).toHaveBeenCalled(); }); it('should close the form', () => { @@ -256,15 +258,13 @@ describe('RiskAssessmentComponent', () => { component.saveProfileClicked(NEW_PROFILE_MOCK, PROFILE_MOCK); expect(openSpy).toHaveBeenCalledWith(SimpleDialogComponent, { - ariaLabel: 'Save profile', data: { title: 'Save profile', content: `You are about to save changes in Primary profile. Are you sure?`, }, - autoFocus: true, + autoFocus: 'dialog', hasBackdrop: true, disableClose: true, - panelClass: 'simple-dialog', }); openSpy.calls.reset(); @@ -278,22 +278,20 @@ describe('RiskAssessmentComponent', () => { component.saveProfileClicked(NEW_PROFILE_MOCK_DRAFT, PROFILE_MOCK); expect(openSpy).toHaveBeenCalledWith(SimpleDialogComponent, { - ariaLabel: 'Save draft profile', data: { title: 'Save draft profile', content: `You are about to save changes in Primary profile. Are you sure?`, }, - autoFocus: true, + autoFocus: 'dialog', hasBackdrop: true, disableClose: true, - panelClass: 'simple-dialog', }); openSpy.calls.reset(); })); it('should call store saveProfile', fakeAsync(() => { - spyOn(component.dialog, 'open').and.returnValue({ + const openSpy = spyOn(component.dialog, 'open').and.returnValue({ afterClosed: () => of(true), } as MatDialogRef); @@ -301,9 +299,23 @@ describe('RiskAssessmentComponent', () => { tick(); - expect(mockRiskAssessmentStore.saveProfile).toHaveBeenCalledWith( - NEW_PROFILE_MOCK - ); + const args = mockRiskAssessmentStore.saveProfile.calls.argsFor(0); + // @ts-expect-error config is in object + expect(args[0].profile).toEqual(NEW_PROFILE_MOCK); + expect(mockRiskAssessmentStore.saveProfile).toHaveBeenCalled(); + openSpy.calls.reset(); + })); + + it('should call store saveProfile and should not open save draft profile modal when profile does not have changes', fakeAsync(() => { + const openSpy = spyOn(component.dialog, 'open').and.returnValue({ + afterClosed: () => of(true), + } as MatDialogRef); + + component.saveProfileClicked(PROFILE_MOCK, PROFILE_MOCK); + + expect(openSpy).not.toHaveBeenCalled(); + expect(mockRiskAssessmentStore.saveProfile).toHaveBeenCalled(); + openSpy.calls.reset(); })); it('should close the form', fakeAsync(() => { @@ -337,15 +349,16 @@ describe('RiskAssessmentComponent', () => { }); describe('with selected profile', () => { - beforeEach(() => { + beforeEach(fakeAsync(() => { component.discard(PROFILE_MOCK); - }); + tick(100); + })); - it('should call setFocusOnCreateButton', () => { + it('should call setFocusOnCreateButton', fakeAsync(() => { expect( mockRiskAssessmentStore.setFocusOnSelectedProfile ).toHaveBeenCalled(); - }); + })); it('should update selected profile', () => { expect( diff --git a/modules/ui/src/app/pages/risk-assessment/risk-assessment.component.ts b/modules/ui/src/app/pages/risk-assessment/risk-assessment.component.ts index dd3d33d9d..b6eae14e2 100644 --- a/modules/ui/src/app/pages/risk-assessment/risk-assessment.component.ts +++ b/modules/ui/src/app/pages/risk-assessment/risk-assessment.component.ts @@ -18,15 +18,17 @@ import { Component, OnDestroy, OnInit, + ViewContainerRef, } from '@angular/core'; import { RiskAssessmentStore } from './risk-assessment.store'; import { SimpleDialogComponent } from '../../components/simple-dialog/simple-dialog.component'; -import { Subject, takeUntil } from 'rxjs'; +import { Subject, takeUntil, timer } from 'rxjs'; import { MatDialog } from '@angular/material/dialog'; import { LiveAnnouncer } from '@angular/cdk/a11y'; import { Profile, ProfileStatus } from '../../model/profile'; import { Observable } from 'rxjs/internal/Observable'; import { DeviceValidators } from '../devices/components/device-form/device.validators'; +import { SuccessDialogComponent } from './components/success-dialog/success-dialog.component'; @Component({ selector: 'app-risk-assessment', @@ -42,7 +44,8 @@ export class RiskAssessmentComponent implements OnInit, OnDestroy { constructor( private store: RiskAssessmentStore, public dialog: MatDialog, - private liveAnnouncer: LiveAnnouncer + private liveAnnouncer: LiveAnnouncer, + public element: ViewContainerRef ) {} ngOnInit() { @@ -94,15 +97,13 @@ export class RiskAssessmentComponent implements OnInit, OnDestroy { selectedProfile: Profile | null ): void { const dialogRef = this.dialog.open(SimpleDialogComponent, { - ariaLabel: 'Delete risk profile', data: { title: 'Delete risk profile?', content: `You are about to delete ${profileName}. Are you sure?`, }, - autoFocus: true, + autoFocus: 'dialog', hasBackdrop: true, disableClose: true, - panelClass: 'simple-dialog', }); dialogRef @@ -113,14 +114,18 @@ export class RiskAssessmentComponent implements OnInit, OnDestroy { this.store.deleteProfile(profileName); this.closeFormAfterDelete(profileName, selectedProfile); this.setFocus(index); + } else { + this.store.setFocusOnSelectedProfile(); } }); } saveProfileClicked(profile: Profile, selectedProfile: Profile | null): void { + this.liveAnnouncer.clear(); if (!selectedProfile) { - this.saveProfile(profile); - this.store.setFocusOnCreateButton(); + this.saveProfile(profile, this.store.setFocusOnCreateButton); + } else if (this.compareProfiles(profile, selectedProfile)) { + this.saveProfile(profile, this.store.setFocusOnSelectedProfile); } else { this.openSaveDialog( selectedProfile.name, @@ -129,18 +134,64 @@ export class RiskAssessmentComponent implements OnInit, OnDestroy { .pipe(takeUntil(this.destroy$)) .subscribe(saveProfile => { if (saveProfile) { - this.saveProfile(profile); - this.store.setFocusOnSelectedProfile(); + this.saveProfile(profile, this.store.setFocusOnSelectedProfile); } }); } } + private compareProfiles(profile1: Profile, profile2: Profile) { + if (profile1.name !== profile2.name) { + return false; + } + if ( + profile1.rename && + (profile1.rename !== profile1.name || profile1.rename !== profile2.name) + ) { + return false; + } + if (profile1.status !== profile2.status) { + return false; + } + + for (const question of profile1.questions) { + const answer1 = question.answer; + const answer2 = profile2.questions?.find( + question2 => question2.question === question.question + )?.answer; + if (answer1 !== undefined && answer2 !== undefined) { + if (typeof question.answer === 'string') { + if (answer1 !== answer2) { + return false; + } + } else { + //the type of answer is array + if (answer1?.length !== answer2?.length) { + return false; + } + if ( + (answer1 as number[]).some( + answer => !(answer2 as number[]).includes(answer) + ) + ) + return false; + } + } else { + return !!answer1 == !!answer2; + } + } + + return true; + } + discard(selectedProfile: Profile | null) { + this.liveAnnouncer.clear(); this.isOpenProfileForm = false; if (selectedProfile) { - this.store.setFocusOnSelectedProfile(); - this.store.updateSelectedProfile(null); + timer(100).subscribe(() => { + this.store.setFocusOnSelectedProfile(); + this.store.updateSelectedProfile(null); + }); } else { this.store.setFocusOnCreateButton(); } @@ -157,8 +208,17 @@ export class RiskAssessmentComponent implements OnInit, OnDestroy { } } - private saveProfile(profile: Profile) { - this.store.saveProfile(profile); + private saveProfile(profile: Profile, focusElement: () => void) { + this.store.saveProfile({ + profile, + onSave: (profile: Profile) => { + if (profile.status === ProfileStatus.VALID) { + this.openSuccessDialog(profile, focusElement); + } else { + focusElement(); + } + }, + }); this.isOpenProfileForm = false; } @@ -178,17 +238,35 @@ export class RiskAssessmentComponent implements OnInit, OnDestroy { draft: boolean = false ): Observable { const dialogRef = this.dialog.open(SimpleDialogComponent, { - ariaLabel: `Save ${draft ? 'draft profile' : 'profile'}`, data: { title: `Save ${draft ? 'draft profile' : 'profile'}`, content: `You are about to save changes in ${profileName}. Are you sure?`, }, + autoFocus: 'dialog', + hasBackdrop: true, + disableClose: true, + }); + + return dialogRef?.afterClosed(); + } + + private openSuccessDialog(profile: Profile, focusElement: () => void): void { + const dialogRef = this.dialog.open(SuccessDialogComponent, { + ariaLabel: 'Risk Assessment Profile Completed', + data: { + profile, + }, autoFocus: true, hasBackdrop: true, disableClose: true, panelClass: 'simple-dialog', }); - return dialogRef?.afterClosed(); + dialogRef + ?.afterClosed() + .pipe(takeUntil(this.destroy$)) + .subscribe(() => { + focusElement(); + }); } } diff --git a/modules/ui/src/app/pages/risk-assessment/risk-assessment.store.spec.ts b/modules/ui/src/app/pages/risk-assessment/risk-assessment.store.spec.ts index 3bc123929..5bd264641 100644 --- a/modules/ui/src/app/pages/risk-assessment/risk-assessment.store.spec.ts +++ b/modules/ui/src/app/pages/risk-assessment/risk-assessment.store.spec.ts @@ -29,7 +29,7 @@ import { import { FocusManagerService } from '../../services/focus-manager.service'; import { AppState } from '../../store/state'; import { selectRiskProfiles } from '../../store/selectors'; -import { fetchRiskProfiles, setRiskProfiles } from '../../store/actions'; +import { setRiskProfiles } from '../../store/actions'; describe('RiskAssessmentStore', () => { let riskAssessmentStore: RiskAssessmentStore; @@ -128,33 +128,35 @@ describe('RiskAssessmentStore', () => { const mockFirstItem = document.createElement('section') as HTMLElement; const mockNullEL = window.document.querySelector(`.mock`) as HTMLElement; - it('should set focus to the next profile item when available', () => { + it('should set focus to the next profile item when available', fakeAsync(() => { const mockData = { nextItem: mockNextItem, firstItem: mockFirstItem, }; riskAssessmentStore.setFocus(mockData); + tick(100); expect( mockFocusManagerService.focusFirstElementInContainer ).toHaveBeenCalledWith(mockNextItem); - }); + })); - it('should set focus to the first profile item when available and no next item', () => { + it('should set focus to the first profile item when available and no next item', fakeAsync(() => { const mockData = { nextItem: mockNullEL, firstItem: mockFirstItem, }; riskAssessmentStore.setFocus(mockData); + tick(100); expect( mockFocusManagerService.focusFirstElementInContainer ).toHaveBeenCalledWith(mockFirstItem); - }); + })); - it('should set focus to the first element in the main when no items', () => { + it('should set focus to the first element in the main when no items', fakeAsync(() => { const mockData = { nextItem: mockNullEL, firstItem: mockFirstItem, @@ -164,11 +166,12 @@ describe('RiskAssessmentStore', () => { store.refreshState(); riskAssessmentStore.setFocus(mockData); + tick(100); expect( mockFocusManagerService.focusFirstElementInContainer ).toHaveBeenCalledWith(); - }); + })); }); describe('setFocusOnCreateButton', () => { @@ -230,10 +233,18 @@ describe('RiskAssessmentStore', () => { }); describe('saveProfile', () => { - it('should dispatch fetchRiskProfiles', () => { - riskAssessmentStore.saveProfile(NEW_PROFILE_MOCK); + it('should dispatch setRiskProfiles', () => { + const onSave = jasmine.createSpy('onSave'); + mockService.fetchProfiles.and.returnValue(of([NEW_PROFILE_MOCK])); + riskAssessmentStore.saveProfile({ + profile: NEW_PROFILE_MOCK, + onSave, + }); - expect(store.dispatch).toHaveBeenCalledWith(fetchRiskProfiles()); + expect(store.dispatch).toHaveBeenCalledWith( + setRiskProfiles({ riskProfiles: [NEW_PROFILE_MOCK] }) + ); + expect(onSave).toHaveBeenCalled(); }); }); }); diff --git a/modules/ui/src/app/pages/risk-assessment/risk-assessment.store.ts b/modules/ui/src/app/pages/risk-assessment/risk-assessment.store.ts index 93e89b434..40a8c523a 100644 --- a/modules/ui/src/app/pages/risk-assessment/risk-assessment.store.ts +++ b/modules/ui/src/app/pages/risk-assessment/risk-assessment.store.ts @@ -17,14 +17,14 @@ import { Injectable } from '@angular/core'; import { ComponentStore } from '@ngrx/component-store'; import { tap, withLatestFrom } from 'rxjs/operators'; -import { delay, exhaustMap } from 'rxjs'; +import { catchError, delay, EMPTY, exhaustMap, throwError, timer } from 'rxjs'; import { TestRunService } from '../../services/test-run.service'; import { Profile, ProfileFormat } from '../../model/profile'; import { FocusManagerService } from '../../services/focus-manager.service'; import { Store } from '@ngrx/store'; import { AppState } from '../../store/state'; import { selectRiskProfiles } from '../../store/selectors'; -import { fetchRiskProfiles, setRiskProfiles } from '../../store/actions'; +import { setRiskProfiles } from '../../store/actions'; export interface AppComponentState { selectedProfile: Profile | null; @@ -76,13 +76,15 @@ export class RiskAssessmentStore extends ComponentStore { return trigger$.pipe( withLatestFrom(this.profiles$), tap(([{ nextItem, firstItem }, profiles]) => { - if (nextItem) { - this.focusManagerService.focusFirstElementInContainer(nextItem); - } else if (profiles.length > 1) { - this.focusManagerService.focusFirstElementInContainer(firstItem); - } else { - this.focusManagerService.focusFirstElementInContainer(); - } + timer(100).subscribe(() => { + if (nextItem) { + this.focusManagerService.focusFirstElementInContainer(nextItem); + } else if (profiles.length > 1) { + this.focusManagerService.focusFirstElementInContainer(firstItem); + } else { + this.focusManagerService.focusFirstElementInContainer(); + } + }); }) ); } @@ -131,14 +133,30 @@ export class RiskAssessmentStore extends ComponentStore { ); }); - saveProfile = this.effect(trigger$ => { + saveProfile = this.effect<{ + profile: Profile; + onSave: ((profile: Profile) => void) | undefined; + }>(trigger$ => { return trigger$.pipe( - exhaustMap((name: Profile) => { - return this.testRunService.saveProfile(name).pipe( - tap(saved => { + exhaustMap(({ profile, onSave }) => { + return this.testRunService.saveProfile(profile).pipe( + exhaustMap(saved => { if (saved) { - this.store.dispatch(fetchRiskProfiles()); + return this.testRunService.fetchProfiles(); + } + return throwError('Failed to upload profile'); + }), + tap(newProfiles => { + this.store.dispatch(setRiskProfiles({ riskProfiles: newProfiles })); + const uploadedProfile = newProfiles.find( + p => p.name === profile.name || p.name === profile.rename + ); + if (onSave && uploadedProfile) { + onSave(uploadedProfile); } + }), + catchError(() => { + return EMPTY; }) ); }) diff --git a/modules/ui/src/app/pages/settings/settings.component.scss b/modules/ui/src/app/pages/settings/settings.component.scss index 770d79a52..6596a3591 100644 --- a/modules/ui/src/app/pages/settings/settings.component.scss +++ b/modules/ui/src/app/pages/settings/settings.component.scss @@ -146,3 +146,10 @@ } } } + +.settings-drawer-header-button:not(.mat-mdc-button-disabled), +.close-button:not(.mat-mdc-button-disabled), +.save-button:not(.mat-mdc-button-disabled) { + cursor: pointer; + pointer-events: auto; +} diff --git a/modules/ui/src/app/pages/settings/settings.component.ts b/modules/ui/src/app/pages/settings/settings.component.ts index 1f9b4f62a..b4fb17c9e 100644 --- a/modules/ui/src/app/pages/settings/settings.component.ts +++ b/modules/ui/src/app/pages/settings/settings.component.ts @@ -52,7 +52,11 @@ export class SettingsComponent implements OnInit, OnDestroy { } @Input() set settingsDisable(value: boolean) { this.isSettingsDisable = value; - value ? this.disableSettings() : this.enableSettings(); + if (value) { + this.disableSettings(); + } else { + this.enableSettings(); + } } public readonly CalloutType = CalloutType; public readonly EventType = EventType; diff --git a/modules/ui/src/app/pages/testrun/components/download-options/download-options.component.scss b/modules/ui/src/app/pages/testrun/components/download-options/download-options.component.scss index cf368929d..a08461b6d 100644 --- a/modules/ui/src/app/pages/testrun/components/download-options/download-options.component.scss +++ b/modules/ui/src/app/pages/testrun/components/download-options/download-options.component.scss @@ -22,7 +22,7 @@ $option-height: 36px; .download-options-field { width: $option-width; - background: mat.get-color-from-palette($color-primary, 50); + background: mat.m2-get-color-from-palette($color-primary, 50); ::ng-deep.mat-mdc-text-field-wrapper { padding: 0 16px; @@ -39,7 +39,7 @@ $option-height: 36px; } ::ng-deep.mat-mdc-select-placeholder { - color: mat.get-color-from-palette($color-primary, 700); + color: mat.m2-get-color-from-palette($color-primary, 700); font-size: 14px; font-style: normal; font-weight: 500; @@ -48,11 +48,11 @@ $option-height: 36px; } ::ng-deep.mat-mdc-select-arrow { - color: mat.get-color-from-palette($color-primary, 700); + color: mat.m2-get-color-from-palette($color-primary, 700); } ::ng-deep .mat-mdc-text-field-wrapper .mdc-notched-outline > * { - border-color: mat.get-color-from-palette($color-primary, 50); + border-color: mat.m2-get-color-from-palette($color-primary, 50); } ::ng-deep diff --git a/modules/ui/src/app/pages/testrun/components/download-options/download-options.component.ts b/modules/ui/src/app/pages/testrun/components/download-options/download-options.component.ts index fddd6822f..81b4a1fee 100644 --- a/modules/ui/src/app/pages/testrun/components/download-options/download-options.component.ts +++ b/modules/ui/src/app/pages/testrun/components/download-options/download-options.component.ts @@ -97,6 +97,9 @@ export class DownloadOptionsComponent { } getReportTitle(data: TestrunStatus) { + if (!data.device) { + return ''; + } return `${data.device.manufacturer} ${data.device.model} ${ data.device.firmware } ${data.status} ${this.getFormattedDateString(data.started)}` diff --git a/modules/ui/src/app/pages/testrun/components/testrun-initiate-form/testrun-initiate-form.component.html b/modules/ui/src/app/pages/testrun/components/testrun-initiate-form/testrun-initiate-form.component.html index c576d242e..6caab0423 100644 --- a/modules/ui/src/app/pages/testrun/components/testrun-initiate-form/testrun-initiate-form.component.html +++ b/modules/ui/src/app/pages/testrun/components/testrun-initiate-form/testrun-initiate-form.component.html @@ -22,6 +22,7 @@ { component.startTestRun(); expect(testRunServiceMock.startTestrun).toHaveBeenCalledWith({ + status: DeviceStatus.VALID, manufacturer: 'Delta', model: 'O3-DIN-CPU', mac_addr: '00:1e:42:35:73:c4', @@ -230,7 +236,7 @@ describe('ProgressInitiateFormComponent', () => { expect(buttonSpy).toHaveBeenCalled(); }); - it('should focus firmware', () => { + it('should focus firmware', fakeAsync(() => { component.selectedDevice = device; component.setFirmwareFocus = true; const firmwareSpy = spyOn( @@ -239,9 +245,10 @@ describe('ProgressInitiateFormComponent', () => { ); component.ngAfterViewChecked(); fixture.detectChanges(); + tick(100); expect(firmwareSpy).toHaveBeenCalled(); - }); + })); }); it('should focus element on focusButton ', () => { @@ -303,12 +310,6 @@ describe('ProgressInitiateFormComponent', () => { expect(deviceItem).toBeTruthy(); }); - it('should have tabindex -1 for device item', () => { - const deviceItem = compiled.querySelector('app-device-item button'); - - expect((deviceItem as HTMLElement).tabIndex).toBe(-1); - }); - it('should display firmware if device selected', () => { const firmware = compiled.querySelector('input'); diff --git a/modules/ui/src/app/pages/testrun/components/testrun-initiate-form/testrun-initiate-form.component.ts b/modules/ui/src/app/pages/testrun/components/testrun-initiate-form/testrun-initiate-form.component.ts index a526e0973..cbec35a0a 100644 --- a/modules/ui/src/app/pages/testrun/components/testrun-initiate-form/testrun-initiate-form.component.ts +++ b/modules/ui/src/app/pages/testrun/components/testrun-initiate-form/testrun-initiate-form.component.ts @@ -24,7 +24,12 @@ import { } from '@angular/core'; import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; import { TestRunService } from '../../../../services/test-run.service'; -import { Device, TestModule, DeviceView } from '../../../../model/device'; +import { + Device, + TestModule, + DeviceStatus, + DeviceView, +} from '../../../../model/device'; import { AbstractControl, FormArray, @@ -33,7 +38,7 @@ import { } from '@angular/forms'; import { DeviceValidators } from '../../../devices/components/device-form/device.validators'; import { EscapableDialogComponent } from '../../../../components/escapable-dialog/escapable-dialog.component'; -import { take } from 'rxjs'; +import { take, timer } from 'rxjs'; import { Store } from '@ngrx/store'; import { AppState } from '../../../../store/state'; import { selectDevices } from '../../../../store/selectors'; @@ -61,6 +66,7 @@ export class TestrunInitiateFormComponent testModules: TestModule[] = []; prevDevice: Device | null = null; setFirmwareFocus = false; + readonly DeviceStatus = DeviceStatus; readonly DeviceView = DeviceView; error$: BehaviorSubject = new BehaviorSubject( null @@ -121,9 +127,11 @@ export class TestrunInitiateFormComponent this.changeDetectorRef.detectChanges(); } if (this.setFirmwareFocus) { - this.firmwareInput?.nativeElement.focus(); - this.setFirmwareFocus = false; - this.changeDetectorRef.detectChanges(); + timer(100).subscribe(() => { + this.firmwareInput?.nativeElement.focus(); + this.setFirmwareFocus = false; + this.changeDetectorRef.detectChanges(); + }); } } diff --git a/modules/ui/src/app/pages/testrun/components/testrun-status-card/testrun-status-card.component.html b/modules/ui/src/app/pages/testrun/components/testrun-status-card/testrun-status-card.component.html index b94c0d55a..be4d3b259 100644 --- a/modules/ui/src/app/pages/testrun/components/testrun-status-card/testrun-status-card.component.html +++ b/modules/ui/src/app/pages/testrun/components/testrun-status-card/testrun-status-card.component.html @@ -40,7 +40,7 @@ *ngTemplateOutlet=" InProgress; context: { - data: data + data: data, } "> @@ -53,7 +53,7 @@ *ngTemplateOutlet=" Finished; context: { - data: data + data: data, } "> @@ -64,7 +64,7 @@ *ngTemplateOutlet=" Finished; context: { - data: data + data: data, } "> @@ -73,7 +73,7 @@ *ngTemplateOutlet=" InProgress; context: { - data: data + data: data, } "> diff --git a/modules/ui/src/app/pages/testrun/components/testrun-status-card/testrun-status-card.component.scss b/modules/ui/src/app/pages/testrun/components/testrun-status-card/testrun-status-card.component.scss index ddf8a41b8..b7c86a91a 100644 --- a/modules/ui/src/app/pages/testrun/components/testrun-status-card/testrun-status-card.component.scss +++ b/modules/ui/src/app/pages/testrun/components/testrun-status-card/testrun-status-card.component.scss @@ -33,11 +33,11 @@ padding: 16px 32px; &.progress { - background-color: mat.get-color-from-palette($color-primary, 700); + background-color: mat.m2-get-color-from-palette($color-primary, 700); } &.completed-success { - background-color: mat.get-color-from-palette($color-accent, 700); + background-color: mat.m2-get-color-from-palette($color-accent, 700); } &.completed-failed { diff --git a/modules/ui/src/app/pages/testrun/components/testrun-status-card/testrun-status-card.component.spec.ts b/modules/ui/src/app/pages/testrun/components/testrun-status-card/testrun-status-card.component.spec.ts index 29fdd7731..2e35d0581 100644 --- a/modules/ui/src/app/pages/testrun/components/testrun-status-card/testrun-status-card.component.spec.ts +++ b/modules/ui/src/app/pages/testrun/components/testrun-status-card/testrun-status-card.component.spec.ts @@ -26,6 +26,7 @@ import { MOCK_PROGRESS_DATA_IN_PROGRESS, MOCK_PROGRESS_DATA_MONITORING, MOCK_PROGRESS_DATA_WAITING_FOR_DEVICE, + MOCK_PROGRESS_DATA_WITH_ERROR, } from '../../../../mocks/testrun.mock'; import { TestrunModule } from '../../testrun.module'; @@ -129,7 +130,7 @@ describe('ProgressStatusCardComponent', () => { }); it('should return correct test result if status "Compliant"', () => { - const expectedResult = '2/2'; + const expectedResult = '2/3'; const result = component.getTestsResult(MOCK_PROGRESS_DATA_COMPLIANT); @@ -144,6 +145,14 @@ describe('ProgressStatusCardComponent', () => { expect(result).toEqual(expectedResult); }); + it('should not include Error and Not Started status in completed test result', () => { + const expectedResult = '1/3'; + + const result = component.getTestsResult(MOCK_PROGRESS_DATA_WITH_ERROR); + + expect(result).toEqual(expectedResult); + }); + it('should return empty string if no data', () => { const expectedResult = ''; diff --git a/modules/ui/src/app/pages/testrun/components/testrun-status-card/testrun-status-card.component.ts b/modules/ui/src/app/pages/testrun/components/testrun-status-card/testrun-status-card.component.ts index 027588b24..d84b692e1 100644 --- a/modules/ui/src/app/pages/testrun/components/testrun-status-card/testrun-status-card.component.ts +++ b/modules/ui/src/app/pages/testrun/components/testrun-status-card/testrun-status-card.component.ts @@ -16,6 +16,7 @@ import { ChangeDetectionStrategy, Component, Input } from '@angular/core'; import { IResult, + StatusOfTestResult, StatusOfTestrun, TestrunStatus, TestsData, @@ -65,11 +66,11 @@ export class TestrunStatusCardComponent { (data.tests as TestsData)?.results?.length && (data.tests as TestsData)?.total ) { - return `${(data.tests as TestsData)?.results?.length}/${ + return `${(data.tests as TestsData)?.results?.filter(result => result.result !== StatusOfTestResult.NotStarted && result.result !== StatusOfTestResult.Error).length}/${ (data.tests as TestsData)?.total }`; } else if ((data.tests as IResult[])?.length) { - return `${(data.tests as IResult[])?.length}/${ + return `${(data.tests as IResult[])?.filter(result => result.result !== StatusOfTestResult.NotStarted && result.result !== StatusOfTestResult.Error).length}/${ (data.tests as IResult[])?.length }`; } @@ -99,7 +100,13 @@ export class TestrunStatusCardComponent { const testData = data.tests as TestsData; if (testData && testData.total && testData.results?.length) { - return Math.round((testData.results.length / testData.total) * 100); + return Math.round( + (testData.results.filter( + result => result.result !== StatusOfTestResult.NotStarted + ).length / + testData.total) * + 100 + ); } return 0; } diff --git a/modules/ui/src/app/pages/testrun/components/testrun-table/testrun-table.component.spec.ts b/modules/ui/src/app/pages/testrun/components/testrun-table/testrun-table.component.spec.ts index 60c325c3d..0632ff75c 100644 --- a/modules/ui/src/app/pages/testrun/components/testrun-table/testrun-table.component.spec.ts +++ b/modules/ui/src/app/pages/testrun/components/testrun-table/testrun-table.component.spec.ts @@ -55,6 +55,7 @@ describe('ProgressTableComponent', () => { green: false, red: true, blue: false, + cyan: false, grey: false, }; diff --git a/modules/ui/src/app/pages/testrun/testrun.component.html b/modules/ui/src/app/pages/testrun/testrun.component.html index 74b414abf..042a7815f 100644 --- a/modules/ui/src/app/pages/testrun/testrun.component.html +++ b/modules/ui/src/app/pages/testrun/testrun.component.html @@ -30,16 +30,29 @@ startNewTestrunButton; context: { hasDevices: vm.hasDevices, - systemStatus: vm.systemStatus + systemStatus: vm.systemStatus, + isAllDevicesOutdated: vm.isAllDevicesOutdated, } ">

- {{ data.device.manufacturer }} {{ data.device.model }} v{{ - data.device.firmware - }} + {{ getTestRunName(data) }}

+ + {{ tag }} +
@@ -89,6 +103,7 @@

{ let router: Router; let mockService: SpyObj; let store: MockStore; - let focusNavigation = true; let mockFocusManagerService: SpyObj; let mockLiveAnnouncer: SpyObj; let mockMqttService: SpyObj; @@ -156,23 +149,14 @@ describe('AppComponent', () => { { provide: TestRunMqttService, useValue: mockMqttService }, { provide: State, - useValue: { - getValue: () => ({ - [appFeatureKey]: { - appComponent: { - focusNavigation: focusNavigation, - }, - }, - }), - }, + useValue: {}, }, provideMockStore({ selectors: [ { selector: selectInterfaces, value: {} }, { selector: selectHasConnectionSettings, value: true }, { selector: selectInternetConnection, value: true }, - { selector: selectError, value: null }, - { selector: selectMenuOpened, value: false }, + { selector: selectSystemConfig, value: { network: {} } }, { selector: selectHasDevices, value: false }, { selector: selectIsAllDevicesOutdated, value: false }, { selector: selectHasExpiredDevices, value: false }, @@ -206,6 +190,7 @@ describe('AppComponent', () => { router = TestBed.get(Router); compiled = fixture.nativeElement as HTMLElement; spyOn(store, 'dispatch').and.callFake(() => {}); + component.appStore.updateSettingMissedError(null); }); it('should create the app', () => { @@ -397,17 +382,21 @@ describe('AppComponent', () => { }); it('should dispatch toggleMenu action', () => { + spyOn(component.appStore, 'toggleMenu'); + const menuBtn = compiled.querySelector( '.app-toolbar-button-menu' ) as HTMLButtonElement; menuBtn.click(); - expect(store.dispatch).toHaveBeenCalledWith(toggleMenu()); + expect(component.appStore.toggleMenu).toHaveBeenCalled(); }); it('should focus navigation on tab press if menu button was clicked', () => { - focusNavigation = true; + component.appStore.updateFocusNavigation(true); + fixture.detectChanges(); + spyOn(component.appStore, 'updateFocusNavigation'); const menuBtn = compiled.querySelector( '.app-toolbar-button-menu' ) as HTMLButtonElement; @@ -415,8 +404,8 @@ describe('AppComponent', () => { menuBtn.dispatchEvent(new KeyboardEvent('keydown', { key: 'Tab' })); const navigation = compiled.querySelector('.app-sidebar'); - expect(store.dispatch).toHaveBeenCalledWith( - updateFocusNavigation({ focusNavigation: false }) + expect(component.appStore.updateFocusNavigation).toHaveBeenCalledWith( + false ); expect( mockFocusManagerService.focusFirstElementInContainer @@ -424,7 +413,8 @@ describe('AppComponent', () => { }); it('should not focus navigation button on tab press if menu button was not clicked', () => { - focusNavigation = false; + component.appStore.updateFocusNavigation(false); + fixture.detectChanges(); const menuBtn = compiled.querySelector( '.app-toolbar-button-menu' ) as HTMLButtonElement; @@ -712,7 +702,7 @@ describe('AppComponent', () => { }); }); - describe('with devices setted, without systemStatus data, but run the tests ', () => { + describe('with devices setted, without systemStatus data, but run the tests', () => { beforeEach(() => { store.overrideSelector(selectHasDevices, true); fixture.detectChanges(); @@ -745,7 +735,7 @@ describe('AppComponent', () => { describe('error', () => { describe('with settingMissedError with one port is missed', () => { beforeEach(() => { - store.overrideSelector(selectError, { + component.appStore.updateSettingMissedError({ isSettingMissed: true, devicePortMissed: true, internetPortMissed: false, @@ -764,7 +754,7 @@ describe('AppComponent', () => { describe('with settingMissedError with two ports are missed', () => { beforeEach(() => { - store.overrideSelector(selectError, { + component.appStore.updateSettingMissedError({ isSettingMissed: true, devicePortMissed: true, internetPortMissed: true, @@ -783,7 +773,7 @@ describe('AppComponent', () => { describe('with no settingMissedError', () => { beforeEach(() => { - store.overrideSelector(selectError, null); + component.appStore.updateSettingMissedError(null); store.overrideSelector(selectHasDevices, true); fixture.detectChanges(); }); diff --git a/modules/ui/src/app/app.component.ts b/modules/ui/src/app/app.component.ts index 7218459c4..ba8d33831 100644 --- a/modules/ui/src/app/app.component.ts +++ b/modules/ui/src/app/app.component.ts @@ -31,12 +31,7 @@ import { Routes } from './model/routes'; import { FocusManagerService } from './services/focus-manager.service'; import { State, Store } from '@ngrx/store'; import { AppState } from './store/state'; -import { - setIsOpenAddDevice, - toggleMenu, - updateFocusNavigation, -} from './store/actions'; -import { appFeatureKey } from './store/reducers'; +import { setIsOpenAddDevice } from './store/actions'; import { SettingsComponent } from './pages/settings/settings.component'; import { AppStore } from './app.store'; import { TestRunService } from './services/test-run.service'; @@ -205,19 +200,19 @@ export class AppComponent implements AfterViewInit { public toggleMenu(event: MouseEvent) { event.stopPropagation(); - this.store.dispatch(toggleMenu()); + this.appStore.toggleMenu(); } /** * When side menu is opened */ - skipToNavigation(event: Event) { - if (this.state.getValue()[appFeatureKey].appComponent.focusNavigation) { + skipToNavigation(event: Event, focusNavigation: boolean) { + if (focusNavigation) { event.preventDefault(); // if not prevented, second element will be focused this.focusManagerService.focusFirstElementInContainer( this.navigation.nativeElement ); - this.store.dispatch(updateFocusNavigation({ focusNavigation: false })); // user will be navigated according to normal flow on tab + this.appStore.updateFocusNavigation(false); // user will be navigated according to normal flow on tab } } diff --git a/modules/ui/src/app/app.store.spec.ts b/modules/ui/src/app/app.store.spec.ts index 4236b24fd..300a250fd 100644 --- a/modules/ui/src/app/app.store.spec.ts +++ b/modules/ui/src/app/app.store.spec.ts @@ -19,7 +19,6 @@ import { AppStore, CALLOUT_STATE_KEY, CONSENT_SHOWN_KEY } from './app.store'; import { MockStore, provideMockStore } from '@ngrx/store/testing'; import { AppState } from './store/state'; import { - selectError, selectHasConnectionSettings, selectHasDevices, selectHasExpiredDevices, @@ -29,10 +28,10 @@ import { selectIsAllDevicesOutdated, selectIsOpenWaitSnackBar, selectIsTestingComplete, - selectMenuOpened, selectReports, selectRiskProfiles, selectStatus, + selectSystemConfig, selectSystemStatus, selectTestModules, } from './store/selectors'; @@ -53,6 +52,7 @@ import { NotificationService } from './services/notification.service'; import { FocusManagerService } from './services/focus-manager.service'; import { TestRunMqttService } from './services/test-run-mqtt.service'; import { MOCK_ADAPTERS } from './mocks/settings.mock'; +import { TestingType } from './model/device'; const mock = (() => { let store: { [key: string]: string } = {}; @@ -87,6 +87,8 @@ describe('AppStore', () => { let mockMqttService: SpyObj; beforeEach(() => { + window.sessionStorage.clear(); + mockService = jasmine.createSpyObj('mockService', [ 'fetchDevices', 'getTestModules', @@ -112,6 +114,8 @@ describe('AppStore', () => { { selector: selectSystemStatus, value: null }, { selector: selectIsTestingComplete, value: false }, { selector: selectRiskProfiles, value: [] }, + { selector: selectSystemConfig, value: { network: {} } }, + { selector: selectInterfaces, value: {} }, ], }), { provide: TestRunService, useValue: mockService }, @@ -130,9 +134,7 @@ describe('AppStore', () => { store.overrideSelector(selectHasRiskProfiles, false); store.overrideSelector(selectReports, []); store.overrideSelector(selectHasConnectionSettings, true); - store.overrideSelector(selectMenuOpened, true); store.overrideSelector(selectInterfaces, {}); - store.overrideSelector(selectError, null); store.overrideSelector(selectStatus, null); spyOn(store, 'dispatch').and.callFake(() => {}); @@ -182,8 +184,9 @@ describe('AppStore', () => { isTestingComplete: false, riskProfiles: [], hasConnectionSettings: true, - isMenuOpen: true, + isMenuOpen: false, interfaces: {}, + focusNavigation: false, settingMissedError: null, calloutState: new Map(), hasInternetConnection: false, @@ -345,5 +348,177 @@ describe('AppStore', () => { expect(mock.getObject(CALLOUT_STATE_KEY)).toBeTruthy(); }); }); + + describe('checkInterfacesInConfig', () => { + it('should update settingMissedError with all false if all ports are present', done => { + appStore.viewModel$.pipe(skip(1), take(1)).subscribe(store => { + expect(store.settingMissedError).toEqual({ + isSettingMissed: false, + devicePortMissed: false, + internetPortMissed: false, + }); + done(); + }); + + store.overrideSelector(selectInterfaces, { + enx00e04c020fa8: '00:e0:4c:02:0f:a8', + enx207bd26205e9: '20:7b:d2:62:05:e9', + }); + store.overrideSelector(selectSystemConfig, { + network: { + device_intf: 'enx00e04c020fa8', + internet_intf: 'enx207bd26205e9', + }, + }); + store.refreshState(); + }); + + it('should update settingMissedError with all true if all ports are missing', done => { + appStore.viewModel$.pipe(skip(1), take(1)).subscribe(store => { + expect(store.settingMissedError).toEqual({ + isSettingMissed: true, + devicePortMissed: true, + internetPortMissed: true, + }); + done(); + }); + + store.overrideSelector(selectInterfaces, { + enx00e04c020fa9: '00:e0:4c:02:0f:a8', + enx207bd26205e8: '20:7b:d2:62:05:e9', + }); + store.overrideSelector(selectSystemConfig, { + network: { + device_intf: 'enx00e04c020fa8', + internet_intf: 'enx207bd26205e9', + }, + }); + store.refreshState(); + }); + + it('should update settingMissedError with devicePortMissed true if device port is missing', done => { + appStore.viewModel$.pipe(skip(1), take(1)).subscribe(store => { + expect(store.settingMissedError).toEqual({ + isSettingMissed: true, + devicePortMissed: true, + internetPortMissed: false, + }); + done(); + }); + + store.overrideSelector(selectInterfaces, { + enx00e04c020fa9: '00:e0:4c:02:0f:a8', + enx207bd26205e8: '20:7b:d2:62:05:e9', + }); + store.overrideSelector(selectSystemConfig, { + network: { + device_intf: 'enx00e04c020fa8', + internet_intf: 'enx207bd26205e8', + }, + }); + store.refreshState(); + }); + + it('should update settingMissedError with internetPortMissed true if device internet is missing', done => { + appStore.viewModel$.pipe(skip(1), take(1)).subscribe(store => { + expect(store.settingMissedError).toEqual({ + isSettingMissed: true, + devicePortMissed: false, + internetPortMissed: true, + }); + done(); + }); + + store.overrideSelector(selectInterfaces, { + enx00e04c020fa9: '00:e0:4c:02:0f:a8', + enx207bd26205e8: '20:7b:d2:62:05:e9', + }); + store.overrideSelector(selectSystemConfig, { + network: { + device_intf: 'enx00e04c020fa9', + internet_intf: 'enx207bd26205e9', + }, + }); + store.refreshState(); + }); + + it('should update settingMissedError with all false if interface are not empty and config is not set', done => { + appStore.viewModel$.pipe(skip(1), take(1)).subscribe(store => { + expect(store.settingMissedError).toEqual({ + isSettingMissed: false, + devicePortMissed: false, + internetPortMissed: false, + }); + done(); + }); + + store.overrideSelector(selectInterfaces, { + enx00e04c020fa8: '00:e0:4c:02:0f:a8', + enx207bd26205e9: '20:7b:d2:62:05:e9', + }); + store.overrideSelector(selectSystemConfig, { + network: { + device_intf: '', + internet_intf: '', + }, + }); + store.refreshState(); + }); + + it('should update settingMissedError with all false if interface are empty and config is not set', done => { + appStore.viewModel$.pipe(skip(1), take(1)).subscribe(store => { + expect(store.settingMissedError).toEqual({ + isSettingMissed: false, + devicePortMissed: false, + internetPortMissed: false, + }); + done(); + }); + + store.overrideSelector(selectInterfaces, {}); + store.overrideSelector(selectSystemConfig, { + network: { + device_intf: '', + internet_intf: '', + }, + }); + store.refreshState(); + }); + + it('should send GA event', done => { + // @ts-expect-error data layer should be defined + window.dataLayer = window.dataLayer || []; + + appStore.viewModel$.pipe(skip(1), take(1)).subscribe(() => { + expect( + // @ts-expect-error data layer should be defined + window.dataLayer.some(event => event.event === 'pilot_is_compliant') + ).toBeTrue(); + done(); + }); + + store.overrideSelector(selectIsTestingComplete, true); + store.overrideSelector(selectSystemStatus, { + status: 'Compliant', + mac_addr: '00:1e:42:35:73:c4', + device: { + manufacturer: 'Delta', + model: '03-DIN-CPU', + mac_addr: '00:1e:42:35:73:c4', + firmware: '1.2.2', + test_pack: TestingType.Pilot, + }, + started: '2023-06-22T09:20:00.123Z', + finished: '2023-06-22T09:26:00.123Z', + report: 'https://api.testrun.io/report.pdf', + tags: [], + tests: { + total: 3, + results: [], + }, + }); + store.refreshState(); + }); + }); }); }); diff --git a/modules/ui/src/app/app.store.ts b/modules/ui/src/app/app.store.ts index 91280724c..a12a536a3 100644 --- a/modules/ui/src/app/app.store.ts +++ b/modules/ui/src/app/app.store.ts @@ -16,9 +16,8 @@ import { Injectable } from '@angular/core'; import { ComponentStore } from '@ngrx/component-store'; -import { tap } from 'rxjs/operators'; +import { tap, withLatestFrom } from 'rxjs/operators'; import { - selectError, selectHasConnectionSettings, selectHasDevices, selectHasExpiredDevices, @@ -27,17 +26,24 @@ import { selectInternetConnection, selectIsAllDevicesOutdated, selectIsTestingComplete, - selectMenuOpened, selectReports, selectRiskProfiles, selectStatus, + selectSystemConfig, selectSystemStatus, } from './store/selectors'; import { Store } from '@ngrx/store'; import { AppState } from './store/state'; import { TestRunService } from './services/test-run.service'; -import { delay, exhaustMap, Observable, skip } from 'rxjs'; -import { Device, TestModule } from './model/device'; +import { + combineLatest, + delay, + exhaustMap, + filter, + Observable, + skip, +} from 'rxjs'; +import { Device, TestingType, TestModule } from './model/device'; import { setDevices, setIsOpenStartTestrun, @@ -47,10 +53,11 @@ import { setTestModules, updateAdapters, } from './store/actions'; -import { TestrunStatus } from './model/testrun-status'; +import { StatusOfTestrun, TestrunStatus } from './model/testrun-status'; import { Adapters, SettingMissedError, + SystemConfig, SystemInterfaces, } from './model/setting'; import { FocusManagerService } from './services/focus-manager.service'; @@ -65,6 +72,12 @@ export interface AppComponentState { isStatusLoaded: boolean; systemStatus: TestrunStatus | null; calloutState: Map; + isMenuOpen: boolean; + /** + * Indicates, if side menu should be focused on keyboard navigation after menu is opened + */ + focusNavigation: boolean; + settingMissedError: SettingMissedError | null; } @Injectable() export class AppStore extends ComponentStore { @@ -80,11 +93,14 @@ export class AppStore extends ComponentStore { private hasConnectionSetting$ = this.store.select( selectHasConnectionSettings ); - private isMenuOpen$ = this.store.select(selectMenuOpened); + private isMenuOpened$ = this.select(state => state.isMenuOpen); + private focusNavigation$ = this.select(state => state.focusNavigation); private interfaces$: Observable = this.store.select(selectInterfaces); + private systemConfig$: Observable = + this.store.select(selectSystemConfig); private settingMissedError$: Observable = - this.store.select(selectError); + this.select(state => state.settingMissedError); systemStatus$: Observable = this.store.select(selectStatus); testrunStatus$: Observable = this.store.select(selectSystemStatus); @@ -106,11 +122,12 @@ export class AppStore extends ComponentStore { isTestingComplete: this.isTestingComplete$, riskProfiles: this.riskProfiles$, hasConnectionSettings: this.hasConnectionSetting$, - isMenuOpen: this.isMenuOpen$, + isMenuOpen: this.isMenuOpened$, interfaces: this.interfaces$, settingMissedError: this.settingMissedError$, calloutState: this.calloutState$, hasInternetConnection: this.hasInternetConnection$, + focusNavigation: this.focusNavigation$, }); updateConsent = this.updater((state, consentShown: boolean) => ({ @@ -134,6 +151,23 @@ export class AppStore extends ComponentStore { isStatusLoaded, })); + updateFocusNavigation = this.updater((state, focusNavigation: boolean) => ({ + ...state, + focusNavigation, + })); + + updateIsMenuOpened = this.updater((state, isMenuOpen: boolean) => ({ + ...state, + isMenuOpen, + })); + + updateSettingMissedError = this.updater( + (state, settingMissedError: SettingMissedError | null) => ({ + ...state, + settingMissedError, + }) + ); + setContent = this.effect(trigger$ => { return trigger$.pipe( tap(() => { @@ -259,6 +293,58 @@ export class AppStore extends ComponentStore { ); }); + toggleMenu = this.effect(trigger$ => { + return trigger$.pipe( + withLatestFrom(this.isMenuOpened$), + tap(([, opened]) => { + this.updateIsMenuOpened(!opened); + if (!opened) { + this.updateFocusNavigation(true); + } + }) + ); + }); + + checkInterfacesInConfig = this.effect(() => { + return combineLatest([ + this.interfaces$.pipe(skip(1)), + this.systemConfig$.pipe(skip(1)), + ]).pipe( + filter(([, { network }]) => network !== null), + tap(([interfaces, { network, single_intf }]) => { + const deviceValid = + network?.device_intf == '' || + (!!network?.device_intf && !!interfaces[network.device_intf]); + const internetValid = single_intf + ? true + : network?.internet_intf == '' || + (!!network?.internet_intf && !!interfaces[network.internet_intf]); + this.updateSettingMissedError({ + isSettingMissed: !deviceValid || !internetValid, + devicePortMissed: !deviceValid, + internetPortMissed: !internetValid, + }); + }) + ); + }); + + sendGAEvent = this.effect(() => { + return combineLatest([this.isTestingComplete$, this.testrunStatus$]).pipe( + filter(([isTestingComplete]) => isTestingComplete === true), + filter( + ([, testrunStatus]) => + testrunStatus?.status === StatusOfTestrun.Compliant && + testrunStatus?.device.test_pack === TestingType.Pilot + ), + tap(() => { + // @ts-expect-error data layer is not null + window.dataLayer.push({ + event: 'pilot_is_compliant', + }); + }) + ); + }); + constructor( private store: Store, private testRunService: TestRunService, @@ -276,6 +362,9 @@ export class AppStore extends ComponentStore { calloutState: calloutState ? new Map(Object.entries(calloutState)) : new Map(), + isMenuOpen: false, + focusNavigation: false, + settingMissedError: null, }); } } diff --git a/modules/ui/src/app/components/download-report-zip/download-report-zip.component.spec.ts b/modules/ui/src/app/components/download-report-zip/download-report-zip.component.spec.ts index 1f0919286..c0a2bd18b 100644 --- a/modules/ui/src/app/components/download-report-zip/download-report-zip.component.spec.ts +++ b/modules/ui/src/app/components/download-report-zip/download-report-zip.component.spec.ts @@ -13,36 +13,23 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { - ComponentFixture, - discardPeriodicTasks, - fakeAsync, - TestBed, - tick, -} from '@angular/core/testing'; +import { ComponentFixture, fakeAsync, TestBed } from '@angular/core/testing'; import { DownloadReportZipComponent } from './download-report-zip.component'; import { of } from 'rxjs'; import { MatDialogRef } from '@angular/material/dialog'; -import { DownloadZipModalComponent } from '../download-zip-modal/download-zip-modal.component'; -import { Router } from '@angular/router'; -import { TestRunService } from '../../services/test-run.service'; -import { Routes } from '../../model/routes'; +import { + DialogCloseAction, + DownloadZipModalComponent, +} from '../download-zip-modal/download-zip-modal.component'; import { RouterTestingModule } from '@angular/router/testing'; import { Component } from '@angular/core'; import { MOCK_PROGRESS_DATA_COMPLIANT } from '../../mocks/testrun.mock'; -import { FocusManagerService } from '../../services/focus-manager.service'; describe('DownloadReportZipComponent', () => { let component: DownloadReportZipComponent; let fixture: ComponentFixture; let compiled: HTMLElement; - let router: Router; - - const testrunServiceMock: jasmine.SpyObj = - jasmine.createSpyObj('testrunServiceMock', ['downloadZip']); - const focusServiceMock: jasmine.SpyObj = - jasmine.createSpyObj('focusServiceMock', ['focusFirstElementInContainer']); beforeEach(async () => { await TestBed.configureTestingModule({ @@ -52,13 +39,8 @@ describe('DownloadReportZipComponent', () => { ]), DownloadReportZipComponent, ], - providers: [ - { provide: TestRunService, useValue: testrunServiceMock }, - { provide: FocusManagerService, useValue: focusServiceMock }, - ], }).compileComponents(); fixture = TestBed.createComponent(DownloadReportZipComponent); - router = TestBed.get(Router); compiled = fixture.nativeElement as HTMLElement; component = fixture.componentInstance; component.url = 'localhost:8080'; @@ -71,87 +53,25 @@ describe('DownloadReportZipComponent', () => { }); describe('#onClick', () => { - beforeEach(() => { - testrunServiceMock.downloadZip.calls.reset(); - }); - - it('should call service if profile is a string', fakeAsync(() => { + it('should open zip modal dialog', fakeAsync(() => { const openSpy = spyOn(component.dialog, 'open').and.returnValue({ - afterClosed: () => of(''), + afterClosed: () => + of({ action: DialogCloseAction.Download, profile: '' }), } as MatDialogRef); - component.onClick(new Event('click')); expect(openSpy).toHaveBeenCalledWith(DownloadZipModalComponent, { ariaLabel: 'Download zip', data: { profiles: [], + url: 'localhost:8080', + isPilot: false, }, autoFocus: true, hasBackdrop: true, disableClose: true, panelClass: 'initiate-test-run-dialog', }); - - tick(); - - expect(testrunServiceMock.downloadZip).toHaveBeenCalled(); - expect(router.url).not.toBe(Routes.RiskAssessment); - openSpy.calls.reset(); - })); - - it('should navigate to risk profiles page if profile is null', fakeAsync(() => { - const openSpy = spyOn(component.dialog, 'open').and.returnValue({ - afterClosed: () => of(null), - } as MatDialogRef); - - fixture.ngZone?.run(() => { - component.onClick(new Event('click')); - - expect(openSpy).toHaveBeenCalledWith(DownloadZipModalComponent, { - ariaLabel: 'Download zip', - data: { - profiles: [], - }, - autoFocus: true, - hasBackdrop: true, - disableClose: true, - panelClass: 'initiate-test-run-dialog', - }); - - tick(100); - - expect(router.url).toBe(Routes.RiskAssessment); - expect( - focusServiceMock.focusFirstElementInContainer - ).toHaveBeenCalled(); - - openSpy.calls.reset(); - discardPeriodicTasks(); - }); - })); - - it('should not call service to download zip if profile is undefined', fakeAsync(() => { - const openSpy = spyOn(component.dialog, 'open').and.returnValue({ - afterClosed: () => of(undefined), - } as MatDialogRef); - - component.onClick(new Event('click')); - - expect(openSpy).toHaveBeenCalledWith(DownloadZipModalComponent, { - ariaLabel: 'Download zip', - data: { - profiles: [], - }, - autoFocus: true, - hasBackdrop: true, - disableClose: true, - panelClass: 'initiate-test-run-dialog', - }); - - tick(); - - expect(testrunServiceMock.downloadZip).not.toHaveBeenCalled(); openSpy.calls.reset(); })); }); diff --git a/modules/ui/src/app/components/download-report-zip/download-report-zip.component.ts b/modules/ui/src/app/components/download-report-zip/download-report-zip.component.ts index c643cc687..742210937 100644 --- a/modules/ui/src/app/components/download-report-zip/download-report-zip.component.ts +++ b/modules/ui/src/app/components/download-report-zip/download-report-zip.component.ts @@ -19,20 +19,15 @@ import { HostBinding, HostListener, Input, - OnDestroy, OnInit, } from '@angular/core'; import { CommonModule, DatePipe } from '@angular/common'; import { Profile } from '../../model/profile'; import { MatDialog } from '@angular/material/dialog'; -import { Subject, takeUntil, timer } from 'rxjs'; -import { Routes } from '../../model/routes'; import { DownloadZipModalComponent } from '../download-zip-modal/download-zip-modal.component'; -import { TestRunService } from '../../services/test-run.service'; -import { Router } from '@angular/router'; import { ReportActionComponent } from '../report-action/report-action.component'; import { MatTooltip, MatTooltipModule } from '@angular/material/tooltip'; -import { FocusManagerService } from '../../services/focus-manager.service'; +import { TestingType } from '../../model/device'; @Component({ selector: 'app-download-report-zip', @@ -45,9 +40,8 @@ import { FocusManagerService } from '../../services/focus-manager.service'; }) export class DownloadReportZipComponent extends ReportActionComponent - implements OnDestroy, OnInit + implements OnInit { - private destroy$: Subject = new Subject(); @Input() profiles: Profile[] = []; @Input() url: string | null | undefined = null; @@ -58,36 +52,18 @@ export class DownloadReportZipComponent event.preventDefault(); event.stopPropagation(); - const dialogRef = this.dialog.open(DownloadZipModalComponent, { + this.dialog.open(DownloadZipModalComponent, { ariaLabel: 'Download zip', data: { profiles: this.profiles, + url: this.url, + isPilot: this.data?.device.test_pack === TestingType.Pilot, }, autoFocus: true, hasBackdrop: true, disableClose: true, panelClass: 'initiate-test-run-dialog', }); - - dialogRef - ?.afterClosed() - .pipe(takeUntil(this.destroy$)) - .subscribe(profile => { - if (profile === undefined) { - return; - } - if (profile === null) { - this.route.navigate([Routes.RiskAssessment]).then(() => - timer(100) - .pipe(takeUntil(this.destroy$)) - .subscribe(() => { - this.focusManagerService.focusFirstElementInContainer(); - }) - ); - } else if (this.url != null) { - this.testrunService.downloadZip(this.getZipLink(this.url), profile); - } - }); } @HostBinding('tabIndex') @@ -105,11 +81,6 @@ export class DownloadReportZipComponent this.tooltip.hide(); } - ngOnDestroy() { - this.destroy$.next(true); - this.destroy$.unsubscribe(); - } - ngOnInit() { if (this.data) { this.tooltip.message = `Download zip for Testrun # ${this.getTestRunId(this.data)}`; @@ -119,15 +90,8 @@ export class DownloadReportZipComponent constructor( datePipe: DatePipe, public dialog: MatDialog, - private testrunService: TestRunService, - private route: Router, - public tooltip: MatTooltip, - private focusManagerService: FocusManagerService + public tooltip: MatTooltip ) { super(datePipe); } - - private getZipLink(reportURL: string): string { - return reportURL.replace('report', 'export'); - } } diff --git a/modules/ui/src/app/components/download-report/download-report.component.spec.ts b/modules/ui/src/app/components/download-report/download-report.component.spec.ts index f29430f54..af711a26b 100644 --- a/modules/ui/src/app/components/download-report/download-report.component.spec.ts +++ b/modules/ui/src/app/components/download-report/download-report.component.spec.ts @@ -21,6 +21,7 @@ import { MOCK_PROGRESS_DATA_COMPLIANT, MOCK_PROGRESS_DATA_NON_COMPLIANT, } from '../../mocks/testrun.mock'; +import { TestrunStatus } from '../../model/testrun-status'; describe('DownloadReportComponent', () => { let component: DownloadReportComponent; @@ -39,13 +40,26 @@ describe('DownloadReportComponent', () => { expect(component).toBeTruthy(); }); - it('#getReportTitle should return data for download property of link', () => { - const expectedResult = - 'delta_03-din-cpu_1.2.2_compliant_22_jun_2023_9:20'; + describe('#getReportTitle', () => { + it('should return data for download property of link', () => { + const expectedResult = + 'delta_03-din-cpu_1.2.2_compliant_22_jun_2023_9:20'; - const result = component.getReportTitle(MOCK_PROGRESS_DATA_COMPLIANT); + const result = component.getReportTitle(MOCK_PROGRESS_DATA_COMPLIANT); - expect(result).toEqual(expectedResult); + expect(result).toEqual(expectedResult); + }); + + it('should return empty string if no device data', () => { + const MOCK_DATA_WITHOUT_DEVICE = { + ...MOCK_PROGRESS_DATA_COMPLIANT, + device: undefined as unknown, + } as TestrunStatus; + + const result = component.getReportTitle(MOCK_DATA_WITHOUT_DEVICE); + + expect(result).toEqual(''); + }); }); describe('#getClass', () => { diff --git a/modules/ui/src/app/components/download-zip-modal/download-zip-modal.component.spec.ts b/modules/ui/src/app/components/download-zip-modal/download-zip-modal.component.spec.ts index 728590ef8..74d0990e1 100644 --- a/modules/ui/src/app/components/download-zip-modal/download-zip-modal.component.spec.ts +++ b/modules/ui/src/app/components/download-zip-modal/download-zip-modal.component.spec.ts @@ -1,6 +1,15 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { + ComponentFixture, + discardPeriodicTasks, + fakeAsync, + TestBed, + tick, +} from '@angular/core/testing'; -import { DownloadZipModalComponent } from './download-zip-modal.component'; +import { + DialogCloseAction, + DownloadZipModalComponent, +} from './download-zip-modal.component'; import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; import { PROFILE_MOCK, @@ -10,31 +19,56 @@ import { import { of } from 'rxjs'; import { NoopAnimationsModule } from '@angular/platform-browser/animations'; import { TestRunService } from '../../services/test-run.service'; +import { Routes } from '../../model/routes'; +import { Router } from '@angular/router'; +import { FocusManagerService } from '../../services/focus-manager.service'; +import { BehaviorSubject } from 'rxjs/internal/BehaviorSubject'; +import { RouterTestingModule } from '@angular/router/testing'; +import { Component } from '@angular/core'; describe('DownloadZipModalComponent', () => { + // @ts-expect-error data layer should be defined + window.dataLayer = window.dataLayer || []; let component: DownloadZipModalComponent; let fixture: ComponentFixture; - - const testRunServiceMock = jasmine.createSpyObj(['getRiskClass']); + let router: Router; + const testRunServiceMock = jasmine.createSpyObj('testRunServiceMock', [ + 'getRiskClass', + 'downloadZip', + ]); + const focusServiceMock: jasmine.SpyObj = + jasmine.createSpyObj('focusServiceMock', ['focusFirstElementInContainer']); + const actionBehaviorSubject$ = new BehaviorSubject({ + action: DialogCloseAction.Close, + }); beforeEach(async () => { await TestBed.configureTestingModule({ - imports: [DownloadZipModalComponent, NoopAnimationsModule], + imports: [ + RouterTestingModule.withRoutes([ + { path: 'risk-assessment', component: FakeRiskAssessmentComponent }, + ]), + DownloadZipModalComponent, + NoopAnimationsModule, + ], providers: [ { provide: MatDialogRef, useValue: { keydownEvents: () => of(new KeyboardEvent('keydown', { code: '' })), close: () => ({}), + beforeClosed: () => actionBehaviorSubject$.asObservable(), }, }, { provide: MAT_DIALOG_DATA, useValue: { profiles: [PROFILE_MOCK_2, PROFILE_MOCK], + url: 'localhost:8080', }, }, { provide: TestRunService, useValue: testRunServiceMock }, + { provide: FocusManagerService, useValue: focusServiceMock }, ], }); }); @@ -44,11 +78,14 @@ describe('DownloadZipModalComponent', () => { TestBed.overrideProvider(MAT_DIALOG_DATA, { useValue: { profiles: [PROFILE_MOCK_2, PROFILE_MOCK, PROFILE_MOCK_3], + url: 'localhost:8080', + isPilot: true, }, }); TestBed.compileComponents(); fixture = TestBed.createComponent(DownloadZipModalComponent); + router = TestBed.get(Router); component = fixture.componentInstance; fixture.detectChanges(); }); @@ -65,33 +102,60 @@ describe('DownloadZipModalComponent', () => { ); }); - it('should close with null on redirect button click', async () => { - const closeSpy = spyOn(component.dialogRef, 'close'); - const redirectLink = fixture.nativeElement.querySelector( - '.redirect-link' - ) as HTMLAnchorElement; + it('should close with Redirect action on redirect button click', fakeAsync(() => { + const result = { + action: DialogCloseAction.Redirect, + }; + actionBehaviorSubject$.next(result); + fixture.detectChanges(); - redirectLink.click(); + fixture.ngZone?.run(() => { + const closeSpy = spyOn(component.dialogRef, 'close'); + const redirectLink = fixture.nativeElement.querySelector( + '.redirect-link' + ) as HTMLAnchorElement; - expect(closeSpy).toHaveBeenCalledWith(null); + redirectLink.click(); - closeSpy.calls.reset(); - }); + tick(2000); - it('should close with undefined on cancel button click', async () => { + expect(router.url).toBe(Routes.RiskAssessment); + expect( + focusServiceMock.focusFirstElementInContainer + ).toHaveBeenCalled(); + expect(closeSpy).toHaveBeenCalledWith(result); + + closeSpy.calls.reset(); + discardPeriodicTasks(); + }); + })); + + it('should close with Close action on cancel button click', async () => { + const result = { + action: DialogCloseAction.Close, + }; const closeSpy = spyOn(component.dialogRef, 'close'); + actionBehaviorSubject$.next(result); + fixture.detectChanges(); + const cancelButton = fixture.nativeElement.querySelector( '.cancel-button' ) as HTMLButtonElement; cancelButton.click(); - expect(closeSpy).toHaveBeenCalledWith(undefined); + expect(closeSpy).toHaveBeenCalledWith(result); closeSpy.calls.reset(); }); - it('should close with profile on download button click', async () => { + it('should close with Download action and profile on download button click', async () => { + const result = { + action: DialogCloseAction.Download, + profile: '', + }; + actionBehaviorSubject$.next(result); + fixture.detectChanges(); const closeSpy = spyOn(component.dialogRef, 'close'); const downloadButton = fixture.nativeElement.querySelector( '.download-button' @@ -99,8 +163,32 @@ describe('DownloadZipModalComponent', () => { downloadButton.click(); - expect(closeSpy).toHaveBeenCalledWith(''); + expect(closeSpy).toHaveBeenCalledWith(result); + expect(testRunServiceMock.downloadZip).toHaveBeenCalled(); + expect(router.url).not.toBe(Routes.RiskAssessment); + closeSpy.calls.reset(); + }); + + it('should send GA event if report is for Pilot program', async () => { + const result = { + action: DialogCloseAction.Download, + profile: '', + }; + actionBehaviorSubject$.next(result); + fixture.detectChanges(); + const closeSpy = spyOn(component.dialogRef, 'close'); + const downloadButton = fixture.nativeElement.querySelector( + '.download-button' + ) as HTMLButtonElement; + + downloadButton.click(); + expect( + // @ts-expect-error data layer should be defined + window.dataLayer.some( + (item: { event: string }) => item.event === 'pilot_download_zip' + ) + ).toBeTruthy(); closeSpy.calls.reset(); }); @@ -132,11 +220,13 @@ describe('DownloadZipModalComponent', () => { TestBed.overrideProvider(MAT_DIALOG_DATA, { useValue: { profiles: [], + url: 'localhost:8080', }, }); TestBed.compileComponents(); fixture = TestBed.createComponent(DownloadZipModalComponent); + router = TestBed.get(Router); component = fixture.componentInstance; fixture.detectChanges(); }); @@ -147,20 +237,35 @@ describe('DownloadZipModalComponent', () => { expect(select.classList.contains('mat-mdc-select-disabled')).toBeTruthy(); }); - it('should close with null on redirect button click', async () => { - const closeSpy = spyOn(component.dialogRef, 'close'); - const redirectLink = fixture.nativeElement.querySelector( - '.redirect-link' - ) as HTMLAnchorElement; + it('should close with Redirect action on redirect button click', fakeAsync(() => { + const result = { + action: DialogCloseAction.Redirect, + }; + actionBehaviorSubject$.next(result); + fixture.detectChanges(); - redirectLink.click(); + fixture.ngZone?.run(() => { + const closeSpy = spyOn(component.dialogRef, 'close'); + const redirectLink = fixture.nativeElement.querySelector( + '.redirect-link' + ) as HTMLAnchorElement; - expect(closeSpy).toHaveBeenCalledWith(null); + redirectLink.click(); - closeSpy.calls.reset(); - }); + tick(2000); + + expect(router.url).toBe(Routes.RiskAssessment); + expect( + focusServiceMock.focusFirstElementInContainer + ).toHaveBeenCalled(); + expect(closeSpy).toHaveBeenCalledWith(result); - it('should close with undefined on cancel button click', async () => { + closeSpy.calls.reset(); + discardPeriodicTasks(); + }); + })); + + it('should close with Close action on cancel button click', async () => { const closeSpy = spyOn(component.dialogRef, 'close'); const cancelButton = fixture.nativeElement.querySelector( '.cancel-button' @@ -168,7 +273,9 @@ describe('DownloadZipModalComponent', () => { cancelButton.click(); - expect(closeSpy).toHaveBeenCalledWith(undefined); + expect(closeSpy).toHaveBeenCalledWith({ + action: DialogCloseAction.Close, + }); closeSpy.calls.reset(); }); @@ -181,9 +288,18 @@ describe('DownloadZipModalComponent', () => { downloadButton.click(); - expect(closeSpy).toHaveBeenCalledWith(''); + expect(closeSpy).toHaveBeenCalledWith({ + action: DialogCloseAction.Download, + profile: '', + }); closeSpy.calls.reset(); }); }); }); + +@Component({ + selector: 'app-fake-risk-assessment-component', + template: '', +}) +class FakeRiskAssessmentComponent {} diff --git a/modules/ui/src/app/components/download-zip-modal/download-zip-modal.component.ts b/modules/ui/src/app/components/download-zip-modal/download-zip-modal.component.ts index a703edb6d..199d89bf5 100644 --- a/modules/ui/src/app/components/download-zip-modal/download-zip-modal.component.ts +++ b/modules/ui/src/app/components/download-zip-modal/download-zip-modal.component.ts @@ -1,4 +1,10 @@ -import { ChangeDetectionStrategy, Component, Inject } from '@angular/core'; +import { + ChangeDetectionStrategy, + Component, + Inject, + OnDestroy, + OnInit, +} from '@angular/core'; import { MAT_DIALOG_DATA, MatDialogActions, @@ -18,14 +24,29 @@ import { MatSelectModule } from '@angular/material/select'; import { MatOptionModule } from '@angular/material/core'; import { TestRunService } from '../../services/test-run.service'; import { Routes } from '../../model/routes'; -import { RouterLink } from '@angular/router'; +import { Router, RouterLink } from '@angular/router'; import { TestrunStatus, StatusOfTestrun } from '../../model/testrun-status'; import { DownloadReportComponent } from '../download-report/download-report.component'; +import { Subject, takeUntil, timer } from 'rxjs'; +import { FocusManagerService } from '../../services/focus-manager.service'; interface DialogData { profiles: Profile[]; testrunStatus?: TestrunStatus; isTestingComplete?: boolean; + url: string | null; + isPilot?: boolean; +} + +export enum DialogCloseAction { + Close, + Redirect, + Download, +} + +export interface DialogCloseResult { + action: DialogCloseAction; + profile: string | null | undefined; } @Component({ @@ -48,7 +69,11 @@ interface DialogData { styleUrl: './download-zip-modal.component.scss', changeDetection: ChangeDetectionStrategy.OnPush, }) -export class DownloadZipModalComponent extends EscapableDialogComponent { +export class DownloadZipModalComponent + extends EscapableDialogComponent + implements OnDestroy, OnInit +{ + private destroy$: Subject = new Subject(); readonly NO_PROFILE = { name: 'No Risk Profile selected', questions: [], @@ -60,7 +85,9 @@ export class DownloadZipModalComponent extends EscapableDialogComponent { constructor( private readonly testRunService: TestRunService, public override dialogRef: MatDialogRef, - @Inject(MAT_DIALOG_DATA) public data: DialogData + @Inject(MAT_DIALOG_DATA) public data: DialogData, + private route: Router, + private focusManagerService: FocusManagerService ) { super(dialogRef); this.profiles = data.profiles.filter( @@ -75,19 +102,70 @@ export class DownloadZipModalComponent extends EscapableDialogComponent { this.selectedProfile = this.profiles[0]; } + ngOnInit() { + this.dialogRef + ?.beforeClosed() + .pipe(takeUntil(this.destroy$)) + .subscribe((result: DialogCloseResult) => { + if (result.action === DialogCloseAction.Close) { + return; + } + if (result.action === DialogCloseAction.Redirect) { + this.route.navigate([Routes.RiskAssessment]).then(() => + timer(1000).subscribe(() => { + this.focusManagerService.focusFirstElementInContainer(); + }) + ); + return; + } + if (this.data.url != null && typeof result.profile === 'string') { + this.testRunService.downloadZip( + this.getZipLink(this.data.url), + result.profile + ); + if (this.data.isPilot) { + // @ts-expect-error data layer is not null + window.dataLayer.push({ + event: 'pilot_download_zip', + }); + } + } + }); + } + + ngOnDestroy() { + this.destroy$.next(true); + this.destroy$.unsubscribe(); + } + cancel(profile?: Profile | null) { if (profile === null) { - this.dialogRef.close(null); + this.dialogRef.close({ + action: DialogCloseAction.Redirect, + } as DialogCloseResult); + return; + } + if (!profile) { + this.dialogRef.close({ + action: DialogCloseAction.Close, + } as DialogCloseResult); return; } - let value = profile?.name; - if (profile && profile?.name === this.NO_PROFILE.name) { + let value = profile.name; + if (value === this.NO_PROFILE.name) { value = ''; } - this.dialogRef.close(value); + this.dialogRef.close({ + action: DialogCloseAction.Download, + profile: value, + } as DialogCloseResult); } public getRiskClass(riskResult: string): RiskResultClassName { return this.testRunService.getRiskClass(riskResult); } + + private getZipLink(reportURL: string): string { + return reportURL.replace('report', 'export'); + } } diff --git a/modules/ui/src/app/components/dynamic-form/dynamic-form.component.html b/modules/ui/src/app/components/dynamic-form/dynamic-form.component.html index 0ea7d3df4..cdbf27695 100644 --- a/modules/ui/src/app/components/dynamic-form/dynamic-form.component.html +++ b/modules/ui/src/app/components/dynamic-form/dynamic-form.component.html @@ -226,7 +226,7 @@ - {{ getOptionValue(option) }} +

{{ @@ -250,10 +250,13 @@ aria-label="{{ label }}" id="{{ formControlName }}-group" [formControlName]="formControlName"> + + {{ getControl(formControlName).value }} + - {{ getOptionValue(option) }} + [value]="getOptionValue(option)" + [innerHTML]="getSanitizedOptionValue(option)"> {{ diff --git a/modules/ui/src/app/components/dynamic-form/dynamic-form.component.scss b/modules/ui/src/app/components/dynamic-form/dynamic-form.component.scss index 14f7086f0..7fda3a91a 100644 --- a/modules/ui/src/app/components/dynamic-form/dynamic-form.component.scss +++ b/modules/ui/src/app/components/dynamic-form/dynamic-form.component.scss @@ -17,7 +17,7 @@ @import 'src/theming/colors'; @import 'src/theming/variables'; -::ng-deep .field-label { +.field-label { margin: 0; color: $grey-800; font-size: 18px; diff --git a/modules/ui/src/app/components/dynamic-form/dynamic-form.component.ts b/modules/ui/src/app/components/dynamic-form/dynamic-form.component.ts index 3d6b13016..d3e1fa3da 100644 --- a/modules/ui/src/app/components/dynamic-form/dynamic-form.component.ts +++ b/modules/ui/src/app/components/dynamic-form/dynamic-form.component.ts @@ -13,7 +13,13 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { Component, inject, Input, OnInit } from '@angular/core'; +import { + Component, + inject, + Input, + OnInit, + ViewEncapsulation, +} from '@angular/core'; import { FormControlType, OptionType, @@ -44,6 +50,7 @@ import { MatCheckboxModule } from '@angular/material/checkbox'; import { TextFieldModule } from '@angular/cdk/text-field'; import { DeviceValidators } from '../../pages/devices/components/device-form/device.validators'; import { ProfileValidators } from '../../pages/risk-assessment/profile-form/profile.validators'; +import { DomSanitizer } from '@angular/platform-browser'; @Component({ selector: 'app-dynamic-form', standalone: true, @@ -68,6 +75,7 @@ import { ProfileValidators } from '../../pages/risk-assessment/profile-form/prof ], templateUrl: './dynamic-form.component.html', styleUrl: './dynamic-form.component.scss', + encapsulation: ViewEncapsulation.None, }) export class DynamicFormComponent implements OnInit { public readonly FormControlType = FormControlType; @@ -83,7 +91,8 @@ export class DynamicFormComponent implements OnInit { constructor( private fb: FormBuilder, private deviceValidators: DeviceValidators, - private profileValidators: ProfileValidators + private profileValidators: ProfileValidators, + private domSanitizer: DomSanitizer ) {} getControl(name: string | number) { return this.formGroup.get(name.toString()) as AbstractControl; @@ -168,4 +177,10 @@ export class DynamicFormComponent implements OnInit { } return option; } + + getSanitizedOptionValue(option: OptionType) { + return this.domSanitizer.bypassSecurityTrustHtml( + this.getOptionValue(option) + ); + } } diff --git a/modules/ui/src/app/components/program-type-icon/program-type-icon.component.ts b/modules/ui/src/app/components/program-type-icon/program-type-icon.component.ts index ccf5b3335..45a34ddfc 100644 --- a/modules/ui/src/app/components/program-type-icon/program-type-icon.component.ts +++ b/modules/ui/src/app/components/program-type-icon/program-type-icon.component.ts @@ -28,6 +28,7 @@ import { MatIcon } from '@angular/material/icon'; padding-right: 4px; } .icon { + display: flex; width: 16px; height: 16px; line-height: 16px; diff --git a/modules/ui/src/app/components/report-action/report-action.component.spec.ts b/modules/ui/src/app/components/report-action/report-action.component.spec.ts index b4d69b149..7408de89d 100644 --- a/modules/ui/src/app/components/report-action/report-action.component.spec.ts +++ b/modules/ui/src/app/components/report-action/report-action.component.spec.ts @@ -2,6 +2,7 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; import { ReportActionComponent } from './report-action.component'; import { MOCK_PROGRESS_DATA_COMPLIANT } from '../../mocks/testrun.mock'; +import { TestrunStatus } from '../../model/testrun-status'; describe('ReportActionComponent', () => { let component: ReportActionComponent; @@ -21,12 +22,25 @@ describe('ReportActionComponent', () => { expect(component).toBeTruthy(); }); - it('#getTestRunId should return data for title of link', () => { - const expectedResult = 'Delta 03-DIN-CPU 1.2.2 22 Jun 2023 9:20'; + describe('#getTestRunId', () => { + it('should return data for title of link', () => { + const expectedResult = 'Delta 03-DIN-CPU 1.2.2 22 Jun 2023 9:20'; - const result = component.getTestRunId(MOCK_PROGRESS_DATA_COMPLIANT); + const result = component.getTestRunId(MOCK_PROGRESS_DATA_COMPLIANT); - expect(result).toEqual(expectedResult); + expect(result).toEqual(expectedResult); + }); + + it('should return title as empty string if no device data', () => { + const MOCK_DATA_WITHOUT_DEVICE = { + ...MOCK_PROGRESS_DATA_COMPLIANT, + device: undefined as unknown, + } as TestrunStatus; + + const result = component.getTestRunId(MOCK_DATA_WITHOUT_DEVICE); + + expect(result).toEqual(''); + }); }); it('#getFormattedDateString should return date as string in the format "d MMM y H:mm"', () => { diff --git a/modules/ui/src/app/components/testing-complete/testing-complete.component.spec.ts b/modules/ui/src/app/components/testing-complete/testing-complete.component.spec.ts index 30c3108e6..494b58823 100644 --- a/modules/ui/src/app/components/testing-complete/testing-complete.component.spec.ts +++ b/modules/ui/src/app/components/testing-complete/testing-complete.component.spec.ts @@ -6,25 +6,25 @@ import { } from '@angular/core/testing'; import { TestingCompleteComponent } from './testing-complete.component'; -import { TestRunService } from '../../services/test-run.service'; import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; -import { Router } from '@angular/router'; import { RouterTestingModule } from '@angular/router/testing'; import { Component } from '@angular/core'; import { MOCK_PROGRESS_DATA_COMPLIANT } from '../../mocks/testrun.mock'; import { of } from 'rxjs'; import { MatDialogRef } from '@angular/material/dialog'; -import { DownloadZipModalComponent } from '../download-zip-modal/download-zip-modal.component'; -import { Routes } from '../../model/routes'; +import { + DialogCloseAction, + DownloadZipModalComponent, +} from '../download-zip-modal/download-zip-modal.component'; +import { FocusManagerService } from '../../services/focus-manager.service'; describe('TestingCompleteComponent', () => { let component: TestingCompleteComponent; let fixture: ComponentFixture; - let router: Router; - - const testrunServiceMock: jasmine.SpyObj = - jasmine.createSpyObj('testrunServiceMock', ['downloadZip']); - + const mockFocusManagerService = jasmine.createSpyObj( + 'mockFocusManagerService', + ['focusFirstElementInContainer'] + ); beforeEach(async () => { await TestBed.configureTestingModule({ imports: [ @@ -34,12 +34,13 @@ describe('TestingCompleteComponent', () => { TestingCompleteComponent, BrowserAnimationsModule, ], - providers: [{ provide: TestRunService, useValue: testrunServiceMock }], + providers: [ + { provide: FocusManagerService, useValue: mockFocusManagerService }, + ], }).compileComponents(); fixture = TestBed.createComponent(TestingCompleteComponent); component = fixture.componentInstance; - router = TestBed.get(Router); component.data = MOCK_PROGRESS_DATA_COMPLIANT; fixture.detectChanges(); }); @@ -49,16 +50,13 @@ describe('TestingCompleteComponent', () => { }); describe('#onInit', () => { - beforeEach(() => { - testrunServiceMock.downloadZip.calls.reset(); - }); - - it('should call downloadZip on service if profile is a string', fakeAsync(() => { + it('should focus first element in container when dialog closes with Close action', fakeAsync(() => { const openSpy = spyOn(component.dialog, 'open').and.returnValue({ - afterClosed: () => of(''), + afterClosed: () => of({ action: DialogCloseAction.Close }), } as MatDialogRef); component.ngOnInit(); + tick(1000); expect(openSpy).toHaveBeenCalledWith(DownloadZipModalComponent, { @@ -67,6 +65,8 @@ describe('TestingCompleteComponent', () => { profiles: [], testrunStatus: MOCK_PROGRESS_DATA_COMPLIANT, isTestingComplete: true, + url: 'https://api.testrun.io/report.pdf', + isPilot: false, }, autoFocus: 'first-tabbable', ariaDescribedBy: 'testing-result-main-info', @@ -75,10 +75,11 @@ describe('TestingCompleteComponent', () => { panelClass: 'initiate-test-run-dialog', }); - tick(); + tick(1000); - expect(testrunServiceMock.downloadZip).toHaveBeenCalled(); - expect(router.url).not.toBe(Routes.RiskAssessment); + expect( + mockFocusManagerService.focusFirstElementInContainer + ).toHaveBeenCalled(); openSpy.calls.reset(); })); }); diff --git a/modules/ui/src/app/components/testing-complete/testing-complete.component.ts b/modules/ui/src/app/components/testing-complete/testing-complete.component.ts index 28da57bee..79fc46c79 100644 --- a/modules/ui/src/app/components/testing-complete/testing-complete.component.ts +++ b/modules/ui/src/app/components/testing-complete/testing-complete.component.ts @@ -7,13 +7,15 @@ import { } from '@angular/core'; import { Subject, takeUntil, timer } from 'rxjs'; import { MatDialog } from '@angular/material/dialog'; -import { TestRunService } from '../../services/test-run.service'; -import { Router } from '@angular/router'; -import { DownloadZipModalComponent } from '../download-zip-modal/download-zip-modal.component'; -import { Routes } from '../../model/routes'; +import { + DialogCloseAction, + DialogCloseResult, + DownloadZipModalComponent, +} from '../download-zip-modal/download-zip-modal.component'; import { Profile } from '../../model/profile'; import { TestrunStatus } from '../../model/testrun-status'; import { FocusManagerService } from '../../services/focus-manager.service'; +import { TestingType } from '../../model/device'; @Component({ selector: 'app-testing-complete', @@ -29,15 +31,15 @@ export class TestingCompleteComponent implements OnDestroy, OnInit { constructor( public dialog: MatDialog, - private testrunService: TestRunService, - private route: Router, private focusManagerService: FocusManagerService ) {} ngOnInit() { - timer(1000).subscribe(() => { - this.openTestingCompleteModal(); - }); + timer(1000) + .pipe(takeUntil(this.destroy$)) + .subscribe(() => { + this.openTestingCompleteModal(); + }); } ngOnDestroy() { this.destroy$.next(true); @@ -51,6 +53,8 @@ export class TestingCompleteComponent implements OnDestroy, OnInit { profiles: this.profiles, testrunStatus: this.data, isTestingComplete: true, + url: this.data?.report, + isPilot: this.data?.device.test_pack === TestingType.Pilot, }, autoFocus: 'first-tabbable', ariaDescribedBy: 'testing-result-main-info', @@ -62,29 +66,14 @@ export class TestingCompleteComponent implements OnDestroy, OnInit { dialogRef ?.afterClosed() .pipe(takeUntil(this.destroy$)) - .subscribe(profile => { - if (profile === undefined) { - // close modal + .subscribe((result: DialogCloseResult) => { + if (result.action === DialogCloseAction.Close) { this.focusFirstElement(); return; } - if (profile === null) { - this.navigateToRiskAssessment(); - } else if (this.data?.report != null) { - this.testrunService.downloadZip( - this.getZipLink(this.data?.report), - profile - ); - } }); } - private navigateToRiskAssessment(): void { - this.route.navigate([Routes.RiskAssessment]).then(() => { - this.focusFirstElement(); - }); - } - private focusFirstElement() { timer(1000) .pipe(takeUntil(this.destroy$)) @@ -92,8 +81,4 @@ export class TestingCompleteComponent implements OnDestroy, OnInit { this.focusManagerService.focusFirstElementInContainer(); }); } - - private getZipLink(reportURL: string): string { - return reportURL.replace('report', 'export'); - } } diff --git a/modules/ui/src/app/mocks/settings.mock.ts b/modules/ui/src/app/mocks/settings.mock.ts index 49a11a895..53f092a0b 100644 --- a/modules/ui/src/app/mocks/settings.mock.ts +++ b/modules/ui/src/app/mocks/settings.mock.ts @@ -33,16 +33,21 @@ export const MOCK_SYSTEM_CONFIG_WITH_DATA: SystemConfig = { monitor_period: 600, }; -export const MOCK_INTERFACES: SystemInterfaces = { - mockDeviceKey: 'mockDeviceValue', - mockInternetKey: 'mockInternetValue', +export const MOCK_SYSTEM_CONFIG_WITH_SINGLE_PORT: SystemConfig = { + network: { + device_intf: 'mockDeviceKey', + internet_intf: '', + }, + log_level: 'DEBUG', + monitor_period: 600, + single_intf: true, }; -export const MOCK_INTERNET_OPTIONS: SystemInterfaces = { - '': 'Not specified', +export const MOCK_INTERFACES: SystemInterfaces = { mockDeviceKey: 'mockDeviceValue', mockInternetKey: 'mockInternetValue', }; + export const MOCK_DEVICE_VALUE: SystemInterfaces = { key: 'mockDeviceKey', value: 'mockDeviceValue', diff --git a/modules/ui/src/app/mocks/testrun.mock.ts b/modules/ui/src/app/mocks/testrun.mock.ts index c90927cd3..3decf9973 100644 --- a/modules/ui/src/app/mocks/testrun.mock.ts +++ b/modules/ui/src/app/mocks/testrun.mock.ts @@ -159,6 +159,12 @@ export const MOCK_PROGRESS_DATA_WAITING_FOR_DEVICE: TestrunStatus = { started: null, }; +export const MOCK_PROGRESS_DATA_VALIDATING: TestrunStatus = { + ...MOCK_PROGRESS_DATA_IN_PROGRESS, + status: StatusOfTestrun.Validating, + started: null, +}; + export const MOCK_PROGRESS_DATA_WITH_ERROR: TestrunStatus = PROGRESS_DATA_RESPONSE(StatusOfTestrun.InProgress, null, { ...TEST_DATA, diff --git a/modules/ui/src/app/model/testrun-status.ts b/modules/ui/src/app/model/testrun-status.ts index faa707f92..f14dce652 100644 --- a/modules/ui/src/app/model/testrun-status.ts +++ b/modules/ui/src/app/model/testrun-status.ts @@ -66,6 +66,7 @@ export enum StatusOfTestrun { Idle = 'Idle', Monitoring = 'Monitoring', Error = 'Error', + Validating = 'Validating Network', } export enum StatusOfTestResult { diff --git a/modules/ui/src/app/pages/devices/components/device-qualification-from/device-qualification-from.component.scss b/modules/ui/src/app/pages/devices/components/device-qualification-from/device-qualification-from.component.scss index 3ce6ee85b..c2088c67b 100644 --- a/modules/ui/src/app/pages/devices/components/device-qualification-from/device-qualification-from.component.scss +++ b/modules/ui/src/app/pages/devices/components/device-qualification-from/device-qualification-from.component.scss @@ -52,10 +52,6 @@ $form-min-width: 732px; color: $primary; } -.device-form-mac-address-error { - white-space: nowrap; -} - .hidden { display: none; } @@ -306,10 +302,9 @@ $form-min-width: 732px; } :host mat-form-field { - &::ng-deep.mat-mdc-form-field-subscript-wrapper:has( - mat-error.error-multiline - ) { - height: 46px; + &::ng-deep.mat-mdc-form-field-error-wrapper { + margin-top: -20px; + position: static; } } diff --git a/modules/ui/src/app/pages/devices/components/device-qualification-from/device-qualification-from.component.spec.ts b/modules/ui/src/app/pages/devices/components/device-qualification-from/device-qualification-from.component.spec.ts index e1b59025b..bcc34b35d 100644 --- a/modules/ui/src/app/pages/devices/components/device-qualification-from/device-qualification-from.component.spec.ts +++ b/modules/ui/src/app/pages/devices/components/device-qualification-from/device-qualification-from.component.spec.ts @@ -17,6 +17,7 @@ import { ComponentFixture, discardPeriodicTasks, fakeAsync, + flush, TestBed, tick, } from '@angular/core/testing'; @@ -50,17 +51,50 @@ import { DeviceStatus, TestingType } from '../../../../model/device'; import { Component, Input } from '@angular/core'; import { QuestionFormat } from '../../../../model/question'; import { BehaviorSubject } from 'rxjs/internal/BehaviorSubject'; +import { selectDevices } from '../../../../store/selectors'; describe('DeviceQualificationFromComponent', () => { let component: DeviceQualificationFromComponent; let fixture: ComponentFixture; let compiled: HTMLElement; const testrunServiceMock: jasmine.SpyObj = - jasmine.createSpyObj('testrunServiceMock', ['fetchQuestionnaireFormat']); + jasmine.createSpyObj('testrunServiceMock', [ + 'fetchQuestionnaireFormat', + 'saveDevice', + ]); const keyboardEvent = new BehaviorSubject( new KeyboardEvent('keydown', { code: '' }) ); + const MOCK_DEVICE = { + status: DeviceStatus.VALID, + manufacturer: '', + model: '', + mac_addr: '', + test_pack: TestingType.Qualification, + type: '', + technology: '', + test_modules: { + udmi: { + enabled: true, + }, + connection: { + enabled: true, + }, + }, + additional_info: [ + { question: 'What type of device is this?', answer: '' }, + { + question: 'Does your device process any sensitive information? ', + answer: '', + }, + { + question: 'Please select the technology this device falls into', + answer: '', + }, + ], + }; + beforeEach(async () => { await TestBed.configureTestingModule({ declarations: [FakeDynamicFormComponent], @@ -90,7 +124,9 @@ describe('DeviceQualificationFromComponent', () => { { provide: MAT_DIALOG_DATA, useValue: {} }, { provide: TestRunService, useValue: testrunServiceMock }, provideNgxMask(), - provideMockStore({}), + provideMockStore({ + selectors: [{ selector: selectDevices, value: [device, device] }], + }), ], }).compileComponents(); @@ -107,6 +143,8 @@ describe('DeviceQualificationFromComponent', () => { testrunServiceMock.fetchQuestionnaireFormat.and.returnValue( of(DEVICES_FORM) ); + + testrunServiceMock.saveDevice.and.returnValue(of(true)); }); it('should create', () => { @@ -160,6 +198,92 @@ describe('DeviceQualificationFromComponent', () => { closeSpy.calls.reset(); })); + it('should close dialog on submit with "Save" action', fakeAsync(() => { + component.device = MOCK_DEVICE; + const closeSpy = spyOn(component.dialogRef, 'close'); + fixture.detectChanges(); + + component.submit(); + tick(); + flush(); + + expect(closeSpy).toHaveBeenCalledWith({ + action: 'Save', + device: MOCK_DEVICE, + }); + + closeSpy.calls.reset(); + })); + + it('should close dialog on delete with "Delete" action', fakeAsync(() => { + const closeSpy = spyOn(component.dialogRef, 'close'); + fixture.detectChanges(); + + component.delete(); + tick(); + + expect(closeSpy).toHaveBeenCalledWith({ + action: 'Delete', + device: MOCK_DEVICE, + index: 0, + }); + + closeSpy.calls.reset(); + })); + + describe('#deviceHasNoChanges', () => { + const deviceProps = [ + { manufacturer: 'test' }, + { model: 'test' }, + { mac_addr: 'test' }, + { test_pack: TestingType.Pilot }, + { type: 'test' }, + { technology: 'test' }, + { + test_modules: { + udmi: { + enabled: false, + }, + }, + }, + { additional_info: undefined }, + { + additional_info: [ + { question: 'What type of device is this?', answer: 'test' }, + ], + }, + ]; + it('should return true if devices the same', () => { + const result = component.deviceHasNoChanges(MOCK_DEVICE, MOCK_DEVICE); + + expect(result).toBeTrue(); + }); + + deviceProps.forEach(item => { + it(`should return false if devices have different props`, () => { + const MOCK_DEVICE_1 = { ...MOCK_DEVICE, ...item }; + const result = component.deviceHasNoChanges(MOCK_DEVICE_1, MOCK_DEVICE); + + expect(result).toBeFalse(); + }); + }); + }); + + it('should trigger onResize method when window is resized ', () => { + fixture.detectChanges(); + const spyOnResize = spyOn(component, 'onResize'); + window.dispatchEvent(new Event('resize')); + fixture.detectChanges(); + expect(spyOnResize).toHaveBeenCalled(); + }); + + it('#goToStep should set selected index', () => { + fixture.detectChanges(); + component.goToStep(0); + + expect(component.stepper.selectedIndex).toBe(0); + }); + it('should close dialog on "cancel" click', () => { fixture.detectChanges(); component.manufacturer.setValue('test'); diff --git a/modules/ui/src/app/pages/devices/components/device-qualification-from/device-qualification-from.component.ts b/modules/ui/src/app/pages/devices/components/device-qualification-from/device-qualification-from.component.ts index 67eb39d4c..09edf8cdc 100644 --- a/modules/ui/src/app/pages/devices/components/device-qualification-from/device-qualification-from.component.ts +++ b/modules/ui/src/app/pages/devices/components/device-qualification-from/device-qualification-from.component.ts @@ -501,7 +501,7 @@ export class DeviceQualificationFromComponent for (const key of keys1) { const val1 = device1.test_modules![key]; const val2 = device2.test_modules![key]; - if (val1.enabled !== val2.enabled) { + if (val1?.enabled !== val2?.enabled) { return false; } } diff --git a/modules/ui/src/app/pages/reports/components/delete-report/delete-report.component.spec.ts b/modules/ui/src/app/pages/reports/components/delete-report/delete-report.component.spec.ts index f85babe34..f7271c377 100644 --- a/modules/ui/src/app/pages/reports/components/delete-report/delete-report.component.spec.ts +++ b/modules/ui/src/app/pages/reports/components/delete-report/delete-report.component.spec.ts @@ -51,13 +51,27 @@ describe('DeleteReportComponent', () => { it('#deleteReport should open delete dialog', () => { const deviceRemovedSpy = spyOn(component.removeDevice, 'emit'); - spyOn(component.dialog, 'open').and.returnValue({ + const openSpy = spyOn(component.dialog, 'open').and.returnValue({ afterClosed: () => of(true), } as MatDialogRef); component.deleteReport(new Event('click')); expect(deviceRemovedSpy).toHaveBeenCalled(); + + expect(openSpy).toHaveBeenCalledWith(SimpleDialogComponent, { + ariaLabel: 'Delete report', + data: { + title: 'Delete report?', + content: + 'You are about to delete Delta 03-DIN-CPU 1.2.2 22 Jun 2023 9:20. Are you sure?', + }, + autoFocus: true, + hasBackdrop: true, + disableClose: true, + panelClass: 'simple-dialog', + }); + openSpy.calls.reset(); }); }); diff --git a/modules/ui/src/app/pages/reports/components/delete-report/delete-report.component.ts b/modules/ui/src/app/pages/reports/components/delete-report/delete-report.component.ts index 74abb27d1..96e178443 100644 --- a/modules/ui/src/app/pages/reports/components/delete-report/delete-report.component.ts +++ b/modules/ui/src/app/pages/reports/components/delete-report/delete-report.component.ts @@ -62,7 +62,7 @@ export class DeleteReportComponent ariaLabel: 'Delete report', data: { title: 'Delete report?', - content: this.getTestRunId(this.data), + content: `You are about to delete ${this.getTestRunId(this.data)}. Are you sure?`, }, autoFocus: true, hasBackdrop: true, diff --git a/modules/ui/src/app/pages/reports/components/filter-chips/filter-chips.component.spec.ts b/modules/ui/src/app/pages/reports/components/filter-chips/filter-chips.component.spec.ts index 34cb9459f..508bb7c54 100644 --- a/modules/ui/src/app/pages/reports/components/filter-chips/filter-chips.component.spec.ts +++ b/modules/ui/src/app/pages/reports/components/filter-chips/filter-chips.component.spec.ts @@ -36,6 +36,40 @@ describe('FilterChipsComponent', () => { expect(component).toBeTruthy(); }); + describe('#clearFilter', () => { + const MOCK_FILTERS = { + deviceInfo: 'Delta', + deviceFirmware: '03', + results: ['Compliant'], + dateRange: { start: '10/2/2024', end: '11/2/2024' }, + }; + + beforeEach(() => { + component.filters = MOCK_FILTERS; + }); + + it(`should clear deviceFirmware filter`, () => { + const result = { ...MOCK_FILTERS, deviceFirmware: '' }; + component.clearFilter('deviceFirmware'); + + expect(component.filters).toEqual(result); + }); + + it(`should clear results filter`, () => { + const clearedFilters = { ...MOCK_FILTERS, results: [] }; + component.clearFilter('results'); + + expect(component.filters).toEqual(clearedFilters); + }); + + it(`should clear dateRange filter`, () => { + const clearedFilters = { ...MOCK_FILTERS, dateRange: '' }; + component.clearFilter('dateRange'); + + expect(component.filters).toEqual(clearedFilters); + }); + }); + describe('DOM tests', () => { describe('"Clear all filters" button', () => { it('should exist', () => { diff --git a/modules/ui/src/app/pages/risk-assessment/profile-form/profile-form.component.html b/modules/ui/src/app/pages/risk-assessment/profile-form/profile-form.component.html index dc1b0a330..8bde474ba 100644 --- a/modules/ui/src/app/pages/risk-assessment/profile-form/profile-form.component.html +++ b/modules/ui/src/app/pages/risk-assessment/profile-form/profile-form.component.html @@ -36,14 +36,12 @@ The Profile name is required - This Profile name is already used for another Risk Assessment - profile + This Profile name is already used for another profile - + +
diff --git a/modules/ui/src/app/pages/risk-assessment/profile-form/profile-form.component.spec.ts b/modules/ui/src/app/pages/risk-assessment/profile-form/profile-form.component.spec.ts index 237f09160..d279a1f14 100644 --- a/modules/ui/src/app/pages/risk-assessment/profile-form/profile-form.component.spec.ts +++ b/modules/ui/src/app/pages/risk-assessment/profile-form/profile-form.component.spec.ts @@ -141,7 +141,7 @@ describe('ProfileFormComponent', () => { expect(error).toBeTruthy(); expect(nameError).toContain( - 'This Profile name is already used for another Risk Assessment profile' + 'This Profile name is already used for another profile' ); }); }); diff --git a/modules/ui/src/app/pages/risk-assessment/profile-form/profile-form.component.ts b/modules/ui/src/app/pages/risk-assessment/profile-form/profile-form.component.ts index fe57d8867..2656221cd 100644 --- a/modules/ui/src/app/pages/risk-assessment/profile-form/profile-form.component.ts +++ b/modules/ui/src/app/pages/risk-assessment/profile-form/profile-form.component.ts @@ -84,6 +84,7 @@ export class ProfileFormComponent implements OnInit, AfterViewInit { @ViewChildren(CdkTextareaAutosize) autosize!: QueryList; @Input() profileFormat!: ProfileFormat[]; + @Input() isCopyProfile!: boolean; @Input() set profiles(profiles: Profile[]) { this.profileList = profiles; @@ -212,7 +213,7 @@ export class ProfileFormComponent implements OnInit, AfterViewInit { const request: any = { questions: [], }; - if (profile) { + if (profile && !this.isCopyProfile) { request.name = profile.name; request.rename = this.nameControl.value?.trim(); } else { diff --git a/modules/ui/src/app/pages/risk-assessment/profile-form/profile.validators.ts b/modules/ui/src/app/pages/risk-assessment/profile-form/profile.validators.ts index 34bac3ebf..10280586d 100644 --- a/modules/ui/src/app/pages/risk-assessment/profile-form/profile.validators.ts +++ b/modules/ui/src/app/pages/risk-assessment/profile-form/profile.validators.ts @@ -36,13 +36,13 @@ export class ProfileValidators { profile: Profile | null ): ValidatorFn { return (control: AbstractControl): ValidationErrors | null => { - const value = control.value?.trim(); + const value = control.value?.trim().toLowerCase(); if ( value && profiles.length && (!profile || !profile.created || - (profile.created && profile?.name !== value)) + (profile.created && profile?.name.toLowerCase() !== value)) ) { const isSameProfileName = this.hasSameProfileName(value, profiles); return isSameProfileName ? { has_same_profile_name: true } : null; @@ -91,7 +91,8 @@ export class ProfileValidators { profiles: Profile[] ): boolean { return ( - profiles.some(profile => profile.name === profileName?.trim()) || false + profiles.some(profile => profile.name.toLowerCase() === profileName) || + false ); } } diff --git a/modules/ui/src/app/pages/risk-assessment/profile-item/profile-item.component.spec.ts b/modules/ui/src/app/pages/risk-assessment/profile-item/profile-item.component.spec.ts index 56f9ad6a4..ccdf0e211 100644 --- a/modules/ui/src/app/pages/risk-assessment/profile-item/profile-item.component.spec.ts +++ b/modules/ui/src/app/pages/risk-assessment/profile-item/profile-item.component.spec.ts @@ -13,7 +13,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { + ComponentFixture, + fakeAsync, + TestBed, + tick, +} from '@angular/core/testing'; import { ProfileItemComponent } from './profile-item.component'; import { @@ -98,6 +103,32 @@ describe('ProfileItemComponent', () => { expect(profileClickedSpy).toHaveBeenCalledWith(PROFILE_MOCK); }); + it('should change tooltip on focusout', fakeAsync(() => { + component.profile = EXPIRED_PROFILE_MOCK; + fixture.detectChanges(); + + fixture.nativeElement.dispatchEvent(new Event('focusout')); + tick(); + + expect(component.tooltip.message).toEqual( + 'Expired. Please, create a new Risk profile.' + ); + })); + + it('#getRiskClass should call getRiskClass on testRunService', () => { + const MOCK_RISK = 'mock value'; + component.getRiskClass(MOCK_RISK); + expect(testRunServiceMock.getRiskClass).toHaveBeenCalledWith(MOCK_RISK); + }); + + it('#enterProfileItem should emit profileClicked', () => { + const profileClickedSpy = spyOn(component.profileClicked, 'emit'); + + component.enterProfileItem(PROFILE_MOCK); + + expect(profileClickedSpy).toHaveBeenCalled(); + }); + describe('with Expired profile', () => { beforeEach(() => { component.enterProfileItem(EXPIRED_PROFILE_MOCK); diff --git a/modules/ui/src/app/pages/risk-assessment/risk-assessment.component.html b/modules/ui/src/app/pages/risk-assessment/risk-assessment.component.html index c5e38e360..620712a2f 100644 --- a/modules/ui/src/app/pages/risk-assessment/risk-assessment.component.html +++ b/modules/ui/src/app/pages/risk-assessment/risk-assessment.component.html @@ -23,6 +23,7 @@

Risk assessment

{ }); }); + it('#profileClicked should call openForm with profile', fakeAsync(() => { + spyOn(component, 'openForm'); + + component.profileClicked(PROFILE_MOCK); + tick(); + + expect(component.openForm).toHaveBeenCalledWith(PROFILE_MOCK); + })); + + it('#copyProfileAndOpenForm should call openForm with copy of profile', fakeAsync(() => { + spyOn(component, 'openForm'); + + component.copyProfileAndOpenForm(PROFILE_MOCK); + tick(); + + expect(component.openForm).toHaveBeenCalledWith(COPY_PROFILE_MOCK); + })); + describe('#saveProfile', () => { describe('with no profile selected', () => { beforeEach(() => { @@ -384,6 +402,7 @@ class FakeProfileItemComponent { }) class FakeProfileFormComponent { @Input() profiles!: Profile[]; + @Input() isCopyProfile!: boolean; @Input() selectedProfile!: Profile; @Input() profileFormat!: ProfileFormat[]; } diff --git a/modules/ui/src/app/pages/risk-assessment/risk-assessment.component.ts b/modules/ui/src/app/pages/risk-assessment/risk-assessment.component.ts index b6eae14e2..e30efe710 100644 --- a/modules/ui/src/app/pages/risk-assessment/risk-assessment.component.ts +++ b/modules/ui/src/app/pages/risk-assessment/risk-assessment.component.ts @@ -40,6 +40,7 @@ import { SuccessDialogComponent } from './components/success-dialog/success-dial export class RiskAssessmentComponent implements OnInit, OnDestroy { viewModel$ = this.store.viewModel$; isOpenProfileForm = false; + isCopyProfile = false; private destroy$: Subject = new Subject(); constructor( private store: RiskAssessmentStore, @@ -71,6 +72,7 @@ export class RiskAssessmentComponent implements OnInit, OnDestroy { } async copyProfileAndOpenForm(profile: Profile) { + this.isCopyProfile = true; await this.openForm(this.getCopyOfProfile(profile)); } @@ -124,7 +126,10 @@ export class RiskAssessmentComponent implements OnInit, OnDestroy { this.liveAnnouncer.clear(); if (!selectedProfile) { this.saveProfile(profile, this.store.setFocusOnCreateButton); - } else if (this.compareProfiles(profile, selectedProfile)) { + } else if ( + this.compareProfiles(profile, selectedProfile) || + this.isCopyProfile + ) { this.saveProfile(profile, this.store.setFocusOnSelectedProfile); } else { this.openSaveDialog( @@ -187,6 +192,7 @@ export class RiskAssessmentComponent implements OnInit, OnDestroy { discard(selectedProfile: Profile | null) { this.liveAnnouncer.clear(); this.isOpenProfileForm = false; + this.isCopyProfile = false; if (selectedProfile) { timer(100).subscribe(() => { this.store.setFocusOnSelectedProfile(); @@ -220,6 +226,7 @@ export class RiskAssessmentComponent implements OnInit, OnDestroy { }, }); this.isOpenProfileForm = false; + this.isCopyProfile = false; } private setFocus(index: number): void { diff --git a/modules/ui/src/app/pages/settings/components/settings-dropdown/settings-dropdown.component.scss b/modules/ui/src/app/pages/settings/components/settings-dropdown/settings-dropdown.component.scss index 16093e336..38e82686c 100644 --- a/modules/ui/src/app/pages/settings/components/settings-dropdown/settings-dropdown.component.scss +++ b/modules/ui/src/app/pages/settings/components/settings-dropdown/settings-dropdown.component.scss @@ -48,6 +48,11 @@ .setting-field { width: 100%; + + &.mat-form-field-disabled { + opacity: 0.6; + } + ::ng-deep .mat-mdc-form-field-infix { min-height: 76px; display: flex; diff --git a/modules/ui/src/app/pages/settings/settings.component.html b/modules/ui/src/app/pages/settings/settings.component.html index 089ebd5eb..9a7671171 100644 --- a/modules/ui/src/app/pages/settings/settings.component.html +++ b/modules/ui/src/app/pages/settings/settings.component.html @@ -34,13 +34,6 @@

System settings

- - Warning! Testrun requires two ports to operate correctly. - - { fixture.detectChanges(); }); - it('should have callout component', () => { - const callout = compiled.querySelector('app-callout'); - - expect(callout).toBeTruthy(); - }); - it('should have disabled "Save" button', () => { component.deviceControl.setValue( MOCK_SYSTEM_CONFIG_WITH_DATA?.network?.device_intf @@ -331,7 +324,7 @@ describe('GeneralSettingsComponent', () => { isLessThanOneInterface: false, interfaces: MOCK_INTERFACES, deviceOptions: MOCK_INTERFACES, - internetOptions: MOCK_INTERNET_OPTIONS, + internetOptions: MOCK_INTERFACES, logLevelOptions: {}, monitoringPeriodOptions: {}, }); diff --git a/modules/ui/src/app/pages/settings/settings.component.ts b/modules/ui/src/app/pages/settings/settings.component.ts index b4fb17c9e..4c9d0dffe 100644 --- a/modules/ui/src/app/pages/settings/settings.component.ts +++ b/modules/ui/src/app/pages/settings/settings.component.ts @@ -83,7 +83,14 @@ export class SettingsComponent implements OnInit, OnDestroy { } get isFormValues(): boolean { - return this.internetControl?.value.value && this.deviceControl?.value.value; + return ( + this.deviceControl?.value?.value && + (this.isInternetControlDisabled || this.internetControl?.value?.value) + ); + } + + get isInternetControlDisabled(): boolean { + return this.internetControl?.disabled; } get isFormError(): boolean { @@ -102,7 +109,7 @@ export class SettingsComponent implements OnInit, OnDestroy { this.createSettingForm(); this.cleanFormErrorMessage(); this.settingsStore.getInterfaces(); - this.settingsStore.getSystemConfig(); + this.getSystemConfig(); this.setDefaultFormValues(); } @@ -112,7 +119,7 @@ export class SettingsComponent implements OnInit, OnDestroy { } this.showLoading(); this.getSystemInterfaces(); - this.settingsStore.getSystemConfig(); + this.getSystemConfig(); this.setDefaultFormValues(); } closeSetting(message: string): void { @@ -177,7 +184,7 @@ export class SettingsComponent implements OnInit, OnDestroy { const data: SystemConfig = { network: { device_intf: device_intf.key, - internet_intf: internet_intf.key, + internet_intf: this.isInternetControlDisabled ? '' : internet_intf.key, }, log_level: log_level.key, monitor_period: Number(monitor_period.key), diff --git a/modules/ui/src/app/pages/settings/settings.store.spec.ts b/modules/ui/src/app/pages/settings/settings.store.spec.ts index b51e1f2a6..969f5cb9f 100644 --- a/modules/ui/src/app/pages/settings/settings.store.spec.ts +++ b/modules/ui/src/app/pages/settings/settings.store.spec.ts @@ -13,12 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { - DEFAULT_INTERNET_OPTION, - LOG_LEVELS, - MONITORING_PERIOD, - SettingsStore, -} from './settings.store'; +import { LOG_LEVELS, MONITORING_PERIOD, SettingsStore } from './settings.store'; import { TestRunService } from '../../services/test-run.service'; import SpyObj = jasmine.SpyObj; import { TestBed } from '@angular/core/testing'; @@ -39,11 +34,11 @@ import { MOCK_DEVICE_VALUE, MOCK_INTERFACE_VALUE, MOCK_INTERFACES, - MOCK_INTERNET_OPTIONS, MOCK_LOG_VALUE, MOCK_PERIOD_VALUE, MOCK_SYSTEM_CONFIG_WITH_DATA, MOCK_SYSTEM_CONFIG_WITH_NO_DATA, + MOCK_SYSTEM_CONFIG_WITH_SINGLE_PORT, } from '../../mocks/settings.mock'; describe('SettingsStore', () => { @@ -120,7 +115,6 @@ describe('SettingsStore', () => { expect(store.interfaces).toEqual(MOCK_INTERFACES); expect(store.deviceOptions).toEqual(MOCK_INTERFACES); expect(store.internetOptions).toEqual({ - '': 'Not specified', mockDeviceKey: 'mockDeviceValue', mockInternetKey: 'mockInternetValue', }); @@ -193,7 +187,7 @@ describe('SettingsStore', () => { settingsStore.viewModel$.pipe(skip(3), take(1)).subscribe(store => { expect(store.interfaces).toEqual(interfaces); expect(store.deviceOptions).toEqual(interfaces); - expect(store.internetOptions).toEqual(MOCK_INTERNET_OPTIONS); + expect(store.internetOptions).toEqual(interfaces); done(); }); @@ -279,6 +273,27 @@ describe('SettingsStore', () => { }); }); + describe('with single port mode', () => { + beforeEach(() => { + settingsStore.setSystemConfig(MOCK_SYSTEM_CONFIG_WITH_SINGLE_PORT); + settingsStore.setInterfaces(MOCK_INTERFACES); + }); + + it('should disable internet control', () => { + const form = fb.group({ + device_intf: ['value'], + internet_intf: [''], + log_level: [''], + monitor_period: ['value'], + }); + settingsStore.setDefaultFormValues(form); + + expect( + (form.get(FormKey.INTERNET) as FormControl).disabled + ).toBeTrue(); + }); + }); + describe('when values are empty', () => { beforeEach(() => { settingsStore.setSystemConfig(MOCK_SYSTEM_CONFIG_WITH_NO_DATA); @@ -300,7 +315,7 @@ describe('SettingsStore', () => { }); expect((form.get(FormKey.INTERNET) as FormControl).value).toEqual({ key: '', - value: DEFAULT_INTERNET_OPTION[''], + value: undefined, }); expect((form.get(FormKey.LOG_LEVEL) as FormControl).value).toEqual({ key: 'INFO', @@ -322,7 +337,6 @@ describe('SettingsStore', () => { mockNewInternetKey: 'mockNewInternetValue', }; const updateInternetOptions = { - '': 'Not specified', mockDeviceKey: 'mockDeviceValue', mockNewInternetKey: 'mockNewInternetValue', }; diff --git a/modules/ui/src/app/pages/settings/settings.store.ts b/modules/ui/src/app/pages/settings/settings.store.ts index fc4a00dc9..87071b222 100644 --- a/modules/ui/src/app/pages/settings/settings.store.ts +++ b/modules/ui/src/app/pages/settings/settings.store.ts @@ -46,10 +46,6 @@ export interface SettingsComponentState { monitoringPeriodOptions: SystemInterfaces; } -export const DEFAULT_INTERNET_OPTION = { - '': 'Not specified', -}; - export const LOG_LEVELS = { DEBUG: 'Every event will be logged', INFO: 'Normal events and issues', @@ -118,10 +114,7 @@ export class SettingsStore extends ComponentStore { ...state, interfaces, deviceOptions: interfaces, - internetOptions: { - ...DEFAULT_INTERNET_OPTION, - ...interfaces, - }, + internetOptions: interfaces, isLessThanOneInterface: Object.keys(interfaces).length < 1, }; }); @@ -180,16 +173,21 @@ export class SettingsStore extends ComponentStore { this.systemConfig$.pipe( withLatestFrom(this.deviceOptions$, this.internetOptions$), tap(([config, deviceOptions, internetOptions]) => { + if (config.single_intf) { + this.disableInternetInterface(formGroup); + } else { + this.setDefaultInternetInterfaceValue( + config.network?.internet_intf, + internetOptions, + formGroup + ); + } + this.setDefaultDeviceInterfaceValue( config.network?.device_intf, deviceOptions, formGroup ); - this.setDefaultInternetInterfaceValue( - config.network?.internet_intf, - internetOptions, - formGroup - ); this.setDefaultLogLevelValue( config.log_level, LOG_LEVELS, @@ -300,6 +298,11 @@ export class SettingsStore extends ComponentStore { ); } + private disableInternetInterface(formGroup: FormGroup) { + const internetControl = formGroup.get(FormKey.INTERNET) as FormControl; + internetControl.disable(); + } + private setDefaultValue( value: string | undefined, defaultValue: string | undefined, diff --git a/modules/ui/src/app/pages/testrun/components/testrun-status-card/testrun-status-card.component.html b/modules/ui/src/app/pages/testrun/components/testrun-status-card/testrun-status-card.component.html index be4d3b259..9ca1d80b2 100644 --- a/modules/ui/src/app/pages/testrun/components/testrun-status-card/testrun-status-card.component.html +++ b/modules/ui/src/app/pages/testrun/components/testrun-status-card/testrun-status-card.component.html @@ -27,6 +27,10 @@

+ + @@ -105,6 +109,12 @@
+ +
+

Validating virtual network

+
+
+

diff --git a/modules/ui/src/app/pages/testrun/components/testrun-status-card/testrun-status-card.component.ts b/modules/ui/src/app/pages/testrun/components/testrun-status-card/testrun-status-card.component.ts index d84b692e1..101591c98 100644 --- a/modules/ui/src/app/pages/testrun/components/testrun-status-card/testrun-status-card.component.ts +++ b/modules/ui/src/app/pages/testrun/components/testrun-status-card/testrun-status-card.component.ts @@ -92,7 +92,8 @@ export class TestrunStatusCardComponent { return ( status === StatusOfTestrun.Monitoring || status === StatusOfTestrun.InProgress || - status === StatusOfTestrun.WaitingForDevice + status === StatusOfTestrun.WaitingForDevice || + status === StatusOfTestrun.Validating ); } diff --git a/modules/ui/src/app/pages/testrun/components/testrun-table/testrun-table.component.scss b/modules/ui/src/app/pages/testrun/components/testrun-table/testrun-table.component.scss index 231df35cd..cc5bc118b 100644 --- a/modules/ui/src/app/pages/testrun/components/testrun-table/testrun-table.component.scss +++ b/modules/ui/src/app/pages/testrun/components/testrun-table/testrun-table.component.scss @@ -59,6 +59,7 @@ $expander-button-size: 28px; padding: 16px; letter-spacing: 0.2px; vertical-align: top; + font-weight: 400; } .tests-item-cell-result { diff --git a/modules/ui/src/app/pages/testrun/components/testrun-table/testrun-table.component.spec.ts b/modules/ui/src/app/pages/testrun/components/testrun-table/testrun-table.component.spec.ts index 0632ff75c..73b7196e8 100644 --- a/modules/ui/src/app/pages/testrun/components/testrun-table/testrun-table.component.spec.ts +++ b/modules/ui/src/app/pages/testrun/components/testrun-table/testrun-table.component.spec.ts @@ -74,6 +74,14 @@ describe('ProgressTableComponent', () => { 'dns.network.hostname_resolutionCompliant' ); }); + + it('#getAriaLabel should return valid message', () => { + component.isAllCollapsed = true; + + const result = component.getAriaLabel(); + + expect(result).toEqual('Expand all rows'); + }); }); describe('DOM tests', () => { @@ -143,6 +151,12 @@ describe('ProgressTableComponent', () => { expect(button).not.toBeNull(); expect(button?.ariaLabel).toBe('Collapse row'); }); + + it('#checkAllCollapsed should return isAllCollapsed', () => { + component.checkAllCollapsed(); + + expect(component.isAllCollapsed).toBeFalse(); + }); }); }); }); diff --git a/modules/ui/src/app/pages/testrun/testrun.component.html b/modules/ui/src/app/pages/testrun/testrun.component.html index 042a7815f..92f8cf958 100644 --- a/modules/ui/src/app/pages/testrun/testrun.component.html +++ b/modules/ui/src/app/pages/testrun/testrun.component.html @@ -36,7 +36,10 @@ ">

-

+

{{ getTestRunName(data) }}

diff --git a/modules/ui/src/app/pages/testrun/testrun.component.scss b/modules/ui/src/app/pages/testrun/testrun.component.scss index 91f4c22d0..9e01bc725 100644 --- a/modules/ui/src/app/pages/testrun/testrun.component.scss +++ b/modules/ui/src/app/pages/testrun/testrun.component.scss @@ -90,6 +90,7 @@ white-space: nowrap; overflow: hidden; text-overflow: ellipsis; + cursor: pointer; } .toolbar-tag-container { diff --git a/modules/ui/src/app/pages/testrun/testrun.component.ts b/modules/ui/src/app/pages/testrun/testrun.component.ts index 133abe532..447da1512 100644 --- a/modules/ui/src/app/pages/testrun/testrun.component.ts +++ b/modules/ui/src/app/pages/testrun/testrun.component.ts @@ -111,7 +111,7 @@ export class TestrunComponent implements OnInit, OnDestroy { getTestRunName(systemStatus: TestrunStatus): string { if (systemStatus?.device) { const device = systemStatus.device; - return `${device.manufacturer} ${device.model} v${device.firmware}`; + return `${device.manufacturer} ${device.model} ${device.firmware}`; } else { return ''; } diff --git a/modules/ui/src/app/pages/testrun/testrun.store.spec.ts b/modules/ui/src/app/pages/testrun/testrun.store.spec.ts index 449ffb408..24ffc6ba2 100644 --- a/modules/ui/src/app/pages/testrun/testrun.store.spec.ts +++ b/modules/ui/src/app/pages/testrun/testrun.store.spec.ts @@ -43,6 +43,7 @@ import { MOCK_PROGRESS_DATA_IN_PROGRESS_EMPTY, MOCK_PROGRESS_DATA_MONITORING, MOCK_PROGRESS_DATA_WAITING_FOR_DEVICE, + MOCK_PROGRESS_DATA_VALIDATING, TEST_DATA_RESULT_WITH_RECOMMENDATIONS, TEST_DATA_TABLE_RESULT, } from '../../mocks/testrun.mock'; @@ -171,6 +172,21 @@ describe('TestrunStore', () => { }); }); + it('should set value with empty values for status "Validating Network"', done => { + const expectedResult = EMPTY_RESULT; + + store.overrideSelector( + selectSystemStatus, + MOCK_PROGRESS_DATA_VALIDATING + ); + store.refreshState(); + + testrunStore.viewModel$.pipe(take(1)).subscribe(store => { + expect(store.dataSource).toEqual(expectedResult); + done(); + }); + }); + it('should set value with empty values for status "Cancelled" and empty result', done => { const expectedResult = EMPTY_RESULT; diff --git a/modules/ui/src/app/pages/testrun/testrun.store.ts b/modules/ui/src/app/pages/testrun/testrun.store.ts index 2dc0c19f1..351ec10d1 100644 --- a/modules/ui/src/app/pages/testrun/testrun.store.ts +++ b/modules/ui/src/app/pages/testrun/testrun.store.ts @@ -99,6 +99,7 @@ export class TestrunStore extends ComponentStore { // perform some additional actions tap(res => { if ( + res?.status === StatusOfTestrun.Validating || res?.status === StatusOfTestrun.WaitingForDevice || res?.status === StatusOfTestrun.Monitoring || (res?.status === StatusOfTestrun.InProgress && @@ -119,6 +120,7 @@ export class TestrunStore extends ComponentStore { tap(res => { const results = (res?.tests as TestsData)?.results || []; if ( + res?.status === StatusOfTestrun.Validating || res?.status === StatusOfTestrun.Monitoring || res?.status === StatusOfTestrun.WaitingForDevice || (res?.status === StatusOfTestrun.Cancelled && !results.length) @@ -208,7 +210,8 @@ export class TestrunStore extends ComponentStore { return ( status === StatusOfTestrun.InProgress || status === StatusOfTestrun.WaitingForDevice || - status === StatusOfTestrun.Monitoring + status === StatusOfTestrun.Monitoring || + status === StatusOfTestrun.Validating ); } diff --git a/modules/ui/src/app/services/notification.service.spec.ts b/modules/ui/src/app/services/notification.service.spec.ts index c02c45f46..175ed661e 100644 --- a/modules/ui/src/app/services/notification.service.spec.ts +++ b/modules/ui/src/app/services/notification.service.spec.ts @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { TestBed } from '@angular/core/testing'; +import { fakeAsync, TestBed, tick } from '@angular/core/testing'; import { NotificationService } from './notification.service'; import { @@ -25,6 +25,8 @@ import { of } from 'rxjs/internal/observable/of'; import { MockStore, provideMockStore } from '@ngrx/store/testing'; import { AppState } from '../store/state'; import { SnackBarComponent } from '../components/snack-bar/snack-bar.component'; +import { setIsOpenWaitSnackBar } from '../store/actions'; +import { FocusManagerService } from './focus-manager.service'; describe('NotificationService', () => { let service: NotificationService; @@ -36,10 +38,14 @@ describe('NotificationService', () => { openFromComponent: () => ({}), }; + const focusServiceMock: jasmine.SpyObj = + jasmine.createSpyObj('focusServiceMock', ['focusFirstElementInContainer']); + beforeEach(() => { TestBed.configureTestingModule({ providers: [ { provide: MatSnackBar, useValue: mockMatSnackBar }, + { provide: FocusManagerService, useValue: focusServiceMock }, provideMockStore({}), ], }); @@ -84,6 +90,22 @@ describe('NotificationService', () => { politeness: 'assertive', }); }); + + it('should open snackbar with addition panelClass', () => { + const openSpy = spyOn(service.snackBar, 'open').and.returnValues({ + afterOpened: () => of(void 0), + afterDismissed: () => of({ dismissedByAction: true }), + } as MatSnackBarRef); + + service.notify('something good happened', 1500, 'mock-class'); + + expect(openSpy).toHaveBeenCalledWith('something good happened', 'OK', { + horizontalPosition: 'center', + panelClass: ['test-run-notification', 'mock-class'], + duration: 1500, + politeness: 'assertive', + }); + }); }); describe('openSnackBar', () => { @@ -103,6 +125,18 @@ describe('NotificationService', () => { panelClass: 'snack-bar-info', }); }); + + it('should call focusFirstElementInContainer', fakeAsync(() => { + spyOn(service.snackBar, 'openFromComponent').and.returnValues({ + afterOpened: () => of(void 0), + afterDismissed: () => of({ dismissedByAction: true }), + } as MatSnackBarRef); + + service.openSnackBar(); + tick(8000); + + expect(focusServiceMock.focusFirstElementInContainer).toHaveBeenCalled(); + })); }); describe('dismiss', () => { @@ -114,4 +148,21 @@ describe('NotificationService', () => { expect(matSnackBarSpy).toHaveBeenCalled(); }); }); + + it('#dismissSnackBar should dispatch setIsOpenWaitSnackBar action', () => { + service.dismissSnackBar(); + + expect(store.dispatch).toHaveBeenCalledWith( + setIsOpenWaitSnackBar({ isOpenWaitSnackBar: false }) + ); + }); + + it('#dismissWithTimout should call dismissSnackBar after timer', fakeAsync(() => { + const dismissSnackBarSpy = spyOn(service, 'dismissSnackBar').and.stub(); + + service.dismissWithTimout(); + tick(5000); + + expect(dismissSnackBarSpy).toHaveBeenCalledWith(); + })); }); diff --git a/modules/ui/src/app/services/test-run-mqtt.service.spec.ts b/modules/ui/src/app/services/test-run-mqtt.service.spec.ts index 19bda437a..6f5c7e325 100644 --- a/modules/ui/src/app/services/test-run-mqtt.service.spec.ts +++ b/modules/ui/src/app/services/test-run-mqtt.service.spec.ts @@ -9,17 +9,23 @@ import { MOCK_ADAPTERS } from '../mocks/settings.mock'; import { Topic } from '../model/topic'; import { MOCK_INTERNET } from '../mocks/topic.mock'; import { MOCK_PROGRESS_DATA_IN_PROGRESS } from '../mocks/testrun.mock'; +import { TestRunService } from './test-run.service'; describe('TestRunMqttService', () => { let service: TestRunMqttService; let mockService: SpyObj; + let testRunServiceMock: SpyObj; beforeEach(() => { mockService = jasmine.createSpyObj(['observe']); + testRunServiceMock = jasmine.createSpyObj(['changeReportURL']); TestBed.configureTestingModule({ imports: [MqttModule.forRoot(MQTT_SERVICE_OPTIONS)], - providers: [{ provide: MqttService, useValue: mockService }], + providers: [ + { provide: MqttService, useValue: mockService }, + { provide: TestRunService, useValue: testRunServiceMock }, + ], }); service = TestBed.inject(TestRunMqttService); }); @@ -75,6 +81,7 @@ describe('TestRunMqttService', () => { mockService.observe.and.returnValue( of(getResponse(MOCK_PROGRESS_DATA_IN_PROGRESS)) ); + testRunServiceMock.changeReportURL.and.returnValue(''); }); it('should subscribe the topic', done => { diff --git a/modules/ui/src/app/services/test-run-mqtt.service.ts b/modules/ui/src/app/services/test-run-mqtt.service.ts index d5e805da6..d53622d28 100644 --- a/modules/ui/src/app/services/test-run-mqtt.service.ts +++ b/modules/ui/src/app/services/test-run-mqtt.service.ts @@ -5,12 +5,16 @@ import { map } from 'rxjs/operators'; import { Adapters } from '../model/setting'; import { TestrunStatus } from '../model/testrun-status'; import { InternetConnection, Topic } from '../model/topic'; +import { TestRunService } from './test-run.service'; @Injectable({ providedIn: 'root', }) export class TestRunMqttService { - constructor(private mqttService: MqttService) {} + constructor( + private mqttService: MqttService, + private testrunService: TestRunService + ) {} getNetworkAdapters(): Observable { return this.topic(Topic.NetworkAdapters); @@ -21,7 +25,12 @@ export class TestRunMqttService { } getStatus(): Observable { - return this.topic(Topic.Status); + return this.topic(Topic.Status).pipe( + map(result => { + result.report = this.testrunService.changeReportURL(result.report); + return result; + }) + ); } private topic(topicName: string): Observable { 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 bcd3ec592..d50a9e8fc 100644 --- a/modules/ui/src/app/services/test-run.service.spec.ts +++ b/modules/ui/src/app/services/test-run.service.spec.ts @@ -154,6 +154,8 @@ describe('TestRunService', () => { }); describe('fetchSystemStatus', () => { + const systemStatusUrl = 'http://localhost:8000/system/status'; + it('should get system status data with no changes', () => { const result = { ...MOCK_PROGRESS_DATA_IN_PROGRESS }; @@ -161,12 +163,22 @@ describe('TestRunService', () => { expect(res).toEqual(result); }); - const req = httpTestingController.expectOne( - 'http://localhost:8000/system/status' - ); + const req = httpTestingController.expectOne(systemStatusUrl); expect(req.request.method).toBe('GET'); req.flush(result); }); + + it('should get system status as empty object if error happens', () => { + const mockError = { error: 'someError' } as ErrorEvent; + + service.fetchSystemStatus().subscribe(res => { + expect(res).toEqual({} as TestrunStatus); + }); + + const req = httpTestingController.expectOne(systemStatusUrl); + + req.error(mockError); + }); }); it('stopTestrun should have necessary request data', () => { @@ -354,6 +366,7 @@ describe('TestRunService', () => { StatusOfTestrun.InProgress, StatusOfTestrun.WaitingForDevice, StatusOfTestrun.Monitoring, + StatusOfTestrun.Validating, ]; const resultsNotInProgress = [ diff --git a/modules/ui/src/app/services/test-run.service.ts b/modules/ui/src/app/services/test-run.service.ts index 2e66234c7..d7539d2ab 100644 --- a/modules/ui/src/app/services/test-run.service.ts +++ b/modules/ui/src/app/services/test-run.service.ts @@ -53,6 +53,14 @@ export class TestRunService { constructor(private http: HttpClient) {} + changeReportURL(url: string): string { + if (!url) { + return ''; + } + // replace url part before '/report' from static to dynamic API URL + return url.replace(/^.*(?=\/report)/, `${API_URL}`); + } + fetchDevices(): Observable { return this.http.get(`${API_URL}/devices`); } @@ -72,7 +80,15 @@ export class TestRunService { } fetchSystemStatus() { - return this.http.get(`${API_URL}/system/status`); + return this.http.get(`${API_URL}/system/status`).pipe( + map(result => { + result.report = this.changeReportURL(result.report); + return result; + }), + catchError(() => { + return of({} as TestrunStatus); + }) + ); } stopTestrun(): Observable { @@ -134,7 +150,14 @@ export class TestRunService { } getHistory(): Observable { - return this.http.get(`${API_URL}/reports`); + return this.http.get(`${API_URL}/reports`).pipe( + map(result => { + result.forEach( + item => (item.report = this.changeReportURL(item.report)) + ); + return result; + }) + ); } public getResultClass(result: string): StatusResultClassName { @@ -169,7 +192,8 @@ export class TestRunService { return ( status === StatusOfTestrun.InProgress || status === StatusOfTestrun.WaitingForDevice || - status === StatusOfTestrun.Monitoring + status === StatusOfTestrun.Monitoring || + status === StatusOfTestrun.Validating ); } diff --git a/modules/ui/src/app/store/actions.ts b/modules/ui/src/app/store/actions.ts index b101ec499..58153ad9a 100644 --- a/modules/ui/src/app/store/actions.ts +++ b/modules/ui/src/app/store/actions.ts @@ -15,42 +15,22 @@ */ import { createAction, props } from '@ngrx/store'; -import { - Adapters, - InterfacesValidation, - SettingMissedError, - SystemConfig, -} from '../model/setting'; +import { Adapters, InterfacesValidation, SystemConfig } from '../model/setting'; import { SystemInterfaces } from '../model/setting'; import { Device, TestModule } from '../model/device'; import { TestrunStatus } from '../model/testrun-status'; import { Profile } from '../model/profile'; -// App component -export const toggleMenu = createAction('[App Component] Toggle Menu'); - -export const fetchInterfaces = createAction('[App Component] Fetch Interfaces'); - export const fetchInterfacesSuccess = createAction( '[App Component] Fetch interfaces Success', props<{ interfaces: SystemInterfaces }>() ); -export const updateFocusNavigation = createAction( - '[App Component] update focus navigation', - props<{ focusNavigation: boolean }>() -); - export const updateValidInterfaces = createAction( '[App Component] Update Valid Interfaces', props<{ validInterfaces: InterfacesValidation }>() ); -export const updateError = createAction( - '[App Component] Update Setting Missed Error', - props<{ settingMissedError: SettingMissedError }>() -); - // Settings export const fetchSystemConfigSuccess = createAction( '[Settings] Fetch System Config Success', diff --git a/modules/ui/src/app/store/effects.spec.ts b/modules/ui/src/app/store/effects.spec.ts index 01491b777..c0a06352d 100644 --- a/modules/ui/src/app/store/effects.spec.ts +++ b/modules/ui/src/app/store/effects.spec.ts @@ -28,11 +28,7 @@ import { Action } from '@ngrx/store'; import * as actions from './actions'; import { MockStore, provideMockStore } from '@ngrx/store/testing'; import { AppState } from './state'; -import { - selectIsOpenWaitSnackBar, - selectMenuOpened, - selectSystemStatus, -} from './selectors'; +import { selectIsOpenWaitSnackBar, selectSystemStatus } from './selectors'; import { device, expired_device } from '../mocks/device.mock'; import { MOCK_PROGRESS_DATA_CANCELLING, @@ -191,191 +187,6 @@ describe('Effects', () => { }); }); - it('onMenuOpened$ should call updateFocusNavigation', done => { - actions$ = of(actions.toggleMenu()); - store.overrideSelector(selectMenuOpened, true); - - effects.onMenuOpened$.subscribe(action => { - expect(action).toEqual( - actions.updateFocusNavigation({ focusNavigation: true }) - ); - done(); - }); - }); - - describe('onValidateInterfaces$', () => { - it('should call updateError and set false if interfaces are not missed', done => { - actions$ = of( - actions.updateValidInterfaces({ - validInterfaces: { - deviceValid: true, - internetValid: true, - }, - }) - ); - - effects.onValidateInterfaces$.subscribe(action => { - expect(action).toEqual( - actions.updateError({ - settingMissedError: { - isSettingMissed: false, - devicePortMissed: false, - internetPortMissed: false, - }, - }) - ); - done(); - }); - }); - - it('should call updateError and set true if interfaces are missed', done => { - actions$ = of( - actions.updateValidInterfaces({ - validInterfaces: { - deviceValid: false, - internetValid: false, - }, - }) - ); - - effects.onValidateInterfaces$.subscribe(action => { - expect(action).toEqual( - actions.updateError({ - settingMissedError: { - isSettingMissed: true, - devicePortMissed: true, - internetPortMissed: true, - }, - }) - ); - done(); - }); - }); - }); - - describe('checkInterfacesInConfig$', () => { - it('should call updateValidInterfaces and set deviceValid as false if device interface is no longer available', done => { - actions$ = of( - actions.fetchInterfacesSuccess({ - interfaces: { - enx00e04c020fa8: '00:e0:4c:02:0f:a8', - enx207bd26205e9: '20:7b:d2:62:05:e9', - }, - }), - actions.fetchSystemConfigSuccess({ - systemConfig: { - network: { - device_intf: 'enx00e04c020fa2', - internet_intf: 'enx207bd26205e9', - }, - }, - }) - ); - - effects.checkInterfacesInConfig$.subscribe(action => { - expect(action).toEqual( - actions.updateValidInterfaces({ - validInterfaces: { - deviceValid: false, - internetValid: true, - }, - }) - ); - done(); - }); - }); - - it('should call updateValidInterfaces and set all true if interface is set and valid', done => { - actions$ = of( - actions.fetchInterfacesSuccess({ - interfaces: { - enx00e04c020fa8: '00:e0:4c:02:0f:a8', - enx207bd26205e9: '20:7b:d2:62:05:e9', - }, - }), - actions.fetchSystemConfigSuccess({ - systemConfig: { - network: { - device_intf: 'enx00e04c020fa8', - internet_intf: 'enx207bd26205e9', - }, - }, - }) - ); - - effects.checkInterfacesInConfig$.subscribe(action => { - expect(action).toEqual( - actions.updateValidInterfaces({ - validInterfaces: { - deviceValid: true, - internetValid: true, - }, - }) - ); - done(); - }); - }); - - it('should call updateValidInterfaces and set all true if interface are empty and config is not set', done => { - actions$ = of( - actions.fetchInterfacesSuccess({ - interfaces: {}, - }), - actions.fetchSystemConfigSuccess({ - systemConfig: { - network: { - device_intf: '', - internet_intf: '', - }, - }, - }) - ); - - effects.checkInterfacesInConfig$.subscribe(action => { - expect(action).toEqual( - actions.updateValidInterfaces({ - validInterfaces: { - deviceValid: true, - internetValid: true, - }, - }) - ); - done(); - }); - }); - - it('should call updateValidInterfaces and set all true if interface are not empty and config is not set', done => { - actions$ = of( - actions.fetchInterfacesSuccess({ - interfaces: { - enx00e04c020fa8: '00:e0:4c:02:0f:a8', - enx207bd26205e9: '20:7b:d2:62:05:e9', - }, - }), - actions.fetchSystemConfigSuccess({ - systemConfig: { - network: { - device_intf: '', - internet_intf: '', - }, - }, - }) - ); - - effects.checkInterfacesInConfig$.subscribe(action => { - expect(action).toEqual( - actions.updateValidInterfaces({ - validInterfaces: { - deviceValid: true, - internetValid: true, - }, - }) - ); - done(); - }); - }); - }); - describe('onFetchSystemConfigSuccess$', () => { it('should dispatch setHasConnectionSettings with true if device_intf is present', done => { actions$ = of( @@ -614,7 +425,7 @@ describe('Effects', () => { effects.checkStatusInReports$.subscribe(action => { expect(action).toEqual( - actions.setTestrunStatus({ systemStatus: IDLE_STATUS }) + actions.fetchSystemStatusSuccess({ systemStatus: IDLE_STATUS }) ); done(); }); diff --git a/modules/ui/src/app/store/effects.ts b/modules/ui/src/app/store/effects.ts index 045b0cc7c..12140eb65 100644 --- a/modules/ui/src/app/store/effects.ts +++ b/modules/ui/src/app/store/effects.ts @@ -24,7 +24,6 @@ import { AppState } from './state'; import { TestRunService } from '../services/test-run.service'; import { filter, - combineLatest, Subject, timer, take, @@ -32,11 +31,7 @@ import { EMPTY, Subscription, } from 'rxjs'; -import { - selectIsOpenWaitSnackBar, - selectMenuOpened, - selectSystemStatus, -} from './selectors'; +import { selectIsOpenWaitSnackBar, selectSystemStatus } from './selectors'; import { IDLE_STATUS, StatusOfTestrun, @@ -68,57 +63,6 @@ export class AppEffects { private internetSubscription: Subscription | undefined; private destroyWaitDeviceInterval$: Subject = new Subject(); - checkInterfacesInConfig$ = createEffect(() => - combineLatest([ - this.actions$.pipe(ofType(AppActions.fetchInterfacesSuccess)), - this.actions$.pipe(ofType(AppActions.fetchSystemConfigSuccess)), - ]).pipe( - filter( - ([ - , - { - systemConfig: { network }, - }, - ]) => network !== null - ), - map( - ([ - { interfaces }, - { - systemConfig: { network }, - }, - ]) => - AppActions.updateValidInterfaces({ - validInterfaces: { - deviceValid: - network?.device_intf == '' || - (!!network?.device_intf && !!interfaces[network.device_intf]), - internetValid: - network?.internet_intf == '' || - (!!network?.internet_intf && - !!interfaces[network.internet_intf]), - }, - }) - ) - ) - ); - - onValidateInterfaces$ = createEffect(() => { - return this.actions$.pipe( - ofType(AppActions.updateValidInterfaces), - map(({ validInterfaces }) => - AppActions.updateError({ - settingMissedError: { - isSettingMissed: - !validInterfaces.deviceValid || !validInterfaces.internetValid, - devicePortMissed: !validInterfaces.deviceValid, - internetPortMissed: !validInterfaces.internetValid, - }, - }) - ) - ); - }); - onFetchSystemConfigSuccess$ = createEffect(() => { return this.actions$.pipe( ofType(AppActions.fetchSystemConfigSuccess), @@ -134,15 +78,6 @@ export class AppEffects { ); }); - onMenuOpened$ = createEffect(() => { - return this.actions$.pipe( - ofType(AppActions.toggleMenu), - withLatestFrom(this.store.select(selectMenuOpened)), - filter(([, opened]) => opened === true), - map(() => AppActions.updateFocusNavigation({ focusNavigation: true })) // user will be navigated to side menu on tab - ); - }); - onSetDevices$ = createEffect(() => { return this.actions$.pipe( ofType(AppActions.setDevices), @@ -346,7 +281,9 @@ export class AppEffects { false ); }), - map(() => AppActions.setTestrunStatus({ systemStatus: IDLE_STATUS })) + map(() => + AppActions.fetchSystemStatusSuccess({ systemStatus: IDLE_STATUS }) + ) ); }); diff --git a/modules/ui/src/app/store/reducers.spec.ts b/modules/ui/src/app/store/reducers.spec.ts index 294de5fac..3411137fe 100644 --- a/modules/ui/src/app/store/reducers.spec.ts +++ b/modules/ui/src/app/store/reducers.spec.ts @@ -14,9 +14,10 @@ * limitations under the License. */ import * as fromReducer from './reducers'; -import { initialAppComponentState, initialSharedState } from './state'; +import { initialState as initialAppState } from './state'; import { fetchInterfacesSuccess, + fetchSystemConfigSuccess, setDeviceInProgress, setDevices, setHasConnectionSettings, @@ -33,10 +34,7 @@ import { setStatus, setTestModules, setTestrunStatus, - toggleMenu, updateAdapters, - updateError, - updateFocusNavigation, updateInternetConnection, } from './actions'; import { device, MOCK_TEST_MODULES } from '../mocks/device.mock'; @@ -48,11 +46,11 @@ import { MOCK_ADAPTERS } from '../mocks/settings.mock'; describe('Reducer', () => { describe('unknown action', () => { it('should return the default state', () => { - const initialState = initialAppComponentState; + const initialState = initialAppState; const action = { type: 'Unknown', }; - const state = fromReducer.appComponentReducer(initialState, action); + const state = fromReducer.rootReducer(initialState, action); expect(state).toBe(initialState); }); @@ -60,13 +58,13 @@ describe('Reducer', () => { describe('fetchInterfacesSuccess action', () => { it('should update state', () => { - const initialState = initialAppComponentState; + const initialState = initialAppState; const newInterfaces = { enx00e04c020fa8: '00:e0:4c:02:0f:a8', enx207bd26205e9: '20:7b:d2:62:05:e9', }; const action = fetchInterfacesSuccess({ interfaces: newInterfaces }); - const state = fromReducer.appComponentReducer(initialState, action); + const state = fromReducer.rootReducer(initialState, action); const newState = { ...initialState, ...{ interfaces: newInterfaces } }; expect(state).toEqual(newState); @@ -74,33 +72,9 @@ describe('Reducer', () => { }); }); - describe('updateFocusNavigation action', () => { - it('should update state', () => { - const initialState = initialAppComponentState; - const action = updateFocusNavigation({ focusNavigation: true }); - const state = fromReducer.appComponentReducer(initialState, action); - - const newState = { ...initialState, ...{ focusNavigation: true } }; - expect(state).toEqual(newState); - expect(state).not.toBe(initialState); - }); - }); - - describe('toggleMenu action', () => { - it('should update state', () => { - const initialState = initialAppComponentState; - const action = toggleMenu(); - const state = fromReducer.appComponentReducer(initialState, action); - - const newState = { ...initialState, ...{ isMenuOpen: true } }; - expect(state).toEqual(newState); - expect(state).not.toBe(initialState); - }); - }); - describe('setHasConnectionSettings action', () => { it('should update state', () => { - const initialState = initialSharedState; + const initialState = initialAppState; const action = setHasConnectionSettings({ hasConnectionSettings: true }); const state = fromReducer.sharedReducer(initialState, action); const newState = { ...initialState, ...{ hasConnectionSettings: true } }; @@ -110,31 +84,9 @@ describe('Reducer', () => { }); }); - describe('updateError action', () => { - it('should update state', () => { - const mockSettingMissedError = { - isSettingMissed: true, - devicePortMissed: true, - internetPortMissed: true, - }; - const initialState = initialAppComponentState; - const action = updateError({ - settingMissedError: mockSettingMissedError, - }); - const state = fromReducer.appComponentReducer(initialState, action); - const newState = { - ...initialState, - ...{ settingMissedError: mockSettingMissedError }, - }; - - expect(state).toEqual(newState); - expect(state).not.toBe(initialState); - }); - }); - describe('setIsOpenAddDevice action', () => { it('should update state', () => { - const initialState = initialSharedState; + const initialState = initialAppState; const action = setIsOpenAddDevice({ isOpenAddDevice: true }); const state = fromReducer.sharedReducer(initialState, action); const newState = { ...initialState, ...{ isOpenAddDevice: true } }; @@ -146,7 +98,7 @@ describe('Reducer', () => { describe('setIsOpenWaitSnackBar action', () => { it('should update state', () => { - const initialState = initialSharedState; + const initialState = initialAppState; const action = setIsOpenWaitSnackBar({ isOpenWaitSnackBar: true }); const state = fromReducer.sharedReducer(initialState, action); const newState = { ...initialState, ...{ isOpenWaitSnackBar: true } }; @@ -158,7 +110,7 @@ describe('Reducer', () => { describe('setHasDevices action', () => { it('should update state', () => { - const initialState = initialSharedState; + const initialState = initialAppState; const action = setHasDevices({ hasDevices: true }); const state = fromReducer.sharedReducer(initialState, action); const newState = { ...initialState, ...{ hasDevices: true } }; @@ -170,7 +122,7 @@ describe('Reducer', () => { describe('setHasExpiredDevices action', () => { it('should update state', () => { - const initialState = initialSharedState; + const initialState = initialAppState; const action = setHasExpiredDevices({ hasExpiredDevices: true }); const state = fromReducer.sharedReducer(initialState, action); const newState = { ...initialState, ...{ hasExpiredDevices: true } }; @@ -182,7 +134,7 @@ describe('Reducer', () => { describe('setIsAllDevicesOutdated action', () => { it('should update state', () => { - const initialState = initialSharedState; + const initialState = initialAppState; const action = setIsAllDevicesOutdated({ isAllDevicesOutdated: true }); const state = fromReducer.sharedReducer(initialState, action); const newState = { ...initialState, ...{ isAllDevicesOutdated: true } }; @@ -194,7 +146,7 @@ describe('Reducer', () => { describe('setDevices action', () => { it('should update state', () => { - const initialState = initialSharedState; + const initialState = initialAppState; const devices = [device, device]; const action = setDevices({ devices }); const state = fromReducer.sharedReducer(initialState, action); @@ -207,7 +159,7 @@ describe('Reducer', () => { describe('setHasRiskProfiles action', () => { it('should update state', () => { - const initialState = initialSharedState; + const initialState = initialAppState; const action = setHasRiskProfiles({ hasRiskProfiles: true }); const state = fromReducer.sharedReducer(initialState, action); const newState = { ...initialState, ...{ hasRiskProfiles: true } }; @@ -219,7 +171,7 @@ describe('Reducer', () => { describe('setRiskProfiles action', () => { it('should update state', () => { - const initialState = initialSharedState; + const initialState = initialAppState; const riskProfiles = [PROFILE_MOCK]; const action = setRiskProfiles({ riskProfiles }); const state = fromReducer.sharedReducer(initialState, action); @@ -232,7 +184,7 @@ describe('Reducer', () => { describe('setDeviceInProgress action', () => { it('should update state', () => { - const initialState = initialSharedState; + const initialState = initialAppState; const deviceInProgress = device; const action = setDeviceInProgress({ device: deviceInProgress }); const state = fromReducer.sharedReducer(initialState, action); @@ -248,7 +200,7 @@ describe('Reducer', () => { describe('setTestrunStatus action', () => { it('should update state', () => { - const initialState = initialSharedState; + const initialState = initialAppState; const action = setTestrunStatus({ systemStatus: MOCK_PROGRESS_DATA_CANCELLING, }); @@ -265,7 +217,7 @@ describe('Reducer', () => { describe('setIsTestingComplete action', () => { it('should update state', () => { - const initialState = initialSharedState; + const initialState = initialAppState; const action = setIsTestingComplete({ isTestingComplete: true, }); @@ -282,7 +234,7 @@ describe('Reducer', () => { describe('setIsOpenStartTestrun action', () => { it('should update state', () => { - const initialState = initialSharedState; + const initialState = initialAppState; const action = setIsOpenStartTestrun({ isOpenStartTestrun: true }); const state = fromReducer.sharedReducer(initialState, action); const newState = { ...initialState, ...{ isOpenStartTestrun: true } }; @@ -294,7 +246,7 @@ describe('Reducer', () => { describe('setStatus action', () => { it('should update state', () => { - const initialState = initialSharedState; + const initialState = initialAppState; const action = setStatus({ status: MOCK_PROGRESS_DATA_CANCELLING.status, }); @@ -311,7 +263,7 @@ describe('Reducer', () => { describe('setReports action', () => { it('should update state', () => { - const initialState = initialSharedState; + const initialState = initialAppState; const action = setReports({ reports: HISTORY, }); @@ -328,7 +280,7 @@ describe('Reducer', () => { describe('setTestModules action', () => { it('should update state', () => { - const initialState = initialSharedState; + const initialState = initialAppState; const action = setTestModules({ testModules: MOCK_TEST_MODULES, }); @@ -345,7 +297,7 @@ describe('Reducer', () => { describe('updateAdapters action', () => { it('should update state', () => { - const initialState = initialSharedState; + const initialState = initialAppState; const action = updateAdapters({ adapters: MOCK_ADAPTERS, }); @@ -362,7 +314,7 @@ describe('Reducer', () => { describe('updateInternetConnection action', () => { it('should update state', () => { - const initialState = initialSharedState; + const initialState = initialAppState; const action = updateInternetConnection({ internetConnection: true }); const state = fromReducer.sharedReducer(initialState, action); const newState = { ...initialState, ...{ internetConnection: true } }; @@ -371,4 +323,22 @@ describe('Reducer', () => { expect(state).not.toBe(initialState); }); }); + + describe('fetchSystemConfigSuccess action', () => { + it('should update state', () => { + const initialState = initialAppState; + + const action = fetchSystemConfigSuccess({ + systemConfig: { network: {} }, + }); + const state = fromReducer.rootReducer(initialState, action); + + const newState = { + ...initialState, + ...{ systemConfig: { network: {} } }, + }; + expect(state).toEqual(newState); + expect(state).not.toBe(initialState); + }); + }); }); diff --git a/modules/ui/src/app/store/reducers.ts b/modules/ui/src/app/store/reducers.ts index bc7350c0b..e51fde1b7 100644 --- a/modules/ui/src/app/store/reducers.ts +++ b/modules/ui/src/app/store/reducers.ts @@ -13,34 +13,14 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { combineReducers, createReducer, on } from '@ngrx/store'; +import { createReducer, on } from '@ngrx/store'; import * as Actions from './actions'; -import { initialAppComponentState, initialSharedState } from './state'; +import { initialState } from './state'; export const appFeatureKey = 'app'; -export const appComponentReducer = createReducer( - initialAppComponentState, - on(Actions.toggleMenu, state => ({ - ...state, - isMenuOpen: !state.isMenuOpen, - })), - on(Actions.fetchInterfacesSuccess, (state, { interfaces }) => ({ - ...state, - interfaces, - })), - on(Actions.updateFocusNavigation, (state, { focusNavigation }) => ({ - ...state, - focusNavigation, - })), - on(Actions.updateError, (state, { settingMissedError }) => ({ - ...state, - settingMissedError, - })) -); - export const sharedReducer = createReducer( - initialSharedState, + initialState, on(Actions.setHasConnectionSettings, (state, { hasConnectionSettings }) => { return { ...state, @@ -148,10 +128,15 @@ export const sharedReducer = createReducer( ...state, internetConnection, }; - }) + }), + on(Actions.fetchInterfacesSuccess, (state, { interfaces }) => ({ + ...state, + interfaces, + })), + on(Actions.fetchSystemConfigSuccess, (state, { systemConfig }) => ({ + ...state, + systemConfig, + })) ); -export const rootReducer = combineReducers({ - appComponent: appComponentReducer, - shared: sharedReducer, -}); +export const rootReducer = sharedReducer; diff --git a/modules/ui/src/app/store/selectors.spec.ts b/modules/ui/src/app/store/selectors.spec.ts index 3919fd349..ba6a52315 100644 --- a/modules/ui/src/app/store/selectors.spec.ts +++ b/modules/ui/src/app/store/selectors.spec.ts @@ -19,15 +19,12 @@ import { selectAdapters, selectDeviceInProgress, selectDevices, - selectError, selectHasConnectionSettings, selectHasDevices, selectHasRiskProfiles, - selectInterfaces, selectIsOpenAddDevice, selectIsOpenStartTestrun, selectIsOpenWaitSnackBar, - selectMenuOpened, selectReports, selectRiskProfiles, selectStatus, @@ -37,46 +34,35 @@ import { selectInternetConnection, selectIsAllDevicesOutdated, selectIsTestingComplete, + selectInterfaces, + selectSystemConfig, } from './selectors'; describe('Selectors', () => { const initialState: AppState = { - appComponent: { - isMenuOpen: false, - interfaces: {}, - isStatusLoaded: false, - devicesLength: 0, - focusNavigation: false, - settingMissedError: null, - }, - shared: { - hasConnectionSettings: false, - isAllDevicesOutdated: false, - devices: [], - hasDevices: false, - hasExpiredDevices: false, - isOpenAddDevice: false, - riskProfiles: [], - hasRiskProfiles: false, - isStopTestrun: false, - isOpenWaitSnackBar: false, - isOpenStartTestrun: false, - systemStatus: null, - deviceInProgress: null, - status: null, - isTestingComplete: false, - reports: [], - testModules: [], - adapters: {}, - internetConnection: null, - }, + hasConnectionSettings: false, + isAllDevicesOutdated: false, + devices: [], + hasDevices: false, + hasExpiredDevices: false, + isOpenAddDevice: false, + riskProfiles: [], + hasRiskProfiles: false, + isStopTestrun: false, + isOpenWaitSnackBar: false, + isOpenStartTestrun: false, + systemStatus: null, + deviceInProgress: null, + status: null, + isTestingComplete: false, + reports: [], + testModules: [], + adapters: {}, + internetConnection: null, + interfaces: {}, + systemConfig: { network: {} }, }; - it('should select the is menu opened', () => { - const result = selectMenuOpened.projector(initialState); - expect(result).toEqual(false); - }); - it('should select interfaces', () => { const result = selectInterfaces.projector(initialState); expect(result).toEqual({}); @@ -87,11 +73,6 @@ describe('Selectors', () => { expect(result).toEqual(false); }); - it('should select settingMissedError', () => { - const result = selectError.projector(initialState); - expect(result).toEqual(null); - }); - it('should select devices', () => { const result = selectDevices.projector(initialState); expect(result).toEqual([]); @@ -176,4 +157,9 @@ describe('Selectors', () => { const result = selectInternetConnection.projector(initialState); expect(result).toEqual(null); }); + + it('should select systemConfig', () => { + const result = selectSystemConfig.projector(initialState); + expect(result).toEqual({ network: {} }); + }); }); diff --git a/modules/ui/src/app/store/selectors.ts b/modules/ui/src/app/store/selectors.ts index 0fec7b492..2a3fb0ed9 100644 --- a/modules/ui/src/app/store/selectors.ts +++ b/modules/ui/src/app/store/selectors.ts @@ -22,108 +22,103 @@ export const selectAppState = createFeatureSelector( fromApp.appFeatureKey ); -export const selectMenuOpened = createSelector( - selectAppState, - (state: AppState) => state.appComponent.isMenuOpen -); - export const selectInterfaces = createSelector( selectAppState, - (state: AppState) => state.appComponent.interfaces + (state: AppState) => state.interfaces ); export const selectHasConnectionSettings = createSelector( selectAppState, - (state: AppState) => state.shared.hasConnectionSettings + (state: AppState) => state.hasConnectionSettings ); export const selectIsOpenAddDevice = createSelector( selectAppState, - (state: AppState) => state.shared.isOpenAddDevice + (state: AppState) => state.isOpenAddDevice ); export const selectHasDevices = createSelector( selectAppState, - (state: AppState) => state.shared.hasDevices + (state: AppState) => state.hasDevices ); export const selectHasExpiredDevices = createSelector( selectAppState, - (state: AppState) => state.shared.hasExpiredDevices + (state: AppState) => state.hasExpiredDevices ); export const selectIsAllDevicesOutdated = createSelector( selectAppState, - (state: AppState) => state.shared.isAllDevicesOutdated + (state: AppState) => state.isAllDevicesOutdated ); export const selectDevices = createSelector( selectAppState, - (state: AppState) => state.shared.devices + (state: AppState) => state.devices ); export const selectDeviceInProgress = createSelector( selectAppState, - (state: AppState) => state.shared.deviceInProgress + (state: AppState) => state.deviceInProgress ); export const selectHasRiskProfiles = createSelector( selectAppState, - (state: AppState) => state.shared.hasRiskProfiles + (state: AppState) => state.hasRiskProfiles ); export const selectRiskProfiles = createSelector( selectAppState, - (state: AppState) => state.shared.riskProfiles -); - -export const selectError = createSelector( - selectAppState, - (state: AppState) => state.appComponent.settingMissedError + (state: AppState) => state.riskProfiles ); export const selectSystemStatus = createSelector( selectAppState, (state: AppState) => { - return state.shared.systemStatus; + return state.systemStatus; } ); export const selectIsTestingComplete = createSelector( selectAppState, - (state: AppState) => state.shared.isTestingComplete + (state: AppState) => state.isTestingComplete ); export const selectIsOpenWaitSnackBar = createSelector( selectAppState, - (state: AppState) => state.shared.isOpenWaitSnackBar + (state: AppState) => state.isOpenWaitSnackBar ); export const selectIsOpenStartTestrun = createSelector( selectAppState, - (state: AppState) => state.shared.isOpenStartTestrun + (state: AppState) => state.isOpenStartTestrun ); export const selectStatus = createSelector( selectAppState, - (state: AppState) => state.shared.status + (state: AppState) => state.status ); export const selectReports = createSelector( selectAppState, - (state: AppState) => state.shared.reports + (state: AppState) => state.reports ); export const selectTestModules = createSelector( selectAppState, - (state: AppState) => state.shared.testModules + (state: AppState) => state.testModules ); export const selectAdapters = createSelector( selectAppState, - (state: AppState) => state.shared.adapters + (state: AppState) => state.adapters ); export const selectInternetConnection = createSelector( selectAppState, - (state: AppState) => state.shared.internetConnection + (state: AppState) => state.internetConnection +); + +export const selectSystemConfig = createSelector( + selectAppState, + (state: AppState) => state.systemConfig ); diff --git a/modules/ui/src/app/store/state.ts b/modules/ui/src/app/store/state.ts index a0a993fd1..2ce978b6f 100644 --- a/modules/ui/src/app/store/state.ts +++ b/modules/ui/src/app/store/state.ts @@ -15,31 +15,13 @@ */ import { TestrunStatus } from '../model/testrun-status'; import { Device, TestModule } from '../model/device'; -import { - Adapters, - SettingMissedError, - SystemInterfaces, -} from '../model/setting'; +import { Adapters, SystemConfig, SystemInterfaces } from '../model/setting'; import { Profile } from '../model/profile'; export interface AppState { - appComponent: AppComponentState; - shared: SharedState; -} - -export interface AppComponentState { - isMenuOpen: boolean; + // app, settings interfaces: SystemInterfaces; - /** - * Indicates, if side menu should be focused on keyboard navigation after menu is opened - */ - focusNavigation: boolean; - settingMissedError: SettingMissedError | null; - isStatusLoaded: boolean; // TODO should be updated in effect when fetch status - devicesLength: number; // TODO should be renamed to focusToggleSettingsBtn (true when devices.length > 0) and updated in effect when fetch device -} - -export interface SharedState { + systemConfig: SystemConfig; devices: Device[]; //used in app, devices, testrun hasDevices: boolean; @@ -67,16 +49,7 @@ export interface SharedState { internetConnection: boolean | null; } -export const initialAppComponentState: AppComponentState = { - isMenuOpen: false, - interfaces: {}, - focusNavigation: false, - isStatusLoaded: false, - devicesLength: 0, - settingMissedError: null, -}; - -export const initialSharedState: SharedState = { +export const initialState: AppState = { hasConnectionSettings: null, isOpenAddDevice: false, isStopTestrun: false, @@ -96,4 +69,6 @@ export const initialSharedState: SharedState = { testModules: [], adapters: {}, internetConnection: null, + interfaces: {}, + systemConfig: { network: {} }, }; diff --git a/modules/ws/ws.Dockerfile b/modules/ws/ws.Dockerfile index 7e9408a47..606cc333e 100644 --- a/modules/ws/ws.Dockerfile +++ b/modules/ws/ws.Dockerfile @@ -1,4 +1,4 @@ -FROM eclipse-mosquitto:2.0.18 +FROM eclipse-mosquitto@sha256:4a46c840adf48e7acd49883206a5c075c14ec95845ee6d30ba935a6719d6b41c RUN mkdir -p /mosquitto/data/ COPY modules/ws/conf/mosquitto.conf /mosquitto/config/mosquitto.conf VOLUME /mosquitto/data/ \ No newline at end of file diff --git a/resources/report/test_report_styles.css b/resources/report/test_report_styles.css index f261cecb3..b1ed9d33c 100644 --- a/resources/report/test_report_styles.css +++ b/resources/report/test_report_styles.css @@ -174,10 +174,16 @@ margin-bottom: 25px; } + .module-summary.not-first{ + margin-top: 10px; + } + + .module-summary thead tr th { text-align: left; padding-top: 15px; - padding-left: 15px; + padding-left: 10px; + padding-right: 5px; font-weight: 500; color: #5F6368; font-size: 14px; @@ -185,8 +191,9 @@ .module-summary tbody tr td { padding-bottom: 15px; - padding-left: 15px; - font-size: 24px; + padding-left: 10px; + padding-right: 5px; + font-size: 22px; } .module-data { @@ -211,6 +218,8 @@ font-weight: 400; border-top: 1px solid #DADCE0; font-family: 'Roboto Mono', monospace; + word-wrap: break-word; + word-break: break-word; } div.steps-to-resolve { diff --git a/resources/report/test_report_template.html b/resources/report/test_report_template.html index 24a082bf8..fbd8d1c68 100644 --- a/resources/report/test_report_template.html +++ b/resources/report/test_report_template.html @@ -115,7 +115,7 @@

Results List ({{ successful_tests }}/{{ tot {% set page_index.value = page_index.value+1 %}
{{ header_macros.header(False, "Testrun report", json_data, device, logo, icon_qualification, icon_pilot)}} -

Non-complaint tests and suggested steps to resolve

+

Non-compliant tests and suggested steps to resolve

{% for step in steps_to_resolve %}
diff --git a/resources/risk_assessment.json b/resources/risk_assessment.json index a4c95be95..d4f2574fb 100644 --- a/resources/risk_assessment.json +++ b/resources/risk_assessment.json @@ -1,7 +1,7 @@ [ { "question": "How will this device be used at Google?", - "description": "Desribe your use case. Add links to user journey diagrams and TDD if available.", + "description": "Describe your use case. Add links to user journey diagrams and TDD if available.", "type": "text-long", "validation": { "max": "512", @@ -60,7 +60,7 @@ "risk": "High" }, { - "text": "A failure in data transmission would likely have a substantial negative impact (https://www.rra.rocks/docs/standard_levels#levels-definitions)", + "text": "A failure in data transmission would likely have a substantial negative impact (https://www.rra.rocks/docs/standard_levels#levels-definitions)", "risk": "High" }, { diff --git a/testing/api/test_api.py b/testing/api/test_api.py index 71df07b6b..e67506a71 100644 --- a/testing/api/test_api.py +++ b/testing/api/test_api.py @@ -25,6 +25,8 @@ import time import pytest import requests +from cryptography import x509 +from cryptography.hazmat.backends import default_backend API = "http://127.0.0.1:8000" LOG_PATH = "/tmp/testrun.log" @@ -48,7 +50,6 @@ BASELINE_MAC_ADDR = "02:42:aa:00:01:01" ALL_MAC_ADDR = "02:42:aa:00:00:01" -TIMESTAMP = "2024-01-01 00:00:00" DEVICE_PROFILE_QUESTIONS = "resources/devices/device_profile.json" def pretty_print(dictionary: dict): @@ -456,7 +457,10 @@ def stop_test(): # Validate system status -def test_start_testrun_success(empty_devices_dir, add_one_device, testrun): # pylint: disable=W0613 +@pytest.mark.parametrize("add_devices", [ + ["device_1"] +],indirect=True) +def test_start_testrun_success(empty_devices_dir, add_devices, testrun): # pylint: disable=W0613 """ Test for testrun started successfully (200) """ # Load the device using load_json utility method @@ -526,7 +530,10 @@ def test_start_testrun_invalid_json(testrun): # pylint: disable=W0613 # Check if 'error' in response assert "error" in response -def test_start_testrun_already_started(empty_devices_dir, add_one_device, # pylint: disable=W0613 +@pytest.mark.parametrize("add_devices", [ + ["device_1"] +],indirect=True) +def test_start_testrun_already_started(empty_devices_dir, add_devices, # pylint: disable=W0613 testrun, start_test): # pylint: disable=W0613 """ Test for testrun already started (409) """ @@ -582,7 +589,10 @@ def test_start_testrun_device_not_found(empty_devices_dir, testrun): # pylint: d # Check if 'error' in response assert "error" in response -def test_start_testrun_error(empty_devices_dir, add_one_device, # pylint: disable=W0613 +@pytest.mark.parametrize("add_devices", [ + ["device_1"] +],indirect=True) +def test_start_testrun_error(empty_devices_dir, add_devices, # pylint: disable=W0613 update_sys_config, testrun, restore_sys_config): # pylint: disable=W0613 """ Test for start testrun internal server error (500) """ @@ -616,8 +626,11 @@ def test_start_testrun_error(empty_devices_dir, add_one_device, # pylint: disabl # Check if 'error' in response assert "error" in response -def test_stop_running_testrun(empty_devices_dir, add_one_device, # pylint: disable=W0613 - testrun, start_test): # pylint: disable=W0613 +@pytest.mark.parametrize("add_devices", [ + ["device_1"] +],indirect=True) +def test_stop_running_testrun(empty_devices_dir, add_devices, # pylint: disable=W0613 + testrun, start_test): # pylint: disable=W0613 """ Test for successfully stop testrun when test is running (200) """ # Send the post request to stop the test @@ -662,8 +675,11 @@ def test_sys_shutdown(testrun): # pylint: disable=W0613 # Check if null in response assert response is None -def test_sys_shutdown_in_progress(empty_devices_dir, add_one_device, # pylint: disable=W0613 - testrun, start_test): # pylint: disable=W0613 +@pytest.mark.parametrize("add_devices", [ + ["device_1"] +],indirect=True) +def test_sys_shutdown_in_progress(empty_devices_dir, add_devices, # pylint: disable=W0613 + testrun, start_test): # pylint: disable=W0613 """ Test system shutdown during an in-progress test (400) """ # Attempt to shutdown while the test is running @@ -693,8 +709,11 @@ def test_sys_status_idle(testrun): # pylint: disable=W0613 # Check if system status is 'Idle' assert response["status"] == "Idle" -def test_sys_status_cancelled(empty_devices_dir, add_one_device, # pylint: disable=W0613 - testrun, start_test, stop_test): # pylint: disable=W0613 +@pytest.mark.parametrize("add_devices", [ + ["device_1"] +],indirect=True) +def test_sys_status_cancelled(empty_devices_dir, add_devices, # pylint: disable=W0613 + testrun, start_test, stop_test): # pylint: disable=W0613 """ Test for system status 'cancelled' (200) """ # Send the get request to retrieve system status @@ -706,8 +725,11 @@ def test_sys_status_cancelled(empty_devices_dir, add_one_device, # pylint: disab # Check if status is 'Cancelled' assert response["status"] == "Cancelled" -def test_sys_status_waiting(empty_devices_dir, add_one_device, # pylint: disable=W0613 - testrun, start_test): # pylint: disable=W0613 +@pytest.mark.parametrize("add_devices", [ + ["device_1"] +],indirect=True) +def test_sys_status_waiting(empty_devices_dir, add_devices, # pylint: disable=W0613 + testrun, start_test): # pylint: disable=W0613 """ Test for system status 'Waiting for Device' (200) """ # Send the get request @@ -768,49 +790,78 @@ def test_get_test_modules(testrun): # pylint: disable=W0613 # Tests for reports endpoints +def get_timestamp(formatted=False): + """ Returns timestamp value from 'started' field from the report + found at 'testing/api/reports/report.json' + By default it will return the raw time format or iso if formatted=True + """ + + # Load the report.json using load_json utility method + report_json = load_json("report.json", directory="testing/api/reports") + + # Assign the timestamp from report.json + timestamp = report_json["started"] + + # If formatted is changed to 'True' + if formatted: + + # Return the iso formatted timestamp + return timestamp.replace(" ", "T") + + # Else return the raw timestamp + return timestamp + @pytest.fixture def create_report_folder(): # pylint: disable=W0613 """ Fixture to create the device reports folder in local/devices """ - def _create_report_folder(device_name, mac_addr, timestamp): + # Load the device using load_json utility method + device = load_json("device_config.json", directory=DEVICE_1_PATH) - # Create the device folder path - main_folder = os.path.join(DEVICES_DIRECTORY, device_name) + # Assign the device mac address + mac_addr = device["mac_addr"] - # Remove the ":" from mac address for the folder structure - mac_addr = mac_addr.replace(":", "") + # Assign the device name + device_name = f'{device["manufacturer"]} {device["model"]}' - # Change the timestamp format for the folder structure - timestamp = timestamp.replace(" ", "T") + # Create the device folder path + main_folder = os.path.join(DEVICES_DIRECTORY, device_name) - # Create the report folder path - report_folder = os.path.join(main_folder, "reports", timestamp, - "test", mac_addr) + # Remove the ":" from mac address for the folder structure + mac_addr = mac_addr.replace(":", "") - # Ensure the report folder exists - os.makedirs(report_folder, exist_ok=True) + # Assign the timestamp from get_timestamp utility method + timestamp = get_timestamp(formatted=True) - # Iterate over the files from 'testing/api/reports' folder - for file in os.listdir(REPORTS_PATH): + # Create the report folder path + report_folder = os.path.join(main_folder, "reports", timestamp, + "test", mac_addr) - # Construct full path of the file from 'testing/api/reports' folder - source_path = os.path.join(REPORTS_PATH, file) + # Ensure the report folder exists + os.makedirs(report_folder, exist_ok=True) - # Construct full path where the file will be copied - target_path = os.path.join(report_folder, file) + # Iterate over the files from 'testing/api/reports' folder + for file in os.listdir(REPORTS_PATH): - # Copy the file - shutil.copy(source_path, target_path) + # Construct full path of the file from 'testing/api/reports' folder + source_path = os.path.join(REPORTS_PATH, file) - return report_folder + # Construct full path where the file will be copied + target_path = os.path.join(report_folder, file) - return _create_report_folder + # Copy the file + shutil.copy(source_path, target_path) -def test_get_reports_no_reports(testrun): # pylint: disable=W0613 +def test_get_reports_no_reports(empty_devices_dir, testrun): # pylint: disable=W0613 """Test get reports when no reports exist""" + # Set the Origin headers to API address + headers = { + "Origin": API + } + # Send a GET request to the /reports endpoint - r = requests.get(f"{API}/reports", timeout=5) + r = requests.get(f"{API}/reports", headers=headers, timeout=5) # Check if the status code is 200 (OK) assert r.status_code == 200 @@ -824,9 +875,60 @@ def test_get_reports_no_reports(testrun): # pylint: disable=W0613 # Check if the response is an empty list assert response == [] -def test_delete_report(empty_devices_dir, add_one_device, # pylint: disable=W0613 - create_report_folder, testrun): # pylint: disable=W0613 - """Test for succesfully delete a report (200)""" +@pytest.mark.parametrize("add_devices", [ + ["device_1"] +],indirect=True) +def test_get_reports(empty_devices_dir, add_devices, # pylint: disable=W0613 + create_report_folder, testrun): # pylint: disable=W0613 + """ Test for get reports when one report is available (200) """ + + # Set the Origin headers to API address + headers = { + "Origin": API + } + + # Get request to retrieve the generated reports + r = requests.get(f"{API}/reports", headers=headers, timeout=5) + + # Parse the json + response = r.json() + + # Check if status code is 200 (ok) + assert r.status_code == 200 + + # Check if response is a list + assert isinstance(response, list) + + # Check if there is one report + assert len(response) == 1 + + # Assign the report from the response list + report = response[0] + + # Assign the expected report properties + expected_keys = [ + "testrun", + "mac_addr", + "device", + "status", + "started", + "finished", + "tests", + "report" + ] + + # Iterate through the expected_keys + for key in expected_keys: + + # Check if the key exists in the report + assert key in report + +@pytest.mark.parametrize("add_devices", [ + ["device_1"] +],indirect=True) +def test_delete_report(empty_devices_dir, add_devices, # pylint: disable=W0613 + create_report_folder, testrun): # pylint: disable=W0613 + """ Test for succesfully delete a report (200) """ # Load the device using load_json utility method device = load_json("device_config.json", directory=DEVICE_1_PATH) @@ -837,13 +939,10 @@ def test_delete_report(empty_devices_dir, add_one_device, # pylint: disable=W061 # Assign the device name device_name = f'{device["manufacturer"]} {device["model"]}' - # Create the report directory - report_folder = create_report_folder(device_name, mac_addr, TIMESTAMP) - # Payload delete_data = { "mac_addr": mac_addr, - "timestamp": TIMESTAMP + "timestamp": get_timestamp() } # Send a DELETE request to remove the report @@ -858,12 +957,18 @@ def test_delete_report(empty_devices_dir, add_one_device, # pylint: disable=W061 # Check if "success" in response assert "success" in response - # Check if report folder has been deleted - assert not os.path.exists(report_folder) + # Construct the 'reports' folder path + reports_folder = os.path.join(device_name, "reports") + + # Check if reports folder has been deleted + assert not os.path.exists(reports_folder) -def test_delete_report_no_payload(empty_devices_dir, add_one_device, # pylint: disable=W0613 - create_report_folder, testrun): # pylint: disable=W0613 - """Test delete report bad request when the payload is missing (400)""" +@pytest.mark.parametrize("add_devices", [ + ["device_1"] +],indirect=True) +def test_delete_report_no_payload(empty_devices_dir, add_devices, # pylint: disable=W0613 + create_report_folder, testrun): # pylint: disable=W0613 + """ Test delete report bad request when the payload is missing (400) """ # Send a DELETE request to remove the report without the payload r = requests.delete(f"{API}/report", timeout=5) @@ -880,21 +985,12 @@ def test_delete_report_no_payload(empty_devices_dir, add_one_device, # pylint: d # Check if the correct error message returned assert "Invalid request received, missing body" in response["error"] -def test_delete_report_invalid_payload(empty_devices_dir, add_one_device, # pylint: disable=W0613 - create_report_folder, testrun): # pylint: disable=W0613 - """Test delete report bad request, mac addr and timestamp are missing (400)""" - - # Load the device using load_json utility method - device = load_json("device_config.json", directory=DEVICE_1_PATH) - - # Assign the device mac address - mac_addr = device["mac_addr"] - - # Assign the device name - device_name = f'{device["manufacturer"]} {device["model"]}' - - # Create the report directory - create_report_folder(device_name, mac_addr, TIMESTAMP) +@pytest.mark.parametrize("add_devices", [ + ["device_1"] +],indirect=True) +def test_delete_report_invalid_payload(empty_devices_dir, add_devices, # pylint: disable=W0613 + create_report_folder, testrun): # pylint: disable=W0613 + """ Test delete report bad request missing mac addr or timestamp (400) """ # Empty payload delete_data = {} @@ -914,9 +1010,12 @@ def test_delete_report_invalid_payload(empty_devices_dir, add_one_device, # pyli # Check if the correct error message returned assert "Missing mac address or timestamp" in response["error"] -def test_delete_report_invalid_timestamp(empty_devices_dir, add_one_device, # pylint: disable=W0613 - create_report_folder, testrun): # pylint: disable=W0613 - """Test delete report bad request when timestamp format is not valid (400)""" +@pytest.mark.parametrize("add_devices", [ + ["device_1"] +],indirect=True) +def test_delete_report_invalid_timestamp(empty_devices_dir, add_devices, # pylint: disable=W0613 + create_report_folder, testrun): # pylint: disable=W0613 + """ Test delete report bad request if timestamp format is not valid (400) """ # Load the device using load_json utility method device = load_json("device_config.json", directory=DEVICE_1_PATH) @@ -924,19 +1023,13 @@ def test_delete_report_invalid_timestamp(empty_devices_dir, add_one_device, # py # Assign the device mac address mac_addr = device["mac_addr"] - # Assign the device name - device_name = f'{device["manufacturer"]} {device["model"]}' - # Assign the incorrect timestamp format - timestamp = "2024-01-01 invalid" - - # Create the report.json - create_report_folder(device_name, mac_addr, timestamp) + invalid_timestamp = "2024-01-01 invalid" # Payload delete_data = { "mac_addr": mac_addr, - "timestamp": timestamp + "timestamp": invalid_timestamp } # Send a DELETE request to remove the report @@ -955,12 +1048,12 @@ def test_delete_report_invalid_timestamp(empty_devices_dir, add_one_device, # py assert "Incorrect timestamp format" in response["error"] def test_delete_report_no_device(empty_devices_dir, testrun): # pylint: disable=W0613 - """Test delete report when device does not exist (404)""" + """ Test delete report when device does not exist (404) """ # Payload to be deleted for a non existing device delete_data = { "mac_addr": "00:1e:42:35:73:c4", - "timestamp": TIMESTAMP + "timestamp": get_timestamp() } # Send the delete request to the endpoint @@ -978,7 +1071,10 @@ def test_delete_report_no_device(empty_devices_dir, testrun): # pylint: disable= # Check if the correct error message returned assert "Could not find device" in response["error"] -def test_delete_report_no_report(empty_devices_dir, add_one_device, testrun): # pylint: disable=W0613 +@pytest.mark.parametrize("add_devices", [ + ["device_1"] +],indirect=True) +def test_delete_report_no_report(empty_devices_dir, add_devices, testrun): # pylint: disable=W0613 """Test for delete report when report does not exist (404)""" # Load the device using load_json utility method @@ -990,7 +1086,7 @@ def test_delete_report_no_report(empty_devices_dir, add_one_device, testrun): # # Prepare the payload for the DELETE request delete_data = { "mac_addr": mac_addr, - "timestamp": TIMESTAMP + "timestamp": get_timestamp() } # Send the delete request to delete the report @@ -1010,24 +1106,21 @@ def test_delete_report_no_report(empty_devices_dir, add_one_device, testrun): # # Check if the correct error message is returned assert "Report not found" in response["error"] -def test_get_report_success(empty_devices_dir, add_one_device, # pylint: disable=W0613 - create_report_folder, testrun): # pylint: disable=W0613 +@pytest.mark.parametrize("add_devices", [ + ["device_1"] +],indirect=True) +def test_get_report_success(empty_devices_dir, add_devices, # pylint: disable=W0613 + create_report_folder, testrun): # pylint: disable=W0613 """Test for successfully get report when report exists (200)""" # Load the device using load_json utility method device = load_json("device_config.json", directory=DEVICE_1_PATH) - # Assign the device mac address - mac_addr = device["mac_addr"] - # Assign the device name device_name = f'{device["manufacturer"]} {device["model"]}' # Assign the timestamp and change the format - timestamp = TIMESTAMP.replace(" ", "T") - - # Create the report for the device - create_report_folder(device_name, mac_addr, timestamp) + timestamp = get_timestamp(formatted=True) # Send the get request r = requests.get(f"{API}/report/{device_name}/{timestamp}", timeout=5) @@ -1038,7 +1131,10 @@ def test_get_report_success(empty_devices_dir, add_one_device, # pylint: disable # Check if the response is a PDF assert r.headers["Content-Type"] == "application/pdf" -def test_get_report_not_found(empty_devices_dir, add_one_device, testrun): # pylint: disable=W0613 +@pytest.mark.parametrize("add_devices", [ + ["device_1"] +],indirect=True) +def test_get_report_not_found(empty_devices_dir, add_devices, testrun): # pylint: disable=W0613 """Test get report when report doesn't exist (404)""" # Load the device using load_json utility method @@ -1047,8 +1143,11 @@ def test_get_report_not_found(empty_devices_dir, add_one_device, testrun): # pyl # Assign the device name device_name = f'{device["manufacturer"]} {device["model"]}' + # Assign the timestamp + timestamp = get_timestamp() + # Send the get request - r = requests.get(f"{API}/report/{device_name}/{TIMESTAMP}", timeout=5) + r = requests.get(f"{API}/report/{device_name}/{timestamp}", timeout=5) # Check if status code is 404 (not found) assert r.status_code == 404 @@ -1065,11 +1164,14 @@ def test_get_report_not_found(empty_devices_dir, add_one_device, testrun): # pyl def test_get_report_device_not_found(empty_devices_dir, testrun): # pylint: disable=W0613 """Test getting a report when the device is not found (404)""" - # Assign device name and timestamp + # Assign device name device_name = "nonexistent_device" + # Assign the timestamp + timestamp = get_timestamp() + # Send the get request - r = requests.get(f"{API}/report/{device_name}/{TIMESTAMP}", timeout=5) + r = requests.get(f"{API}/report/{device_name}/{timestamp}", timeout=5) # Check if is 404 (not found) assert r.status_code == 404 @@ -1083,19 +1185,18 @@ def test_get_report_device_not_found(empty_devices_dir, testrun): # pylint: disa # Check if the correct error message is returned assert "Device not found" in response["error"] -def test_export_report_device_not_found(empty_devices_dir, testrun, # pylint: disable=W0613 - create_report_folder): +def test_export_report_device_not_found(empty_devices_dir, create_report_folder, # pylint: disable=W0613 + testrun): # pylint: disable=W0613 """Test for export the report result when the device could not be found""" - # Assign the non-existing device name, mac_addr + # Assign the non-existing device name device_name = "non existing device" - mac_addr = "00:1e:42:35:73:c4" - # Create the report for the non-existing device - create_report_folder(device_name, mac_addr, TIMESTAMP) + # Assign the timestamp + timestamp = get_timestamp() # Send the post request - r = requests.post(f"{API}/export/{device_name}/{TIMESTAMP}", timeout=5) + r = requests.post(f"{API}/export/{device_name}/{timestamp}", timeout=5) # Check if is 404 (not found) assert r.status_code == 404 @@ -1109,27 +1210,27 @@ def test_export_report_device_not_found(empty_devices_dir, testrun, # pylint: di # Check if the correct error message returned assert "A device with that name could not be found" in response["error"] -def test_export_report_profile_not_found(empty_devices_dir, add_one_device, # pylint: disable=W0613 - create_report_folder, testrun): # pylint: disable=W0613 +@pytest.mark.parametrize("add_devices", [ + ["device_1"] +],indirect=True) +def test_export_report_profile_not_found(empty_devices_dir, add_devices, # pylint: disable=W0613 + create_report_folder, testrun): # pylint: disable=W0613 """Test for export report result when the profile is not found""" # Load the device using load_json utility method device = load_json("device_config.json", directory=DEVICE_1_PATH) - # Assign the device mac address - mac_addr = device["mac_addr"] - # Assign the device name device_name = f'{device["manufacturer"]} {device["model"]}' - # Create the report for the device - create_report_folder(device_name, mac_addr, TIMESTAMP) + # Assign the timestamp + timestamp = get_timestamp() # Add a non existing profile into the payload payload = {"profile": "non_existent_profile"} # Send the post request - r = requests.post(f"{API}/export/{device_name}/{TIMESTAMP}", + r = requests.post(f"{API}/export/{device_name}/{timestamp}", json=payload, timeout=5) @@ -1145,7 +1246,10 @@ def test_export_report_profile_not_found(empty_devices_dir, add_one_device, # py # Check if the correct error message returned assert "A profile with that name could not be found" in response["error"] -def test_export_report_not_found(empty_devices_dir, add_one_device, testrun): # pylint: disable=W0613 +@pytest.mark.parametrize("add_devices", [ + ["device_1"] +],indirect=True) +def test_export_report_not_found(empty_devices_dir, add_devices, testrun): # pylint: disable=W0613 """Test for export the report result when the report could not be found""" # Load the device using load_json utility method @@ -1154,8 +1258,11 @@ def test_export_report_not_found(empty_devices_dir, add_one_device, testrun): # # Assign the device name device_name = f'{device["manufacturer"]} {device["model"]}' + # Assign the timestamp + timestamp = get_timestamp() + # Send the post request to trigger the zipping process - r = requests.post(f"{API}/export/{device_name}/{TIMESTAMP}", timeout=10) + r = requests.post(f"{API}/export/{device_name}/{timestamp}", timeout=10) # Check if status code is 500 (Internal Server Error) assert r.status_code == 404 @@ -1169,9 +1276,12 @@ def test_export_report_not_found(empty_devices_dir, add_one_device, testrun): # # Check if the correct error message is returned assert "Report could not be found" in response["error"] -def test_export_report_with_profile(empty_devices_dir, add_one_device, # pylint: disable=W0613 - empty_profiles_dir, add_one_profile, # pylint: disable=W0613 - create_report_folder, testrun): # pylint: disable=W0613 +@pytest.mark.parametrize("add_devices, add_profiles", [ + (["device_1"], ["valid_profile.json"]) +], indirect=True) +def test_export_report_with_profile(empty_devices_dir, add_devices, # pylint: disable=W0613 + empty_profiles_dir, add_profiles, # pylint: disable=W0613 + create_report_folder, testrun): # pylint: disable=W0613 """Test export results with existing profile when report exists (200)""" # Load the profile using load_json utility method @@ -1180,17 +1290,11 @@ def test_export_report_with_profile(empty_devices_dir, add_one_device, # pylint: # Load the device using load_json utility method device = load_json("device_config.json", directory=DEVICE_1_PATH) - # Assign the device mac address - mac_addr = device["mac_addr"] - # Assign the device name device_name = f'{device["manufacturer"]} {device["model"]}' # Assign the timestamp and change the format - timestamp = TIMESTAMP.replace(" ", "T") - - # Create the report for the device - create_report_folder(device_name, mac_addr, timestamp) + timestamp = get_timestamp(formatted=True) # Send the post request r = requests.post(f"{API}/export/{device_name}/{timestamp}", @@ -1203,8 +1307,11 @@ def test_export_report_with_profile(empty_devices_dir, add_one_device, # pylint: # Check if the response is a zip file assert r.headers["Content-Type"] == "application/zip" -def test_export_results_with_no_profile(empty_devices_dir, add_one_device, # pylint: disable=W0613 - create_report_folder, testrun): # pylint: disable=W0613 +@pytest.mark.parametrize("add_devices", [ + ["device_1"] +],indirect=True) +def test_export_results_with_no_profile(empty_devices_dir, add_devices, # pylint: disable=W0613 + create_report_folder, testrun): # pylint: disable=W0613 """Test export results with no profile when report exists (200)""" # Load the device using load_json utility method @@ -1213,14 +1320,8 @@ def test_export_results_with_no_profile(empty_devices_dir, add_one_device, # pyl # Assign the device name device_name = f'{device["manufacturer"]} {device["model"]}' - # Assign the device mac address - mac_addr = device["mac_addr"] - # Assign the timestamp and change the format - timestamp = TIMESTAMP.replace(" ", "T") - - # Create the report for the device - create_report_folder(device_name, mac_addr, timestamp) + timestamp = get_timestamp(formatted=True) # Send the post request r = requests.post(f"{API}/export/{device_name}/{timestamp}", timeout=5) @@ -1232,59 +1333,40 @@ def test_export_results_with_no_profile(empty_devices_dir, add_one_device, # pyl assert r.headers["Content-Type"] == "application/zip" # Tests for device endpoints - @pytest.fixture() -def add_one_device(): - """Fixture to create one device during tests""" +def add_devices(request): + """ Upload specified device to local/devices """ - # Load the device configurations using load_json utility method - device = load_json("device_config.json", directory=DEVICE_1_PATH) + # Access the parameter (devices list) provided to the fixture + devices = request.param - # Assign the device name - device_name = f'{device["manufacturer"]} {device["model"]}' - - # Construct full path of the device from 'testing/api/devices/device_1' - source_path = os.path.join(DEVICE_1_PATH, "device_config.json") - - # Construct full path where the device will be copied - target_path = os.path.join(DEVICES_DIRECTORY, device_name) - - # Create the target directory if it doesn't exist - os.makedirs(target_path, exist_ok=True) - - # Copy device_config from 'testing/api/devices/device_1' to 'local/devices' - shutil.copy(source_path, target_path) - -@pytest.fixture() -def add_two_devices(): - """Fixture to create two devices during tests""" - - # List of device folders from 'testing/api/devices' - devices = ["device_1", "device_2"] - - for file in devices: + # Iterate over the device names provided + for device in devices: # Construct the full path for the device_config.json - device_path = os.path.join(DEVICES_PATH, file) + device_path = os.path.join(DEVICES_PATH, device) # Load the device configurations using load_json utility method device = load_json("device_config.json", directory=device_path) - # Assign the device name - device_name = f'{device["manufacturer"]} {device["model"]}' + # Assign the device name for the target directory + target_device_name = f'{device["manufacturer"]} {device["model"]}' # Construct the source path of the device config file source_path = os.path.join(device_path, "device_config.json") - # Construct the target path where the profile will be copied - target_path = os.path.join(DEVICES_DIRECTORY, device_name) + # Construct the target path where the device config will be copied + target_path = os.path.join(DEVICES_DIRECTORY, target_device_name) # Create the target directory if it doesn't exist os.makedirs(target_path, exist_ok=True) - # Copy the profile from source to target + # Copy the device config from source to target shutil.copy(source_path, target_path) + # Return the list with devices names + return devices + def delete_all_devices(): """Utility method to delete all devices from local/devices""" @@ -1333,7 +1415,7 @@ def empty_devices_dir(): delete_all_devices() def get_all_devices(): - """ Returns list with paths to all devices from local/devices""" + """ Returns list with paths to all devices from local/devices """ # List to store the paths of all 'device_config.json' files devices = [] @@ -1360,7 +1442,7 @@ def get_all_devices(): return devices def device_exists(device_mac): - """Utility method to check if device exists""" + """ Utility method to check if device exists """ # Send the get request r = requests.get(f"{API}/devices", timeout=5) @@ -1375,12 +1457,17 @@ def device_exists(device_mac): # Return if mac address is in the list of devices return any(p["mac_addr"] == device_mac for p in devices) -def test_get_devices_no_devices(empty_devices_dir, testrun): # pylint: disable=W0613 - """ Test for get devices endpoint when no devices are available (200) """ - - # Error handling if there are devices in local/devices - if len(get_all_devices()) != 0: - raise Exception("Expected no devices in local/devices") +@pytest.mark.parametrize( + "add_devices", + [ + [], + ["device_1"], + ["device_1", "device_2"] + ], + indirect=True +) +def test_get_devices(empty_devices_dir, add_devices, testrun): # pylint: disable=W0613 + """ Test get devices when none, one or two devices are available (200) """ # Send the get request to retrieve all devices r = requests.get(f"{API}/devices", timeout=5) @@ -1394,51 +1481,10 @@ def test_get_devices_no_devices(empty_devices_dir, testrun): # pylint: disable=W # Check if response is a list assert isinstance(response, list) - # Check if the list is empty - assert len(response) == 0 + # Check if the number of devices matches the number of devices available + assert len(response) == len(add_devices) - # Check if there are no devices in local/devices - assert len(get_all_devices()) == 0 - -def test_get_devices_one_device(empty_devices_dir, add_one_device, testrun): # pylint: disable=W0613 - """ Test for get devices endpoint when one device is created (200) """ - - # Error handling if there is not one device in local/devices - if len(get_all_devices()) != 1: - raise Exception("Expected one device in local/devices") - - # Send get request to the "/devices" endpoint - r = requests.get(f"{API}/devices", timeout=5) - - # Check if status code is 200 (OK) - assert r.status_code == 200 - - # Parse the json response (devices) - response = r.json() - - # Check if response contains one device - assert len(response) == 1 - -def test_get_devices_two_devices(empty_devices_dir, add_two_devices, testrun): # pylint: disable=W0613 - """ Test for get devices endpoint when two devices are created (200) """ - - # Error handling if there are not two devices in local/devices - if len(get_all_devices()) != 2: - raise Exception("Expected two devices in local/devices") - - # Send get request to the "/devices" endpoint - r = requests.get(f"{API}/devices", timeout=5) - - # Check if status code is 200 (OK) - assert r.status_code == 200 - - # Parse the response (devices) - response = r.json() - - # Check if response contains one device - assert len(response) == 2 - - # Assign the expected fields from device + # Assign the expected device fields expected_fields = [ "status", "mac_addr", @@ -1450,11 +1496,14 @@ def test_get_devices_two_devices(empty_devices_dir, add_two_devices, testrun): # "test_modules", ] - # Iterate over all expected_fields list - for field in expected_fields: + # If devices are in the list + if len(add_devices) > 0: + + # Iterate over all expected_fields list + for field in expected_fields: - # Check if both devices have the expected fields - assert all(field in r for r in response) + # Check if devices have the expected fields + assert all(field in device for device in response) def test_create_device(empty_devices_dir, testrun): # pylint: disable=W0613 """ Test for successfully create device endpoint (201) """ @@ -1504,7 +1553,10 @@ def test_create_device(empty_devices_dir, testrun): # pylint: disable=W0613 # Check if both devices have been found assert len(created_devices) == 2 -def test_create_device_already_exists(empty_devices_dir, add_one_device, # pylint: disable=W0613 +@pytest.mark.parametrize("add_devices", [ + ["device_1"] +],indirect=True) +def test_create_device_already_exists(empty_devices_dir, add_devices, # pylint: disable=W0613 testrun): # pylint: disable=W0613 """ Test for crete device when device already exists (409) """ @@ -1573,8 +1625,11 @@ def test_create_device_invalid_request(empty_devices_dir, testrun): # pylint: di # Check if 'local/device' has no devices assert len(get_all_devices()) == 0 -def test_edit_device(empty_devices_dir, add_one_device, # pylint: disable=W0613 - testrun): # pylint: disable=W0613 +@pytest.mark.parametrize("add_devices", [ + ["device_1"] +],indirect=True) +def test_edit_device(empty_devices_dir, add_devices, # pylint: disable=W0613 + testrun): # pylint: disable=W0613 """ Test for successfully edit device (200) """ # Error handling if there is not one devices in local/devices @@ -1704,8 +1759,15 @@ def test_edit_device_invalid_json(empty_devices_dir, testrun): # pylint: disable # Check if 'error' in response assert "error" in response -def test_edit_device_mac_already_exists( empty_devices_dir, add_two_devices, # pylint: disable=W0613 - testrun): # pylint: disable=W0613 +@pytest.mark.parametrize( + "add_devices", + [ + ["device_1", "device_2"] + ], + indirect=True +) +def test_edit_device_mac_already_exists( empty_devices_dir, add_devices, # pylint: disable=W0613 + testrun): # pylint: disable=W0613 """ Test for edit device when the mac address already exists (409) """ # Load the first device (payload) using load_json utility method @@ -1744,8 +1806,11 @@ def test_edit_device_mac_already_exists( empty_devices_dir, add_two_devices, # p # Check if 'error' in response assert "error" in response -def test_edit_device_test_in_progress(empty_devices_dir, add_one_device, # pylint: disable=W0613 - testrun, start_test): # pylint: disable=W0613 +@pytest.mark.parametrize("add_devices", [ + ["device_1"] +],indirect=True) +def test_edit_device_test_in_progress(empty_devices_dir, add_devices, # pylint: disable=W0613 + testrun, start_test): # pylint: disable=W0613 """ Test for edit device when a test is in progress (403) """ # Load the device (payload) using load_json utility method @@ -1803,8 +1868,11 @@ def test_edit_device_test_in_progress(empty_devices_dir, add_one_device, # pyli # Check that device "manufacturer" was not updated assert device["model"] != updated_device["model"] -def test_edit_device_invalid_manufacturer(empty_devices_dir, add_one_device, # pylint: disable=W0613 - testrun): # pylint: disable=W0613 +@pytest.mark.parametrize("add_devices", [ + ["device_1"] +],indirect=True) +def test_edit_device_invalid_manufacturer(empty_devices_dir, add_devices, # pylint: disable=W0613 + testrun): # pylint: disable=W0613 """ Test for edit device invalid chars in 'manufacturer' field (400) """ # Load the device @@ -1826,7 +1894,10 @@ def test_edit_device_invalid_manufacturer(empty_devices_dir, add_one_device, # p # Check if 'error' in response assert "error" in response -def test_edit_device_invalid_model(empty_devices_dir, add_one_device, testrun): # pylint: disable=W0613 +@pytest.mark.parametrize("add_devices", [ + ["device_1"] +],indirect=True) +def test_edit_device_invalid_model(empty_devices_dir, add_devices, testrun): # pylint: disable=W0613 """ Test for edit device invalid chars in 'model' field (400) """ # Load the device @@ -1870,7 +1941,10 @@ def test_edit_long_chars(empty_devices_dir, testrun): # pylint: disable=W0613 # Check if 'error' in response assert "error" in response -def test_delete_device(empty_devices_dir, add_one_device, testrun): # pylint: disable=W0613 +@pytest.mark.parametrize("add_devices", [ + ["device_1"] +],indirect=True) +def test_delete_device(empty_devices_dir, add_devices, testrun): # pylint: disable=W0613 """ Test for succesfully delete device endpoint (200) """ # Load the device @@ -1935,7 +2009,10 @@ def test_delete_device_not_found(empty_devices_dir, testrun): # pylint: disable= # Check if error in response assert "error" in response -def test_delete_device_no_mac(empty_devices_dir, add_one_device, testrun): # pylint: disable=W0613 +@pytest.mark.parametrize("add_devices", [ + ["device_1"] +],indirect=True) +def test_delete_device_no_mac(empty_devices_dir, add_devices, testrun): # pylint: disable=W0613 """ Test for delete device when no mac address in payload (400) """ # Assign an empty payload (no mac address) @@ -1958,8 +2035,11 @@ def test_delete_device_no_mac(empty_devices_dir, add_one_device, testrun): # pyl # Check that device wasn't deleted from 'local/devices' assert len(get_all_devices()) == 1 -def test_delete_device_testrun_in_progress(empty_devices_dir, add_one_device, # pylint: disable=W0613 - testrun, start_test): # pylint: disable=W0613 +@pytest.mark.parametrize("add_devices", [ + ["device_1"] +],indirect=True) +def test_delete_device_testrun_in_progress(empty_devices_dir, add_devices, # pylint: disable=W0613 + testrun, start_test): # pylint: disable=W0613 """ Test for delete device when testrun is in progress (403) """ # Load the device details @@ -2157,7 +2237,7 @@ def delete_all_certs(): # System related issues print(f"Error removing {item}: {err}") -def load_certificate_file(cert_filename): +def load_cert_file(cert_filename): """ Utility method to load a certificate file in binary read mode """ # Construct the full file path @@ -2169,86 +2249,59 @@ def load_certificate_file(cert_filename): # Return the certificate file return cert_file.read() -@pytest.fixture() -def reset_certs(): - """ Delete the certificates before and after each test """ - - # Delete before the test - delete_all_certs() +def extract_name(cert_data): + """ Utility method to extract the Common Name (CN) from cert data """ - yield + # Load the cert using the cryptography library + cert = x509.load_pem_x509_certificate(cert_data, default_backend()) - # Delete after the test - delete_all_certs() + # Extract and return the common name value + return cert.subject.get_attributes_for_oid(x509.NameOID.COMMON_NAME)[0].value @pytest.fixture() -def add_cert(): - """ Upload certificates during tests """ - - # Utility method to upload a certificate - def _upload_cert(filename): +def add_certs(request): + """ Upload specified certificates to local/root_certs """ - # Load the certificate using the utility method - cert_file = load_certificate_file(filename) + # Access the parameter (certs list) provided to the fixture + certs = request.param - # Send a POST request to the API endpoint to upload the certificate - response = requests.post( - f"{API}/system/config/certs", - files={"file": (filename, cert_file, "application/x-x509-ca-cert")}, - timeout=5) + # Iterate over the certificate names provided + for cert in certs: - # Return the response - return response - - # Returning the reference to upload_certificate - return _upload_cert - -def test_get_certs_no_certs(reset_certs, testrun): # pylint: disable=W0613 - """ Test for get certificate when no certificates are available (200) """ - - # Send the get request to "/system/config/certs" endpoint - r = requests.get(f"{API}/system/config/certs", timeout=5) + # Construct the full path for cert from 'testing/api/certificates' + source_path = os.path.join(CERTS_PATH, cert) - # Check if status code is 200 (OK) - assert r.status_code == 200 - - # Parse the response (certificates) - response = r.json() - - # Check if response is a list - assert isinstance(response, list) + # Copy the cert from 'testing/api/certificates' to 'local/root_certs' + shutil.copy(source_path, CERTS_DIRECTORY) - # Check if the list is empty - assert len(response) == 0 + # Return the list with certs name + return certs -def test_get_certs(testrun, reset_certs, add_cert): # pylint: disable=W0613 - """ Test for get certificates (one and two certificates) (200) """ - - # Use add_cert fixture to upload the first certificate - add_cert("crt.pem") - - # Send the get request to "/system/config/certs" endpoint - r = requests.get(f"{API}/system/config/certs", timeout=5) - - # Check if status code is 200 (OK) - assert r.status_code == 200 +@pytest.fixture() +def reset_certs(): + """ Delete the certificates before and after each test """ - # Parse the response (certificates) - response = r.json() + # Delete before the test + delete_all_certs() - # Check if response is a list - assert isinstance(response, list) + yield - # Check if response contains one certificate - assert len(response) == 1 + # Delete after the test + delete_all_certs() - # Use add_cert fixture to upload the second certificate - add_cert("WR2.pem") +# Use parametrize to create a test suite for 3 scenarios +@pytest.mark.parametrize("add_certs", [ + [], + ["crt.pem"], + ["crt.pem", "WR2.pem"], +], indirect=True) +def test_get_certs(reset_certs, add_certs, testrun): # pylint: disable=W0613 + """ Test for get certs when none, one or two certs are available (200) """ - # Send the get request to "/system/config/certs" endpoint + # Send the GET request to "/system/config/certs" endpoint r = requests.get(f"{API}/system/config/certs", timeout=5) - # Check if status code is 200 (OK) + # Check if the status code is 200 (OK) assert r.status_code == 200 # Parse the response (certificates) @@ -2257,14 +2310,14 @@ def test_get_certs(testrun, reset_certs, add_cert): # pylint: disable=W0613 # Check if response is a list assert isinstance(response, list) - # Check if response contains two certificates - assert len(response) == 2 + # Check if the number of certs matches the number of certs available + assert len(response) == len(add_certs) -def test_upload_cert(testrun, reset_certs): # pylint: disable=W0613 +def test_upload_cert(reset_certs, testrun): # pylint: disable=W0613 """ Test for upload certificate successfully (200) """ # Load the first certificate file content using the utility method - cert_file = load_certificate_file("crt.pem") + cert_file = load_cert_file("crt.pem") # Send a POST request to the API endpoint to upload the certificate r = requests.post( @@ -2286,7 +2339,7 @@ def test_upload_cert(testrun, reset_certs): # pylint: disable=W0613 assert response["filename"] == "crt.pem" # Load the second certificate file using the utility method - cert_file = load_certificate_file("WR2.pem") + cert_file = load_cert_file("WR2.pem") # Send a POST request to the API endpoint to upload the second certificate r = requests.post( @@ -2319,11 +2372,11 @@ def test_upload_cert(testrun, reset_certs): # pylint: disable=W0613 # Check if "WR2.pem" exists assert any(cert["filename"] == "WR2.pem" for cert in response) -def test_upload_invalid_cert_format(testrun, reset_certs): # pylint: disable=W0613 +def test_upload_invalid_cert_format(reset_certs, testrun): # pylint: disable=W0613 """ Test for upload an invalid certificate format (400) """ # Load the first certificate file content using the utility method - cert_file = load_certificate_file("invalid.pem") + cert_file = load_cert_file("invalid.pem") # Send a POST request to the API endpoint to upload the certificate r = requests.post( @@ -2341,14 +2394,14 @@ def test_upload_invalid_cert_format(testrun, reset_certs): # pylint: disable=W06 # Check if "error" key is in response assert "error" in response -def test_upload_invalid_cert_name(testrun, reset_certs): # pylint: disable=W0613 +def test_upload_invalid_cert_name(reset_certs, testrun): # pylint: disable=W0613 """ Test for upload a valid certificate with invalid filename (400) """ # Assign the invalid certificate name to a variable cert_name = "invalidname1234567891234.pem" # Load the first certificate file content using the utility method - cert_file = load_certificate_file(cert_name) + cert_file = load_cert_file(cert_name) # Send a POST request to the API endpoint to upload the certificate r = requests.post( @@ -2366,33 +2419,12 @@ def test_upload_invalid_cert_name(testrun, reset_certs): # pylint: disable=W0613 # Check if "error" key is in response assert "error" in response -def test_upload_existing_cert(testrun, reset_certs): # pylint: disable=W0613 +@pytest.mark.parametrize("add_certs", [["crt.pem"]], indirect=True) +def test_upload_existing_cert(reset_certs, add_certs, testrun): # pylint: disable=W0613 """ Test for upload an existing certificate (409) """ - # Load the first certificate file content using the utility method - cert_file = load_certificate_file("crt.pem") - - # Send a POST request to the API endpoint to upload the certificate - r = requests.post( - f"{API}/system/config/certs", - files={"file": ("crt.pem", cert_file, "application/x-x509-ca-cert")}, - timeout=5 - ) - - # Check if status code is 201 (Created) - assert r.status_code == 201 - - # Parse the response - response = r.json() - - # Check if 'filename' field is in the response - assert "filename" in response - - # Check if the certificate name is 'crt.pem' - assert response["filename"] == "crt.pem" - - # Load the same certificate file content using the utility method - cert_file = load_certificate_file("crt.pem") + # Load the cert file content using the utility method + cert_file = load_cert_file("crt.pem") # Send a POST request to the API endpoint to upload the second certificate r = requests.post( @@ -2410,28 +2442,46 @@ def test_upload_existing_cert(testrun, reset_certs): # pylint: disable=W0613 # Check if "error" key is in response assert "error" in response -def test_delete_cert(testrun, reset_certs, add_cert): # pylint: disable=W0613 +@pytest.mark.parametrize("add_certs", [["crt.pem", "WR2.pem"]], indirect=True) +def test_delete_cert_success(reset_certs, add_certs, testrun): # pylint: disable=W0613 """ Test for successfully deleting an existing certificate (200) """ - # Use the add_cert fixture to upload the first certificate - add_cert("crt.pem") + # Load the first cert details to extract the 'name' value + uploaded_cert = load_cert_file("crt.pem") + + # Assign the 'name' value from certificate + cert_name = extract_name(uploaded_cert) + + # Assign the payload + delete_payload = {"name": cert_name} + + # Send delete certificate request + r = requests.delete(f"{API}/system/config/certs", + data=json.dumps(delete_payload), + timeout=5) + + # Check if status code is 200 (OK) + assert r.status_code == 200 - # Retrieve the uploaded certificate's details + # Send the get request to display all certificates r = requests.get(f"{API}/system/config/certs", timeout=5) # Parse the json response response = r.json() - # Extract the name of the uploaded certificate - uploaded_cert = next( - (cert for cert in response if cert["filename"] == "crt.pem") - ) + # Check that the certificate is no longer listed + assert not any(cert["filename"] == "crt.pem" for cert in response) - # Assign the certificate name - cert_name = uploaded_cert["name"] + # Load the second cert details to extract the 'name' value + uploaded_cert = load_cert_file("WR2.pem") - # Send delete certificate request + # Assign the 'name' value from certificate + cert_name = extract_name(uploaded_cert) + + # Assign the payload delete_payload = {"name": cert_name} + + # Send delete certificate request r = requests.delete(f"{API}/system/config/certs", data=json.dumps(delete_payload), timeout=5) @@ -2446,14 +2496,12 @@ def test_delete_cert(testrun, reset_certs, add_cert): # pylint: disable=W0613 response = r.json() # Check that the certificate is no longer listed - assert not any(cert["filename"] == "crt.pem" for cert in response) + assert not any(cert["filename"] == "WR2.pem" for cert in response) -def test_delete_cert_bad_request(testrun, reset_certs, add_cert): # pylint: disable=W0613 +@pytest.mark.parametrize("add_certs", [["crt.pem"]], indirect=True) +def test_delete_cert_bad_request(reset_certs, add_certs, testrun): # pylint: disable=W0613 """ Test for delete a certificate without providing the name (400)""" - # Use the add_cert fixture to upload the certificate - add_cert("crt.pem") - # Empty payload delete_payload = {} @@ -2471,7 +2519,7 @@ def test_delete_cert_bad_request(testrun, reset_certs, add_cert): # pylint: disa # Check if error in response assert "error" in response -def test_delete_cert_not_found(testrun, reset_certs): # pylint: disable=W0613 +def test_delete_cert_not_found(reset_certs, testrun): # pylint: disable=W0613 """ Test for delete certificate when does not exist (404) """ # Attempt to delete a certificate with a name that doesn't exist @@ -2494,21 +2542,14 @@ def test_delete_cert_not_found(testrun, reset_certs): # pylint: disable=W0613 # Tests for profile endpoints @pytest.fixture() -def add_one_profile(): - """ Create one profile during tests """ - - # Construct full path of the profile from 'testing/api/profiles' folder - source_path = os.path.join(PROFILES_PATH, "valid_profile.json") +def add_profiles(request): + """ Upload specified profile to local/risk_profiles """ - # Copy the profile from 'testing/api/profiles' to 'local/risk_profiles' - shutil.copy(source_path, PROFILES_DIRECTORY) - -@pytest.fixture() -def add_two_profiles(): - """ Create two profiles during tests """ + # Access the parameter (profiles list) provided to the fixture + profiles = request.param - # Iterate over the files from 'testing/api/profiles' folder - for profile in os.listdir(PROFILES_PATH): + # Iterate over the profile names provided + for profile in profiles: # Construct full path of the file from 'testing/api/profiles' folder source_path = os.path.join(PROFILES_PATH, profile) @@ -2516,6 +2557,9 @@ def add_two_profiles(): # Copy the file_name from 'testing/api/profiles' to 'local/risk_profiles' shutil.copy(source_path, PROFILES_DIRECTORY) + # Return the list with profiles name + return profiles + def delete_all_profiles(): """Utility method to delete all profiles from local/risk_profiles""" @@ -2579,6 +2623,32 @@ def profile_exists(profile_name): # Return if name is in the list of profiles return any(p["name"] == profile_name for p in profiles) +@pytest.fixture() +def remove_risk_assessment(): + """ Fixture to remove and restore risk_assessment.json """ + + # Path to the risk_assessment.json file + risk_assessment_path = os.path.join("resources", "risk_assessment.json") + + # Backup path for the risk_assessment.json file + backup_path = os.path.join("resources", "risk_assessment_backup.json") + + # Create a backup of the risk_assessment.json file + if os.path.exists(risk_assessment_path): + shutil.copy(risk_assessment_path, backup_path) + + # Delete the risk_assessment.json file + if os.path.exists(risk_assessment_path): + os.remove(risk_assessment_path) + + # Run the test + yield + + # Restore the risk assessment file after the test + if os.path.exists(backup_path): + shutil.copy(backup_path, risk_assessment_path) + os.remove(backup_path) + def test_get_profiles_format(testrun): # pylint: disable=W0613 """ Test for profiles format (200) """ @@ -2599,31 +2669,19 @@ def test_get_profiles_format(testrun): # pylint: disable=W0613 assert "question" in item assert "type" in item -def test_get_profiles_no_profiles(empty_profiles_dir, testrun): # pylint: disable=W0613 - """ Test for get profiles when no profiles created (200) """ - - # Send the get request to "/profiles" endpoint - r = requests.get(f"{API}/profiles", timeout=5) - - # Check if status code is 200 (OK) - assert r.status_code == 200 - - # Parse the response (profiles) - response = r.json() - - # Check if response is a list - assert isinstance(response, list) - - # Check if the list is empty - assert len(response) == 0 - -def test_get_profiles_one_profile(empty_profiles_dir, add_one_profile, testrun): # pylint: disable=W0613 - """ Test for get profiles when one profile is created (200) """ +# Use parametrize to create a test suite for 3 scenarios +@pytest.mark.parametrize("add_profiles", [ + [], + ["valid_profile.json"], + ["valid_profile.json", "draft_profile.json"], +], indirect=True) +def test_get_profiles(empty_profiles_dir, add_profiles, testrun): # pylint: disable=W0613 + """ Test get profiles when none, one or two profiles are available (200) """ # Send get request to the "/profiles" endpoint r = requests.get(f"{API}/profiles", timeout=5) - # Check if status code is 200 (OK) + # Check if the status code is 200 (OK) assert r.status_code == 200 # Parse the response (profiles) @@ -2632,50 +2690,43 @@ def test_get_profiles_one_profile(empty_profiles_dir, add_one_profile, testrun): # Check if response is a list assert isinstance(response, list) - # Check if response contains one profile - assert len(response) == 1 - - # Check that each profile has the expected fields - for profile in response: - for field in ["name", "status", "created", "version", "questions", "risk"]: - assert field in profile - - # Assign profile["questions"] - profile_questions = profile["questions"] + # Check if the number of profiles matches the number of profiles available + assert len(response) == len(add_profiles) - # Check if "questions" value is a list - assert isinstance(profile_questions, list) + # Assign the expected profile fields + expected_fields = [ + "name", "status", "created", "version", "questions", "risk" + ] - # Check that "questions" value has the expected fields - for element in profile_questions: + # Check if profile exist + if len(add_profiles) > 0: - # Check if each element is dict - assert isinstance(element, dict) + # Iterate through profiles + for profile in response: - # Check if "question" key is in dict element - assert "question" in element + # Iterate through expected_fields list + for field in expected_fields: - # Check if "asnswer" key is in dict element - assert "answer" in element + # Check if the field is in profile + assert field in profile -def test_get_profiles_two_profiles(empty_profiles_dir, add_two_profiles, # pylint: disable=W0613 - testrun): # pylint: disable=W0613 - """ Test for get profiles when two profiles are created (200) """ + # Assign profile["questions"] + profile_questions = profile["questions"] - # Send the get request to "/profiles" endpoint - r = requests.get(f"{API}/profiles", timeout=5) + # Check if "questions" value is a list + assert isinstance(profile_questions, list) - # Parse the response (profiles) - response = r.json() + # Check that "questions" value has the expected fields + for element in profile_questions: - # Check if status code is 200 (OK) - assert r.status_code == 200 + # Check if each element is dict + assert isinstance(element, dict) - # Check if response is a list - assert isinstance(response, list) + # Check if "question" key is in dict element + assert "question" in element - # Check if response contains two profiles - assert len(response) == 2 + # Check if "asnswer" key is in dict element + assert "answer" in element def test_create_profile(testrun): # pylint: disable=W0613 """ Test for create profile when profile does not exist (201) """ @@ -2719,7 +2770,10 @@ def test_create_profile(testrun): # pylint: disable=W0613 # Check if profile was created assert created_profile is not None -def test_update_profile(empty_profiles_dir, add_one_profile, testrun): # pylint: disable=W0613 +@pytest.mark.parametrize("add_profiles", [ + ["valid_profile.json"] +], indirect=True) +def test_update_profile(empty_profiles_dir, add_profiles, testrun): # pylint: disable=W0613 """ Test for update profile when profile already exists (200) """ # Load the profile using load_json utility method @@ -2777,7 +2831,34 @@ def test_update_profile(empty_profiles_dir, add_one_profile, testrun): # pylint: # Check if profile was updated assert updated_profile_check is not None -def test_update_profile_invalid_json(empty_profiles_dir, add_one_profile, # pylint: disable=W0613 +def test_update_profile_no_profiles_format(empty_profiles_dir, # pylint: disable=W0613 + remove_risk_assessment, testrun): # pylint: disable=W0613 + """Test for profile update when profiles format is not available (501)""" + + # Prepare a valid profile update request + profile_update = load_json("valid_profile.json", directory=PROFILES_PATH) + + # Send a POST request to update the profile + r = requests.post(f"{API}/profiles", + data=json.dumps(profile_update), + timeout=5) + + # Check if the response status code is 501 (Not Implemented) + assert r.status_code == 501 + + # Parse the response + response = r.json() + + # Check if "error" key is present in the response + assert "error" in response + + # Check if the error message matches the expected response + assert response["error"] == "Risk profiles are not available right now" + +@pytest.mark.parametrize("add_profiles", [ + ["valid_profile.json"] +], indirect=True) +def test_update_profile_invalid_json(empty_profiles_dir, add_profiles, # pylint: disable=W0613 testrun): # pylint: disable=W0613 """ Test for update profile invalid JSON payload (400) """ @@ -2820,7 +2901,10 @@ def test_create_profile_invalid_json(empty_profiles_dir, testrun): # pylint: dis # Check if "error" key in response assert "error" in response -def test_delete_profile(empty_profiles_dir, add_one_profile, testrun): # pylint: disable=W0613 +@pytest.mark.parametrize("add_profiles", [ + ["valid_profile.json"] +], indirect=True) +def test_delete_profile(empty_profiles_dir, add_profiles, testrun): # pylint: disable=W0613 """ Test for successfully delete profile (200) """ # Load the profile using load_json utility method @@ -2922,7 +3006,10 @@ def test_delete_profile_invalid_json(empty_profiles_dir, testrun): # pylint: dis # Check if "error" key in response assert "error" in response -def test_delete_profile_server_error(empty_profiles_dir, add_one_profile, # pylint: disable=W0613 +@pytest.mark.parametrize("add_profiles", [ + ["valid_profile.json"] +], indirect=True) +def test_delete_profile_server_error(empty_profiles_dir, add_profiles, # pylint: disable=W0613 testrun): # pylint: disable=W0613 """ Test for delete profile causing internal server error (500) """ @@ -2994,7 +3081,10 @@ def test_delete_device_testrun_running(testing_devices, testrun): # pylint: disa assert r.status_code == 403 @pytest.mark.skip() -def test_stop_running_test(empty_devices_dir, add_one_device, testrun): # pylint: disable=W0613 +@pytest.mark.parametrize("add_devices", [ + ["device_1"] +],indirect=True) +def test_stop_running_test(empty_devices_dir, add_devices, testrun): # pylint: disable=W0613 """ Test for successfully stop testrun when test is running (200) """ # Load the device and mac address using add_device utility method diff --git a/testing/baseline/test_baseline b/testing/baseline/test_baseline index 4ab0d75e8..44a17d348 100755 --- a/testing/baseline/test_baseline +++ b/testing/baseline/test_baseline @@ -40,7 +40,7 @@ sudo cp testing/baseline/system.json local/system.json # Copy device configs to testrun sudo cp -r testing/device_configs/* local/devices -sudo bin/testrun --single-intf --no-ui --validate > $TESTRUN_OUT 2>&1 & +sudo bin/testrun --single-intf --no-ui --target 02:42:aa:00:01:01 -fw 1.0 --validate > $TESTRUN_OUT 2>&1 & TPID=$! # Time to wait for testrun to be ready diff --git a/testing/device_configs/tester1/device_config.json b/testing/device_configs/tester1/device_config.json index f9eeccb6a..0c94febd2 100644 --- a/testing/device_configs/tester1/device_config.json +++ b/testing/device_configs/tester1/device_config.json @@ -2,6 +2,9 @@ "manufacturer": "Google", "model": "Tester 1", "mac_addr": "02:42:aa:00:00:01", + "type": "Controller - FCU", + "technology": "Hardware - Fitness", + "test_pack": "Device Qualification", "test_modules": { "dns": { "enabled": true @@ -24,5 +27,31 @@ "tls": { "enabled": false } - } + }, + "additional_info": [ + { + "question": "What type of device is this?", + "answer": "Controller - FCU" + }, + { + "question": "Please select the technology this device falls into", + "answer": "Hardware - Fitness" + }, + { + "question": "Does your device process any sensitive information? ", + "answer": "No" + }, + { + "question": "Can all non-essential services be disabled on your device?", + "answer": "Yes" + }, + { + "question": "Is there a second IP port on the device?", + "answer": "No" + }, + { + "question": "Can the second IP port on your device be disabled?", + "answer": "N/A" + } + ] } diff --git a/testing/device_configs/tester2/device_config.json b/testing/device_configs/tester2/device_config.json index ccbc14585..d816cb2ef 100644 --- a/testing/device_configs/tester2/device_config.json +++ b/testing/device_configs/tester2/device_config.json @@ -2,6 +2,9 @@ "manufacturer": "Google", "model": "Tester 2", "mac_addr": "02:42:aa:00:00:02", + "type": "Controller - FCU", + "technology": "Hardware - Fitness", + "test_pack": "Device Qualification", "test_modules": { "dns": { "enabled": true @@ -24,5 +27,31 @@ "tls": { "enabled": false } - } + }, + "additional_info": [ + { + "question": "What type of device is this?", + "answer": "Controller - FCU" + }, + { + "question": "Please select the technology this device falls into", + "answer": "Hardware - Fitness" + }, + { + "question": "Does your device process any sensitive information? ", + "answer": "No" + }, + { + "question": "Can all non-essential services be disabled on your device?", + "answer": "Yes" + }, + { + "question": "Is there a second IP port on the device?", + "answer": "No" + }, + { + "question": "Can the second IP port on your device be disabled?", + "answer": "N/A" + } + ] } diff --git a/testing/device_configs/tester3/device_config.json b/testing/device_configs/tester3/device_config.json index b7792027e..7b0206ca3 100644 --- a/testing/device_configs/tester3/device_config.json +++ b/testing/device_configs/tester3/device_config.json @@ -2,6 +2,9 @@ "manufacturer": "Google", "model": "Tester 3", "mac_addr": "02:42:aa:00:00:03", + "type": "Controller - FCU", + "technology": "Hardware - Fitness", + "test_pack": "Device Qualification", "test_modules": { "dns": { "enabled": false @@ -18,5 +21,31 @@ "nmap": { "enabled": false } - } + }, + "additional_info": [ + { + "question": "What type of device is this?", + "answer": "Controller - FCU" + }, + { + "question": "Please select the technology this device falls into", + "answer": "Hardware - Fitness" + }, + { + "question": "Does your device process any sensitive information? ", + "answer": "No" + }, + { + "question": "Can all non-essential services be disabled on your device?", + "answer": "Yes" + }, + { + "question": "Is there a second IP port on the device?", + "answer": "No" + }, + { + "question": "Can the second IP port on your device be disabled?", + "answer": "N/A" + } + ] } diff --git a/testing/unit/conn/conn_module_test.py b/testing/unit/conn/conn_module_test.py index 0687fc1a6..1513545f0 100644 --- a/testing/unit/conn/conn_module_test.py +++ b/testing/unit/conn/conn_module_test.py @@ -133,14 +133,42 @@ def connection_port_speed_autonegotiation_fail_test(self): def connection_switch_dhcp_snooping_icmp_test(self): LOGGER.info('connection_switch_dhcp_snooping_icmp_test') conn_module = ConnectionModule(module=MODULE, - log_dir=OUTPUT_DIR, - results_dir=OUTPUT_DIR, - startup_capture_file=STARTUP_CAPTURE_FILE, - monitor_capture_file=MONITOR_CAPTURE_FILE) - result = conn_module._connection_switch_dhcp_snooping() # pylint: disable=W0212 + results_dir=OUTPUT_DIR, + startup_capture_file=STARTUP_CAPTURE_FILE, + monitor_capture_file=MONITOR_CAPTURE_FILE) + result = conn_module._connection_switch_dhcp_snooping() # pylint: disable=W0212 LOGGER.info(result) self.assertEqual(result[0], True) + def communication_network_type_test(self): + LOGGER.info('communication_network_type_test') + conn_module = ConnectionModule(module=MODULE, + results_dir=OUTPUT_DIR, + startup_capture_file=STARTUP_CAPTURE_FILE, + monitor_capture_file=MONITOR_CAPTURE_FILE) + result = conn_module._communication_network_type() # pylint: disable=W0212 + details_expected = { + 'mac_address': '98:f0:7b:d1:87:06', + 'multicast': { + 'from': 11, + 'to': 0 + }, + 'broadcast': { + 'from': 13, + 'to': 0 + }, + 'unicast': { + 'from': 0, + 'to': 0 + } + } + LOGGER.info(result) + self.assertEqual(result[0], 'Informational') + self.assertEqual(result[1], 'Packet types detected: Multicast, Broadcast') + self.assertEqual(result[2], details_expected) + #self.assertEqual(result[0], True) + + if __name__ == '__main__': suite = unittest.TestSuite() @@ -163,6 +191,9 @@ def connection_switch_dhcp_snooping_icmp_test(self): suite.addTest( ConnectionModuleTest('connection_switch_dhcp_snooping_icmp_test')) + # DHCP Snooping related tests + suite.addTest(ConnectionModuleTest('communication_network_type_test')) + runner = unittest.TextTestRunner() test_result = runner.run(suite) diff --git a/testing/unit/dns/dns_module_test.py b/testing/unit/dns/dns_module_test.py index 5c100cbf9..d530498dd 100644 --- a/testing/unit/dns/dns_module_test.py +++ b/testing/unit/dns/dns_module_test.py @@ -49,7 +49,6 @@ def setUpClass(cls): # Test the module report generation def dns_module_report_test(self): dns_module = DNSModule(module=MODULE, - log_dir=OUTPUT_DIR, results_dir=OUTPUT_DIR, dns_server_capture_file=DNS_SERVER_CAPTURE_FILE, startup_capture_file=STARTUP_CAPTURE_FILE, @@ -98,7 +97,6 @@ def dns_module_report_no_dns_test(self): wrpcap(monitor_cap_file, packets_monitor) dns_module = DNSModule(module='dns', - log_dir=OUTPUT_DIR, results_dir=OUTPUT_DIR, dns_server_capture_file=dns_server_cap_file, startup_capture_file=startup_cap_file, diff --git a/testing/unit/ntp/ntp_module_test.py b/testing/unit/ntp/ntp_module_test.py index af74722f1..52bd32aa9 100644 --- a/testing/unit/ntp/ntp_module_test.py +++ b/testing/unit/ntp/ntp_module_test.py @@ -16,6 +16,7 @@ import unittest from scapy.all import rdpcap, NTP, wrpcap import os +import shutil import sys MODULE = 'ntp' @@ -47,7 +48,6 @@ def setUpClass(cls): # Test the module report generation def ntp_module_report_test(self): ntp_module = NTPModule(module=MODULE, - log_dir=OUTPUT_DIR, results_dir=OUTPUT_DIR, ntp_server_capture_file=NTP_SERVER_CAPTURE_FILE, startup_capture_file=STARTUP_CAPTURE_FILE, @@ -63,6 +63,11 @@ def ntp_module_report_test(self): with open(LOCAL_REPORT, 'r', encoding='utf-8') as file: report_local = file.read() + # Copy the generated html report to a new file + new_report_name = 'ntp_local.html' + new_report_path = os.path.join(OUTPUT_DIR, new_report_name) + shutil.copy(report_out_path, new_report_path) + self.assertEqual(report_out, report_local) # Test the module report generation if no DNS traffic @@ -96,7 +101,6 @@ def ntp_module_report_no_ntp_test(self): wrpcap(monitor_cap_file, packets_monitor) ntp_module = NTPModule(module='dns', - log_dir=OUTPUT_DIR, results_dir=OUTPUT_DIR, ntp_server_capture_file=ntp_server_cap_file, startup_capture_file=startup_cap_file, @@ -112,6 +116,11 @@ def ntp_module_report_no_ntp_test(self): with open(LOCAL_REPORT_NO_NTP, 'r', encoding='utf-8') as file: report_local = file.read() + # Copy the generated html report to a new file + new_report_name = 'ntp_no_ntp.html' + new_report_path = os.path.join(OUTPUT_DIR, new_report_name) + shutil.copy(report_out_path, new_report_path) + self.assertEqual(report_out, report_local) if __name__ == '__main__': diff --git a/testing/unit/ntp/reports/ntp_report_local.html b/testing/unit/ntp/reports/ntp_report_local.html index 025881db5..1fe5e3f3a 100644 --- a/testing/unit/ntp/reports/ntp_report_local.html +++ b/testing/unit/ntp/reports/ntp_report_local.html @@ -18,7 +18,7 @@

NTP Module

- +
diff --git a/testing/unit/report/report_compliant.json b/testing/unit/report/report_compliant.json index 17e994d20..6b84a8d39 100644 --- a/testing/unit/report/report_compliant.json +++ b/testing/unit/report/report_compliant.json @@ -4,6 +4,9 @@ "manufacturer": "Testrun", "model": "Faux", "firmware": "1.0.0", + "type": "Controller - FCU", + "technology": "Hardware - Fitness", + "test_pack": "Device Qualification", "test_modules": { "connection": { "enabled": true @@ -23,7 +26,33 @@ "protocol": { "enabled": true } - } + }, + "additional_info": [ + { + "question": "What type of device is this?", + "answer": "Controller - FCU" + }, + { + "question": "Please select the technology this device falls into", + "answer": "Hardware - Fitness" + }, + { + "question": "Does your device process any sensitive information? ", + "answer": "No" + }, + { + "question": "Can all non-essential services be disabled on your device?", + "answer": "Yes" + }, + { + "question": "Is there a second IP port on the device?", + "answer": "No" + }, + { + "question": "Can the second IP port on your device be disabled?", + "answer": "N/A" + } + ] }, "status": "Compliant", "started": "2024-04-10 21:21:47", @@ -68,7 +97,7 @@ }, { "name": "connection.switch.arp_inspection", - "description": "Device uses ARP", + "description": "Device uses ARP correctly", "expected_behavior": "Device continues to operate correctly when ARP inspection is enabled on the switch. No functionality is lost with ARP inspection enabled.", "required_result": "Required", "result": "Compliant" diff --git a/testing/unit/report/report_noncompliant.json b/testing/unit/report/report_noncompliant.json index 98fbeb284..6619bba19 100644 --- a/testing/unit/report/report_noncompliant.json +++ b/testing/unit/report/report_noncompliant.json @@ -4,6 +4,9 @@ "manufacturer": "Testrun", "model": "Faux", "firmware": "1.0.0", + "type": "Controller - FCU", + "technology": "Hardware - Fitness", + "test_pack": "Device Qualification", "test_modules": { "connection": { "enabled": true @@ -23,7 +26,33 @@ "protocol": { "enabled": true } - } + }, + "additional_info": [ + { + "question": "What type of device is this?", + "answer": "Controller - FCU" + }, + { + "question": "Please select the technology this device falls into", + "answer": "Hardware - Fitness" + }, + { + "question": "Does your device process any sensitive information? ", + "answer": "No" + }, + { + "question": "Can all non-essential services be disabled on your device?", + "answer": "Yes" + }, + { + "question": "Is there a second IP port on the device?", + "answer": "No" + }, + { + "question": "Can the second IP port on your device be disabled?", + "answer": "N/A" + } + ] }, "status": "Non-Compliant", "started": "2024-04-10 21:21:47", @@ -77,7 +106,7 @@ }, { "name": "connection.switch.arp_inspection", - "description": "Device uses ARP", + "description": "Device uses ARP correctly", "expected_behavior": "Device continues to operate correctly when ARP inspection is enabled on the switch. No functionality is lost with ARP inspection enabled.", "required_result": "Required", "result": "Compliant" diff --git a/testing/unit/report/report_test.py b/testing/unit/report/report_test.py index e5c8b61a5..f706059b6 100644 --- a/testing/unit/report/report_test.py +++ b/testing/unit/report/report_test.py @@ -61,6 +61,7 @@ def create_report(self, results_file_path): # Load each module html report reports_md = [] + reports_md.append(self.get_module_html_report('tls')) reports_md.append(self.get_module_html_report('dns')) reports_md.append(self.get_module_html_report('services')) reports_md.append(self.get_module_html_report('ntp')) @@ -70,12 +71,16 @@ def create_report(self, results_file_path): # Create the HTML filename based on the JSON name file_name = os.path.splitext(os.path.basename(results_file_path))[0] - report_out_file = os.path.join(OUTPUT_DIR, file_name + '.html') + report_html_file = os.path.join(OUTPUT_DIR, file_name + '.html') + report_pdf_file = os.path.join(OUTPUT_DIR, file_name + '.pdf') # Save report as HTML file - with open(report_out_file, 'w', encoding='utf-8') as file: + with open(report_html_file, 'w', encoding='utf-8') as file: file.write(report.to_html()) + with open(report_pdf_file, 'wb') as file: + file.write(report.to_pdf().getvalue()) + def report_compliant_test(self): """Generate a report for the compliant test""" diff --git a/testing/unit/run_test_module.sh b/testing/unit/run_test_module.sh old mode 100644 new mode 100755 index 37516ee72..8e31e6860 --- a/testing/unit/run_test_module.sh +++ b/testing/unit/run_test_module.sh @@ -15,6 +15,10 @@ # limitations under the License. # Must be run from the root directory of Testrun + +# Read the JSON file into a variable +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
01(T!bpGu1zaVm7AY%TB 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𝔐KWjnwTmBT8ELi5&SCSlM-G2iH0t63GN0fZIis)S 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

2J7d0Wc6byu%OgMoVm>;)`emB?#(qa#jST~ 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 literal 0 HcmV?d00001 diff --git a/testing/unit/tls/captures/multi_page_startup.pcap b/testing/unit/tls/captures/multi_page_startup.pcap new file mode 100644 index 0000000000000000000000000000000000000000..e6089ee2ba16fb803fe9e65254590bc29a9b4a8c GIT binary patch literal 11129 zcmds-3v3is6o&7e-R-v9vMnJ2#HNCxK%up;Ezk#0Xu+yQ%Cku2QA$A|lt*bQph#-% z5-ll&1Qq0!U<3jtf+oD$pnyCR5J>8&%HCVXAbPxC=fGYn%2?NcI223!*EqO`p^ zA@+F5@$fK0!%3K#Q2G0ori3cIazbo2n@jHUDfmo)oEH>OBTrb)LGQZkG@Ig(p7xbt zcBrCv_v}!eHZVI4=#Zi~idM&e8flbs>m#jN4Xj?^tXdPn>b2J4MpjLav}!W2TF+TE zy$DuUt(V7}Zp@h}#aNDEv(B7eQd~N{ zG@`6Dp}4SgenQWP!dZn2XO|R~mzs)Z&%%b(2UH&kbykb&v$TG`Kd4w4Rddo64|(+I z;vv^QDIT&H;2lkZc;wfcjTFSjn!DQdW6iC;qSdN5!bk;xaOY+$rpsKn5vzYwl!n9G?-)v%m16J*LHdk067FJ)gx z8RhIT=8v8bp^_gABgB;8o-@4?_?&$KSNvEx!z&`0x*1KG!J!Vdx|>A$dY$`e6&Z9o zlaO(8{P2=8FTpWRQgL|hVN7}5KxIC{?`(~L^xG9SsUvolHB2t$PNkcaD%jbjVc}A) z*aW}msvsAS0qxPan}#bdS0L%5jXFQu$~8$Lar51b39(`m%&Lzjh0g?%(J1)GjShB@ z9-fvKeMn&HIhW1Xfc_$xT zrZzjhU$7|e<;Gs%_r*Fne@YJHYOGTIXR8Zu6lm`xjnzCwiLTX7vzI@8dQ=orUdS*R zRV<6HVp*V0DODL~8SJ^MvxScW=-#g=Kzk=Cs^zaQJU@BTH*nrg9qam+#O*N`t3$@}Mrbc#XQo7O(3^;yStguH0$;tN+g!)yBsycMwb$?K>PDa%3%fXIaU!eT4#meiTcvz8sMtyju(E8I1LYayy5dHbMI; zvN=C9fYlMwJLHCkz{d}+dsJaJNgDN9X#}7`bjS-TUP4{fq5@E18lW;7f*rebpp-4u zRHGoMFaszvpV!pyAW-@NCH`2jDgA-+V~U_^S~sF_FmkhmQkMn&h;ovgi`Z0up4D%p zj1wX~Jb_qw7=^ZX2ou`2zp%1e=8TB+r`$nh^#OH6LJiiUUa7mY&mR<2)&bzv-j%_Q zT_#W#-U&8kAW(J`>(*W&yYEE$mvecogP59qt%8)-4(fai**)hVpl|KclJv^5U`IF$ zd~}wFf)dltV>LM{4e@O45D%c5phI3z$0Ssi78O7>$pO@c3{PIIXoslU`wVq14Qu3@ z2g}p_DO^w^eL%IBP>ovDsdqB>_=AEPnG0SWn`2VnYv|^eAg|bl0{zG^k1>5pa`c|o z(EwuV1FD0Bx~oM65K}~Tf8cuWB;PqI&-49!?}WCT4L-D-79;f%ItwW_8^n)7ATpX1cpY%6D6IK89?@KzjVzmYOm0fSx`f*byEMl)IBd zK}pJTJ&V&-AzZpP!zy4a?)N@+YqFXn|`h$XbVkDs2!AmXnOe{DV8U>W02@zx>TpjM3H}>!*ln(EDmq6_D zwxT5PSCUP#XukuODGejbOKzU+)QG=mH@WI)&;6gj6;+HZN};q*E4siG78M=a$gT;Ojv-+;9{<8hZZT2Oa@HdU59_G*dVk97A)k?UL55=R#|I z$!I58B%3Dhs}po7#^10Fwtq5HEcC`V<0c2bB8#JDl)DMf&@kzZc`Sb7AF|vxjPigq z!zc@GQ5L{Vv>fGHqbNti?@|Vzb`sKp2R5kLaEwQs7Y3|ucgbaaADO#ZHu1=T6?-}Htgy7?+x}b!2IMM zJmm4SYVEF7tCo^!j05aXaMiK|T(vODiysI+{T<6SZZM**Z|n$dowpeN{m6%0|7qOQ Q%iNX^%@~ literal 0 HcmV?d00001 diff --git a/testing/unit/tls/captures/multi_page_tls.pcap b/testing/unit/tls/captures/multi_page_tls.pcap new file mode 100644 index 0000000000000000000000000000000000000000..fc76ebe99579a935f81c128d90f418088cb567be GIT binary patch literal 235069 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 literal 0 HcmV?d00001 diff --git a/testing/unit/tls/certs/_.google.com.crt b/testing/unit/tls/certs/_.google.com.crt index 442bf6ec3..99bb16833 100644 --- a/testing/unit/tls/certs/_.google.com.crt +++ b/testing/unit/tls/certs/_.google.com.crt @@ -1,77 +1,77 @@ ------BEGIN CERTIFICATE----- -MIIN4zCCDMugAwIBAgIRAMX7uoXXmJy3CoNgnp7ELOMwDQYJKoZIhvcNAQELBQAw -OzELMAkGA1UEBhMCVVMxHjAcBgNVBAoTFUdvb2dsZSBUcnVzdCBTZXJ2aWNlczEM -MAoGA1UEAxMDV1IyMB4XDTI0MDcwMTA2MzU0M1oXDTI0MDkyMzA2MzU0MlowFzEV -MBMGA1UEAwwMKi5nb29nbGUuY29tMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE -1mu/GyluisEPwCtCgrJeDhhGpQ9pvMzgMsbtBi3cFm0+bGTyNyoTRDSTdUsLRJNJ -BRF2O/M8i6CCi6PrYenOhKOCC88wggvLMA4GA1UdDwEB/wQEAwIHgDATBgNVHSUE -DDAKBggrBgEFBQcDATAMBgNVHRMBAf8EAjAAMB0GA1UdDgQWBBTQ+KGTW7bq4kmI -AhTEY+bMVA8+mDAfBgNVHSMEGDAWgBTeGx7teRXUPjckwyG77DQ5bUKyMDBYBggr -BgEFBQcBAQRMMEowIQYIKwYBBQUHMAGGFWh0dHA6Ly9vLnBraS5nb29nL3dyMjAl -BggrBgEFBQcwAoYZaHR0cDovL2kucGtpLmdvb2cvd3IyLmNydDCCCaUGA1UdEQSC -CZwwggmYggwqLmdvb2dsZS5jb22CFiouYXBwZW5naW5lLmdvb2dsZS5jb22CCSou -YmRuLmRldoIVKi5vcmlnaW4tdGVzdC5iZG4uZGV2ghIqLmNsb3VkLmdvb2dsZS5j -b22CGCouY3Jvd2Rzb3VyY2UuZ29vZ2xlLmNvbYIYKi5kYXRhY29tcHV0ZS5nb29n -bGUuY29tggsqLmdvb2dsZS5jYYILKi5nb29nbGUuY2yCDiouZ29vZ2xlLmNvLmlu -gg4qLmdvb2dsZS5jby5qcIIOKi5nb29nbGUuY28udWuCDyouZ29vZ2xlLmNvbS5h -coIPKi5nb29nbGUuY29tLmF1gg8qLmdvb2dsZS5jb20uYnKCDyouZ29vZ2xlLmNv -bS5jb4IPKi5nb29nbGUuY29tLm14gg8qLmdvb2dsZS5jb20udHKCDyouZ29vZ2xl -LmNvbS52boILKi5nb29nbGUuZGWCCyouZ29vZ2xlLmVzggsqLmdvb2dsZS5mcoIL -Ki5nb29nbGUuaHWCCyouZ29vZ2xlLml0ggsqLmdvb2dsZS5ubIILKi5nb29nbGUu -cGyCCyouZ29vZ2xlLnB0gg8qLmdvb2dsZWFwaXMuY26CESouZ29vZ2xldmlkZW8u -Y29tggwqLmdzdGF0aWMuY26CECouZ3N0YXRpYy1jbi5jb22CD2dvb2dsZWNuYXBw -cy5jboIRKi5nb29nbGVjbmFwcHMuY26CEWdvb2dsZWFwcHMtY24uY29tghMqLmdv -b2dsZWFwcHMtY24uY29tggxna2VjbmFwcHMuY26CDiouZ2tlY25hcHBzLmNughJn -b29nbGVkb3dubG9hZHMuY26CFCouZ29vZ2xlZG93bmxvYWRzLmNughByZWNhcHRj -aGEubmV0LmNughIqLnJlY2FwdGNoYS5uZXQuY26CEHJlY2FwdGNoYS1jbi5uZXSC -EioucmVjYXB0Y2hhLWNuLm5ldIILd2lkZXZpbmUuY26CDSoud2lkZXZpbmUuY26C -EWFtcHByb2plY3Qub3JnLmNughMqLmFtcHByb2plY3Qub3JnLmNughFhbXBwcm9q -ZWN0Lm5ldC5jboITKi5hbXBwcm9qZWN0Lm5ldC5jboIXZ29vZ2xlLWFuYWx5dGlj -cy1jbi5jb22CGSouZ29vZ2xlLWFuYWx5dGljcy1jbi5jb22CF2dvb2dsZWFkc2Vy -dmljZXMtY24uY29tghkqLmdvb2dsZWFkc2VydmljZXMtY24uY29tghFnb29nbGV2 -YWRzLWNuLmNvbYITKi5nb29nbGV2YWRzLWNuLmNvbYIRZ29vZ2xlYXBpcy1jbi5j -b22CEyouZ29vZ2xlYXBpcy1jbi5jb22CFWdvb2dsZW9wdGltaXplLWNuLmNvbYIX -Ki5nb29nbGVvcHRpbWl6ZS1jbi5jb22CEmRvdWJsZWNsaWNrLWNuLm5ldIIUKi5k -b3VibGVjbGljay1jbi5uZXSCGCouZmxzLmRvdWJsZWNsaWNrLWNuLm5ldIIWKi5n -LmRvdWJsZWNsaWNrLWNuLm5ldIIOZG91YmxlY2xpY2suY26CECouZG91YmxlY2xp -Y2suY26CFCouZmxzLmRvdWJsZWNsaWNrLmNughIqLmcuZG91YmxlY2xpY2suY26C -EWRhcnRzZWFyY2gtY24ubmV0ghMqLmRhcnRzZWFyY2gtY24ubmV0gh1nb29nbGV0 -cmF2ZWxhZHNlcnZpY2VzLWNuLmNvbYIfKi5nb29nbGV0cmF2ZWxhZHNlcnZpY2Vz -LWNuLmNvbYIYZ29vZ2xldGFnc2VydmljZXMtY24uY29tghoqLmdvb2dsZXRhZ3Nl -cnZpY2VzLWNuLmNvbYIXZ29vZ2xldGFnbWFuYWdlci1jbi5jb22CGSouZ29vZ2xl -dGFnbWFuYWdlci1jbi5jb22CGGdvb2dsZXN5bmRpY2F0aW9uLWNuLmNvbYIaKi5n -b29nbGVzeW5kaWNhdGlvbi1jbi5jb22CJCouc2FmZWZyYW1lLmdvb2dsZXN5bmRp -Y2F0aW9uLWNuLmNvbYIWYXBwLW1lYXN1cmVtZW50LWNuLmNvbYIYKi5hcHAtbWVh -c3VyZW1lbnQtY24uY29tggtndnQxLWNuLmNvbYINKi5ndnQxLWNuLmNvbYILZ3Z0 -Mi1jbi5jb22CDSouZ3Z0Mi1jbi5jb22CCzJtZG4tY24ubmV0gg0qLjJtZG4tY24u -bmV0ghRnb29nbGVmbGlnaHRzLWNuLm5ldIIWKi5nb29nbGVmbGlnaHRzLWNuLm5l -dIIMYWRtb2ItY24uY29tgg4qLmFkbW9iLWNuLmNvbYIUZ29vZ2xlc2FuZGJveC1j -bi5jb22CFiouZ29vZ2xlc2FuZGJveC1jbi5jb22CHiouc2FmZW51cC5nb29nbGVz -YW5kYm94LWNuLmNvbYINKi5nc3RhdGljLmNvbYIUKi5tZXRyaWMuZ3N0YXRpYy5j -b22CCiouZ3Z0MS5jb22CESouZ2NwY2RuLmd2dDEuY29tggoqLmd2dDIuY29tgg4q -LmdjcC5ndnQyLmNvbYIQKi51cmwuZ29vZ2xlLmNvbYIWKi55b3V0dWJlLW5vY29v -a2llLmNvbYILKi55dGltZy5jb22CC2FuZHJvaWQuY29tgg0qLmFuZHJvaWQuY29t -ghMqLmZsYXNoLmFuZHJvaWQuY29tggRnLmNuggYqLmcuY26CBGcuY2+CBiouZy5j -b4IGZ29vLmdsggp3d3cuZ29vLmdsghRnb29nbGUtYW5hbHl0aWNzLmNvbYIWKi5n -b29nbGUtYW5hbHl0aWNzLmNvbYIKZ29vZ2xlLmNvbYISZ29vZ2xlY29tbWVyY2Uu -Y29tghQqLmdvb2dsZWNvbW1lcmNlLmNvbYIIZ2dwaHQuY26CCiouZ2dwaHQuY26C -CnVyY2hpbi5jb22CDCoudXJjaGluLmNvbYIIeW91dHUuYmWCC3lvdXR1YmUuY29t -gg0qLnlvdXR1YmUuY29tghR5b3V0dWJlZWR1Y2F0aW9uLmNvbYIWKi55b3V0dWJl -ZWR1Y2F0aW9uLmNvbYIPeW91dHViZWtpZHMuY29tghEqLnlvdXR1YmVraWRzLmNv -bYIFeXQuYmWCByoueXQuYmWCGmFuZHJvaWQuY2xpZW50cy5nb29nbGUuY29tghMq -LmFuZHJvaWQuZ29vZ2xlLmNughIqLmNocm9tZS5nb29nbGUuY26CFiouZGV2ZWxv -cGVycy5nb29nbGUuY24wEwYDVR0gBAwwCjAIBgZngQwBAgEwNgYDVR0fBC8wLTAr -oCmgJ4YlaHR0cDovL2MucGtpLmdvb2cvd3IyL0dTeVQxTjRQQnJnLmNybDCCAQQG -CisGAQQB1nkCBAIEgfUEgfIA8AB1ANq2v2s/tbYin5vCu1xr6HCRcWy7UYSFNL2k -PTBI1/urAAABkG04oVMAAAQDAEYwRAIgAaPd62uAnl5mGI2sgPrRH/vRicAYxwaQ -hj9qnla8Z2ACIBwm4r6O2jnYSzMfFYoxaWa7Zn+7bgbUb1uUrzEe4xl7AHcA7s3Q -ZNXbGs7FXLedtM0TojKHRny87N7DUUhZRnEftZsAAAGQbTihHQAABAMASDBGAiEA -7+ZDT9MHQdLS4XEbXqbGAkbp7GQIYbSdoZg/DALMIm8CIQD3cwXxry/8R2I9BQXA -7SM0047FardcOzEV40z+kMDJ4jANBgkqhkiG9w0BAQsFAAOCAQEAd5GYYcv4ZG6P -OiQgV8RVO0TFSBfYUwNQDyeFQFyaagPvP3LbsJC5m9X22jdf64HpIcyKr7Aw4JIY -RzhqvlIZ2t8Hf1McRavJzCl4hER1dLNlzhCL1z+Wq/xiRz5VqVOXv1okG5YgGiEW -UQCBXPFM8nTAror5sMk1e/2IQOI4/4A2MhWZBrpVwYyaKEJUcdKLq9EbDRHVfsNo -0BQCnt/thD6IzL3FYWsEF0636Trxd9chE2r3JF30ORJm8q90Xz2aWwrFGlB0i8tW -uYv+WTi3qW9o2lLaqfsSTjoOmtGxANknI/0LARuBST3xSGk5GD+r/r8J/6LcCe8x -qvx81n6dcQ== ------END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIOCjCCDPKgAwIBAgIQQagVgnQLepAJtGHnFyDA9DANBgkqhkiG9w0BAQsFADA7 +MQswCQYDVQQGEwJVUzEeMBwGA1UEChMVR29vZ2xlIFRydXN0IFNlcnZpY2VzMQww +CgYDVQQDEwNXUjIwHhcNMjQxMDIxMDgzNjU3WhcNMjUwMTEzMDgzNjU2WjAXMRUw +EwYDVQQDDAwqLmdvb2dsZS5jb20wWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAARD +7W/bU6abojd0puaRMYsiXqZjXddRl8yW2qlTpw+HOHg3bA183UxWTtZx+yeHSVQE +3k0jMGvR7C4B3FY+n92ao4IL9zCCC/MwDgYDVR0PAQH/BAQDAgeAMBMGA1UdJQQM +MAoGCCsGAQUFBwMBMAwGA1UdEwEB/wQCMAAwHQYDVR0OBBYEFIz1r4xlXzoNaxGY +xiGq0aGZuvJpMB8GA1UdIwQYMBaAFN4bHu15FdQ+NyTDIbvsNDltQrIwMFgGCCsG +AQUFBwEBBEwwSjAhBggrBgEFBQcwAYYVaHR0cDovL28ucGtpLmdvb2cvd3IyMCUG +CCsGAQUFBzAChhlodHRwOi8vaS5wa2kuZ29vZy93cjIuY3J0MIIJzQYDVR0RBIIJ +xDCCCcCCDCouZ29vZ2xlLmNvbYIWKi5hcHBlbmdpbmUuZ29vZ2xlLmNvbYIJKi5i +ZG4uZGV2ghUqLm9yaWdpbi10ZXN0LmJkbi5kZXaCEiouY2xvdWQuZ29vZ2xlLmNv +bYIYKi5jcm93ZHNvdXJjZS5nb29nbGUuY29tghgqLmRhdGFjb21wdXRlLmdvb2ds +ZS5jb22CCyouZ29vZ2xlLmNhggsqLmdvb2dsZS5jbIIOKi5nb29nbGUuY28uaW6C +DiouZ29vZ2xlLmNvLmpwgg4qLmdvb2dsZS5jby51a4IPKi5nb29nbGUuY29tLmFy +gg8qLmdvb2dsZS5jb20uYXWCDyouZ29vZ2xlLmNvbS5icoIPKi5nb29nbGUuY29t +LmNvgg8qLmdvb2dsZS5jb20ubXiCDyouZ29vZ2xlLmNvbS50coIPKi5nb29nbGUu +Y29tLnZuggsqLmdvb2dsZS5kZYILKi5nb29nbGUuZXOCCyouZ29vZ2xlLmZyggsq +Lmdvb2dsZS5odYILKi5nb29nbGUuaXSCCyouZ29vZ2xlLm5sggsqLmdvb2dsZS5w +bIILKi5nb29nbGUucHSCDyouZ29vZ2xlYXBpcy5jboIRKi5nb29nbGV2aWRlby5j +b22CDCouZ3N0YXRpYy5jboIQKi5nc3RhdGljLWNuLmNvbYIPZ29vZ2xlY25hcHBz +LmNughEqLmdvb2dsZWNuYXBwcy5jboIRZ29vZ2xlYXBwcy1jbi5jb22CEyouZ29v +Z2xlYXBwcy1jbi5jb22CDGdrZWNuYXBwcy5jboIOKi5na2VjbmFwcHMuY26CEmdv +b2dsZWRvd25sb2Fkcy5jboIUKi5nb29nbGVkb3dubG9hZHMuY26CEHJlY2FwdGNo +YS5uZXQuY26CEioucmVjYXB0Y2hhLm5ldC5jboIQcmVjYXB0Y2hhLWNuLm5ldIIS +Ki5yZWNhcHRjaGEtY24ubmV0ggt3aWRldmluZS5jboINKi53aWRldmluZS5jboIR +YW1wcHJvamVjdC5vcmcuY26CEyouYW1wcHJvamVjdC5vcmcuY26CEWFtcHByb2pl +Y3QubmV0LmNughMqLmFtcHByb2plY3QubmV0LmNughdnb29nbGUtYW5hbHl0aWNz +LWNuLmNvbYIZKi5nb29nbGUtYW5hbHl0aWNzLWNuLmNvbYIXZ29vZ2xlYWRzZXJ2 +aWNlcy1jbi5jb22CGSouZ29vZ2xlYWRzZXJ2aWNlcy1jbi5jb22CEWdvb2dsZXZh +ZHMtY24uY29tghMqLmdvb2dsZXZhZHMtY24uY29tghFnb29nbGVhcGlzLWNuLmNv +bYITKi5nb29nbGVhcGlzLWNuLmNvbYIVZ29vZ2xlb3B0aW1pemUtY24uY29tghcq +Lmdvb2dsZW9wdGltaXplLWNuLmNvbYISZG91YmxlY2xpY2stY24ubmV0ghQqLmRv +dWJsZWNsaWNrLWNuLm5ldIIYKi5mbHMuZG91YmxlY2xpY2stY24ubmV0ghYqLmcu +ZG91YmxlY2xpY2stY24ubmV0gg5kb3VibGVjbGljay5jboIQKi5kb3VibGVjbGlj +ay5jboIUKi5mbHMuZG91YmxlY2xpY2suY26CEiouZy5kb3VibGVjbGljay5jboIR +ZGFydHNlYXJjaC1jbi5uZXSCEyouZGFydHNlYXJjaC1jbi5uZXSCHWdvb2dsZXRy +YXZlbGFkc2VydmljZXMtY24uY29tgh8qLmdvb2dsZXRyYXZlbGFkc2VydmljZXMt +Y24uY29tghhnb29nbGV0YWdzZXJ2aWNlcy1jbi5jb22CGiouZ29vZ2xldGFnc2Vy +dmljZXMtY24uY29tghdnb29nbGV0YWdtYW5hZ2VyLWNuLmNvbYIZKi5nb29nbGV0 +YWdtYW5hZ2VyLWNuLmNvbYIYZ29vZ2xlc3luZGljYXRpb24tY24uY29tghoqLmdv +b2dsZXN5bmRpY2F0aW9uLWNuLmNvbYIkKi5zYWZlZnJhbWUuZ29vZ2xlc3luZGlj +YXRpb24tY24uY29tghZhcHAtbWVhc3VyZW1lbnQtY24uY29tghgqLmFwcC1tZWFz +dXJlbWVudC1jbi5jb22CC2d2dDEtY24uY29tgg0qLmd2dDEtY24uY29tggtndnQy +LWNuLmNvbYINKi5ndnQyLWNuLmNvbYILMm1kbi1jbi5uZXSCDSouMm1kbi1jbi5u +ZXSCFGdvb2dsZWZsaWdodHMtY24ubmV0ghYqLmdvb2dsZWZsaWdodHMtY24ubmV0 +ggxhZG1vYi1jbi5jb22CDiouYWRtb2ItY24uY29tghRnb29nbGVzYW5kYm94LWNu +LmNvbYIWKi5nb29nbGVzYW5kYm94LWNuLmNvbYIeKi5zYWZlbnVwLmdvb2dsZXNh +bmRib3gtY24uY29tgg0qLmdzdGF0aWMuY29tghQqLm1ldHJpYy5nc3RhdGljLmNv +bYIKKi5ndnQxLmNvbYIRKi5nY3BjZG4uZ3Z0MS5jb22CCiouZ3Z0Mi5jb22CDiou +Z2NwLmd2dDIuY29tghAqLnVybC5nb29nbGUuY29tghYqLnlvdXR1YmUtbm9jb29r +aWUuY29tggsqLnl0aW1nLmNvbYILYW5kcm9pZC5jb22CDSouYW5kcm9pZC5jb22C +EyouZmxhc2guYW5kcm9pZC5jb22CBGcuY26CBiouZy5jboIEZy5jb4IGKi5nLmNv +ggZnb28uZ2yCCnd3dy5nb28uZ2yCFGdvb2dsZS1hbmFseXRpY3MuY29tghYqLmdv +b2dsZS1hbmFseXRpY3MuY29tggpnb29nbGUuY29tghJnb29nbGVjb21tZXJjZS5j +b22CFCouZ29vZ2xlY29tbWVyY2UuY29tgghnZ3BodC5jboIKKi5nZ3BodC5jboIK +dXJjaGluLmNvbYIMKi51cmNoaW4uY29tggh5b3V0dS5iZYILeW91dHViZS5jb22C +DSoueW91dHViZS5jb22CEW11c2ljLnlvdXR1YmUuY29tghMqLm11c2ljLnlvdXR1 +YmUuY29tghR5b3V0dWJlZWR1Y2F0aW9uLmNvbYIWKi55b3V0dWJlZWR1Y2F0aW9u +LmNvbYIPeW91dHViZWtpZHMuY29tghEqLnlvdXR1YmVraWRzLmNvbYIFeXQuYmWC +ByoueXQuYmWCGmFuZHJvaWQuY2xpZW50cy5nb29nbGUuY29tghMqLmFuZHJvaWQu +Z29vZ2xlLmNughIqLmNocm9tZS5nb29nbGUuY26CFiouZGV2ZWxvcGVycy5nb29n +bGUuY24wEwYDVR0gBAwwCjAIBgZngQwBAgEwNgYDVR0fBC8wLTAroCmgJ4YlaHR0 +cDovL2MucGtpLmdvb2cvd3IyL29RNm55cjhGMG0wLmNybDCCAQQGCisGAQQB1nkC +BAIEgfUEgfIA8AB1AH1ZHhLheCp7HGFnfF79+NCHXBSgTpWeuQMv2Q6MLnm4AAAB +kq5v6a4AAAQDAEYwRAIgIqLlTU1VytekLFYe6z7B9QZvvAFEtlvZ/rQAndvx4BcC +IDhSWEOXYUPI6kr3vu50z40WJeAuKG8/FkmCKArIiuRJAHcAzxFW7tUufK/zh1vZ +aS6b6RpxZ0qwF+ysAdJbd87MOwgAAAGSrm/pugAABAMASDBGAiEA8kgns/UoNAL7 +/3WsQF/6ifdMBOunXlADyPxvWbpRPe0CIQCQphqwbXlfq7EkCyUnzvMJmMs9PDoM +v7elVg36zqA5HjANBgkqhkiG9w0BAQsFAAOCAQEAOp1JjUVTIoBGg90DPzx1y/0N +3qu5UVDGbuuPlDFzHmjjnGb20C/s6pJzAuUxMvvIClk6m12PCbS/F6ESGYf9QzIU +BmU5BEiOHVSuTQrnDvUzrspB0eSe2qEqfDDvoaSb7MPX5kO7crHdBtG+DFUzUa/C +ZYsgbFW7or+ennD8oJT6NFCvPah7iIJwx5S1NIBPEot8IwzKBP9VrjVBhp9yXXr1 +hWai/Z2gOacoJsLzbEifB+hlwldNgYwdSnVkTe6n5FZIygGxGm97Dp0v54g0Qs+U +4cfvwPIfx7D8oBz0EiCPlRm89TjTr0y8LqujdfiZdfbQQsUsNgsVNtIiiY0CSw== +-----END CERTIFICATE----- diff --git a/testing/unit/tls/reports/tls_report_ext_local.html b/testing/unit/tls/reports/tls_report_ext_local.html new file mode 100644 index 000000000..df1ebada2 --- /dev/null +++ b/testing/unit/tls/reports/tls_report_ext_local.html @@ -0,0 +1,114 @@ +

TLS Module

+ + +
Source
+ + + + + + + + + + + + + + + + + + + +
ExpiryLengthTypePort numberSigned by
2027-07-25 15:33:09888EC443Sub CA
+ +
+
+
Certificate Information
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
PropertyValue
Version3 (0x2)
Signature Alg.sha256WithRSAEncryption
Validity from2022-07-26 15:33:09
Valid to2027-07-25 15:33:09
+ +
+
+
Subject Information
+ + + + + + + + + + + + + + + + + + + + +
PropertyValue
CUS
CNapc27D605.nam.gad.schneider-electric.com
+
+
+ + +
Certificate Extensions
+ + + + + + + + + + + + + + +
PropertyValue
subjectAltNameap9643_qa1941270129.nam.gad.schneider-electric.com
+

Outbound Connections

+ + + + + + + + + + + +
Destination IPPort
+ \ No newline at end of file diff --git a/testing/unit/tls/reports/tls_report_ext_local.md b/testing/unit/tls/reports/tls_report_ext_local.md deleted file mode 100644 index 878fa0743..000000000 --- a/testing/unit/tls/reports/tls_report_ext_local.md +++ /dev/null @@ -1,33 +0,0 @@ -# TLS Module - -### Certificate -| Property | Value | -|---|---| -| Version | 3 (0x2) | -| Signature Alg. | sha256WithRSAEncryption | -| Validity from | 2022-07-26 15:33:09 | -| Valid to | 2027-07-25 15:33:09 | - -### Subject -| Distinguished Name | Value | -|---|---| -| C | US -| CN | apc27D605.nam.gad.schneider-electric.com - -### Issuer -| Distinguished Name | Value | -|---|---| -| C | US -| O | IT Division -| CN | Sub CA - -### Extensions -| Extension | Value | -|---|---| -| subjectAltName | ap9643_qa1941270129.nam.gad.schneider-electric.com - -## Summary - -| # | Expiry | Length | Type | Port No. | Signed by | -|-------|---------------------------|----------|--------|------------|-------------| -| 1 | 2027-07-25 15:33:09 | 888 | EC | 443 | Sub CA | \ No newline at end of file diff --git a/testing/unit/tls/reports/tls_report_local.html b/testing/unit/tls/reports/tls_report_local.html new file mode 100644 index 000000000..610381444 --- /dev/null +++ b/testing/unit/tls/reports/tls_report_local.html @@ -0,0 +1,410 @@ +

TLS Module

+ + + + + + + + + + + + + + + + + + + + + + +
ExpiryLengthTypePort numberSigned by
2049-12-31 23:59:59779EC35288None
+ +
+
+
Certificate Information
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
PropertyValue
Version3 (0x2)
Signature Alg.sha256WithRSAEncryption
Validity from2023-03-29 18:37:51
Valid to2049-12-31 23:59:59
+ +
+
+
Subject Information
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
PropertyValue
CUS
STPennsylvania
LCoopersburg
OLutron Electronics Co.\, Inc.
CNathena04E580B9
+
+
+ + +
Certificate Extensions
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
PropertyValue
authorityKeyIdentifierkey_identifier=accca4f9bd2a47dae81a8f4c87ed2c8edcfd07bf, authority_cert_issuer=None, authority_cert_serial_number=None
subjectKeyIdentifierdigest=37d90a274635e963081520f98411bda240d30252
basicConstraintsca=False, path_length=None
keyUsagedigital_signature=True, key_cert_sign=False, key_encipherment=False, crl_sign=False
+ + + + + + + + + + + + + + + + + + + + + + + +
ExpiryLengthTypePort numberSigned by
2119-02-05 00:00:00619EC443AthenaProcessor685E1CCB6ECB
+ +
+
+
Certificate Information
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
PropertyValue
Version3 (0x2)
Signature Alg.ecdsa-with-SHA256
Validity from2019-03-01 00:00:00
Valid to2119-02-05 00:00:00
+ +
+
+
Subject Information
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
PropertyValue
CUS
STPennsylvania
LCoopersburg
OLutron Electronics Co.\, Inc.
CNIPLServer4E580B9
+
+
+ + +
Certificate Extensions
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
PropertyValue
keyUsagedigital_signature=True, key_cert_sign=False, key_encipherment=True, crl_sign=False
extendedKeyUsageserverAuth, Unknown OID
authorityKeyIdentifierkey_identifier=dff100033b0ab36497bbcd2f3e0515ea7b2f7ea0, authority_cert_issuer=None, authority_cert_serial_number=None
subjectAltNameIPLServer4E580B9
+ + + + + + + + + + + + + + + + + + + + + + + +
ExpiryLengthTypePort numberSigned by
2049-12-31 23:59:59779EC47188None
+ +
+
+
Certificate Information
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
PropertyValue
Version3 (0x2)
Signature Alg.sha256WithRSAEncryption
Validity from2023-03-29 18:37:51
Valid to2049-12-31 23:59:59
+ +
+
+
Subject Information
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
PropertyValue
CUS
STPennsylvania
LCoopersburg
OLutron Electronics Co.\, Inc.
CNathena04E580B9
+
+
+ + +
Certificate Extensions
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
PropertyValue
authorityKeyIdentifierkey_identifier=accca4f9bd2a47dae81a8f4c87ed2c8edcfd07bf, authority_cert_issuer=None, authority_cert_serial_number=None
subjectKeyIdentifierdigest=37d90a274635e963081520f98411bda240d30252
basicConstraintsca=False, path_length=None
keyUsagedigital_signature=True, key_cert_sign=False, key_encipherment=False, crl_sign=False
+

Outbound Connections

+ + + + + + + + + + + + + + + + + +
Destination IPPort
224.0.0.2515353
209.244.0.3Unknown
3.227.250.136443
3.227.203.88443
34.226.101.2528883
3.227.250.208443
52.94.225.110443
+ \ No newline at end of file diff --git a/testing/unit/tls/reports/tls_report_local.md b/testing/unit/tls/reports/tls_report_local.md deleted file mode 100644 index dc3866dc6..000000000 --- a/testing/unit/tls/reports/tls_report_local.md +++ /dev/null @@ -1,35 +0,0 @@ -# TLS Module - -### Certificate -| Property | Value | -|---|---| -| Version | 1 (0x0) | -| Signature Alg. | sha256WithRSAEncryption | -| Validity from | 2022-09-21 19:57:57 | -| Valid to | 2027-09-21 19:57:57 | - -### Subject -| Distinguished Name | Value | -|---|---| -| C | US -| ST | California -| L | Concord -| O | BuildingsIoT -| OU | Software -| CN | EasyIO_FS-32 - -### Issuer -| Distinguished Name | Value | -|---|---| -| C | US -| ST | California -| L | Concord -| O | BuildingsIoT -| OU | Software -| CN | BuildingsIoT RSA Signing CA - -## Summary - -| # | Expiry | Length | Type | Port No. | Signed by | -|-------|---------------------------|----------|--------|------------|-------------| -| 1 | 2027-09-21 19:57:57 | 901 | RSA | 443 | BuildingsIoT RSA Signing CA | \ No newline at end of file diff --git a/testing/unit/tls/reports/tls_report_no_cert_local.html b/testing/unit/tls/reports/tls_report_no_cert_local.html new file mode 100644 index 000000000..c025ee9e8 --- /dev/null +++ b/testing/unit/tls/reports/tls_report_no_cert_local.html @@ -0,0 +1,5 @@ +

TLS Module

+
+
+ No TLS certificates found on the device +
\ No newline at end of file diff --git a/testing/unit/tls/reports/tls_report_no_cert_local.md b/testing/unit/tls/reports/tls_report_no_cert_local.md deleted file mode 100644 index 6de5bb88a..000000000 --- a/testing/unit/tls/reports/tls_report_no_cert_local.md +++ /dev/null @@ -1,9 +0,0 @@ -# TLS Module - -- No device certificates detected - - -## Summary - -| # | Expiry | Length | Type | Port No. | Signed by | -|-------|---------------------------|----------|--------|------------|-------------| \ No newline at end of file diff --git a/testing/unit/tls/reports/tls_report_single.html b/testing/unit/tls/reports/tls_report_single.html new file mode 100644 index 000000000..6106068a6 --- /dev/null +++ b/testing/unit/tls/reports/tls_report_single.html @@ -0,0 +1,118 @@ +

TLS Module

+ + + + + + + + + + + + + + + + + + + + + + +
ExpiryLengthTypePort numberSigned by
2027-09-21 19:57:57901RSA443BuildingsIoT RSA Signing CA
+ +
+
+
Certificate Information
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
PropertyValue
Version1 (0x0)
Signature Alg.sha256WithRSAEncryption
Validity from2022-09-21 19:57:57
Valid to2027-09-21 19:57:57
+ +
+
+
Subject Information
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
PropertyValue
CUS
STCalifornia
LConcord
OBuildingsIoT
OUSoftware
CNEasyIO_FS-32
+
+
+ + +

Outbound Connections

+ + + + + + + + + + + +
Destination IPPort
+ \ No newline at end of file diff --git a/testing/unit/tls/tls_module_test.py b/testing/unit/tls/tls_module_test.py index fc37ade40..f42c7e9d4 100644 --- a/testing/unit/tls/tls_module_test.py +++ b/testing/unit/tls/tls_module_test.py @@ -12,6 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. """Module run all the TLS related unit tests""" +from tls_module import TLSModule from tls_util import TLSUtil import os import unittest @@ -38,9 +39,11 @@ CERT_DIR = os.path.join(TEST_FILES_DIR, 'certs/') ROOT_CERTS_DIR = os.path.join(TEST_FILES_DIR, 'root_certs') -LOCAL_REPORT = os.path.join(REPORTS_DIR, 'tls_report_local.md') -LOCAL_REPORT_EXT = os.path.join(REPORTS_DIR, 'tls_report_ext_local.md') -LOCAL_REPORT_NO_CERT = os.path.join(REPORTS_DIR, 'tls_report_no_cert_local.md') +LOCAL_REPORT = os.path.join(REPORTS_DIR, 'tls_report_local.html') +LOCAL_REPORT_SINGLE = os.path.join(REPORTS_DIR, 'tls_report_single.html') +LOCAL_REPORT_EXT = os.path.join(REPORTS_DIR, 'tls_report_ext_local.html') +LOCAL_REPORT_NO_CERT = os.path.join(REPORTS_DIR, + 'tls_report_no_cert_local.html') CONF_FILE = 'modules/test/' + MODULE + '/conf/module_config.json' INTERNET_IFACE = 'eth0' @@ -197,6 +200,7 @@ def security_tls_v1_2_fail_server_test(self): self.assertFalse(test_results[0]) # Test 1.2 server when 1.3 and 1.2 failed connection is established + def security_tls_v1_2_none_server_test(self): tls_1_2_results = None, 'No cert' tls_1_3_results = None, 'No cert' @@ -225,7 +229,7 @@ def security_tls_client_skip_test(self): capture_file = os.path.join(CAPTURES_DIR, 'no_tls.pcap') # Run the client test - test_results = TLS_UTIL.validate_tls_client(client_ip='172.27.253.167', + test_results = TLS_UTIL.validate_tls_client(client_mac='00:15:5d:0c:86:b9', tls_version='1.2', capture_files=[capture_file]) print(str(test_results)) @@ -269,8 +273,8 @@ def test_client_tls(self, os.makedirs(OUTPUT_DIR, exist_ok=True) capture_file = OUTPUT_DIR + '/client_tls.pcap' - # Resolve the client ip used - client_ip = self.get_interface_ip(INTERNET_IFACE) + # Resolve the client mac used + client_mac = self.get_interface_mac(INTERNET_IFACE) # Genrate TLS outbound traffic if tls_generate is None: @@ -278,7 +282,7 @@ def test_client_tls(self, self.generate_tls_traffic(capture_file, tls_generate, disable_valid_ciphers) # Run the client test - return TLS_UTIL.validate_tls_client(client_ip=client_ip, + return TLS_UTIL.validate_tls_client(client_mac=client_mac, tls_version=tls_version, capture_files=[capture_file]) @@ -287,7 +291,7 @@ def test_client_tls_with_non_tls_client(self): capture_file = os.path.join(CAPTURES_DIR, 'monitor.pcap') # Run the client test - test_results = TLS_UTIL.validate_tls_client(client_ip='10.10.10.14', + test_results = TLS_UTIL.validate_tls_client(client_mac='70:b3:d5:96:c0:00', tls_version='1.2', capture_files=[capture_file]) print(str(test_results)) @@ -300,7 +304,7 @@ def security_tls_client_unsupported_tls_client(self): capture_file = os.path.join(CAPTURES_DIR, 'unsupported_tls.pcap') # Run the client test - test_results = TLS_UTIL.validate_tls_client(client_ip='172.27.253.167', + test_results = TLS_UTIL.validate_tls_client(client_mac='00:15:5d:0c:86:b9', tls_version='1.2', capture_files=[capture_file]) print(str(test_results)) @@ -313,83 +317,135 @@ def security_tls_client_allowed_protocols_test(self): capture_file = os.path.join(CAPTURES_DIR, 'monitor_with_quic.pcap') # Run the client test - test_results = TLS_UTIL.validate_tls_client(client_ip='10.10.10.15', + test_results = TLS_UTIL.validate_tls_client(client_mac='e4:5f:01:5f:92:9c', tls_version='1.2', capture_files=[capture_file]) print(str(test_results)) self.assertTrue(test_results[0]) - # Commented out whilst TLS report is recreated - # def tls_module_report_test(self): - # print('\ntls_module_report_test') - # os.environ['DEVICE_MAC'] = '38:d1:35:01:17:fe' - # pcap_file = os.path.join(CAPTURES_DIR, 'tls.pcap') - # tls = TLSModule(module=MODULE, - # log_dir=OUTPUT_DIR, - # conf_file=CONF_FILE, - # results_dir=OUTPUT_DIR, - # startup_capture_file=pcap_file, - # monitor_capture_file=pcap_file, - # tls_capture_file=pcap_file) - # report_out_path = tls.generate_module_report() - - # with open(report_out_path, 'r', encoding='utf-8') as file: - # report_out = file.read() - - # # Read the local good report - # with open(LOCAL_REPORT, 'r', encoding='utf-8') as file: - # report_local = file.read() - - # self.assertEqual(report_out, report_local) - - # Commented out whilst TLS report is recreated - # def tls_module_report_ext_test(self): - # print('\ntls_module_report_ext_test') - # os.environ['DEVICE_MAC'] = '28:29:86:27:d6:05' - # pcap_file = os.path.join(CAPTURES_DIR, 'tls_ext.pcap') - # tls = TLSModule(module=MODULE, - # log_dir=OUTPUT_DIR, - # conf_file=CONF_FILE, - # results_dir=OUTPUT_DIR, - # startup_capture_file=pcap_file, - # monitor_capture_file=pcap_file, - # tls_capture_file=pcap_file) - # report_out_path = tls.generate_module_report() - - # # Read the generated report - # with open(report_out_path, 'r', encoding='utf-8') as file: - # report_out = file.read() - - # # Read the local good report - # with open(LOCAL_REPORT_EXT, 'r', encoding='utf-8') as file: - # report_local = file.read() - - # self.assertEqual(report_out, report_local) - - # Commented out whilst TLS report is recreated - # def tls_module_report_no_cert_test(self): - # print('\ntls_module_report_no_cert_test') - # os.environ['DEVICE_MAC'] = '' - # pcap_file = os.path.join(CAPTURES_DIR, 'tls_ext.pcap') - # tls = TLSModule(module=MODULE, - # log_dir=OUTPUT_DIR, - # conf_file=CONF_FILE, - # results_dir=OUTPUT_DIR, - # startup_capture_file=pcap_file, - # monitor_capture_file=pcap_file, - # tls_capture_file=pcap_file) - - # report_out_path = tls.generate_module_report() - - # # Read the generated report - # with open(report_out_path, 'r', encoding='utf-8') as file: - # report_out = file.read() - - # # Read the local good report - # with open(LOCAL_REPORT_NO_CERT, 'r', encoding='utf-8') as file: - # report_local = file.read() - - # self.assertEqual(report_out, report_local) + def outbound_connections_test(self): + """ Test generation of the outbound connection ips""" + print('\noutbound_connections_test') + capture_file = os.path.join(CAPTURES_DIR, 'monitor.pcap') + ip_dst = TLS_UTIL.get_all_outbound_connections( + device_mac='70:b3:d5:96:c0:00', capture_files=[capture_file]) + print(str(ip_dst)) + # Expected set of IPs and ports in tuple format + expected_ips = { + ('216.239.35.0', 123), + ('8.8.8.8', 'Unknown'), + ('8.8.8.8', 53), + ('18.140.82.197', 443), + ('18.140.82.197', 22), + ('224.0.0.22', 'Unknown'), + ('18.140.82.197', 80) + } + # Compare as sets since returned order is not guaranteed + self.assertEqual( + set(ip_dst), + expected_ips) + + def outbound_connections_report_test(self): + """ Test generation of the outbound connection ips""" + print('\noutbound_connections_report_test') + capture_file = os.path.join(CAPTURES_DIR, 'monitor.pcap') + ip_dst = TLS_UTIL.get_all_outbound_connections( + device_mac='70:b3:d5:96:c0:00', capture_files=[capture_file]) + tls = TLSModule(module=MODULE) + gen_html = tls.generate_outbound_connection_table(ip_dst) + print(gen_html) + + def tls_module_report_multi_page_test(self): + print('\ntls_module_report_test') + os.environ['DEVICE_MAC'] = '68:5e:1c:cb:6e:cb' + startup_pcap_file = os.path.join(CAPTURES_DIR, 'multi_page_startup.pcap') + monitor_pcap_file = os.path.join(CAPTURES_DIR, 'multi_page_monitor.pcap') + tls_pcap_file = os.path.join(CAPTURES_DIR, 'multi_page_tls.pcap') + tls = TLSModule(module=MODULE, + results_dir=OUTPUT_DIR, + startup_capture_file=startup_pcap_file, + monitor_capture_file=monitor_pcap_file, + tls_capture_file=tls_pcap_file) + report_out_path = tls.generate_module_report() + with open(report_out_path, 'r', encoding='utf-8') as file: + report_out = file.read() + + # Read the local good report + with open(LOCAL_REPORT, 'r', encoding='utf-8') as file: + report_local = file.read() + + self.assertEqual(report_out, report_local) + + def tls_module_report_test(self): + print('\ntls_module_report_test') + os.environ['DEVICE_MAC'] = '38:d1:35:01:17:fe' + pcap_file = os.path.join(CAPTURES_DIR, 'tls.pcap') + tls = TLSModule(module=MODULE, + results_dir=OUTPUT_DIR, + startup_capture_file=pcap_file, + monitor_capture_file=pcap_file, + tls_capture_file=pcap_file) + report_out_path = tls.generate_module_report() + with open(report_out_path, 'r', encoding='utf-8') as file: + report_out = file.read() + + # Read the local good report + with open(LOCAL_REPORT_SINGLE, 'r', encoding='utf-8') as file: + report_local = file.read() + self.assertEqual(report_out, report_local) + + def tls_module_report_ext_test(self): + print('\ntls_module_report_ext_test') + os.environ['DEVICE_MAC'] = '28:29:86:27:d6:05' + pcap_file = os.path.join(CAPTURES_DIR, 'tls_ext.pcap') + tls = TLSModule(module=MODULE, + results_dir=OUTPUT_DIR, + startup_capture_file=pcap_file, + monitor_capture_file=pcap_file, + tls_capture_file=pcap_file) + report_out_path = tls.generate_module_report() + + # Read the generated report + with open(report_out_path, 'r', encoding='utf-8') as file: + report_out = file.read() + + # Read the local good report + with open(LOCAL_REPORT_EXT, 'r', encoding='utf-8') as file: + report_local = file.read() + + # Copy the generated html report to a new file + new_report_name = 'tls_report_ext_local.html' + new_report_path = os.path.join(OUTPUT_DIR, new_report_name) + shutil.copy(report_out_path, new_report_path) + + self.assertEqual(report_out, report_local) + + def tls_module_report_no_cert_test(self): + print('\ntls_module_report_no_cert_test') + os.environ['DEVICE_MAC'] = '' + pcap_file = os.path.join(CAPTURES_DIR, 'tls_ext.pcap') + tls = TLSModule(module=MODULE, + results_dir=OUTPUT_DIR, + startup_capture_file=pcap_file, + monitor_capture_file=pcap_file, + tls_capture_file=pcap_file) + + report_out_path = tls.generate_module_report() + + # Read the generated report + with open(report_out_path, 'r', encoding='utf-8') as file: + report_out = file.read() + + # Read the local good report + with open(LOCAL_REPORT_NO_CERT, 'r', encoding='utf-8') as file: + report_local = file.read() + + # Copy the generated html report to a new file + new_report_name = 'tls_report_no_cert_local.html' + new_report_path = os.path.join(OUTPUT_DIR, new_report_name) + shutil.copy(report_out_path, new_report_path) + + self.assertEqual(report_out, report_local) def generate_tls_traffic(self, capture_file, @@ -470,11 +526,11 @@ def start_capture_thread(self, timeout): return capture_thread - def get_interface_ip(self, interface_name): + def get_interface_mac(self, interface_name): try: addresses = netifaces.ifaddresses(interface_name) - ipv4 = addresses[netifaces.AF_INET][0]['addr'] - return ipv4 + mac = addresses[netifaces.AF_LINK][0]['addr'] + return mac except (ValueError, KeyError) as e: print(f'Error: {e}') return None @@ -540,6 +596,7 @@ def download_public_cert(self, hostname, port=443): if __name__ == '__main__': suite = unittest.TestSuite() suite.addTest(TLSModuleTest('client_hello_packets_test')) + # TLS 1.2 server tests suite.addTest(TLSModuleTest('security_tls_v1_2_server_test')) suite.addTest(TLSModuleTest('security_tls_v1_2_for_1_3_server_test')) @@ -553,6 +610,7 @@ def download_public_cert(self, hostname, port=443): # # TLS 1.3 server tests suite.addTest(TLSModuleTest('security_tls_v1_3_server_test')) + # TLS client tests suite.addTest(TLSModuleTest('security_tls_v1_2_client_test')) suite.addTest(TLSModuleTest('security_tls_v1_3_client_test')) @@ -564,10 +622,11 @@ def download_public_cert(self, hostname, port=443): # Test the results options for tls server tests suite.addTest(TLSModuleTest('security_tls_server_results_test')) - # # Test various report module outputs - # suite.addTest(TLSModuleTest('tls_module_report_test')) - # suite.addTest(TLSModuleTest('tls_module_report_ext_test')) - # suite.addTest(TLSModuleTest('tls_module_report_no_cert_test')) + # Test various report module outputs + suite.addTest(TLSModuleTest('tls_module_report_test')) + suite.addTest(TLSModuleTest('tls_module_report_ext_test')) + suite.addTest(TLSModuleTest('tls_module_report_no_cert_test')) + suite.addTest(TLSModuleTest('tls_module_report_multi_page_test')) # Test signature validation methods suite.addTest(TLSModuleTest('tls_module_trusted_ca_cert_chain_test')) @@ -576,6 +635,9 @@ def download_public_cert(self, hostname, port=443): suite.addTest(TLSModuleTest('security_tls_client_allowed_protocols_test')) + suite.addTest(TLSModuleTest('outbound_connections_test')) + suite.addTest(TLSModuleTest('outbound_connections_report_test')) + runner = unittest.TextTestRunner() test_result = runner.run(suite) From faa6cd381a26ab4d0e9397a8df0b8a2017e5950f Mon Sep 17 00:00:00 2001 From: J Boddey Date: Thu, 20 Feb 2025 20:37:58 +0000 Subject: [PATCH 5/9] Merge release v2.1.1 into main (#1114) --- .github/workflows/package.yml | 6 +- .github/workflows/testing.yml | 4 - docs/dev/mockoon.json | 111 +- docs/dev/postman.json | 898 +++- docs/get_started.md | 3 +- framework/python/src/api/api.py | 117 +- framework/python/src/common/risk_profile.py | 48 +- framework/python/src/common/statuses.py | 12 +- framework/python/src/common/testreport.py | 243 +- framework/python/src/core/session.py | 45 +- framework/python/src/core/testrun.py | 27 +- .../src/net_orc/network_orchestrator.py | 3 +- .../python/src/test_orc/test_orchestrator.py | 177 +- framework/python/src/test_orc/test_pack.py | 75 + framework/requirements.txt | 4 +- make/DEBIAN/control | 2 +- modules/test/base/base.Dockerfile | 8 + modules/test/base/python/requirements.txt | 5 +- modules/test/base/python/src/test_module.py | 8 +- modules/test/conn/python/requirements.txt | 2 +- modules/test/dns/dns.Dockerfile | 6 +- modules/test/dns/python/src/dns_module.py | 134 +- .../test/dns/resources/report_template.jinja2 | 31 + modules/test/ntp/ntp.Dockerfile | 5 +- modules/test/ntp/python/src/ntp_module.py | 141 +- .../test/ntp/resources/report_template.jinja2 | 31 + .../services/python/src/services_module.py | 111 +- .../services/resources/report_template.jinja2 | 29 + modules/test/services/services.Dockerfile | 5 +- modules/test/tls/conf/module_config.json | 2 +- modules/test/tls/python/requirements.txt | 4 +- modules/test/tls/python/src/http_scan.py | 76 + modules/test/tls/python/src/tls_module.py | 351 +- modules/test/tls/python/src/tls_util.py | 47 +- .../test/tls/resources/report_template.jinja2 | 90 + modules/test/tls/tls.Dockerfile | 3 + modules/ui/angular.json | 4 +- modules/ui/package-lock.json | 3610 +++++++++-------- modules/ui/src/app/app.store.spec.ts | 4 +- modules/ui/src/app/app.store.ts | 4 +- .../download-report.component.ts | 8 +- .../download-zip-modal.component.html | 13 +- .../download-zip-modal.component.ts | 14 +- modules/ui/src/app/mocks/reports.mock.ts | 31 +- modules/ui/src/app/mocks/testrun.mock.ts | 38 +- modules/ui/src/app/model/testrun-status.ts | 24 +- .../device-qualification-from.component.scss | 1 - .../filter-dialog/filter-dialog.component.ts | 11 +- .../app/pages/reports/reports.component.html | 4 +- .../ui/src/app/pages/reports/reports.store.ts | 15 +- .../profile-form.component.spec.ts | 3 + .../profile-form/profile-form.component.ts | 2 +- .../profile-form/profile.validators.ts | 10 + .../download-options.component.ts | 6 +- .../testrun-initiate-form.component.ts | 13 +- .../testrun-status-card.component.html | 13 +- .../testrun-status-card.component.scss | 4 + .../testrun-status-card.component.spec.ts | 17 +- .../testrun-status-card.component.ts | 32 +- .../testrun-table.component.html | 9 + .../testrun-table.component.scss | 87 +- .../testrun-table.component.spec.ts | 20 +- .../testrun-table/testrun-table.component.ts | 8 + .../app/pages/testrun/testrun.store.spec.ts | 16 + .../ui/src/app/pages/testrun/testrun.store.ts | 3 + .../src/app/services/test-run.service.spec.ts | 8 +- .../ui/src/app/services/test-run.service.ts | 3 + modules/ui/src/app/store/effects.ts | 5 +- modules/ui/src/index.html | 4 +- resources/report/module_report_base.jinja2 | 54 + resources/report/test_report_styles.css | 114 +- .../{pilot.json => pilot/config.json} | 80 +- .../report_templates/device_profile.jinja | 26 + .../pilot/report_templates/header.jinja | 33 + .../pilot/report_templates/icon.png | Bin 0 -> 536 bytes .../report_templates/report_template.html | 98 + .../report_templates/resolve_steps.jinja | 19 + .../pilot/report_templates/results.jinja | 67 + .../pilot/report_templates/summary.jinja | 54 + resources/test_packs/pilot/test_pack.py | 60 + .../config.json} | 6 +- .../report_templates/device_profile.jinja | 26 + .../report_templates/header.jinja | 33 + .../qualification/report_templates/icon.png | Bin 0 -> 428 bytes .../report_templates/report_template.html | 90 + .../report_templates/resolve_steps.jinja | 19 + .../report_templates/results.jinja | 67 + .../report_templates/summary.jinja | 52 + .../test_packs/qualification/test_pack.py | 56 + testing/api/profiles/invalid_name.json | 39 + testing/api/reports/report.json | 123 +- testing/api/reports/report.pdf | Bin 29659 -> 59498 bytes testing/api/test_api.py | 203 +- .../unit/dns/reports/dns_report_local.html | 202 +- .../dns/reports/dns_report_local_no_dns.html | 72 +- testing/unit/ntp/ntp_module_test.py | 5 +- .../unit/ntp/reports/ntp_report_local.html | 254 +- .../ntp/reports/ntp_report_local_no_ntp.html | 73 +- .../pilot_do_not_proceed_noncompliant.json | 346 ++ .../unit/report/pilot_proceed_compliant.json | 297 ++ .../report/pilot_proceed_noncompliant.json | 346 ++ ...iant.json => qualification_compliant.json} | 5 +- ...t.json => qualification_noncompliant.json} | 3 +- testing/unit/report/report_test.py | 63 +- testing/unit/run_test_module.sh | 2 +- .../services_report_all_closed_local.html | 68 +- .../reports/services_report_local.html | 121 +- .../tls/reports/tls_report_ext_local.html | 182 +- .../unit/tls/reports/tls_report_local.html | 735 ++-- .../tls/reports/tls_report_no_cert_local.html | 36 +- .../unit/tls/reports/tls_report_single.html | 201 +- testing/unit/tls/tls_module_test.py | 65 +- 112 files changed, 7741 insertions(+), 3562 deletions(-) create mode 100644 modules/test/dns/resources/report_template.jinja2 create mode 100644 modules/test/ntp/resources/report_template.jinja2 create mode 100644 modules/test/services/resources/report_template.jinja2 create mode 100644 modules/test/tls/python/src/http_scan.py create mode 100644 modules/test/tls/resources/report_template.jinja2 create mode 100644 resources/report/module_report_base.jinja2 rename resources/test_packs/{pilot.json => pilot/config.json} (58%) create mode 100644 resources/test_packs/pilot/report_templates/device_profile.jinja create mode 100644 resources/test_packs/pilot/report_templates/header.jinja create mode 100644 resources/test_packs/pilot/report_templates/icon.png create mode 100644 resources/test_packs/pilot/report_templates/report_template.html create mode 100644 resources/test_packs/pilot/report_templates/resolve_steps.jinja create mode 100644 resources/test_packs/pilot/report_templates/results.jinja create mode 100644 resources/test_packs/pilot/report_templates/summary.jinja create mode 100644 resources/test_packs/pilot/test_pack.py rename resources/test_packs/{qualification.json => qualification/config.json} (97%) create mode 100644 resources/test_packs/qualification/report_templates/device_profile.jinja create mode 100644 resources/test_packs/qualification/report_templates/header.jinja create mode 100644 resources/test_packs/qualification/report_templates/icon.png create mode 100644 resources/test_packs/qualification/report_templates/report_template.html create mode 100644 resources/test_packs/qualification/report_templates/resolve_steps.jinja create mode 100644 resources/test_packs/qualification/report_templates/results.jinja create mode 100644 resources/test_packs/qualification/report_templates/summary.jinja create mode 100644 resources/test_packs/qualification/test_pack.py create mode 100644 testing/api/profiles/invalid_name.json create mode 100644 testing/unit/report/pilot_do_not_proceed_noncompliant.json create mode 100644 testing/unit/report/pilot_proceed_compliant.json create mode 100644 testing/unit/report/pilot_proceed_noncompliant.json rename testing/unit/report/{report_compliant.json => qualification_compliant.json} (97%) rename testing/unit/report/{report_noncompliant.json => qualification_noncompliant.json} (97%) diff --git a/.github/workflows/package.yml b/.github/workflows/package.yml index eae056eca..1a2e219bb 100644 --- a/.github/workflows/package.yml +++ b/.github/workflows/package.yml @@ -40,7 +40,7 @@ jobs: - name: Checkout source uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - name: Download package - uses: actions/download-artifact@v4 + uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8 with: name: testrun_package - name: Install dependencies @@ -74,7 +74,7 @@ jobs: - name: Checkout source uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - name: Download package - uses: actions/download-artifact@v4 + uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8 with: name: testrun_package - name: Install dependencies @@ -108,7 +108,7 @@ jobs: - name: Checkout source uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - name: Download package - uses: actions/download-artifact@v4 + uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8 with: name: testrun_package - name: Install dependencies diff --git a/.github/workflows/testing.yml b/.github/workflows/testing.yml index 9ba417f9f..4e78f18b5 100644 --- a/.github/workflows/testing.yml +++ b/.github/workflows/testing.yml @@ -69,10 +69,6 @@ jobs: - name: Install Testrun shell: bash {0} run: cmd/install -l - - name: Build Testrun - shell: bash {0} - run: cmd/build - timeout-minutes: 10 - name: Run tests for conn module shell: bash {0} run: bash testing/unit/run_test_module.sh conn captures ethtool output diff --git a/docs/dev/mockoon.json b/docs/dev/mockoon.json index a73eb5beb..394800402 100644 --- a/docs/dev/mockoon.json +++ b/docs/dev/mockoon.json @@ -605,7 +605,7 @@ "responses": [ { "uuid": "9536ff4c-f97f-4880-b9fc-f477686ad6b8", - "body": "[\n {\n \"mac_addr\": \"00:1e:42:35:73:c6\",\n \"device\": {\n \"mac_addr\": \"00:1e:42:35:73:c4\",\n \"manufacturer\": \"Teltonika\",\n \"model\": \"TRB140\",\n \"firmware\": \"1.2.3\",\n \"test_modules\": {\n \"connection\": {\n \"enabled\": false\n },\n \"ntp\": {\n \"enabled\": true\n },\n \"dns\": {\n \"enabled\": true\n },\n \"services\": {\n \"enabled\": true\n },\n \"tls\": {\n \"enabled\": true\n },\n \"protocol\": {\n \"enabled\": true\n }\n }\n },\n \"status\": \"Non-Compliant\",\n \"started\": \"2024-05-03 12:09:59\",\n \"finished\": \"2024-05-03 12:15:51\",\n \"tests\": {\n \"total\": 20,\n \"results\": [\n {\n \"name\": \"protocol.valid_bacnet\",\n \"description\": \"BACnet discovery could not resolve any devices\",\n \"expected_behavior\": \"BACnet traffic can be seen on the network and packets are valid and not malformed\",\n \"required_result\": \"Recommended\",\n \"result\": \"Skipped\"\n },\n {\n \"name\": \"protocol.bacnet.version\",\n \"description\": \"No BACnet devices discovered.\",\n \"expected_behavior\": \"The BACnet client implements an up to date version of BACnet\",\n \"required_result\": \"Recommended\",\n \"result\": \"Skipped\"\n },\n {\n \"name\": \"protocol.valid_modbus\",\n \"description\": \"Failed to establish Modbus connection to device\",\n \"expected_behavior\": \"Any Modbus functionality works as expected and valid Modbus traffic can be observed\",\n \"required_result\": \"Recommended\",\n \"result\": \"Non-Compliant\"\n },\n {\n \"name\": \"ntp.network.ntp_support\",\n \"description\": \"Device sent NTPv3 packets. NTPv3 is not allowed.\",\n \"expected_behavior\": \"The device sends an NTPv4 request to the configured NTP server.\",\n \"required_result\": \"Required\",\n \"result\": \"Non-Compliant\"\n },\n {\n \"name\": \"ntp.network.ntp_dhcp\",\n \"description\": \"Device sent NTP request to non-DHCP provided server\",\n \"expected_behavior\": \"Device can accept NTP server address, provided by the DHCP server (DHCP OFFER PACKET)\",\n \"required_result\": \"Roadmap\",\n \"result\": \"Non-Compliant\"\n },\n {\n \"name\": \"security.services.ftp\",\n \"description\": \"No FTP server found\",\n \"expected_behavior\": \"There is no FTP service running on any port\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"security.ssh.version\",\n \"description\": \"SSH server found running protocol 2.0\",\n \"expected_behavior\": \"SSH server is not running or server is SSHv2\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"security.services.telnet\",\n \"description\": \"No telnet server found\",\n \"expected_behavior\": \"There is no Telnet service running on any port\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"security.services.smtp\",\n \"description\": \"No SMTP server found\",\n \"expected_behavior\": \"There is no SMTP service running on any port\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"security.services.http\",\n \"description\": \"Found HTTP server running on port 80/tcp\",\n \"expected_behavior\": \"Device is unreachable on port 80 (or any other port) and only responds to HTTPS requests on port 443 (or any other port if HTTP is used at all)\",\n \"required_result\": \"Required\",\n \"result\": \"Non-Compliant\"\n },\n {\n \"name\": \"security.services.pop\",\n \"description\": \"No POP server found\",\n \"expected_behavior\": \"There is no POP service running on any port\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"security.services.imap\",\n \"description\": \"No IMAP server found\",\n \"expected_behavior\": \"There is no IMAP service running on any port\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"security.services.snmpv3\",\n \"description\": \"No SNMP server found\",\n \"expected_behavior\": \"Device is unreachable on port 161 (or any other port) and device is unreachable on port 162 (or any other port) unless SNMP is essential in which case it is SNMPv3 is used.\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"security.services.vnc\",\n \"description\": \"No VNC server found\",\n \"expected_behavior\": \"Device cannot be accessed / connected to via VNC on any port\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"security.services.tftp\",\n \"description\": \"No TFTP server found\",\n \"expected_behavior\": \"There is no TFTP service running on any port\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"ntp.network.ntp_server\",\n \"description\": \"No NTP server found\",\n \"expected_behavior\": \"The device does not respond to NTP requests when it's IP is set as the NTP server on another device\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"dns.network.hostname_resolution\",\n \"description\": \"DNS traffic detected from device\",\n \"expected_behavior\": \"The device sends DNS requests.\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"dns.network.from_dhcp\",\n \"description\": \"DNS traffic detected only to DHCP provided server\",\n \"expected_behavior\": \"The device sends DNS requests to the DNS server provided by the DHCP server\",\n \"required_result\": \"Roadmap\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"security.tls.v1_2_server\",\n \"description\": \"TLS 1.2 not validated: Certificate has expired\\nEC key length passed: 256 >= 224\\nDevice certificate has not been signed\\nTLS 1.3 not validated: Certificate has expired\\nEC key length passed: 256 >= 224\\nDevice certificate has not been signed\",\n \"expected_behavior\": \"TLS 1.2 certificate is issued to the web browser client when accessed\",\n \"required_result\": \"Required\",\n \"result\": \"Non-Compliant\"\n },\n {\n \"name\": \"security.tls.v1_2_client\",\n \"description\": \"No outbound connections were found.\",\n \"expected_behavior\": \"The packet indicates a TLS connection with at least TLS 1.2 and support for ECDH and ECDSA ciphers\",\n \"required_result\": \"Required\",\n \"result\": \"Skipped\"\n }\n ]\n },\n \"report\": \"http://localhost:8000/report/123 123/2024-05-03T12:09:59\"\n }\n]", + "body": "[\n {\n \"mac_addr\": \"00:1e:42:35:73:c6\",\n \"device\": {\n \"mac_addr\": \"00:1e:42:35:73:c4\",\n \"manufacturer\": \"Teltonika\",\n \"model\": \"TRB140\",\n \"firmware\": \"1.2.3\",\n \"test_modules\": {\n \"connection\": {\n \"enabled\": false\n },\n \"ntp\": {\n \"enabled\": true\n },\n \"dns\": {\n \"enabled\": true\n },\n \"services\": {\n \"enabled\": true\n },\n \"tls\": {\n \"enabled\": true\n },\n \"protocol\": {\n \"enabled\": true\n }\n }\n },\n \"status\": \"Non-Compliant\",\n \"started\": \"2024-05-03 12:09:59\",\n \"finished\": \"2024-05-03 12:15:51\",\n \"tests\": {\n \"total\": 20,\n \"results\": [\n {\n \"name\": \"protocol.valid_bacnet\",\n \"description\": \"BACnet discovery could not resolve any devices\",\n \"expected_behavior\": \"BACnet traffic can be seen on the network and packets are valid and not malformed\",\n \"required_result\": \"Recommended\",\n \"result\": \"Skipped\"\n },\n {\n \"name\": \"protocol.bacnet.version\",\n \"description\": \"No BACnet devices discovered.\",\n \"expected_behavior\": \"The BACnet client implements an up to date version of BACnet\",\n \"required_result\": \"Recommended\",\n \"result\": \"Skipped\"\n },\n {\n \"name\": \"protocol.valid_modbus\",\n \"description\": \"Failed to establish Modbus connection to device\",\n \"expected_behavior\": \"Any Modbus functionality works as expected and valid Modbus traffic can be observed\",\n \"required_result\": \"Recommended\",\n \"result\": \"Non-Compliant\"\n },\n {\n \"name\": \"ntp.network.ntp_support\",\n \"description\": \"Device sent NTPv3 packets. NTPv3 is not allowed.\",\n \"expected_behavior\": \"The device sends an NTPv4 request to the configured NTP server.\",\n \"required_result\": \"Required\",\n \"result\": \"Non-Compliant\"\n },\n {\n \"name\": \"ntp.network.ntp_dhcp\",\n \"description\": \"Device sent NTP request to non-DHCP provided server\",\n \"expected_behavior\": \"Device can accept NTP server address, provided by the DHCP server (DHCP OFFER PACKET)\",\n \"required_result\": \"Roadmap\",\n \"result\": \"Non-Compliant\"\n },\n {\n \"name\": \"security.services.ftp\",\n \"description\": \"No FTP server found\",\n \"expected_behavior\": \"There is no FTP service running on any port\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"security.ssh.version\",\n \"description\": \"SSH server found running protocol 2.0\",\n \"expected_behavior\": \"SSH server is not running or server is SSHv2\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"security.services.telnet\",\n \"description\": \"No telnet server found\",\n \"expected_behavior\": \"There is no Telnet service running on any port\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"security.services.smtp\",\n \"description\": \"No SMTP server found\",\n \"expected_behavior\": \"There is no SMTP service running on any port\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"security.services.http\",\n \"description\": \"Found HTTP server running on port 80/tcp\",\n \"expected_behavior\": \"Device is unreachable on port 80 (or any other port) and only responds to HTTPS requests on port 443 (or any other port if HTTP is used at all)\",\n \"required_result\": \"Required\",\n \"result\": \"Non-Compliant\"\n },\n {\n \"name\": \"security.services.pop\",\n \"description\": \"No POP server found\",\n \"expected_behavior\": \"There is no POP service running on any port\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"security.services.imap\",\n \"description\": \"No IMAP server found\",\n \"expected_behavior\": \"There is no IMAP service running on any port\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"security.services.snmpv3\",\n \"description\": \"No SNMP server found\",\n \"expected_behavior\": \"Device is unreachable on port 161 (or any other port) and device is unreachable on port 162 (or any other port) unless SNMP is essential in which case it is SNMPv3 is used.\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"security.services.vnc\",\n \"description\": \"No VNC server found\",\n \"expected_behavior\": \"Device cannot be accessed / connected to via VNC on any port\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"security.services.tftp\",\n \"description\": \"No TFTP server found\",\n \"expected_behavior\": \"There is no TFTP service running on any port\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"ntp.network.ntp_server\",\n \"description\": \"No NTP server found\",\n \"expected_behavior\": \"The device does not respond to NTP requests when it's IP is set as the NTP server on another device\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"dns.network.hostname_resolution\",\n \"description\": \"DNS traffic detected from device\",\n \"expected_behavior\": \"The device sends DNS requests.\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"dns.network.from_dhcp\",\n \"description\": \"DNS traffic detected only to DHCP provided server\",\n \"expected_behavior\": \"The device sends DNS requests to the DNS server provided by the DHCP server\",\n \"required_result\": \"Roadmap\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"security.tls.v1_2_server\",\n \"description\": \"TLS 1.2 not validated: Certificate has expired\\nEC key length passed: 256 >= 224\\nDevice certificate has not been signed\\nTLS 1.3 not validated: Certificate has expired\\nEC key length passed: 256 >= 224\\nDevice certificate has not been signed\",\n \"expected_behavior\": \"TLS 1.2 certificate is issued to the web browser client when accessed\",\n \"required_result\": \"Required\",\n \"result\": \"Non-Compliant\"\n },\n {\n \"name\": \"security.tls.v1_2_client\",\n \"description\": \"No outbound TLS connections were found.\",\n \"expected_behavior\": \"The packet indicates a TLS connection with at least TLS 1.2 and support for ECDH and ECDSA ciphers\",\n \"required_result\": \"Required\",\n \"result\": \"Skipped\"\n }\n ]\n },\n \"report\": \"http://localhost:8000/report/123 123/2024-05-03T12:09:59\"\n }\n]", "latency": 0, "statusCode": 200, "label": "", @@ -1602,6 +1602,111 @@ } ], "responseMode": null + }, + { + "uuid": "af7fdcb0-721d-4198-a8ef-c6d8c4eba8c8", + "type": "http", + "documentation": "Get a Testrun PDF profile", + "method": "post", + "endpoint": "report/{profile_name}", + "responses": [ + { + "uuid": "9a759f46-4bc4-433a-be86-e456f069c217", + "body": "", + "latency": 0, + "statusCode": 200, + "label": "Profile found - no device selected", + "headers": [], + "bodyType": "FILE", + "filePath": "", + "databucketID": "", + "sendFileAsBody": false, + "rules": [], + "rulesOperator": "OR", + "disableTemplating": false, + "fallbackTo404": false, + "default": true, + "crudKey": "id", + "callbacks": [] + }, + { + "uuid": "c9a09ae7-3158-4956-93ac-4c8a90dfced8", + "body": "", + "latency": 0, + "statusCode": 200, + "label": "Profile found - device selected ", + "headers": [], + "bodyType": "FILE", + "filePath": "", + "databucketID": "", + "sendFileAsBody": false, + "rules": [], + "rulesOperator": "OR", + "disableTemplating": false, + "fallbackTo404": false, + "default": false, + "crudKey": "id", + "callbacks": [] + }, + { + "uuid": "5f98471e-15b6-47a4-a68d-e98c3a538b40", + "body": "{\n \"error\": \"Profile could not be found\"\n}", + "latency": 0, + "statusCode": 404, + "label": "Profile not found", + "headers": [], + "bodyType": "INLINE", + "filePath": "", + "databucketID": "", + "sendFileAsBody": false, + "rules": [], + "rulesOperator": "OR", + "disableTemplating": false, + "fallbackTo404": false, + "default": false, + "crudKey": "id", + "callbacks": [] + }, + { + "uuid": "767d9e78-386e-4bf7-bec8-71a005efdce9", + "body": "{\n \"error\": \"A device with that mac address could not be found\"\n}", + "latency": 0, + "statusCode": 404, + "label": "Device not found", + "headers": [], + "bodyType": "INLINE", + "filePath": "", + "databucketID": "", + "sendFileAsBody": false, + "rules": [], + "rulesOperator": "OR", + "disableTemplating": false, + "fallbackTo404": false, + "default": false, + "crudKey": "id", + "callbacks": [] + }, + { + "uuid": "5d76bea0-39c1-45f2-80f1-de6f770cb999", + "body": "{\n \"error\": \"Error retrieving the profile PDF\"\n}", + "latency": 0, + "statusCode": 500, + "label": "Error occured", + "headers": [], + "bodyType": "INLINE", + "filePath": "", + "databucketID": "", + "sendFileAsBody": false, + "rules": [], + "rulesOperator": "OR", + "disableTemplating": false, + "fallbackTo404": false, + "default": false, + "crudKey": "id", + "callbacks": [] + } + ], + "responseMode": null } ], "rootChildren": [ @@ -1700,6 +1805,10 @@ { "type": "route", "uuid": "26f0f76f-e787-4ebe-a3f8-ea3a6004bc15" + }, + { + "type": "route", + "uuid": "af7fdcb0-721d-4198-a8ef-c6d8c4eba8c8" } ], "proxyMode": false, diff --git a/docs/dev/postman.json b/docs/dev/postman.json index 642090dd4..1e8a9cafb 100644 --- a/docs/dev/postman.json +++ b/docs/dev/postman.json @@ -1068,7 +1068,7 @@ } ], "cookie": [], - "body": "[\n {\n \"testrun\": {\n \"version\": \"2.0\"\n },\n \"mac_addr\": \"00:1e:42:28:9e:4a\",\n \"device\": {\n \"mac_addr\": \"00:1e:42:28:9e:4a\",\n \"manufacturer\": \"Teltonika\",\n \"model\": \"TRB140\",\n \"firmware\": \"test\",\n \"test_modules\": {\n \"protocol\": {\n \"enabled\": true\n },\n \"services\": {\n \"enabled\": true\n },\n \"ntp\": {\n \"enabled\": true\n },\n \"tls\": {\n \"enabled\": true\n },\n \"connection\": {\n \"enabled\": true\n },\n \"dns\": {\n \"enabled\": true\n }\n }\n },\n \"status\": \"Non-Compliant\",\n \"started\": \"2000-01-01 00:00:00\",\n \"finished\": \"2000-01-01 00:30:00\",\n \"tests\": {\n \"total\": 40,\n \"results\": [\n {\n \"name\": \"protocol.valid_bacnet\",\n \"description\": \"BACnet device could not be discovered\",\n \"expected_behavior\": \"BACnet traffic can be seen on the network and packets are valid and not malformed\",\n \"required_result\": \"Recommended\",\n \"result\": \"Feature Not Detected\"\n },\n {\n \"name\": \"protocol.bacnet.version\",\n \"description\": \"Device did not respond to BACnet discovery\",\n \"expected_behavior\": \"The BACnet client implements an up to date version of BACnet\",\n \"required_result\": \"Recommended\",\n \"result\": \"Feature Not Detected\"\n },\n {\n \"name\": \"protocol.valid_modbus\",\n \"description\": \"Device did not respond to Modbus connection\",\n \"expected_behavior\": \"Any Modbus functionality works as expected and valid Modbus traffic can be observed\",\n \"required_result\": \"Recommended\",\n \"result\": \"Feature Not Detected\"\n },\n {\n \"name\": \"security.services.ftp\",\n \"description\": \"No FTP server found\",\n \"expected_behavior\": \"There is no FTP service running on any port\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"security.ssh.version\",\n \"description\": \"SSH server found running protocol 2.0\",\n \"expected_behavior\": \"SSH server is not running or server is SSHv2\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"security.services.telnet\",\n \"description\": \"No telnet server found\",\n \"expected_behavior\": \"There is no Telnet service running on any port\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"security.services.smtp\",\n \"description\": \"No SMTP server found\",\n \"expected_behavior\": \"There is no SMTP service running on any port\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"security.services.http\",\n \"description\": \"Found HTTP server running on port 80/tcp\",\n \"expected_behavior\": \"Device is unreachable on port 80 (or any other port) and only responds to HTTPS requests on port 443 (or any other port if HTTP is used at all)\",\n \"required_result\": \"Required\",\n \"result\": \"Non-Compliant\",\n \"recommendations\": [\n \"Disable all unsecure HTTP servers\",\n \"Setup TLS on the web server\"\n ]\n },\n {\n \"name\": \"security.services.pop\",\n \"description\": \"No POP server found\",\n \"expected_behavior\": \"There is no POP service running on any port\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"security.services.imap\",\n \"description\": \"No IMAP server found\",\n \"expected_behavior\": \"There is no IMAP service running on any port\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"security.services.snmpv3\",\n \"description\": \"No SNMP server found\",\n \"expected_behavior\": \"Device is unreachable on port 161 (or any other port) and device is unreachable on port 162 (or any other port) unless SNMP is essential in which case it is SNMPv3 is used.\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"security.services.vnc\",\n \"description\": \"No VNC server found\",\n \"expected_behavior\": \"Device cannot be accessed / connected to via VNC on any port\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"security.services.tftp\",\n \"description\": \"No TFTP server found\",\n \"expected_behavior\": \"There is no TFTP service running on any port\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"ntp.network.ntp_server\",\n \"description\": \"No NTP server found\",\n \"expected_behavior\": \"The device does not respond to NTP requests when it's IP is set as the NTP server on another device\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"connection.port_link\",\n \"description\": \"No port errors detected\",\n \"expected_behavior\": \"When the etherent cable is connected to the port, the device triggers the port to its enabled \\\"Link UP\\\" (LEDs illuminate on device and switch ports if present) state, and the switch shows no errors with the LEDs and when interrogated with a \\\"show interface\\\" command on most network switches.\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"connection.port_speed\",\n \"description\": \"Succesfully auto-negotiated speeds above 10 Mbps\",\n \"expected_behavior\": \"When the ethernet cable is connected to the port, the device autonegotiates a speed that can be checked with the \\\"show interface\\\" command on most network switches. The output of this command must also show that the \\\"configured speed\\\" is set to \\\"auto\\\".\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"connection.port_duplex\",\n \"description\": \"Succesfully auto-negotiated full duplex\",\n \"expected_behavior\": \"When the ethernet cable is connected to the port, the device autonegotiates a full-duplex connection.\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"connection.switch.arp_inspection\",\n \"description\": \"Device uses ARP\",\n \"expected_behavior\": \"Device continues to operate correctly when ARP inspection is enabled on the switch. No functionality is lost with ARP inspection enabled.\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"connection.switch.dhcp_snooping\",\n \"description\": \"Device does not act as a DHCP server\",\n \"expected_behavior\": \"Device continues to operate correctly when DHCP snooping is enabled on the switch.\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"connection.dhcp_address\",\n \"description\": \"Device responded to leased ip address\",\n \"expected_behavior\": \"The device is not setup with a static IP address. The device accepts an IP address from a DHCP server (RFC 2131) and responds succesfully to an ICMP echo (ping) request.\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"connection.mac_address\",\n \"description\": \"MAC address found: 00:1e:42:28:9e:4a\",\n \"expected_behavior\": \"N/A\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"connection.mac_oui\",\n \"description\": \"OUI Manufacturer found: Teltonika\",\n \"expected_behavior\": \"The MAC address prefix is registered in the IEEE Organizationally Unique Identifier database.\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"connection.private_address\",\n \"description\": \"All subnets are supported\",\n \"expected_behavior\": \"The device under test accepts IP addresses within all ranges specified in RFC 1918 and communicates using these addresses. The Internet Assigned Numbers Authority (IANA) has reserved the following three blocks of the IP address space for private internets. 10.0.0.0 - 10.255.255.255.255 (10/8 prefix). 172.16.0.0 - 172.31.255.255 (172.16/12 prefix). 192.168.0.0 - 192.168.255.255 (192.168/16 prefix)\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"connection.shared_address\",\n \"description\": \"All subnets are supported\",\n \"expected_behavior\": \"The device under test accepts IP addresses within the ranges specified in RFC 6598 and communicates using these addresses\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"connection.dhcp_disconnect\",\n \"description\": \"An error occured whilst running this test\",\n \"expected_behavior\": \"A client SHOULD use DHCP to reacquire or verify its IP address and network parameters whenever the local network parameters may have changed; e.g., at system boot time or after a disconnection from the local network, as the local network configuration may change without the client's or user's knowledge. If a client has knowledge ofa previous network address and is unable to contact a local DHCP server, the client may continue to use the previous network addres until the lease for that address expires. If the lease expires before the client can contact a DHCP server, the client must immediately discontinue use of the previous network address and may inform local users of the problem.\",\n \"required_result\": \"Required\",\n \"result\": \"Error\"\n },\n {\n \"name\": \"connection.single_ip\",\n \"description\": \"Device is using multiple IP addresses\",\n \"expected_behavior\": \"The device under test does not behave as a network switch and only requets one IP address. This test is to avoid that devices implement network switches that allow connecting strings of daisy chained devices to one single network port, as this would not make 802.1x port based authentication possible.\",\n \"required_result\": \"Required\",\n \"result\": \"Non-Compliant\",\n \"recommendations\": [\n \"Ensure that all ports on the device are isolated\",\n \"Ensure only one DHCP client is running\"\n ]\n },\n {\n \"name\": \"connection.target_ping\",\n \"description\": \"Device responds to ping\",\n \"expected_behavior\": \"The device under test responds to an ICMP echo (ping) request.\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"connection.ipaddr.ip_change\",\n \"description\": \"Device has accepted an IP address change\",\n \"expected_behavior\": \"If the lease expires before the client receiveds a DHCPACK, the client moves to INIT state, MUST immediately stop any other network processing and requires network initialization parameters as if the client were uninitialized. If the client then receives a DHCPACK allocating the client its previous network addres, the client SHOULD continue network processing. If the client is given a new network address, it MUST NOT continue using the previous network address and SHOULD notify the local users of the problem.\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"connection.ipaddr.dhcp_failover\",\n \"description\": \"Secondary DHCP server lease confirmed active in device\",\n \"expected_behavior\": \"\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"connection.ipv6_slaac\",\n \"description\": \"Device does not support IPv6 SLAAC\",\n \"expected_behavior\": \"The device under test complies with RFC4862 and forms a valid IPv6 SLAAC address\",\n \"required_result\": \"Required\",\n \"result\": \"Non-Compliant\",\n \"recommendations\": [\n \"Install a network manager that supports IPv6\",\n \"Disable DHCPv6\"\n ]\n },\n {\n \"name\": \"connection.ipv6_ping\",\n \"description\": \"No IPv6 SLAAC address found. Cannot ping\",\n \"expected_behavior\": \"The device responds to the ping as per RFC4443\",\n \"required_result\": \"Required\",\n \"result\": \"Non-Compliant\",\n \"recommendations\": [\n \"Enable ping response to IPv6 ICMP requests in network manager settings\",\n \"Create a firewall exception to allow ICMPv6 via LAN\"\n ]\n },\n {\n \"name\": \"security.tls.v1_2_server\",\n \"description\": \"TLS 1.2 certificate is invalid\",\n \"expected_behavior\": \"TLS 1.2 certificate is issued to the web browser client when accessed\",\n \"required_result\": \"Required if Applicable\",\n \"result\": \"Non-Compliant\",\n \"recommendations\": [\n \"Enable TLS 1.2 support in the web server configuration\",\n \"Disable TLS 1.0 and 1.1\",\n \"Sign the certificate used by the web server\"\n ]\n },\n {\n \"name\": \"security.tls.v1_2_client\",\n \"description\": \"No outbound connections were found\",\n \"expected_behavior\": \"The packet indicates a TLS connection with at least TLS 1.2 and support for ECDH and ECDSA ciphers\",\n \"required_result\": \"Required if Applicable\",\n \"result\": \"Feature Not Detected\"\n },\n {\n \"name\": \"security.tls.v1_3_server\",\n \"description\": \"TLS 1.3 certificate is invalid\",\n \"expected_behavior\": \"TLS 1.3 certificate is issued to the web browser client when accessed\",\n \"required_result\": \"Informational\",\n \"result\": \"Informational\"\n },\n {\n \"name\": \"security.tls.v1_3_client\",\n \"description\": \"No outbound connections were found\",\n \"expected_behavior\": \"The packet indicates a TLS connection with at least TLS 1.3\",\n \"required_result\": \"Informational\",\n \"result\": \"Informational\"\n },\n {\n \"name\": \"ntp.network.ntp_support\",\n \"description\": \"Device has not sent any NTP requests\",\n \"expected_behavior\": \"The device sends an NTPv4 request to the configured NTP server.\",\n \"required_result\": \"Required\",\n \"result\": \"Non-Compliant\",\n \"recommendations\": [\n \"Set the NTP version to v4 in the NTP client\",\n \"Install an NTP client that supports NTPv4\"\n ]\n },\n {\n \"name\": \"ntp.network.ntp_dhcp\",\n \"description\": \"Device has not sent any NTP requests\",\n \"expected_behavior\": \"Device can accept NTP server address, provided by the DHCP server (DHCP OFFER PACKET)\",\n \"required_result\": \"Roadmap\",\n \"result\": \"Feature Not Detected\"\n },\n {\n \"name\": \"dns.network.hostname_resolution\",\n \"description\": \"DNS traffic detected from device\",\n \"expected_behavior\": \"The device sends DNS requests.\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"dns.network.from_dhcp\",\n \"description\": \"DNS traffic detected only to DHCP provided server\",\n \"expected_behavior\": \"The device sends DNS requests to the DNS server provided by the DHCP server\",\n \"required_result\": \"Informational\",\n \"result\": \"Informational\"\n },\n {\n \"name\": \"dns.mdns\",\n \"description\": \"No MDNS traffic detected from the device\",\n \"expected_behavior\": \"Device may send MDNS requests\",\n \"required_result\": \"Informational\",\n \"result\": \"Informational\"\n }\n ]\n },\n \"report\": \"http://localhost:8000/report/Teltonika TRB140/2024-09-10T13:19:24\"\n }\n]" + "body": "[\n {\n \"testrun\": {\n \"version\": \"2.0\"\n },\n \"mac_addr\": \"00:1e:42:28:9e:4a\",\n \"device\": {\n \"mac_addr\": \"00:1e:42:28:9e:4a\",\n \"manufacturer\": \"Teltonika\",\n \"model\": \"TRB140\",\n \"firmware\": \"test\",\n \"test_modules\": {\n \"protocol\": {\n \"enabled\": true\n },\n \"services\": {\n \"enabled\": true\n },\n \"ntp\": {\n \"enabled\": true\n },\n \"tls\": {\n \"enabled\": true\n },\n \"connection\": {\n \"enabled\": true\n },\n \"dns\": {\n \"enabled\": true\n }\n }\n },\n \"status\": \"Non-Compliant\",\n \"started\": \"2000-01-01 00:00:00\",\n \"finished\": \"2000-01-01 00:30:00\",\n \"tests\": {\n \"total\": 40,\n \"results\": [\n {\n \"name\": \"protocol.valid_bacnet\",\n \"description\": \"BACnet device could not be discovered\",\n \"expected_behavior\": \"BACnet traffic can be seen on the network and packets are valid and not malformed\",\n \"required_result\": \"Recommended\",\n \"result\": \"Feature Not Detected\"\n },\n {\n \"name\": \"protocol.bacnet.version\",\n \"description\": \"Device did not respond to BACnet discovery\",\n \"expected_behavior\": \"The BACnet client implements an up to date version of BACnet\",\n \"required_result\": \"Recommended\",\n \"result\": \"Feature Not Detected\"\n },\n {\n \"name\": \"protocol.valid_modbus\",\n \"description\": \"Device did not respond to Modbus connection\",\n \"expected_behavior\": \"Any Modbus functionality works as expected and valid Modbus traffic can be observed\",\n \"required_result\": \"Recommended\",\n \"result\": \"Feature Not Detected\"\n },\n {\n \"name\": \"security.services.ftp\",\n \"description\": \"No FTP server found\",\n \"expected_behavior\": \"There is no FTP service running on any port\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"security.ssh.version\",\n \"description\": \"SSH server found running protocol 2.0\",\n \"expected_behavior\": \"SSH server is not running or server is SSHv2\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"security.services.telnet\",\n \"description\": \"No telnet server found\",\n \"expected_behavior\": \"There is no Telnet service running on any port\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"security.services.smtp\",\n \"description\": \"No SMTP server found\",\n \"expected_behavior\": \"There is no SMTP service running on any port\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"security.services.http\",\n \"description\": \"Found HTTP server running on port 80/tcp\",\n \"expected_behavior\": \"Device is unreachable on port 80 (or any other port) and only responds to HTTPS requests on port 443 (or any other port if HTTP is used at all)\",\n \"required_result\": \"Required\",\n \"result\": \"Non-Compliant\",\n \"recommendations\": [\n \"Disable all unsecure HTTP servers\",\n \"Setup TLS on the web server\"\n ]\n },\n {\n \"name\": \"security.services.pop\",\n \"description\": \"No POP server found\",\n \"expected_behavior\": \"There is no POP service running on any port\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"security.services.imap\",\n \"description\": \"No IMAP server found\",\n \"expected_behavior\": \"There is no IMAP service running on any port\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"security.services.snmpv3\",\n \"description\": \"No SNMP server found\",\n \"expected_behavior\": \"Device is unreachable on port 161 (or any other port) and device is unreachable on port 162 (or any other port) unless SNMP is essential in which case it is SNMPv3 is used.\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"security.services.vnc\",\n \"description\": \"No VNC server found\",\n \"expected_behavior\": \"Device cannot be accessed / connected to via VNC on any port\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"security.services.tftp\",\n \"description\": \"No TFTP server found\",\n \"expected_behavior\": \"There is no TFTP service running on any port\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"ntp.network.ntp_server\",\n \"description\": \"No NTP server found\",\n \"expected_behavior\": \"The device does not respond to NTP requests when it's IP is set as the NTP server on another device\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"connection.port_link\",\n \"description\": \"No port errors detected\",\n \"expected_behavior\": \"When the etherent cable is connected to the port, the device triggers the port to its enabled \\\"Link UP\\\" (LEDs illuminate on device and switch ports if present) state, and the switch shows no errors with the LEDs and when interrogated with a \\\"show interface\\\" command on most network switches.\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"connection.port_speed\",\n \"description\": \"Succesfully auto-negotiated speeds above 10 Mbps\",\n \"expected_behavior\": \"When the ethernet cable is connected to the port, the device autonegotiates a speed that can be checked with the \\\"show interface\\\" command on most network switches. The output of this command must also show that the \\\"configured speed\\\" is set to \\\"auto\\\".\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"connection.port_duplex\",\n \"description\": \"Succesfully auto-negotiated full duplex\",\n \"expected_behavior\": \"When the ethernet cable is connected to the port, the device autonegotiates a full-duplex connection.\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"connection.switch.arp_inspection\",\n \"description\": \"Device uses ARP\",\n \"expected_behavior\": \"Device continues to operate correctly when ARP inspection is enabled on the switch. No functionality is lost with ARP inspection enabled.\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"connection.switch.dhcp_snooping\",\n \"description\": \"Device does not act as a DHCP server\",\n \"expected_behavior\": \"Device continues to operate correctly when DHCP snooping is enabled on the switch.\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"connection.dhcp_address\",\n \"description\": \"Device responded to leased ip address\",\n \"expected_behavior\": \"The device is not setup with a static IP address. The device accepts an IP address from a DHCP server (RFC 2131) and responds succesfully to an ICMP echo (ping) request.\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"connection.mac_address\",\n \"description\": \"MAC address found: 00:1e:42:28:9e:4a\",\n \"expected_behavior\": \"N/A\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"connection.mac_oui\",\n \"description\": \"OUI Manufacturer found: Teltonika\",\n \"expected_behavior\": \"The MAC address prefix is registered in the IEEE Organizationally Unique Identifier database.\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"connection.private_address\",\n \"description\": \"All subnets are supported\",\n \"expected_behavior\": \"The device under test accepts IP addresses within all ranges specified in RFC 1918 and communicates using these addresses. The Internet Assigned Numbers Authority (IANA) has reserved the following three blocks of the IP address space for private internets. 10.0.0.0 - 10.255.255.255.255 (10/8 prefix). 172.16.0.0 - 172.31.255.255 (172.16/12 prefix). 192.168.0.0 - 192.168.255.255 (192.168/16 prefix)\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"connection.shared_address\",\n \"description\": \"All subnets are supported\",\n \"expected_behavior\": \"The device under test accepts IP addresses within the ranges specified in RFC 6598 and communicates using these addresses\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"connection.dhcp_disconnect\",\n \"description\": \"An error occured whilst running this test\",\n \"expected_behavior\": \"A client SHOULD use DHCP to reacquire or verify its IP address and network parameters whenever the local network parameters may have changed; e.g., at system boot time or after a disconnection from the local network, as the local network configuration may change without the client's or user's knowledge. If a client has knowledge ofa previous network address and is unable to contact a local DHCP server, the client may continue to use the previous network addres until the lease for that address expires. If the lease expires before the client can contact a DHCP server, the client must immediately discontinue use of the previous network address and may inform local users of the problem.\",\n \"required_result\": \"Required\",\n \"result\": \"Error\"\n },\n {\n \"name\": \"connection.single_ip\",\n \"description\": \"Device is using multiple IP addresses\",\n \"expected_behavior\": \"The device under test does not behave as a network switch and only requets one IP address. This test is to avoid that devices implement network switches that allow connecting strings of daisy chained devices to one single network port, as this would not make 802.1x port based authentication possible.\",\n \"required_result\": \"Required\",\n \"result\": \"Non-Compliant\",\n \"recommendations\": [\n \"Ensure that all ports on the device are isolated\",\n \"Ensure only one DHCP client is running\"\n ]\n },\n {\n \"name\": \"connection.target_ping\",\n \"description\": \"Device responds to ping\",\n \"expected_behavior\": \"The device under test responds to an ICMP echo (ping) request.\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"connection.ipaddr.ip_change\",\n \"description\": \"Device has accepted an IP address change\",\n \"expected_behavior\": \"If the lease expires before the client receiveds a DHCPACK, the client moves to INIT state, MUST immediately stop any other network processing and requires network initialization parameters as if the client were uninitialized. If the client then receives a DHCPACK allocating the client its previous network addres, the client SHOULD continue network processing. If the client is given a new network address, it MUST NOT continue using the previous network address and SHOULD notify the local users of the problem.\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"connection.ipaddr.dhcp_failover\",\n \"description\": \"Secondary DHCP server lease confirmed active in device\",\n \"expected_behavior\": \"\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"connection.ipv6_slaac\",\n \"description\": \"Device does not support IPv6 SLAAC\",\n \"expected_behavior\": \"The device under test complies with RFC4862 and forms a valid IPv6 SLAAC address\",\n \"required_result\": \"Required\",\n \"result\": \"Non-Compliant\",\n \"recommendations\": [\n \"Install a network manager that supports IPv6\",\n \"Disable DHCPv6\"\n ]\n },\n {\n \"name\": \"connection.ipv6_ping\",\n \"description\": \"No IPv6 SLAAC address found. Cannot ping\",\n \"expected_behavior\": \"The device responds to the ping as per RFC4443\",\n \"required_result\": \"Required\",\n \"result\": \"Non-Compliant\",\n \"recommendations\": [\n \"Enable ping response to IPv6 ICMP requests in network manager settings\",\n \"Create a firewall exception to allow ICMPv6 via LAN\"\n ]\n },\n {\n \"name\": \"security.tls.v1_2_server\",\n \"description\": \"TLS 1.2 certificate is invalid\",\n \"expected_behavior\": \"TLS 1.2 certificate is issued to the web browser client when accessed\",\n \"required_result\": \"Required if Applicable\",\n \"result\": \"Non-Compliant\",\n \"recommendations\": [\n \"Enable TLS 1.2 support in the web server configuration\",\n \"Disable TLS 1.0 and 1.1\",\n \"Sign the certificate used by the web server\"\n ]\n },\n {\n \"name\": \"security.tls.v1_2_client\",\n \"description\": \"No outbound TLS connections were found\",\n \"expected_behavior\": \"The packet indicates a TLS connection with at least TLS 1.2 and support for ECDH and ECDSA ciphers\",\n \"required_result\": \"Required if Applicable\",\n \"result\": \"Feature Not Detected\"\n },\n {\n \"name\": \"security.tls.v1_3_server\",\n \"description\": \"TLS 1.3 certificate is invalid\",\n \"expected_behavior\": \"TLS 1.3 certificate is issued to the web browser client when accessed\",\n \"required_result\": \"Informational\",\n \"result\": \"Informational\"\n },\n {\n \"name\": \"security.tls.v1_3_client\",\n \"description\": \"No outbound TLS connections were found\",\n \"expected_behavior\": \"The packet indicates a TLS connection with at least TLS 1.3\",\n \"required_result\": \"Informational\",\n \"result\": \"Informational\"\n },\n {\n \"name\": \"ntp.network.ntp_support\",\n \"description\": \"Device has not sent any NTP requests\",\n \"expected_behavior\": \"The device sends an NTPv4 request to the configured NTP server.\",\n \"required_result\": \"Required\",\n \"result\": \"Non-Compliant\",\n \"recommendations\": [\n \"Set the NTP version to v4 in the NTP client\",\n \"Install an NTP client that supports NTPv4\"\n ]\n },\n {\n \"name\": \"ntp.network.ntp_dhcp\",\n \"description\": \"Device has not sent any NTP requests\",\n \"expected_behavior\": \"Device can accept NTP server address, provided by the DHCP server (DHCP OFFER PACKET)\",\n \"required_result\": \"Roadmap\",\n \"result\": \"Feature Not Detected\"\n },\n {\n \"name\": \"dns.network.hostname_resolution\",\n \"description\": \"DNS traffic detected from device\",\n \"expected_behavior\": \"The device sends DNS requests.\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"dns.network.from_dhcp\",\n \"description\": \"DNS traffic detected only to DHCP provided server\",\n \"expected_behavior\": \"The device sends DNS requests to the DNS server provided by the DHCP server\",\n \"required_result\": \"Informational\",\n \"result\": \"Informational\"\n },\n {\n \"name\": \"dns.mdns\",\n \"description\": \"No MDNS traffic detected from the device\",\n \"expected_behavior\": \"Device may send MDNS requests\",\n \"required_result\": \"Informational\",\n \"result\": \"Informational\"\n }\n ]\n },\n \"report\": \"http://localhost:8000/report/Teltonika TRB140/2024-09-10T13:19:24\"\n }\n]" }, { "name": "No Test Reports (200)", @@ -1348,7 +1348,7 @@ ] }, { - "name": "Export", + "name": "Export Report", "request": { "method": "POST", "header": [], @@ -2305,7 +2305,6 @@ { "key": "Content-Type", "value": "application/json", - "name": "Content-Type", "description": "", "type": "text" } @@ -2501,7 +2500,6 @@ { "key": "Content-Type", "value": "application/json", - "name": "Content-Type", "description": "", "type": "text" } @@ -2578,7 +2576,6 @@ { "key": "Content-Type", "value": "application/json", - "name": "Content-Type", "description": "", "type": "text" } @@ -2622,7 +2619,6 @@ { "key": "Content-Type", "value": "application/json", - "name": "Content-Type", "description": "", "type": "text" } @@ -2666,7 +2662,6 @@ { "key": "Content-Type", "value": "application/json", - "name": "Content-Type", "description": "", "type": "text" } @@ -2705,6 +2700,895 @@ }, "description": "Delete a root CA certificate" }, + "response": [ + { + "name": "Delete Certificate (200)", + "originalRequest": { + "method": "DELETE", + "header": [], + "body": { + "mode": "raw", + "raw": "{\n \"name\": \"GTS Root R1\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "http://localhost:8000/system/config/certs", + "protocol": "http", + "host": [ + "localhost" + ], + "port": "8000", + "path": [ + "system", + "config", + "certs" + ] + } + }, + "status": "OK", + "code": 200, + "_postman_previewlanguage": "json", + "header": [ + { + "key": "Content-Type", + "value": "application/json", + "description": "", + "type": "text" + } + ], + "cookie": [], + "body": "{\n \"success\": \"Successfully deleted the certificate\"\n}" + }, + { + "name": "Not Found (404)", + "originalRequest": { + "method": "DELETE", + "header": [], + "body": { + "mode": "raw", + "raw": "{\n \"name\": \"non-existing name\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "http://localhost:8000/system/config/certs", + "protocol": "http", + "host": [ + "localhost" + ], + "port": "8000", + "path": [ + "system", + "config", + "certs" + ] + } + }, + "status": "Not Found", + "code": 404, + "_postman_previewlanguage": "json", + "header": [ + { + "key": "Content-Type", + "value": "application/json", + "description": "", + "type": "text" + } + ], + "cookie": [], + "body": "{\n \"error\": \"A certificate with that name could not be found\"\n}" + }, + { + "name": "Invalid JSON (400)", + "originalRequest": { + "method": "DELETE", + "header": [], + "body": { + "mode": "raw", + "raw": "{\n\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "http://localhost:8000/system/config/certs", + "protocol": "http", + "host": [ + "localhost" + ], + "port": "8000", + "path": [ + "system", + "config", + "certs" + ] + } + }, + "status": "Bad Request", + "code": 400, + "_postman_previewlanguage": "json", + "header": [ + { + "key": "Content-Type", + "value": "application/json", + "description": "", + "type": "text" + } + ], + "cookie": [], + "body": "{\n \"error\": \"Received a bad request\"\n}" + } + ] + }, + { + "name": "Get Profile Format", + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "http://localhost:8000/profiles/format", + "protocol": "http", + "host": [ + "localhost" + ], + "port": "8000", + "path": [ + "profiles", + "format" + ] + }, + "description": "Obtain the current format of the risk assessment questionnaire" + }, + "response": [ + { + "name": "Get Profile Format (200)", + "originalRequest": { + "method": "GET", + "header": [], + "url": { + "raw": "http://localhost:8000/profiles/format", + "protocol": "http", + "host": [ + "localhost" + ], + "port": "8000", + "path": [ + "profiles", + "format" + ] + } + }, + "status": "OK", + "code": 200, + "_postman_previewlanguage": "json", + "header": [ + { + "key": "Content-Type", + "value": "application/json", + "description": "" + } + ], + "cookie": [], + "body": "[\n {\n \"question\": \"How will this device be used at Google?\",\n \"description\": \"Desribe your use case. Add links to user journey diagrams and TDD if available.\",\n \"type\": \"text-long\",\n \"validation\": {\n \"max\": \"512\",\n \"required\": true\n }\n },\n {\n \"question\": \"Is this device going to be managed by Google or a third party?\",\n \"description\": \"A manufacturer or supplier is considered third party in this case\",\n \"type\": \"select\",\n \"options\": [\n \"Google\",\n \"Third Party\"\n ],\n \"validation\": {\n \"required\": true\n }\n },\n {\n \"question\": \"Will the third-party device administrator be able to grant access to authorized Google personnel upon request?\",\n \"type\": \"select\",\n \"options\": [\n \"Yes\",\n \"No\",\n \"N/A\"\n ],\n \"default\": \"N/A\",\n \"validation\": {\n \"required\": true\n }\n },\n {\n \"category\": \"Data Transmission\",\n \"question\": \"Which of the following statements are true about this device?\",\n \"description\": \"This tells us about the types of data that are transmitted from this device and how the transmission is performed from a technical standpoint.\",\n \"type\": \"select-multiple\",\n \"options\": [\n \"PII/PHI, confidential/sensitive business data, Intellectual Property and Trade Secrets, Critical Infrastructure and Identity Assets to a domain outside Alphabet's ownership\",\n \"Data transmission occurs across less-trusted networks (e.g. the internet).\",\n \"A failure in data transmission would likely have a substantial negative impact (https://www.rra.rocks/docs/standard_levels#levels-definitions)\",\n \"A confidentiality breach during transmission would have a substantial negative impact\",\n \"The device does not encrypt data during transmission\",\n \"None of the above\"\n ],\n \"validation\": {\n \"required\": true\n }\n },\n {\n \"category\": \"Data Transmission\",\n \"question\": \"Does the network protocol assure server-to-client identity verification?\",\n \"type\": \"select\",\n \"options\": [\n \"Yes\",\n \"No\",\n \"I don't know\"\n ],\n \"validation\": {\n \"required\": true\n }\n },\n {\n \"category\": \"Remote Operation\",\n \"question\": \"Click the statements that best describe the characteristics of this device.\",\n \"description\": \"This tells us about how this device is managed remotely.\",\n \"type\": \"select-multiple\",\n \"options\": [\n \"PII/PHI, or confidential business data is accessible from the device without authentication\",\n \"Unrecoverable actions (e.g. disk wipe) can be performed remotely\",\n \"Authentication is not required for remote access\",\n \"The management interface is accessible from the public internet\",\n \"Static credentials are used for administration\",\n \"None of the above\"\n ],\n \"validation\": {\n \"required\": true\n }\n },\n {\n \"category\": \"Operating Environment\",\n \"question\": \"Are any of the following statements true about this device?\",\n \"description\": \"This informs us about what other systems and processes this device is a part of.\",\n \"type\": \"select-multiple\",\n \"options\": [\n \"The device monitors an environment for active risks to human life.\",\n \"The device is used to convey people, or critical property.\",\n \"The device controls robotics in human-accessible spaces.\",\n \"The device controls physical access systems.\",\n \"The device is involved in processes required by regulations, or compliance. (ex. privacy, security, safety regulations)\",\n \"The device's failure would cause faults in other high-criticality processes.\",\n \"None of the above\"\n ],\n \"validation\": {\n \"required\": true\n }\n },\n {\n \"question\": \"Comments\",\n \"description\": \"Anything else to share?\",\n \"type\": \"text-long\",\n \"validation\": {\n \"max\": \"512\"\n }\n }\n]" + } + ] + }, + { + "name": "List Profiles", + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "http://localhost:8000/profiles", + "protocol": "http", + "host": [ + "localhost" + ], + "port": "8000", + "path": [ + "profiles" + ] + }, + "description": "Get a list of risk assessment profiles saved by the user" + }, + "response": [ + { + "name": "List Profiles (200)", + "originalRequest": { + "method": "GET", + "header": [], + "url": { + "raw": "http://localhost:8000/profiles", + "protocol": "http", + "host": [ + "localhost" + ], + "port": "8000", + "path": [ + "profiles" + ] + } + }, + "status": "OK", + "code": 200, + "_postman_previewlanguage": "json", + "header": [ + { + "key": "Content-Type", + "value": "application/json", + "description": "" + } + ], + "cookie": [], + "body": "[\n {\n \"name\": \"New Profile\",\n \"version\": \"2.0\",\n \"created\": \"2024-10-08\",\n \"status\": \"Valid\",\n \"risk\": \"High\",\n \"questions\": [\n {\n \"question\": \"How will this device be used at Google?\",\n \"answer\": \"Yes\"\n },\n {\n \"question\": \"Is this device going to be managed by Google or a third party?\",\n \"answer\": \"Google\",\n \"risk\": \"Limited\"\n },\n {\n \"question\": \"Will the third-party device administrator be able to grant access to authorized Google personnel upon request?\",\n \"answer\": \"No\",\n \"risk\": \"Limited\"\n },\n {\n \"question\": \"Which of the following statements are true about this device?\",\n \"answer\": [\n 0,\n 1,\n 2\n ],\n \"risk\": \"High\"\n },\n {\n \"question\": \"Does the network protocol assure server-to-client identity verification?\",\n \"answer\": \"Yes\",\n \"risk\": \"Limited\"\n },\n {\n \"question\": \"Click the statements that best describe the characteristics of this device.\",\n \"answer\": [\n 0\n ],\n \"risk\": \"High\"\n },\n {\n \"question\": \"Are any of the following statements true about this device?\",\n \"answer\": [\n 0\n ],\n \"risk\": \"High\"\n },\n {\n \"question\": \"Comments\",\n \"answer\": \"\"\n }\n ]\n }\n]" + }, + { + "name": "No Profiles (200)", + "originalRequest": { + "method": "GET", + "header": [], + "url": { + "raw": "http://localhost:8000/profiles", + "protocol": "http", + "host": [ + "localhost" + ], + "port": "8000", + "path": [ + "profiles" + ] + } + }, + "status": "OK", + "code": 200, + "_postman_previewlanguage": "json", + "header": [ + { + "key": "Content-Type", + "value": "application/json", + "description": "" + } + ], + "cookie": [], + "body": "[\n \n]" + } + ] + }, + { + "name": "Export Profile", + "request": { + "method": "POST", + "header": [], + "url": { + "raw": "http://localhost:8000/profile/{profile_name}", + "protocol": "http", + "host": [ + "localhost" + ], + "port": "8000", + "path": [ + "profile", + "{profile_name}" + ] + }, + "description": "Get the PDF report for a specific device and timestamp" + }, + "response": [ + { + "name": "Get Profile No Device (200)", + "originalRequest": { + "method": "POST", + "header": [], + "url": { + "raw": "http://localhost:8000/profile/Test", + "protocol": "http", + "host": [ + "localhost" + ], + "port": "8000", + "path": [ + "profile", + "Test" + ] + } + }, + "status": "OK", + "code": 200, + "_postman_previewlanguage": "text", + "header": [ + { + "key": "Content-Type", + "value": "application/pdf", + "description": "", + "type": "text" + } + ], + "cookie": [], + "body": "profile.pdf" + }, + { + "name": "Get Profile with Device (200))", + "originalRequest": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\"mac_addr\": \"00:1e:42:35:73:c4\"}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "http://localhost:8000/profile/Test", + "protocol": "http", + "host": [ + "localhost" + ], + "port": "8000", + "path": [ + "profile", + "Test" + ] + } + }, + "status": "OK", + "code": 200, + "_postman_previewlanguage": "text", + "header": [ + { + "key": "Content-Type", + "value": "application/pdf", + "description": "", + "type": "text" + } + ], + "cookie": [], + "body": "profile.pdf" + }, + { + "name": "Profile Not Found (404)", + "originalRequest": { + "method": "POST", + "header": [], + "url": { + "raw": "http://localhost:8000/profile/NonExistingProfile", + "protocol": "http", + "host": [ + "localhost" + ], + "port": "8000", + "path": [ + "profile", + "NonExistingProfile" + ] + } + }, + "status": "Not Found", + "code": 404, + "_postman_previewlanguage": "json", + "header": [ + { + "key": "Content-Type", + "value": "application/json", + "description": "", + "type": "text" + } + ], + "cookie": [], + "body": "{\n \"error\": \"Profile could not be found\"\n}" + }, + { + "name": "Device Not Found (404)", + "originalRequest": { + "method": "GET", + "header": [ + { + "key": "Content-Type", + "value": "application/json", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\"mac_addr\": \"non_existing_mac_addr\"}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "http://localhost:8000/profile/Test", + "protocol": "http", + "host": [ + "localhost" + ], + "port": "8000", + "path": [ + "profile", + "Test" + ] + } + }, + "status": "Not Found", + "code": 404, + "_postman_previewlanguage": "json", + "header": [ + { + "key": "Content-Type", + "value": "application/json", + "description": "", + "type": "text" + } + ], + "cookie": [], + "body": "{\n \"error\": \"A device with that mac address could not be found\"\n}" + }, + { + "name": "Internal Server Error (500) Copy", + "originalRequest": { + "method": "POST", + "header": [], + "url": { + "raw": "http://localhost:8000/profile/Test", + "protocol": "http", + "host": [ + "localhost" + ], + "port": "8000", + "path": [ + "profile", + "Test" + ] + } + }, + "status": "Internal Server Error", + "code": 500, + "_postman_previewlanguage": "json", + "header": [ + { + "key": "Content-Type", + "value": "application/json", + "description": "", + "type": "text" + } + ], + "cookie": [], + "body": "{\n \"error\": \"Error retrieving the profile PDF\"\n}" + } + ] + }, + { + "name": "Update Profile", + "request": { + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "{\n \"name\": \"Testing\",\n \"status\": \"Valid\",\n \"questions\": [\n {\n \"question\": \"What type of device is this?\",\n \"answer\": \"IoT Gateway\"\n },\n {\n \"question\": \"How will this device be used at Google?\",\n \"answer\": \"asdasd\"\n },\n {\n \"question\": \"Is this device going to be managed by Google or a third party?\",\n \"answer\": \"Google\"\n },\n {\n \"question\": \"Will the third-party device administrator be able to grant access to authorized Google personnel upon request?\",\n \"answer\": \"N/A\"\n },\n {\n \"question\": \"Are any of the following statements true about your device?\",\n \"answer\": [\n 3\n ]\n },\n {\n \"question\": \"Which of the following statements are true about this device?\",\n \"answer\": [\n 5\n ]\n },\n {\n \"question\": \"Does the network protocol assure server-to-client identity verification?\",\n \"answer\": \"Yes\"\n },\n {\n \"question\": \"Click the statements that best describe the characteristics of this device.\",\n \"answer\": [\n 5\n ]\n },\n {\n \"question\": \"Are any of the following statements true about this device?\",\n \"answer\": [\n 6\n ]\n },\n {\n \"question\": \"Comments\",\n \"answer\": \"\"\n }\n ]\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "http://localhost:8000/profiles", + "protocol": "http", + "host": [ + "localhost" + ], + "port": "8000", + "path": [ + "profiles" + ] + }, + "description": "Create or update a risk assessment questionnaire response" + }, + "response": [ + { + "name": "Update Profile (200)", + "originalRequest": { + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "\n{\n \"name\": \"New Profile\",\n \"rename\": \"Updated New Profile\",\n \"version\": \"2.0.1\",\n \"created\": \"2024-10-08\",\n \"status\": \"Valid\",\n \"risk\": \"High\",\n \"questions\": [\n {\n \"question\": \"How will this device be used at Google?\",\n \"answer\": \"Yes\"\n },\n {\n \"question\": \"Is this device going to be managed by Google or a third party?\",\n \"answer\": \"Google\",\n \"risk\": \"Limited\"\n },\n {\n \"question\": \"Will the third-party device administrator be able to grant access to authorized Google personnel upon request?\",\n \"answer\": \"No\",\n \"risk\": \"Limited\"\n },\n {\n \"question\": \"Which of the following statements are true about this device?\",\n \"answer\": [\n 0,\n 1,\n 2\n ],\n \"risk\": \"High\"\n },\n {\n \"question\": \"Does the network protocol assure server-to-client identity verification?\",\n \"answer\": \"Yes\",\n \"risk\": \"Limited\"\n },\n {\n \"question\": \"Click the statements that best describe the characteristics of this device.\",\n \"answer\": [\n 0\n ],\n \"risk\": \"High\"\n },\n {\n \"question\": \"Are any of the following statements true about this device?\",\n \"answer\": [\n 0\n ],\n \"risk\": \"High\"\n },\n {\n \"question\": \"Comments\",\n \"answer\": \"\"\n }\n ]\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "http://localhost:8000/profiles", + "protocol": "http", + "host": [ + "localhost" + ], + "port": "8000", + "path": [ + "profiles" + ] + } + }, + "status": "OK", + "code": 200, + "_postman_previewlanguage": "json", + "header": [ + { + "key": "Content-Type", + "value": "application/json", + "description": "", + "type": "text" + } + ], + "cookie": [], + "body": "{\n \"success\": \"Successfully updated that profile\"\n}" + }, + { + "name": "Create Profile (201)", + "originalRequest": { + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "\n{\n \"name\": \"New Profile\",\n \"version\": \"2.0\",\n \"created\": \"2024-10-08\",\n \"status\": \"Valid\",\n \"risk\": \"High\",\n \"questions\": [\n {\n \"question\": \"How will this device be used at Google?\",\n \"answer\": \"Yes\"\n },\n {\n \"question\": \"Is this device going to be managed by Google or a third party?\",\n \"answer\": \"Google\",\n \"risk\": \"Limited\"\n },\n {\n \"question\": \"Will the third-party device administrator be able to grant access to authorized Google personnel upon request?\",\n \"answer\": \"No\",\n \"risk\": \"Limited\"\n },\n {\n \"question\": \"Which of the following statements are true about this device?\",\n \"answer\": [\n 0,\n 1,\n 2\n ],\n \"risk\": \"High\"\n },\n {\n \"question\": \"Does the network protocol assure server-to-client identity verification?\",\n \"answer\": \"Yes\",\n \"risk\": \"Limited\"\n },\n {\n \"question\": \"Click the statements that best describe the characteristics of this device.\",\n \"answer\": [\n 0\n ],\n \"risk\": \"High\"\n },\n {\n \"question\": \"Are any of the following statements true about this device?\",\n \"answer\": [\n 0\n ],\n \"risk\": \"High\"\n },\n {\n \"question\": \"Comments\",\n \"answer\": \"\"\n }\n ]\n}\n", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "http://localhost:8000/profiles", + "protocol": "http", + "host": [ + "localhost" + ], + "port": "8000", + "path": [ + "profiles" + ] + } + }, + "status": "Created", + "code": 201, + "_postman_previewlanguage": "json", + "header": [ + { + "key": "Content-Type", + "value": "application/json", + "description": "", + "type": "text" + } + ], + "cookie": [], + "body": "{\n \"success\": \"Successfully created a new profile\"\n}" + }, + { + "name": "Invalid JSON (400)", + "originalRequest": { + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "{\n\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "http://localhost:8000/profiles", + "protocol": "http", + "host": [ + "localhost" + ], + "port": "8000", + "path": [ + "profiles" + ] + } + }, + "status": "Bad Request", + "code": 400, + "_postman_previewlanguage": "json", + "header": [ + { + "key": "Content-Type", + "value": "application/json", + "description": "", + "type": "text" + } + ], + "cookie": [], + "body": "{\n \"error\": \"Invalid request received\"\n}" + }, + { + "name": "Not Implemented (501)", + "originalRequest": { + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "[\n {\n \"name\": \"New Profile\",\n \"version\": \"2.0\",\n \"created\": \"2024-10-08\",\n \"status\": \"Valid\",\n \"risk\": \"High\",\n \"questions\": [\n {\n \"question\": \"How will this device be used at Google?\",\n \"answer\": \"Yes\"\n },\n {\n \"question\": \"Is this device going to be managed by Google or a third party?\",\n \"answer\": \"Google\",\n \"risk\": \"Limited\"\n },\n {\n \"question\": \"Will the third-party device administrator be able to grant access to authorized Google personnel upon request?\",\n \"answer\": \"No\",\n \"risk\": \"Limited\"\n },\n {\n \"question\": \"Which of the following statements are true about this device?\",\n \"answer\": [\n 0,\n 1,\n 2\n ],\n \"risk\": \"High\"\n },\n {\n \"question\": \"Does the network protocol assure server-to-client identity verification?\",\n \"answer\": \"Yes\",\n \"risk\": \"Limited\"\n },\n {\n \"question\": \"Click the statements that best describe the characteristics of this device.\",\n \"answer\": [\n 0\n ],\n \"risk\": \"High\"\n },\n {\n \"question\": \"Are any of the following statements true about this device?\",\n \"answer\": [\n 0\n ],\n \"risk\": \"High\"\n },\n {\n \"question\": \"Comments\",\n \"answer\": \"\"\n }\n ]\n }\n]", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "http://localhost:8000/profiles", + "protocol": "http", + "host": [ + "localhost" + ], + "port": "8000", + "path": [ + "profiles" + ] + } + }, + "status": "Not Implemented", + "code": 501, + "_postman_previewlanguage": "json", + "header": [ + { + "key": "Content-Type", + "value": "application/json", + "description": "", + "type": "text" + } + ], + "cookie": [], + "body": "{\n \"error\": \"Risk profiles are not available right now\"\n}" + } + ] + }, + { + "name": "Delete Profile", + "request": { + "method": "DELETE", + "header": [], + "body": { + "mode": "raw", + "raw": "{\n \"name\": \"New Profile\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "http://localhost:8000/profiles", + "protocol": "http", + "host": [ + "localhost" + ], + "port": "8000", + "path": [ + "profiles" + ] + }, + "description": "Delete an existing risk assessment questionaire response" + }, + "response": [ + { + "name": "Delete Profile (200)", + "originalRequest": { + "method": "DELETE", + "header": [], + "body": { + "mode": "raw", + "raw": "{\n \"name\": \"New Profile\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "http://localhost:8000/profiles", + "protocol": "http", + "host": [ + "localhost" + ], + "port": "8000", + "path": [ + "profiles" + ] + } + }, + "status": "OK", + "code": 200, + "_postman_previewlanguage": "json", + "header": [ + { + "key": "Content-Type", + "value": "application/json", + "description": "", + "type": "text" + } + ], + "cookie": [], + "body": "{\n \"success\": \"Successfully deleted that profile\"\n}" + }, + { + "name": "Not Found (404)", + "originalRequest": { + "method": "DELETE", + "header": [], + "body": { + "mode": "raw", + "raw": "{\n \"name\": \"non-existing profile\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "http://localhost:8000/profiles", + "protocol": "http", + "host": [ + "localhost" + ], + "port": "8000", + "path": [ + "profiles" + ] + } + }, + "status": "Not Found", + "code": 404, + "_postman_previewlanguage": "json", + "header": [ + { + "key": "Content-Type", + "value": "application/json", + "description": "", + "type": "text" + } + ], + "cookie": [], + "body": "{\n \"error\": \"A profile with that name could not be found\"\n}" + }, + { + "name": "Internal Serves Error (500)", + "originalRequest": { + "method": "DELETE", + "header": [], + "body": { + "mode": "raw", + "raw": "{\n \"name\": \"non-existing profile\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "http://localhost:8000/profiles", + "protocol": "http", + "host": [ + "localhost" + ], + "port": "8000", + "path": [ + "profiles" + ] + } + }, + "status": "Internal Server Error", + "code": 500, + "_postman_previewlanguage": "json", + "header": [ + { + "key": "Content-Type", + "value": "application/json", + "description": "", + "type": "text" + } + ], + "cookie": [], + "body": "{\n \"error\": \"An error occurred whilst deleting that profile\"\n}" + }, + { + "name": "Invalid JSON (400)", + "originalRequest": { + "method": "DELETE", + "header": [], + "body": { + "mode": "raw", + "raw": "{\n\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "http://localhost:8000/profiles", + "protocol": "http", + "host": [ + "localhost" + ], + "port": "8000", + "path": [ + "profiles" + ] + } + }, + "status": "Bad Request", + "code": 400, + "_postman_previewlanguage": "json", + "header": [ + { + "key": "Content-Type", + "value": "application/json", + "description": "", + "type": "text" + } + ], + "cookie": [], + "body": "{\n \"error\": \"Invalid request received\"\n}" + } + ] + }, + { + "name": "http://localhost:8000/profile/Test", + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\"mac_addr\": \"non_existing_mac_addr\"}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "http://localhost:8000/profile/Test", + "protocol": "http", + "host": [ + "localhost" + ], + "port": "8000", + "path": [ + "profile", + "Test" + ] + }, + "description": "Delete a root CA certificate" + }, "response": [ { "name": "Delete Certificate (200)", diff --git a/docs/get_started.md b/docs/get_started.md index 370f98a77..f0c39c284 100644 --- a/docs/get_started.md +++ b/docs/get_started.md @@ -73,7 +73,8 @@ Follow these steps to start Testrun: 1. Start Testrun with the command `sudo testrun` - To run Testrun in network-only mode (without running any tests), use the `--net-only` option. - To run Testrun with just one interface (connected to the device), use the `--single-intf` option. - + + **Note**: Tests that require an internet connection (e.g TLS client, DNS) will produce a non-compliant result. ## Test your device diff --git a/framework/python/src/api/api.py b/framework/python/src/api/api.py index 2bba5e62f..8febe03bb 100644 --- a/framework/python/src/api/api.py +++ b/framework/python/src/api/api.py @@ -43,6 +43,7 @@ DEVICE_ADDITIONAL_INFO_KEY = "additional_info" DEVICES_PATH = "local/devices" +PROFILES_PATH = "local/risk_profiles" RESOURCES_PATH = "resources" DEVICE_FOLDER_PATH = "devices" @@ -133,6 +134,9 @@ def __init__(self, testrun): self._router.add_api_route("/profiles", self.delete_profile, methods=["DELETE"]) + self._router.add_api_route("/profile/{profile_name}", + self.export_profile, + methods=["POST"]) # Allow all origins to access the API origins = ["*"] @@ -278,7 +282,8 @@ async def start_testrun(self, request: Request, response: Response): TestrunStatus.IN_PROGRESS, TestrunStatus.WAITING_FOR_DEVICE, TestrunStatus.MONITORING, - TestrunStatus.VALIDATING + TestrunStatus.VALIDATING, + TestrunStatus.STARTING ]: LOGGER.debug("Testrun is already running. Cannot start another instance") @@ -341,7 +346,8 @@ async def stop_testrun(self, response: Response): not in [TestrunStatus.IN_PROGRESS, TestrunStatus.WAITING_FOR_DEVICE, TestrunStatus.MONITORING, - TestrunStatus.VALIDATING]): + TestrunStatus.VALIDATING, + TestrunStatus.STARTING]): response.status_code = 404 return self._generate_msg(False, "Testrun is not currently running") @@ -359,8 +365,9 @@ def shutdown(self, response: Response): # Check that Testrun is not currently running if (self._session.get_status() not in [TestrunStatus.CANCELLED, - TestrunStatus.COMPLIANT, - TestrunStatus.NON_COMPLIANT, + TestrunStatus.PROCEED, + TestrunStatus.DO_NOT_PROCEED, + TestrunStatus.COMPLETE, TestrunStatus.IDLE ]): LOGGER.debug("Unable to shutdown Testrun as Testrun is in progress") @@ -521,12 +528,14 @@ async def delete_device(self, request: Request, response: Response): if (self._session.get_target_device() == device and self._session.get_status() not in [TestrunStatus.CANCELLED, - TestrunStatus.COMPLIANT, - TestrunStatus.NON_COMPLIANT + TestrunStatus.COMPLETE, + TestrunStatus.PROCEED, + TestrunStatus.DO_NOT_PROCEED ]): + response.status_code = 403 return self._generate_msg( - False, "Cannot delete this device whilst " + "it is being tested") + False, "Cannot delete this device whilst it is being tested") # Delete device self._testrun.delete_device(device) @@ -540,7 +549,7 @@ async def delete_device(self, request: Request, response: Response): LOGGER.error(e) response.status_code = 500 return self._generate_msg( - False, "An error occured whilst deleting " + "the device") + False, "An error occured whilst deleting the device") async def save_device(self, request: Request, response: Response): LOGGER.debug("Received device post request") @@ -644,8 +653,9 @@ async def edit_device(self, request: Request, response: Response): if (self._session.get_target_device() == device and self._session.get_status() not in [TestrunStatus.CANCELLED, - TestrunStatus.COMPLIANT, - TestrunStatus.NON_COMPLIANT + TestrunStatus.COMPLETE, + TestrunStatus.PROCEED, + TestrunStatus.DO_NOT_PROCEED ]): response.status_code = 403 return self._generate_msg( @@ -926,6 +936,93 @@ async def delete_profile(self, request: Request, response: Response): return self._generate_msg(True, "Successfully deleted that profile") + async def export_profile(self, request: Request, response: Response, + profile_name): + + LOGGER.debug(f"Received get profile request for {profile_name}") + + device = None + + try: + req_raw = (await request.body()).decode("UTF-8") + req_json = json.loads(req_raw) + + # Check if device mac_addr has been specified + if "mac_addr" in req_json and len(req_json.get("mac_addr")) > 0: + device_mac_addr = req_json.get("mac_addr") + device = self.get_session().get_device(device_mac_addr) + + # If device is not found return 404 + 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") + + except JSONDecodeError: + # Device not specified + pass + + # Retrieve the profile + profile = self._session.get_profile(profile_name) + + # If the profile not found return 404 + if profile is None: + LOGGER.info("Profile not found, returning 404") + response.status_code = 404 + return self._generate_msg(False, "Profile could not be found") + + # If device has been added into the body + if device: + + try: + + # Path where the PDF will be saved + profile_pdf_path = os.path.join(PROFILES_PATH, f"{profile_name}.pdf") + + # Write the PDF content + with open(profile_pdf_path, "wb") as f: + f.write(profile.to_pdf(device).getvalue()) + + # Return the pdf file + if os.path.isfile(profile_pdf_path): + return FileResponse(profile_pdf_path) + else: + LOGGER.info("Profile could not be found, returning 404") + response.status_code = 404 + return self._generate_msg(False, "Profile could not be found") + + # Exceptions if the PDF creation fails + except Exception as e: + LOGGER.error(f"Error creating the profile PDF: {e}") + response.status_code = 500 + return self._generate_msg(False, "Error retrieving the profile PDF") + + # If device not added into the body + else: + + try: + + # Path where the PDF will be saved + profile_pdf_path = os.path.join(PROFILES_PATH, f"{profile_name}.pdf") + + # Write the PDF content + with open(profile_pdf_path, "wb") as f: + f.write(profile.to_pdf_no_device().getvalue()) + + # Return the pdf file + if os.path.isfile(profile_pdf_path): + return FileResponse(profile_pdf_path) + else: + LOGGER.info("Profile could not be found, returning 404") + response.status_code = 404 + return self._generate_msg(False, "Profile could not be found") + + # Exceptions if the PDF creation fails + except Exception as e: + LOGGER.error(f"Error creating the profile PDF: {e}") + response.status_code = 500 + return self._generate_msg(False, "Error retrieving the profile PDF") + # Certificates def get_certs(self): LOGGER.debug("Received certs list request") diff --git a/framework/python/src/common/risk_profile.py b/framework/python/src/common/risk_profile.py index 559117aec..fe613f69a 100644 --- a/framework/python/src/common/risk_profile.py +++ b/framework/python/src/common/risk_profile.py @@ -351,7 +351,7 @@ def to_html(self, device): logo_img_b64 = base64.b64encode(f.read()).decode('utf-8') self._device = self._format_device_profile(device) - pages = self._generate_report_pages() + pages = self._generate_report_pages(device) return self._template.render( styles=self._template_styles, manufacturer=self._device.manufacturer, @@ -366,7 +366,33 @@ def to_html(self, device): created_at=self.created.strftime('%d.%m.%Y') ) - def _generate_report_pages(self): + def to_html_no_device(self): + """Returns the risk profile in HTML format without device info""" + + high_risk_message = '''The device has been assessed to be high + risk due to the nature of the answers provided + about the device functionality.''' + limited_risk_message = '''The device has been assessed to be limited risk + due to the nature of the answers provided about + the device functionality.''' + + with open(test_run_img_file, 'rb') as f: + logo_img_b64 = base64.b64encode(f.read()).decode('utf-8') + + pages = self._generate_report_pages() + return self._template.render( + styles=self._template_styles, + logo=logo_img_b64, + risk=self.risk, + high_risk_message=high_risk_message, + limited_risk_message=limited_risk_message, + pages=pages, + total_pages=len(pages), + version=self.version, + created_at=self.created.strftime('%d.%m.%Y') + ) + + def _generate_report_pages(self, device=None): # Text block heght block_height = 18 @@ -391,8 +417,11 @@ def _generate_report_pages(self): current_page = [] index = 1 - questions = deepcopy(self._device.additional_info) - questions.extend(self.questions) + questions = deepcopy(self.questions) + + if device: + questions = deepcopy(self._device.additional_info) + questions.extend(self.questions) for question in questions: @@ -456,6 +485,17 @@ def to_pdf(self, device): HTML(string=html).write_pdf(pdf_bytes) return pdf_bytes + def to_pdf_no_device(self): + """Returns the risk profile in PDF format without device info""" + + # Resolve the data as html first + html = self.to_html_no_device() + + # Convert HTML to PDF in memory using weasyprint + pdf_bytes = BytesIO() + HTML(string=html).write_pdf(pdf_bytes) + return pdf_bytes + # Adding risks to device profile questions def _format_device_profile(self, device): device_copy = deepcopy(device) diff --git a/framework/python/src/common/statuses.py b/framework/python/src/common/statuses.py index c7487868a..33516390e 100644 --- a/framework/python/src/common/statuses.py +++ b/framework/python/src/common/statuses.py @@ -15,18 +15,26 @@ class TestrunStatus: + """Statuses for overall testing""" IDLE = "Idle" + STARTING = "Starting" WAITING_FOR_DEVICE = "Waiting for Device" MONITORING = "Monitoring" IN_PROGRESS = "In Progress" CANCELLED = "Cancelled" - COMPLIANT = "Compliant" - NON_COMPLIANT = "Non-Compliant" STOPPING = "Stopping" VALIDATING = "Validating Network" + COMPLETE = "Complete" + PROCEED = "Proceed" + DO_NOT_PROCEED = "Do Not Proceed" +class TestrunResult: + """Statuses for the Testrun result""" + COMPLIANT = "Compliant" + NON_COMPLIANT = "Non-Compliant" class TestResult: + """Statuses for test results""" IN_PROGRESS = "In Progress" COMPLIANT = "Compliant" NON_COMPLIANT = "Non-Compliant" diff --git a/framework/python/src/common/testreport.py b/framework/python/src/common/testreport.py index 990a3427a..04d58a45c 100644 --- a/framework/python/src/common/testreport.py +++ b/framework/python/src/common/testreport.py @@ -16,14 +16,14 @@ from datetime import datetime from weasyprint import HTML from io import BytesIO -from common import util -from common.statuses import TestrunStatus +from common import util, logger +from common.statuses import TestrunStatus, TestrunResult +from test_orc import test_pack import base64 import os from test_orc.test_case import TestCase -from jinja2 import Environment, FileSystemLoader +from jinja2 import Environment, FileSystemLoader, BaseLoader from collections import OrderedDict -import re from bs4 import BeautifulSoup @@ -32,7 +32,12 @@ TESTS_FIRST_PAGE = 11 TESTS_PER_PAGE = 20 TEST_REPORT_STYLES = 'test_report_styles.css' -TEST_REPORT_TEMPLATE = 'test_report_template.html' +TEMPLATES_FOLDER = 'report_templates' +TEST_REPORT_TEMPLATE = 'report_template.html' +ICON = 'icon.png' + + +LOGGER = logger.get_logger('REPORT') # Locate parent directory current_dir = os.path.dirname(os.path.realpath(__file__)) @@ -45,35 +50,44 @@ report_resource_dir = os.path.join(root_dir, RESOURCES_DIR) test_run_img_file = os.path.join(report_resource_dir, 'testrun.png') -qualification_icon = os.path.join(report_resource_dir, 'qualification-icon.png') -pilot_icon = os.path.join(report_resource_dir, 'pilot-icon.png') class TestReport(): """Represents a previous Testrun report.""" def __init__(self, - status=TestrunStatus.NON_COMPLIANT, + result=TestrunResult.NON_COMPLIANT, started=None, finished=None, total_tests=0): self._device = {} self._mac_addr = None - self._status: str = status + self._status: TestrunStatus = TestrunStatus.COMPLETE + self._result: TestrunResult = result self._started = started self._finished = finished self._total_tests = total_tests self._results = [] self._module_reports = [] + self._module_templates = [] self._report_url = '' self._cur_page = 0 + def update_device_profile(self, additional_info): + self._device['device_profile'] = additional_info + def add_module_reports(self, module_reports): self._module_reports = module_reports + def add_module_templates(self, module_templates): + self._module_templates = module_templates + def get_status(self): return self._status + def get_result(self): + return self._result + def get_started(self): return self._started @@ -109,6 +123,7 @@ def to_json(self): report_json['mac_addr'] = self._mac_addr report_json['device'] = self._device report_json['status'] = self._status + report_json['result'] = self._result report_json['started'] = self._started.strftime(DATE_TIME_FORMAT) report_json['finished'] = self._finished.strftime(DATE_TIME_FORMAT) @@ -162,6 +177,10 @@ def from_json(self, json_file): self._device['device_profile'] = json_file['device']['additional_info'] self._status = json_file['status'] + + if 'result' in json_file: + self._result = json_file['result'] + self._started = datetime.strptime(json_file['started'], DATE_TIME_FORMAT) self._finished = datetime.strptime(json_file['finished'], DATE_TIME_FORMAT) @@ -201,9 +220,22 @@ def to_pdf(self): def to_html(self): + # Obtain test pack + current_test_pack = test_pack.TestPack.get_test_pack( + self._device['test_pack']) + template_folder = os.path.join(current_test_pack.path, + TEMPLATES_FOLDER) # Jinja template - template_env = Environment(loader=FileSystemLoader(report_resource_dir)) + template_env = Environment( + loader=FileSystemLoader( + template_folder + ), + trim_blocks=True, + lstrip_blocks=True + ) template = template_env.get_template(TEST_REPORT_TEMPLATE) + + # Report styles with open(os.path.join(report_resource_dir, TEST_REPORT_STYLES), 'r', @@ -215,13 +247,11 @@ def to_html(self): with open(test_run_img_file, 'rb') as f: logo = base64.b64encode(f.read()).decode('utf-8') - json_data=self.to_json() + # Icon + with open(os.path.join(template_folder, ICON), 'rb') as f: + icon = base64.b64encode(f.read()).decode('utf-8') - # Icons - with open(qualification_icon, 'rb') as f: - icon_qualification = base64.b64encode(f.read()).decode('utf-8') - with open(pilot_icon, 'rb') as f: - icon_pilot = base64.b64encode(f.read()).decode('utf-8') + json_data=self.to_json() # Convert the timestamp strings to datetime objects start_time = datetime.strptime(json_data['started'], '%Y-%m-%d %H:%M:%S') @@ -237,25 +267,25 @@ def to_html(self): successful_tests += 1 # Obtain the steps to resolve - steps_to_resolve = self._get_steps_to_resolve(json_data) - - # Obtain optional recommendations - optional_steps_to_resolve = self._get_optional_steps_to_resolve(json_data) + logic = current_test_pack.get_logic() + steps_to_resolve_ = logic.get_steps_to_resolve(json_data) - module_reports = self._get_module_pages() + module_reports = self._module_reports + env_module = Environment(loader=BaseLoader()) pages_num = self._pages_num(json_data) - total_pages = pages_num + len(module_reports) + 1 - if len(steps_to_resolve) > 0: - total_pages += 1 - if (len(optional_steps_to_resolve) > 0 - and json_data['device']['test_pack'] == 'Pilot Assessment' - ): - total_pages += 1 - - return template.render(styles=styles, + module_templates = [ + env_module.from_string(s).render( + name=current_test_pack.name, + device=json_data['device'], + logo=logo, + icon=icon, + version=self._version, + ) for s in self._module_templates + ] + + return self._add_page_counter(template.render(styles=styles, logo=logo, - icon_qualification=icon_qualification, - icon_pilot=icon_pilot, + icon=icon, version=self._version, json_data=json_data, device=json_data['device'], @@ -265,14 +295,22 @@ def to_html(self): successful_tests=successful_tests, total_tests=self._total_tests, test_results=json_data['tests']['results'], - steps_to_resolve=steps_to_resolve, - optional_steps_to_resolve=optional_steps_to_resolve, + steps_to_resolve=steps_to_resolve_, module_reports=module_reports, pages_num=pages_num, - total_pages=total_pages, tests_first_page=TESTS_FIRST_PAGE, tests_per_page=TESTS_PER_PAGE, - ) + module_templates=module_templates + )) + + def _add_page_counter(self, html): + # Add page nums and total page + soup = BeautifulSoup(html, features='html5lib') + page_index_divs = soup.find_all('div', class_='page-index') + total_pages = len(page_index_divs) + for index, div in enumerate(page_index_divs): + div.string = f'Page {index+1}/{total_pages}' + return str(soup) def _pages_num(self, json_data): @@ -312,136 +350,3 @@ def _device_modules(self, device): reverse=True) ) return sorted_modules - - def _get_steps_to_resolve(self, json_data): - tests_with_recommendations = [] - - # Collect all tests with recommendations - for test in json_data['tests']['results']: - if 'recommendations' in test: - tests_with_recommendations.append(test) - - return tests_with_recommendations - - def _get_optional_steps_to_resolve(self, json_data): - tests_with_recommendations = [] - - # Collect all tests with recommendations - for test in json_data['tests']['results']: - if 'optional_recommendations' in test: - tests_with_recommendations.append(test) - - return tests_with_recommendations - - - def _split_module_report_to_pages(self, reports): - """Split report to pages by headers""" - reports_transformed = [] - - for report in reports: - if len(re.findall(' 1: - indices = [] - index = report.find('
= content_max_size: - str_el = '' - if current_size > (content_max_size - 85 - module_summary_padding): - rows = el.findChildren('tr', recursive=True) - table_header = str(rows.pop(0)) - table_1 = table_2 = f''' -
- {table_header}''' - rows_count = (content_max_size - 85 - module_summary_padding) // 42 - table_1 += ''.join(map(str, rows[:rows_count-1])) - table_1 += '
' - table_2 += ''.join(map(str, rows[rows_count-1:])) - table_2 += '' - page_content += table_1 - reports.append(page_content) - page_content = table_2 - current_size = len(rows[rows_count:]) * 42 - else: - if el.name == 'table': - el_header = children[index-1] - if el_header.name.startswith('h'): - page_content = ''.join(page_content.rsplit(str(el_header), 1)) - str_el = str(el_header) + str(el) - content_size = current_size + 50 - else: - str_el = str(el) - content_size = current_size - reports.append(page_content) - page_content = str_el - else: - page_content += str(el) - content_size += current_size - reports.append(page_content) - return reports diff --git a/framework/python/src/core/session.py b/framework/python/src/core/session.py index 17d583219..baa7db056 100644 --- a/framework/python/src/core/session.py +++ b/framework/python/src/core/session.py @@ -20,7 +20,7 @@ from fastapi.encoders import jsonable_encoder from common import util, logger, mqtt from common.risk_profile import RiskProfile -from common.statuses import TestrunStatus, TestResult +from common.statuses import TestrunStatus, TestResult, TestrunResult from net_orc.ip_control import IPControl # Certificate dependencies @@ -85,6 +85,7 @@ def __init__(self, root_dir): self._root_dir = root_dir self._status = TestrunStatus.IDLE + self._result = None self._description = None # Target test device @@ -100,6 +101,9 @@ def __init__(self, root_dir): # All historical reports self._module_reports = [] + # Module report templates + self._module_templates = [] + # Parameters specified when starting Testrun self._runtime_params = [] @@ -156,7 +160,7 @@ def __init__(self, root_dir): def start(self): self.reset() - self._status = TestrunStatus.WAITING_FOR_DEVICE + self._status = TestrunStatus.STARTING self._started = datetime.datetime.now() def get_started(self): @@ -383,12 +387,18 @@ def get_ipv4_subnet(self): def get_ipv6_subnet(self): return self._ipv6_subnet - def get_status(self): + def get_status(self) -> TestrunStatus: return self._status - def set_status(self, status): + def set_status(self, status: TestrunStatus): self._status = status + def get_result(self) -> TestrunResult: + return self._result + + def set_result(self, result: TestrunResult): + self._result = result + def set_description(self, desc: str): self._description = desc @@ -398,6 +408,9 @@ def get_test_results(self): def get_module_reports(self): return self._module_reports + def get_module_templates(self): + return self._module_templates + def get_report_tests(self): """Returns the current test results in JSON-friendly format (in Python dictionary)""" @@ -467,6 +480,9 @@ def set_test_result_error(self, result, description=None): def add_module_report(self, module_report): self._module_reports.append(module_report) + def add_module_template(self, module_template): + self._module_templates.append(module_template) + def get_all_reports(self): reports = [] @@ -539,6 +555,9 @@ def _load_profiles(self): for risk_profile_file in os.listdir( os.path.join(self._root_dir, PROFILES_DIR)): + if not risk_profile_file.endswith('.json'): + continue + LOGGER.debug(f'Discovered profile {risk_profile_file}') # Open the risk profile file @@ -660,7 +679,7 @@ def _remove_invalid_questions(self, questions): valid_questions.append(question) else: - LOGGER.debug(f'Removed unrecognised question: {question["question"]}') + LOGGER.debug(f'Removed unrecognised question: {question["question"]}') # pylint: disable=W1405 # Return the list of valid questions return valid_questions @@ -683,6 +702,15 @@ def validate_profile_json(self, profile_json): LOGGER.error('Name field left empty') return False + # Check if profile name has special characters + for field in ['name', 'rename']: + profile_name = profile_json.get(field) + if profile_name: + for char in profile_name: + if char in r"\<>?/:;@''][=^": + LOGGER.error('Profile name should not contain special characters') + return False + # Error handling if 'questions' not in request if 'questions' not in profile_json and valid: LOGGER.error('Missing "questions" field in profile') @@ -706,7 +734,7 @@ def validate_profile_json(self, profile_json): question.get('question')) if format_q is None: - LOGGER.error(f'Unrecognised question: {question.get("question")}') + LOGGER.error(f'Unrecognised question: {question.get("question")}') # pylint: disable=W1405 # Just ignore additional questions continue @@ -786,11 +814,13 @@ def delete_profile(self, profile): def reset(self): self.set_status(TestrunStatus.IDLE) + self.set_result(None) self.set_description(None) self.set_target_device(None) self._report_url = None self._total_tests = 0 self._module_reports = [] + self._module_templates = [] self._results = [] self._started = None self._finished = None @@ -816,6 +846,9 @@ def to_json(self): 'tests': results } + if self.get_result() is not None: + session_json['result'] = self.get_result() + if self._report_url is not None: session_json['report'] = self.get_report_url() diff --git a/framework/python/src/core/testrun.py b/framework/python/src/core/testrun.py index 5d4e78e9c..f3fe33c80 100644 --- a/framework/python/src/core/testrun.py +++ b/framework/python/src/core/testrun.py @@ -22,6 +22,8 @@ import signal import sys import time +import docker.errors + from common import logger, util, mqtt from common.device import Device from common.testreport import TestReport @@ -32,8 +34,6 @@ from net_orc import network_orchestrator as net_orc from test_orc import test_orchestrator as test_orc -from docker.errors import ImageNotFound - LOGGER = logger.get_logger('testrun') DEFAULT_CONFIG_FILE = 'local/system.json' @@ -375,6 +375,7 @@ def start(self): self._device_stable, [NetworkEvent.DEVICE_STABLE]) self.get_net_orc().start_listener() + self.get_session().set_status(TestrunStatus.WAITING_FOR_DEVICE) LOGGER.info('Waiting for devices on the network...') # Keep application running until stopped @@ -463,6 +464,8 @@ def _device_discovered(self, mac_addr): if device is not None: if mac_addr != device.mac_addr: + LOGGER.info(f'Found device with mac addr: {mac_addr} but was ignored') + LOGGER.info(f'Expected device mac address is {device.mac_addr}') # Ignore discovered device because it is not the target device return else: @@ -485,10 +488,7 @@ def _device_stable(self, mac_addr): LOGGER.info(f'Device with mac address {mac_addr} is ready for testing.') self._set_status(TestrunStatus.IN_PROGRESS) - result = self._test_orc.run_test_modules() - - if result is not None: - self._set_status(result) + self._test_orc.run_test_modules() self._stop_network() @@ -513,7 +513,7 @@ def start_ui(self): hostname='testrun.io', detach=True, ports={'80': 8080}) - except ImageNotFound as ie: + except docker.errors.ImageNotFound as ie: LOGGER.error('An error occured whilst starting the UI. ' + 'Please investigate and try again.') LOGGER.error(ie) @@ -529,6 +529,11 @@ def _stop_ui(self): container = client.containers.get('tr-ui') if container is not None: container.kill() + # If the container has been started without auto-remove flag remove it + try: + container.remove() + except docker.errors.APIError: + pass except docker.errors.NotFound: pass @@ -549,7 +554,7 @@ def start_ws(self): '9001': 9001, '1883': 1883 }) - except ImageNotFound as ie: + except docker.errors.ImageNotFound as ie: LOGGER.error('An error occured whilst starting the websockets server. ' + 'Please investigate and try again.') LOGGER.error(ie) @@ -562,5 +567,11 @@ def _stop_ws(self): container = client.containers.get('tr-ws') if container is not None: container.kill() + # If the container has been started without auto-remove flag remove it + try: + container.remove() + except docker.errors.APIError: + pass + except docker.errors.NotFound: pass diff --git a/framework/python/src/net_orc/network_orchestrator.py b/framework/python/src/net_orc/network_orchestrator.py index 37858c4e1..25e036ef7 100644 --- a/framework/python/src/net_orc/network_orchestrator.py +++ b/framework/python/src/net_orc/network_orchestrator.py @@ -713,7 +713,8 @@ def internet_conn_checker(self, mqtt_client: mqtt.MQTT, topic: str): if self.get_session().get_status() not in [ TestrunStatus.WAITING_FOR_DEVICE, TestrunStatus.MONITORING, - TestrunStatus.IN_PROGRESS + TestrunStatus.IN_PROGRESS, + TestrunStatus.STARTING ]: message['connection'] = None diff --git a/framework/python/src/test_orc/test_orchestrator.py b/framework/python/src/test_orc/test_orchestrator.py index 8e275b2cf..4334349c6 100644 --- a/framework/python/src/test_orc/test_orchestrator.py +++ b/framework/python/src/test_orc/test_orchestrator.py @@ -22,7 +22,7 @@ from datetime import datetime from common import logger, util from common.testreport import TestReport -from common.statuses import TestrunStatus, TestResult +from common.statuses import TestrunStatus, TestrunResult, TestResult from core.docker.test_docker_module import TestModule from test_orc.test_case import TestCase from test_orc.test_pack import TestPack @@ -37,6 +37,8 @@ RUNTIME_TEST_DIR = os.path.join(RUNTIME_DIR, "test") TEST_PACKS_DIR = os.path.join(RESOURCES_DIR, "test_packs") +TEST_PACK_CONFIG_FILE = "config.json" +TEST_PACK_LOGIC_FILE = "test_pack.py" TEST_MODULES_DIR = "modules/test" MODULE_CONFIG = "conf/module_config.json" @@ -177,6 +179,7 @@ def run_test_modules(self): generated_report_json = self._generate_report() report.from_json(generated_report_json) report.add_module_reports(self.get_session().get_module_reports()) + report.add_module_templates(self.get_session().get_module_templates()) device.add_report(report) self._write_reports(report) @@ -189,13 +192,17 @@ def run_test_modules(self): # Default message is empty (better than an error message). # This should never be shown message: str = "" - if report.get_status() == TestrunStatus.COMPLIANT: + if report.get_result() == TestrunResult.COMPLIANT: message = test_pack.get_message("compliant_description") - elif report.get_status() == TestrunStatus.NON_COMPLIANT: + elif report.get_result() == TestrunResult.NON_COMPLIANT: message = test_pack.get_message("non_compliant_description") self.get_session().set_description(message) + # Set result and status at the end + self.get_session().set_result(report.get_result()) + self.get_session().set_status(report.get_status()) + # Move testing output from runtime to local device folder self._timestamp_results(device) @@ -204,8 +211,6 @@ def run_test_modules(self): LOGGER.debug("Old test results cleaned") - return report.get_status() - def _write_reports(self, test_report): out_dir = os.path.join( @@ -230,44 +235,40 @@ def _write_reports(self, test_report): def _generate_report(self): + device = self.get_session().get_target_device() + test_pack_name = device.test_pack + test_pack = self.get_test_pack(test_pack_name) + report = {} report["testrun"] = {"version": self.get_session().get_version()} - report["mac_addr"] = self.get_session().get_target_device().mac_addr - report["device"] = self.get_session().get_target_device().to_dict() + report["mac_addr"] = device.mac_addr + report["device"] = device.to_dict() report["started"] = self.get_session().get_started().strftime( "%Y-%m-%d %H:%M:%S") report["finished"] = self.get_session().get_finished().strftime( "%Y-%m-%d %H:%M:%S") - report["status"] = self._calculate_result() + + # Update the result + result = test_pack.get_logic().calculate_result( + self.get_session().get_test_results()) + report["result"] = result + + # Update the status + status = test_pack.get_logic().calculate_status( + result, + self.get_session().get_test_results()) + report["status"] = status + report["tests"] = self.get_session().get_report_tests() report["report"] = ( self._api_url + "/" + SAVED_DEVICE_REPORTS.replace( "{device_folder}", - self.get_session().get_target_device().device_folder) + + device.device_folder) + self.get_session().get_started().strftime("%Y-%m-%dT%H:%M:%S")) return report - def _calculate_result(self): - result = TestResult.COMPLIANT - for test_result in self.get_session().get_test_results(): - - # Check Required tests - if (test_result.required_result.lower() == "required" - and test_result.result not in [ - TestResult.COMPLIANT, - TestResult.ERROR - ]): - result = TestResult.NON_COMPLIANT - - # Check Required if Applicable tests - elif (test_result.required_result.lower() == "required if applicable" - and test_result.result == TestResult.NON_COMPLIANT): - result = TestResult.NON_COMPLIANT - - return result - def _cleanup_old_test_results(self, device): if device.max_device_reports is not None: @@ -348,7 +349,7 @@ def _timestamp_results(self, device): return completed_results_dir - def zip_results(self, device, timestamp, profile): + def zip_results(self, device, timestamp: str, profile): try: LOGGER.debug("Archiving test results") @@ -357,6 +358,50 @@ def zip_results(self, device, timestamp, profile): LOCAL_DEVICE_REPORTS.replace("{device_folder}", device.device_folder), timestamp) + # Report file path + report_path = os.path.join( + LOCAL_DEVICE_REPORTS.replace("{device_folder}", device.device_folder), + timestamp, "test", device.mac_addr.replace(":", "")) + + # Parse string timestamp + date_timestamp: datetime.datetime = datetime.strptime( + timestamp, "%Y-%m-%dT%H:%M:%S") + + # Find the report + test_report = None + for report in device.get_reports(): + if report.get_started() == date_timestamp: + test_report = report + + # This should not happen as the timestamp is checked in api.py first + if test_report is None: + return None + + # Copy the original report for comparison + original_report = copy.deepcopy(test_report) + + # Update the report with 'additional_info' field + test_report.update_device_profile(device.additional_info) + + # Overwrite report only if additional_info has been updated + if original_report.to_json() != test_report.to_json(): + + # Write the json report + with open(os.path.join(report_path, "report.json"), + "w", + encoding="utf-8") as f: + json.dump(test_report.to_json(), f, indent=2) + + # Write the html report + with open(os.path.join(report_path, "report.html"), + "w", + encoding="utf-8") as f: + f.write(test_report.to_html()) + + # Write the pdf report + with open(os.path.join(report_path, "report.pdf"), "wb") as f: + f.write(test_report.to_pdf().getvalue()) + # Define temp directory to store files before zipping results_dir = os.path.join(f"/tmp/testrun/{time.time()}") @@ -487,6 +532,20 @@ def _run_test_module(self, module): if time.time() > test_module_timeout: LOGGER.error("Module timeout exceeded, killing module: " + module.name) module.stop(kill=True) + + # Update the test description for the tests + for test in module.tests: + + # Copy the test so we don't alter the source + test_copy = copy.deepcopy(test) + + # Update test + test_copy.result = TestResult.ERROR + test_copy.description = ( + "Module timeout exceeded. Try increasing the timeout value." + ) + self.get_session().add_test_result(test_copy) + break # Save all container logs to file @@ -511,14 +570,13 @@ def _run_test_module(self, module): for test_result in module_results: # Convert dict from json into TestCase object - test_case = TestCase( - name=test_result["name"], - result=test_result["result"], - description=test_result["description"]) + test_case = TestCase(name=test_result["name"], + result=test_result["result"], + description=test_result["description"]) # Add steps to resolve if test is non-compliant - if (test_case.result == TestResult.NON_COMPLIANT and - "recommendations" in test_result): + if (test_case.result == TestResult.NON_COMPLIANT + and "recommendations" in test_result): test_case.recommendations = test_result["recommendations"] else: test_case.recommendations = [] @@ -549,8 +607,17 @@ def _run_test_module(self, module): self.get_session().add_module_report(module_report) except (FileNotFoundError, PermissionError): LOGGER.debug("Test module did not produce a html module report") + # Get the Jinja report + jinja_file = f"{module.container_runtime_dir}/{module.name}_report.j2.html" + try: + with open(jinja_file, "r", encoding="utf-8") as f: + module_template = f.read() + LOGGER.debug(f"Adding module template for module {module.name}") + self.get_session().add_module_template(module_template) + except (FileNotFoundError, PermissionError): + LOGGER.debug("Test module did not produce a module template") - LOGGER.info(f"Test module {module.name} has finished") + # LOGGER.info(f"Test module {module.name} has finished") def _get_container_logs(self, log_stream): """Resolve all current log data in the containers log_stream @@ -595,23 +662,7 @@ def _get_module_container(self, module): def _load_test_packs(self): - for test_pack_file in os.listdir(TEST_PACKS_DIR): - - LOGGER.debug(f"Loading test pack {test_pack_file}") - - with open(os.path.join( - self._root_path, - TEST_PACKS_DIR, - test_pack_file), encoding="utf-8") as f: - test_pack_json = json.load(f) - - test_pack: TestPack = TestPack( - name = test_pack_json["name"], - tests = test_pack_json["tests"], - language = test_pack_json["language"] - ) - - self._test_packs.append(test_pack) + self._test_packs = TestPack.get_test_packs() def _load_test_modules(self): """Load network modules from module_config.json.""" @@ -626,6 +677,12 @@ def _load_test_modules(self): # corrupted during DHCP changes in the conn module if "protocol" in module_dirs: module_dirs.insert(0, module_dirs.pop(module_dirs.index("protocol"))) + # Check if the directory services exists and move it higher in the index + # so it always runs before connection. Connection may cause too many + # DHCP changes causing nmap to use wrong IP during scan + if "services" in module_dirs and "conn" in module_dirs: + module_dirs.insert(module_dirs.index("conn"), + module_dirs.pop(module_dirs.index("services"))) for module_dir in module_dirs: @@ -656,9 +713,7 @@ def _load_test_module(self, module_dir): module_conf_file = os.path.join(self._root_path, modules_dir, module_dir, MODULE_CONFIG) - module = TestModule(module_conf_file, - self, - self.get_session(), + module = TestModule(module_conf_file, self, self.get_session(), extra_hosts) if module.depends_on is not None: self._load_test_module(module.depends_on) @@ -670,10 +725,7 @@ def get_test_packs(self) -> List[TestPack]: return self._test_packs def get_test_pack(self, name: str) -> TestPack: - for test_pack in self._test_packs: - if test_pack.name.lower() == name.lower(): - return test_pack - return None + return TestPack.get_test_pack(name, self._test_packs) def _stop_modules(self, kill=False): LOGGER.info("Stopping test modules") @@ -719,6 +771,5 @@ def _set_test_modules_error(self, current_test): start_idx = current_test if i == self._current_module else 0 for j in range(start_idx, len(self._test_modules_running[i].tests)): self.get_session().set_test_result_error( - self._test_modules_running[i].tests[j], - "Test did not run, the device was disconnected" - ) + self._test_modules_running[i].tests[j], + "Test did not run, the device was disconnected") diff --git a/framework/python/src/test_orc/test_pack.py b/framework/python/src/test_orc/test_pack.py index a2e7c5f97..eb9c852c2 100644 --- a/framework/python/src/test_orc/test_pack.py +++ b/framework/python/src/test_orc/test_pack.py @@ -13,9 +13,20 @@ # limitations under the License. """Represents a testing pack.""" +from types import ModuleType from typing import List, Dict from dataclasses import dataclass, field from collections import defaultdict +import os +import sys +import json +import importlib + +RESOURCES_DIR = "resources" + +TEST_PACKS_DIR = os.path.join(RESOURCES_DIR, "test_packs") +TEST_PACK_CONFIG_FILE = "config.json" +TEST_PACK_LOGIC_FILE = "test_pack.py" @dataclass @@ -26,6 +37,8 @@ class TestPack: # pylint: disable=too-few-public-methods,too-many-instance-attr description: str = "" tests: List[dict] = field(default_factory=lambda: []) language: Dict = field(default_factory=lambda: defaultdict(dict)) + pack_logic: ModuleType = None + path: str = "" def get_test(self, test_name: str) -> str: """Get details of a test from the test pack""" @@ -44,6 +57,9 @@ def get_required_result(self, test_name: str) -> str: return "Informational" + def get_logic(self): + return self.pack_logic + def get_message(self, name: str) -> str: if name in self.language: return self.language[name] @@ -56,3 +72,62 @@ def to_dict(self): "tests": self.tests, "language": self.language } + + @staticmethod + def load_logic(source, module_name=None): + """Reads file source and loads it as a module""" + + spec = importlib.util.spec_from_file_location(module_name, source) + module = importlib.util.module_from_spec(spec) + + # Add the module to sys.modules + sys.modules[module_name] = module + + # Execute the module + spec.loader.exec_module(module) + + return module + + @staticmethod + def get_test_packs() -> List["TestPack"]: + + root_path = os.path.dirname( + os.path.dirname( + os.path.dirname( + os.path.dirname(os.path.dirname(os.path.realpath(__file__)))))) + test_packs = [] + + for test_pack_folder in os.listdir(TEST_PACKS_DIR): + test_pack_path = os.path.join( + root_path, + TEST_PACKS_DIR, + test_pack_folder + ) + + with open(os.path.join( + test_pack_path, + TEST_PACK_CONFIG_FILE), encoding="utf-8") as f: + test_pack_json = json.load(f) + + test_pack: TestPack = TestPack( + name = test_pack_json["name"], + tests = test_pack_json["tests"], + language = test_pack_json["language"], + pack_logic = TestPack.load_logic( + os.path.join(test_pack_path, TEST_PACK_LOGIC_FILE), + "test_pack_" + test_pack_folder + "_logic" + ), + path = test_pack_path + ) + test_packs.append(test_pack) + + return test_packs + + @staticmethod + def get_test_pack(name: str, test_packs: List["TestPack"]=None) -> "TestPack": + if test_packs is None: + test_packs = TestPack.get_test_packs() + for test_pack in test_packs: + if test_pack.name.lower() == name.lower(): + return test_pack + return None diff --git a/framework/requirements.txt b/framework/requirements.txt index 6299ef6d4..b5726aca2 100644 --- a/framework/requirements.txt +++ b/framework/requirements.txt @@ -28,7 +28,7 @@ responses==0.25.3 markdown==3.5.2 # Requirements for the session -cryptography==44.0.0 +cryptography==44.0.1 pytz==2024.1 # Requirements for the risk profile @@ -41,5 +41,5 @@ paho-mqtt==2.1.0 APScheduler==3.10.4 # Requirements for reports generation -Jinja2==3.1.4 +Jinja2==3.1.5 beautifulsoup4==4.12.3 diff --git a/make/DEBIAN/control b/make/DEBIAN/control index d4024828e..2a0235082 100644 --- a/make/DEBIAN/control +++ b/make/DEBIAN/control @@ -1,5 +1,5 @@ Package: Testrun -Version: 2.1 +Version: 2.1.1 Architecture: amd64 Maintainer: Google Homepage: https://github.com/google/testrun diff --git a/modules/test/base/base.Dockerfile b/modules/test/base/base.Dockerfile index 253270ea9..7a82301a7 100644 --- a/modules/test/base/base.Dockerfile +++ b/modules/test/base/base.Dockerfile @@ -80,5 +80,13 @@ COPY --from=builder /usr/local/etc/oui.txt /usr/local/etc/oui.txt # Activate the virtual environment by setting the PATH ENV PATH="/opt/venv/bin:$PATH" +# Common resource folder +ENV REPORT_TEMPLATE_PATH=/testrun/resources +# Jinja base template +ENV BASE_TEMPLATE_FILE=module_report_base.jinja2 + +# Copy base template +COPY resources/report/$BASE_TEMPLATE_FILE $REPORT_TEMPLATE_PATH/ + # Start the test module ENTRYPOINT [ "/testrun/bin/start" ] \ No newline at end of file diff --git a/modules/test/base/python/requirements.txt b/modules/test/base/python/requirements.txt index 0ed8a792d..5faa12fc8 100644 --- a/modules/test/base/python/requirements.txt +++ b/modules/test/base/python/requirements.txt @@ -6,4 +6,7 @@ protobuf==5.28.0 # User defined packages grpcio==1.67.1 grpcio-tools==1.67.1 -netifaces==0.11.0 \ No newline at end of file +netifaces==0.11.0 + +# Requirements for reports generation +Jinja2==3.1.5 diff --git a/modules/test/base/python/src/test_module.py b/modules/test/base/python/src/test_module.py index 21de78143..42ee3ff85 100644 --- a/modules/test/base/python/src/test_module.py +++ b/modules/test/base/python/src/test_module.py @@ -42,6 +42,8 @@ def __init__(self, self._ipv6_subnet = os.environ.get('IPV6_SUBNET', '') self._dev_iface_mac = os.environ.get('DEV_IFACE_MAC', '') self._device_test_pack = json.loads(os.environ.get('DEVICE_TEST_PACK', '')) + self._report_template_folder = os.environ.get('REPORT_TEMPLATE_PATH') + self._base_template_file=os.environ.get('BASE_TEMPLATE_FILE') self._add_logger(log_name=log_name) self._config = self._read_config( conf_file=conf_file if conf_file is not None else CONF_FILE) @@ -137,14 +139,14 @@ def run_tests(self): else: result = getattr(self, test_method_name)() except Exception as e: # pylint: disable=W0718 - LOGGER.error(f'An error occurred whilst running {test["name"]}') + LOGGER.error(f'An error occurred whilst running {test["name"]}') # pylint: disable=W1405 LOGGER.error(e) traceback.print_exc() else: - LOGGER.error(f'Test {test["name"]} has not been implemented') + LOGGER.error(f'Test {test["name"]} has not been implemented') # pylint: disable=W1405 result = TestResult.ERROR, 'This test could not be found' else: - LOGGER.debug(f'Test {test["name"]} is disabled') + LOGGER.debug(f'Test {test["name"]} is disabled') # pylint: disable=W1405 result = (TestResult.DISABLED, 'This test did not run because it is disabled') diff --git a/modules/test/conn/python/requirements.txt b/modules/test/conn/python/requirements.txt index 4075f79c9..d0f5db19a 100644 --- a/modules/test/conn/python/requirements.txt +++ b/modules/test/conn/python/requirements.txt @@ -2,7 +2,7 @@ # Package dependencies should always be defined before the user defined # packages to prevent auto-upgrades of stable dependencies cffi==1.17.1 -cryptography==43.0.3 +cryptography==44.0.1 pycparser==2.22 six==1.16.0 diff --git a/modules/test/dns/dns.Dockerfile b/modules/test/dns/dns.Dockerfile index 461e87899..53f8f31f8 100644 --- a/modules/test/dns/dns.Dockerfile +++ b/modules/test/dns/dns.Dockerfile @@ -31,4 +31,8 @@ COPY $MODULE_DIR/conf /testrun/conf COPY $MODULE_DIR/bin /testrun/bin # Copy over all python files -COPY $MODULE_DIR/python /testrun/python \ No newline at end of file +COPY $MODULE_DIR/python /testrun/python + +# Copy Jinja template +COPY $MODULE_DIR/resources/report_template.jinja2 $REPORT_TEMPLATE_PATH/ + diff --git a/modules/test/dns/python/src/dns_module.py b/modules/test/dns/python/src/dns_module.py index fe244f0a7..c1db567ae 100644 --- a/modules/test/dns/python/src/dns_module.py +++ b/modules/test/dns/python/src/dns_module.py @@ -17,19 +17,21 @@ from test_module import TestModule import os from collections import Counter +from jinja2 import Environment, FileSystemLoader LOG_NAME = 'test_dns' -MODULE_REPORT_FILE_NAME = 'dns_report.html' +MODULE_REPORT_FILE_NAME = 'dns_report.j2.html' DNS_SERVER_CAPTURE_FILE = '/runtime/network/dns.pcap' STARTUP_CAPTURE_FILE = '/runtime/device/startup.pcap' MONITOR_CAPTURE_FILE = '/runtime/device/monitor.pcap' LOGGER = None +REPORT_TEMPLATE_FILE = 'report_template.jinja2' class DNSModule(TestModule): """DNS Test module""" - def __init__(self, + def __init__(self, # pylint: disable=R0917 module, conf_file=None, results_dir=None, @@ -48,11 +50,37 @@ def __init__(self, LOGGER = self._get_logger() def generate_module_report(self): + # Load Jinja2 template + page_max_height = 850 + header_height = 48 + summary_height = 135 + row_height = 44 + loader=FileSystemLoader(self._report_template_folder) + template = Environment( + loader=loader, + trim_blocks=True, + lstrip_blocks=True + ).get_template(REPORT_TEMPLATE_FILE) + module_header='DNS Module' + # Summary table headers + summary_headers = [ + 'Requests to local DNS server', + 'Requests to external DNS servers', + 'Total DNS requests', + 'Total DNS responses', + ] + # Module data Headers + module_data_headers = [ + 'Source', + 'Destination', + 'Resolved IP', + 'Type', + 'URL', + 'Count', + ] # Extract DNS data from the pcap file dns_table_data = self.extract_dns_data() - html_content = '

DNS Module

' - # Set the summary variables local_requests = sum( 1 for row in dns_table_data @@ -67,79 +95,59 @@ def generate_module_report(self): if row['Type'] == 'Response') # Add summary table - html_content += (f''' - - - - - - - - - - - - - - - - -
Requests to local DNS serverRequests to external DNS serversTotal DNS requestsTotal DNS responses
{local_requests}{external_requests}{total_requests}{total_responses}
- ''') - + summary_data = [ + local_requests, + external_requests, + total_requests, + total_responses, + ] + + module_data = [] if (total_requests + total_responses) > 0: - table_content = ''' - - - - - - - - - - - - ''' - # Count unique combinations counter = Counter((row['Source'], row['Destination'], row['ResolvedIP'], row['Type'], row['Data']) for row in dns_table_data) # Generate the HTML table with the count column for (src, dst, res_ip, typ, dat), count in counter.items(): - table_content += f''' - - - - - - - - ''' - - table_content += ''' - -
SourceDestinationResolved IPTypeURLCount
{src}{dst}{res_ip}{typ}{dat}{count}
''' - - html_content += table_content - - else: - html_content += (''' -
-
- No DNS traffic detected from the device -
''') - - LOGGER.debug('Module report:\n' + html_content) + module_data.append({ + 'src': src, + 'dst': dst, + 'res_ip': res_ip, + 'typ': typ, + 'dat': dat, + 'count': count, + }) + # Handling the possible table split + table_height = (len(module_data) + 1) * row_height + page_useful_space = page_max_height - header_height - summary_height + pages = table_height // (page_useful_space) + rows_on_page = (page_useful_space) // row_height + start = 0 + report_html = '' + for page in range(pages+1): + end = start + min(len(module_data), rows_on_page) + module_header_repr = module_header if page == 0 else None + page_html = template.render( + base_template=self._base_template_file, + module_header=module_header_repr, + summary_headers=summary_headers, + summary_data=summary_data, + module_data_headers=module_data_headers, + module_data=module_data[start:end] + ) + report_html += page_html + start = end + + LOGGER.debug('Module report:\n' + report_html) # Use os.path.join to create the complete file path report_path = os.path.join(self._results_dir, MODULE_REPORT_FILE_NAME) # Write the content to a file with open(report_path, 'w', encoding='utf-8') as file: - file.write(html_content) + file.write(report_html) LOGGER.info('Module report generated at: ' + str(report_path)) diff --git a/modules/test/dns/resources/report_template.jinja2 b/modules/test/dns/resources/report_template.jinja2 new file mode 100644 index 000000000..8e701a8e3 --- /dev/null +++ b/modules/test/dns/resources/report_template.jinja2 @@ -0,0 +1,31 @@ +{% extends base_template %} +{% block content %} +{% if module_data %} + + + + {% for header in module_data_headers %} + + {% endfor %} + + + + {% for row in module_data %} + + + + + + + + + {% endfor %} + +
{{ header }}
{{ row['src'] }}
{{ row['dst'] }}
{{ row['res_ip'] }}
{{ row['typ'] }}
{{ row['dat'] }}
{{ row['count'] }}
+{% else %} +
+
+ No DNS traffic detected from the device +
+{% endif %} +{% endblock %} \ No newline at end of file diff --git a/modules/test/ntp/ntp.Dockerfile b/modules/test/ntp/ntp.Dockerfile index 4d9701464..c7ae7fee1 100644 --- a/modules/test/ntp/ntp.Dockerfile +++ b/modules/test/ntp/ntp.Dockerfile @@ -31,4 +31,7 @@ COPY $MODULE_DIR/conf /testrun/conf COPY $MODULE_DIR/bin /testrun/bin # Copy over all python files -COPY $MODULE_DIR/python /testrun/python \ No newline at end of file +COPY $MODULE_DIR/python /testrun/python + +# Copy Jinja template +COPY $MODULE_DIR/resources/report_template.jinja2 $REPORT_TEMPLATE_PATH/ \ No newline at end of file diff --git a/modules/test/ntp/python/src/ntp_module.py b/modules/test/ntp/python/src/ntp_module.py index 33729a8d1..8bc609502 100644 --- a/modules/test/ntp/python/src/ntp_module.py +++ b/modules/test/ntp/python/src/ntp_module.py @@ -17,19 +17,21 @@ from scapy.error import Scapy_Exception import os from collections import defaultdict +from jinja2 import Environment, FileSystemLoader LOG_NAME = 'test_ntp' -MODULE_REPORT_FILE_NAME = 'ntp_report.html' +MODULE_REPORT_FILE_NAME = 'ntp_report.j2.html' NTP_SERVER_CAPTURE_FILE = '/runtime/network/ntp.pcap' STARTUP_CAPTURE_FILE = '/runtime/device/startup.pcap' MONITOR_CAPTURE_FILE = '/runtime/device/monitor.pcap' LOGGER = None +REPORT_TEMPLATE_FILE = 'report_template.jinja2' class NTPModule(TestModule): """NTP Test module""" - def __init__(self, + def __init__(self, # pylint: disable=R0917 module, conf_file=None, results_dir=None, @@ -50,11 +52,38 @@ def __init__(self, LOGGER = self._get_logger() def generate_module_report(self): + # Load Jinja2 template + page_max_height = 910 + header_height = 48 + summary_height = 135 + row_height = 42 + loader=FileSystemLoader(self._report_template_folder) + template = Environment( + loader=loader, + trim_blocks=True, + lstrip_blocks=True, + ).get_template(REPORT_TEMPLATE_FILE) + module_header='NTP Module' + # Summary table headers + summary_headers = [ + 'Requests to local NTP server', + 'Requests to external NTP servers', + 'Total NTP requests', + 'Total NTP responses' + ] + # Module data Headers + module_data_headers = [ + 'Source', + 'Destination', + 'Type', + 'Version', + 'Count', + 'Sync Request Average', + ] + # Extract NTP data from the pcap file ntp_table_data = self.extract_ntp_data() - html_content = '

NTP Module

' - # Set the summary variables local_requests = sum( 1 for row in ntp_table_data @@ -68,6 +97,14 @@ def generate_module_report(self): total_responses = sum(1 for row in ntp_table_data if row['Type'] == 'Server') + # Summary table data + summary_data = [ + local_requests, + external_requests, + total_requests, + total_responses + ] + # Initialize a dictionary to store timestamps for each unique combination timestamps = defaultdict(list) @@ -95,42 +132,9 @@ def generate_module_report(self): average_time_between_requests[key] = avg_diff - # Add summary table - html_content += (f''' - - - - - - - - - - - - - - - - - -
Requests to local NTP serverRequests to external NTP serversTotal NTP requestsTotal NTP responses
{local_requests}{external_requests}{total_requests}{total_responses}
- ''') - + # Module table data + module_table_data = [] if total_requests + total_responses > 0: - table_content = ''' - - - - - - - - - - - - ''' # Generate the HTML table with the count column for (src, dst, typ, @@ -145,37 +149,44 @@ def generate_module_report(self): else: avg_formatted_time = 'N/A' - table_content += f''' - - - - - - - - ''' - - table_content += ''' - -
SourceDestinationTypeVersionCountSync Request Average
{src}{dst}{typ}{version}{cnt}{avg_formatted_time}
- ''' - html_content += table_content - - else: - html_content += (''' -
-
- No NTP traffic detected from the device -
''') - - LOGGER.debug('Module report:\n' + html_content) + module_table_data.append({ + 'src': src, + 'dst': dst, + 'typ': typ, + 'version': version, + 'cnt': cnt, + 'avg_fmt': avg_formatted_time + }) + + # Handling the possible table split + table_height = (len(module_table_data) + 1) * row_height + page_useful_space = page_max_height - header_height - summary_height + pages = table_height // (page_useful_space) + rows_on_page = ((page_useful_space) // row_height) - 1 + start = 0 + report_html = '' + for page in range(pages+1): + end = start + min(len(module_table_data), rows_on_page) + module_header_repr = module_header if page == 0 else None + page_html = template.render( + base_template=self._base_template_file, + module_header=module_header_repr, + summary_headers=summary_headers, + summary_data=summary_data, + module_data_headers=module_data_headers, + module_data=module_table_data[start:end] + ) + report_html += page_html + start = end + + LOGGER.debug('Module report:\n' + report_html) # Use os.path.join to create the complete file path report_path = os.path.join(self._results_dir, MODULE_REPORT_FILE_NAME) # Write the content to a file with open(report_path, 'w', encoding='utf-8') as file: - file.write(html_content) + file.write(report_html) LOGGER.info('Module report generated at: ' + str(report_path)) @@ -276,7 +287,7 @@ def _ntp_network_ntp_support(self): result = False, 'Device has not sent any NTP requests' if device_sends_ntp3 and device_sends_ntp4: - result = False, ('Device sent NTPv3 and NTPv4 packets') + result = True, ('Device sent NTPv3 and NTPv4 packets') elif device_sends_ntp3: result = False, ('Device sent NTPv3 packets') elif device_sends_ntp4: diff --git a/modules/test/ntp/resources/report_template.jinja2 b/modules/test/ntp/resources/report_template.jinja2 new file mode 100644 index 000000000..c3601b67b --- /dev/null +++ b/modules/test/ntp/resources/report_template.jinja2 @@ -0,0 +1,31 @@ +{% extends base_template %} +{% block content %} +{% if module_data %} + + + + {% for header in module_data_headers %} + + {% endfor %} + + + + {% for row in module_data %} + + + + + + + + + {% endfor %} + +
{{ header }}
{{ row['src'] }}{{ row['dst'] }}{{ row['typ'] }}{{ row['version'] }}{{ row['cnt'] }}{{ row['avg_fmt'] }}
+{% else %} +
+
+ No NTP traffic detected from the device +
+{% endif %} +{% endblock %} \ No newline at end of file diff --git a/modules/test/services/python/src/services_module.py b/modules/test/services/python/src/services_module.py index 1a783e7dc..3ec713962 100644 --- a/modules/test/services/python/src/services_module.py +++ b/modules/test/services/python/src/services_module.py @@ -19,17 +19,19 @@ import xmltodict from test_module import TestModule import os +from jinja2 import Environment, FileSystemLoader LOG_NAME = 'test_services' -MODULE_REPORT_FILE_NAME = 'services_report.html' +MODULE_REPORT_FILE_NAME = 'services_report.j2.html' NMAP_SCAN_RESULTS_SCAN_FILE = 'services_scan_results.json' LOGGER = None +REPORT_TEMPLATE_FILE = 'report_template.jinja2' class ServicesModule(TestModule): """Services Test module""" - def __init__(self, + def __init__(self, # pylint: disable=R0917 module, conf_file=None, results_dir=None, @@ -52,6 +54,26 @@ def __init__(self, self._run_nmap() def generate_module_report(self): + # Load Jinja2 template + loader=FileSystemLoader(self._report_template_folder) + template = Environment( + loader=loader, + trim_blocks=True, + lstrip_blocks=True + ).get_template(REPORT_TEMPLATE_FILE) + module_header = 'Services Module' + summary_headers = [ + 'TCP ports open', + 'UDP ports open', + 'Total ports open', + ] + module_data_headers = [ + 'Port', + 'State', + 'Service', + 'Version', + ] + # Use os.path.join to create the complete file path nmap_scan_results_file = os.path.join(self._nmap_scan_results_path, NMAP_SCAN_RESULTS_SCAN_FILE) @@ -81,65 +103,32 @@ def generate_module_report(self): else: udp_open += 1 - html_content = '

Services Module

' - - # Add summary table - html_content += (f''' - - - - - - - - - - - - - - -
TCP ports openUDP ports openTotal ports open
{tcp_open}{udp_open}{tcp_open + udp_open}
- ''') + summary_data = [ + tcp_open, + udp_open, + tcp_open + udp_open, + ] + module_data = [] if (tcp_open + udp_open) > 0: - - table_content = ''' - - - - - - - - - - ''' - for row in nmap_table_data: - - table_content += (f''' - - - - - - ''') - - table_content += ''' - -
PortStateServiceVersion
{row['Port']}/{row['Type']}{row['State']}{row['Service']}{row['Version']}
- ''' - - html_content += table_content - - else: - - html_content += (''' -
-
- No open ports detected -
''') + port = row['Port'] + type_ = row['Type'] + module_data.append({ + 'Port': f'{port}/{type_}', + 'State': row['State'], + 'Service': row['Service'], + 'Version': row['Version'], + }) + + html_content = template.render( + base_template=self._base_template_file, + module_header=module_header, + summary_headers=summary_headers, + summary_data=summary_data, + module_data_headers=module_data_headers, + module_data=module_data, + ) LOGGER.debug('Module report:\n' + html_content) @@ -156,6 +145,8 @@ def generate_module_report(self): def _run_nmap(self): LOGGER.info('Running nmap') + self._device_ipv4_addr = self._get_device_ipv4() + LOGGER.info('Resolved device IP: ' + str(self._device_ipv4_addr)) # Run the monitor method asynchronously to keep this method non-blocking self._tcp_scan_thread = threading.Thread(target=self._scan_tcp_ports) @@ -202,7 +193,9 @@ def _scan_tcp_ports(self): --version-intensity 7 -T4 -oX - {self._ipv4_addr}''')[0] LOGGER.info('TCP port scan complete') + LOGGER.debug(f'TCP Scan results raw: {nmap_results}') nmap_results_json = self._nmap_results_to_json(nmap_results) + LOGGER.debug(f'TCP Scan results JSON: {nmap_results_json}') self._scan_tcp_results = self._process_nmap_json_results( nmap_results_json=nmap_results_json) @@ -228,7 +221,9 @@ def _scan_udp_ports(self): nmap_results = util.run_command( # pylint: disable=E1120 f'nmap -sU -sV -p {port_list} -oX - {self._ipv4_addr}')[0] LOGGER.info('UDP port scan complete') + LOGGER.debug(f'UDP Scan results raw: {nmap_results}') nmap_results_json = self._nmap_results_to_json(nmap_results) + LOGGER.debug(f'UDP Scan results JSON: {nmap_results_json}') self._scan_udp_results = self._process_nmap_json_results( nmap_results_json=nmap_results_json) diff --git a/modules/test/services/resources/report_template.jinja2 b/modules/test/services/resources/report_template.jinja2 new file mode 100644 index 000000000..c9b1438ae --- /dev/null +++ b/modules/test/services/resources/report_template.jinja2 @@ -0,0 +1,29 @@ +{% extends base_template %} +{% block content %} +{% if module_data %} + + + + {% for header in module_data_headers %} + + {% endfor %} + + + + {% for row in module_data %} + + + + + + + {% endfor %} + +
{{ header }}
{{ row['Port'] }}{{ row['State'] }}{{ row['Service'] }}{{ row['Version'] }}
+{% else %} +
+
+ No open ports detected +
+{% endif %} +{% endblock %} \ No newline at end of file diff --git a/modules/test/services/services.Dockerfile b/modules/test/services/services.Dockerfile index 8dcaafcc1..1277c2f8d 100644 --- a/modules/test/services/services.Dockerfile +++ b/modules/test/services/services.Dockerfile @@ -31,4 +31,7 @@ COPY $MODULE_DIR/conf /testrun/conf COPY $MODULE_DIR/bin /testrun/bin # Copy over all python files -COPY $MODULE_DIR/python /testrun/python \ No newline at end of file +COPY $MODULE_DIR/python /testrun/python + +# Copy Jinja template +COPY $MODULE_DIR/resources/report_template.jinja2 $REPORT_TEMPLATE_PATH/ \ No newline at end of file diff --git a/modules/test/tls/conf/module_config.json b/modules/test/tls/conf/module_config.json index 9c83c85df..228fbb64d 100644 --- a/modules/test/tls/conf/module_config.json +++ b/modules/test/tls/conf/module_config.json @@ -9,7 +9,7 @@ "docker": { "depends_on": "base", "enable_container": true, - "timeout": 420 + "timeout": 540 }, "tests":[ { diff --git a/modules/test/tls/python/requirements.txt b/modules/test/tls/python/requirements.txt index 4f241ef86..2cbc1db46 100644 --- a/modules/test/tls/python/requirements.txt +++ b/modules/test/tls/python/requirements.txt @@ -13,9 +13,9 @@ termcolor==2.4.0 urllib3==2.2.2 # User defined packages -cryptography==43.0.0 +cryptography==44.0.1 pyOpenSSL==24.3.0 lxml==5.1.0 # Requirement of pyshark but if upgraded automatically above 5.1 will cause a pyshark==0.6 requests==2.32.3 - +python-nmap==0.7.1 diff --git a/modules/test/tls/python/src/http_scan.py b/modules/test/tls/python/src/http_scan.py new file mode 100644 index 000000000..a25f5215d --- /dev/null +++ b/modules/test/tls/python/src/http_scan.py @@ -0,0 +1,76 @@ +# Copyright 2023 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +"""Module that contains various methods for scaning for HTTP/HTTPS services""" +import nmap +import socket +import ssl + +LOGGER = None + + +class HTTPScan(): + """Helper class to scan for all HTTP/HTTPS services for a device""" + + def __init__(self, logger): + global LOGGER + LOGGER = logger + + def scan_all_ports(self, ip): + """Scans all ports and identifies potential HTTP/HTTPS ports.""" + nm = nmap.PortScanner() + nm.scan(hosts=ip, ports='1-65535', arguments='--open -sV') + + http_ports = [] + for host in nm.all_hosts(): + for proto in nm[host].all_protocols(): + for port in nm[host][proto].keys(): + service = nm[host][proto][port]['name'] + if 'http' in service: + http_ports.append(port) + return http_ports + + def is_https(self, ip, port): + """Attempts a TLS handshake to determine if the port serves HTTPS.""" + try: + context = ssl.create_default_context() + context.check_hostname = False + context.verify_mode = ssl.CERT_NONE + with socket.create_connection((ip, port), timeout=2) as sock: + with context.wrap_socket(sock, server_hostname=ip): + return True + except ssl.SSLError: + return False + except Exception: # pylint: disable=W0718 + return False + + def verify_http_or_https(self, ip, ports): + """Classifies each port as HTTP or HTTPS.""" + results = {} + for port in ports: + if self.is_https(ip, port): + results[port] = 'HTTPS' + else: + results[port] = 'HTTP' + return results + + def scan_for_http_services(self, ip_address): + LOGGER.info(f'Scanning for HTTP ports on {ip_address}') + http_ports = self.scan_all_ports(ip_address) + results = None + if len(http_ports) > 0: + LOGGER.info(f'Checking HTTP ports on {ip_address}: {http_ports}') + results = self.verify_http_or_https(ip_address, http_ports) + for port, service_type in results.items(): + LOGGER.info(f'Port {port}: {service_type}') + return results diff --git a/modules/test/tls/python/src/tls_module.py b/modules/test/tls/python/src/tls_module.py index e9163843a..3ccd96b59 100644 --- a/modules/test/tls/python/src/tls_module.py +++ b/modules/test/tls/python/src/tls_module.py @@ -16,6 +16,7 @@ from test_module import TestModule from tls_util import TLSUtil +from http_scan import HTTPScan import os import pyshark from binascii import hexlify @@ -25,20 +26,22 @@ from cryptography.hazmat.primitives.asymmetric import rsa, dsa, ec from cryptography.x509 import AuthorityKeyIdentifier, SubjectKeyIdentifier, BasicConstraints, KeyUsage from cryptography.x509 import GeneralNames, DNSName, ExtendedKeyUsage, ObjectIdentifier, SubjectAlternativeName +from jinja2 import Environment, FileSystemLoader LOG_NAME = 'test_tls' -MODULE_REPORT_FILE_NAME = 'tls_report.html' +MODULE_REPORT_FILE_NAME = 'tls_report.j2.html' STARTUP_CAPTURE_FILE = '/runtime/device/startup.pcap' MONITOR_CAPTURE_FILE = '/runtime/device/monitor.pcap' TLS_CAPTURE_FILE = '/runtime/output/tls.pcap' GATEWAY_CAPTURE_FILE = '/runtime/network/gateway.pcap' LOGGER = None +REPORT_TEMPLATE_FILE = 'report_template.jinja2' class TLSModule(TestModule): """The TLS testing module.""" - def __init__(self, + def __init__(self, # pylint: disable=R0917 module, conf_file=None, results_dir=None, @@ -55,9 +58,32 @@ def __init__(self, global LOGGER LOGGER = self._get_logger() self._tls_util = TLSUtil(LOGGER) + self._http_scan = HTTPScan(LOGGER) + self._scan_results = None def generate_module_report(self): - html_content = '

TLS Module

' + # Load Jinja2 template + loader=FileSystemLoader(self._report_template_folder) + template = Environment( + loader=loader, + trim_blocks=True, + lstrip_blocks=True + ).get_template(REPORT_TEMPLATE_FILE) + module_header='TLS Module' + # Summary table headers + summary_headers = [ + 'Expiry', + 'Length', + 'Type', + 'Port number', + 'Signed by', + ] + # Cert table headers + cert_table_headers = ['Property', 'Value'] + # Outbound connections table headers + outbound_headers = ['Destination IP', 'Port'] + pages = {} + outbound_conns = None # List of capture files to scan pcap_files = [ @@ -66,39 +92,12 @@ def generate_module_report(self): ] certificates = self.extract_certificates_from_pcap(pcap_files, self._device_mac) - if len(certificates) > 0: - cert_tables = [] # pylint: disable=W0612 for cert_num, ((ip_address, port), cert) in enumerate(certificates.items()): - - # Add summary table - summary_table = ''' - - - - - - - - - - - - ''' - - # Generate the certificate table - cert_table = ''' -
ExpiryLengthTypePort numberSigned by
- - - - - - - ''' + pages[cert_num] = {} # Extract certificate data not_valid_before = cert.not_valid_before @@ -124,50 +123,18 @@ def generate_module_report(self): cert.public_bytes(encoding=serialization.Encoding.DER)) # Append certification information - cert_table += f''' - - - - - - - - - - - - - - - - - -
PropertyValue
Version{version_value}
Signature Alg.{signature_alg_value}
Validity from{not_before}
Valid to{not_after}
- ''' - - subject_table = ''' - - - - - - - - ''' + pages[cert_num]['cert_info_data'] = { + 'Version': version_value, + 'Signature Alg.': signature_alg_value, + 'Validity from': not_before, + 'Valid to': not_after, + } # Append the subject information + pages[cert_num]['subject_data'] = {} for val in cert.subject.rdns: dn = val.rfc4514_string().split('=') - subject_table += f''' - - - - - ''' - - subject_table += ''' - -
PropertyValue
{dn[0]}{dn[1]}
''' + pages[cert_num]['subject_data'][dn[0]] = dn[1] # Append issuer information for val in cert.issuer.rdns: @@ -175,101 +142,72 @@ def generate_module_report(self): if 'CN' in dn[0]: signed_by = dn[1] - ext_table = '' - # Append extensions information if cert.extensions: - - ext_table = ''' -
Certificate Extensions
- - - - - - - - ''' - + pages[cert_num]['cert_ext'] = {} for extension in cert.extensions: if isinstance(extension.value, list): for extension_value in extension.value: - ext_table += f''' - - - - - ''' + extension_name = extension.oid._name + formatted_value = self.format_extension_value( + extension_value.value) + pages[cert_num]['cert_ext'][extension_name] = formatted_value else: - ext_table += f''' - - - - - ''' - - ext_table += ''' - -
PropertyValue
{extension.oid._name}{self.format_extension_value(extension_value.value)}
{extension.oid._name}{self.format_extension_value(extension.value)}
''' - - # Add summary table row - summary_table += f''' - - {not_after} - {cert_length} - {public_key_type} - {port} - {signed_by} - - - - ''' - - # Merge all table HTML - summary_table = f'\n{summary_table}' - - summary_table += f''' -
-
-
Certificate Information
- {cert_table} -
-
-
Subject Information
- {subject_table} -
-
''' - - if ext_table is not None: - summary_table += f'\n\n{ext_table}' - - cert_tables.append(summary_table) + formatted_value = self.format_extension_value( + extension.value) + pages[cert_num]['cert_ext'][extension.oid._name] = formatted_value + + pages[cert_num]['summary_data'] = [ + not_after, + cert_length, + public_key_type, + port, + signed_by + ] outbound_conns = self._tls_util.get_all_outbound_connections( device_mac=self._device_mac, capture_files=pcap_files) - conn_table = self.generate_outbound_connection_table(outbound_conns) - - html_content += '\n'.join('\n' + tables for tables in cert_tables) - html_content += conn_table + report_jinja = '' + if pages: + for num,page in pages.items(): + module_header_repr = module_header if num == 0 else None + cert_ext=page['cert_ext'] if 'cert_ext' in page else None + page_html = template.render( + base_template=self._base_template_file, + module_header=module_header_repr, + summary_headers=summary_headers, + summary_data=page['summary_data'], + cert_info_data=page['cert_info_data'], + subject_data=page['subject_data'], + cert_table_headers=cert_table_headers, + cert_ext=cert_ext, + ountbound_headers=outbound_headers, + ) + report_jinja += page_html + if outbound_conns: + out_page = template.render( + base_template=self._base_template_file, + ountbound_headers=outbound_headers, + outbound_conns=outbound_conns + ) + report_jinja += out_page else: - html_content += (''' -
-
- No TLS certificates found on the device -
''') - - LOGGER.debug('Module report:\n' + html_content) + report_jinja = template.render( + base_template=self._base_template_file, + module_header = module_header, + ) + LOGGER.debug('Module report:\n' + report_jinja) # Use os.path.join to create the complete file path - report_path = os.path.join(self._results_dir, MODULE_REPORT_FILE_NAME) + jinja_path = os.path.join(self._results_dir, MODULE_REPORT_FILE_NAME) # Write the content to a file - with open(report_path, 'w', encoding='utf-8') as file: - file.write(html_content) + with open(jinja_path, 'w', encoding='utf-8') as file: + file.write(report_jinja) - LOGGER.info('Module report generated at: ' + str(report_path)) - return report_path + LOGGER.info('Module report generated at: ' + str(jinja_path)) + return jinja_path def format_extension_value(self, value): if isinstance(value, bytes): @@ -353,8 +291,8 @@ def extract_certificates_from_pcap(self, pcap_files, mac_address): all_packets = [] # Iterate over each file for pcap_file in pcap_files: - # Open the capture file - packets = pyshark.FileCapture(pcap_file) + # Open the capture file and filter by tls + packets = pyshark.FileCapture(pcap_file, display_filter='tls') try: # Iterate over each packet in the file and add it to the list for packet in packets: @@ -387,53 +325,102 @@ def extract_certificates_from_pcap(self, pcap_files, mac_address): def _security_tls_v1_2_server(self): LOGGER.info('Running security.tls.v1_2_server') - self._resolve_device_ip() # If the ipv4 address wasn't resolved yet, try again + self._resolve_device_ip() + ports_valid = [] + ports_invalid = [] + result = None + details = '' + description = '' if self._device_ipv4_addr is not None: - tls_1_2_results = self._tls_util.validate_tls_server( - self._device_ipv4_addr, tls_version='1.2') - tls_1_3_results = self._tls_util.validate_tls_server( - self._device_ipv4_addr, tls_version='1.3') - results = self._tls_util.process_tls_server_results( - tls_1_2_results, tls_1_3_results) + if self._scan_results is None: + self._scan_results = self._http_scan.scan_for_http_services( + self._device_ipv4_addr) + if self._scan_results is not None: + for port, service_type in self._scan_results.items(): + if 'HTTPS' in service_type: + LOGGER.info(f'Inspecting Service on port {port}: {service_type}') + tls_1_2_results = self._tls_util.validate_tls_server( + host=self._device_ipv4_addr, port=port, tls_version='1.2') + tls_1_3_results = self._tls_util.validate_tls_server( + host=self._device_ipv4_addr, port=port, tls_version='1.3') + port_results = self._tls_util.process_tls_server_results( + tls_1_2_results, tls_1_3_results, port=port) + if port_results is not None: + result = port_results[ + 0] if result is None else result and port_results[0] + details += port_results[1] + if port_results[0]: + ports_valid.append(port) + else: + ports_invalid.append(port) + elif 'HTTP' in service_type: + # Any non-HTTPS service detetcted is automatically invalid + ports_invalid.append(port) + details += f'\nHTTP service detected on port {port}' + result = False + LOGGER.debug(f'Valid Ports: {ports_valid}') + LOGGER.debug(f'Invalid Ports: {ports_invalid}') # Determine results and return proper messaging and details - description = '' - result = results[0] - details = results[1] if result is None: result = 'Feature Not Detected' description = 'TLS 1.2 certificate could not be validated' elif result: - description = 'TLS 1.2 certificate is valid' + ports_csv = ','.join(map(str,ports_valid)) + description = f'TLS 1.2 certificate valid on ports: {ports_csv}' else: - description = 'TLS 1.2 certificate is invalid' + ports_csv = ','.join(map(str,ports_invalid)) + description = f'TLS 1.2 certificate invalid on ports: {ports_csv}' return result, description, details - else: LOGGER.error('Could not resolve device IP address. Skipping') return 'Error', 'Could not resolve device IP address' def _security_tls_v1_3_server(self): LOGGER.info('Running security.tls.v1_3_server') - self._resolve_device_ip() # If the ipv4 address wasn't resolved yet, try again + self._resolve_device_ip() + ports_valid = [] + ports_invalid = [] + result = None + details = '' + description = '' if self._device_ipv4_addr is not None: - results = self._tls_util.validate_tls_server(self._device_ipv4_addr, - tls_version='1.3') + if self._scan_results is None: + self._scan_results = self._http_scan.scan_for_http_services( + self._device_ipv4_addr) + if self._scan_results is not None: + for port, service_type in self._scan_results.items(): + if 'HTTPS' in service_type: + LOGGER.info(f'Inspecting Service on port {port}: {service_type}') + port_results = self._tls_util.validate_tls_server( + self._device_ipv4_addr, tls_version='1.3', port=port) + if port_results is not None: + result = port_results[ + 0] if result is None else result and port_results[0] + details += port_results[1] + if port_results[0]: + ports_valid.append(port) + else: + ports_invalid.append(port) + elif 'HTTP' in service_type: + # Any non-HTTPS service detetcted is automatically invalid + ports_invalid.append(port) + details += f'\nHTTP service detected on port {port}' + result = False + LOGGER.debug(f'Valid Ports: {ports_valid}') + LOGGER.debug(f'Invalid Ports: {ports_invalid}') # Determine results and return proper messaging and details - description = '' - result = results[0] - details = results[1] - description = '' if result is None: result = 'Feature Not Detected' description = 'TLS 1.3 certificate could not be validated' - elif results[0]: - description = 'TLS 1.3 certificate is valid' + elif result: + ports_csv = ','.join(map(str,ports_valid)) + description = f'TLS 1.3 certificate valid on ports: {ports_csv}' else: - description = 'TLS 1.3 certificate is invalid' + ports_csv = ','.join(map(str,ports_invalid)) + description = f'TLS 1.3 certificate invalid on ports: {ports_csv}' return result, description, details - else: LOGGER.error('Could not resolve device IP address') return 'Error', 'Could not resolve device IP address' @@ -472,14 +459,14 @@ def _security_tls_v1_0_client(self): def _security_tls_v1_2_client(self): LOGGER.info('Running security.tls.v1_2_client') return self._validate_tls_client(self._device_mac, - '1.2', - unsupported_versions=['1.0', '1.1']) + '1.2', + unsupported_versions=['1.0', '1.1']) def _security_tls_v1_3_client(self): LOGGER.info('Running security.tls.v1_3_client') return self._validate_tls_client(self._device_mac, - '1.3', - unsupported_versions=['1.0', '1.1']) + '1.3', + unsupported_versions=['1.0', '1.1']) def _validate_tls_client(self, client_mac, @@ -509,10 +496,10 @@ def _validate_tls_client(self, result_message = f'TLS {tls_version} client connections invalid' else: result_state = 'Feature Not Detected' - result_message = 'No outbound connections were found' + result_message = 'No outbound TLS connections were found' return result_state, result_message, result_details, result_tags def _resolve_device_ip(self): # If the ipv4 address wasn't resolved yet, try again - if self._device_ipv4_addr is None: + if self._device_ipv4_addr is None: # pylint: disable=E0203 self._device_ipv4_addr = self._get_device_ipv4() diff --git a/modules/test/tls/python/src/tls_util.py b/modules/test/tls/python/src/tls_util.py index 37cd89133..d92379aab 100644 --- a/modules/test/tls/python/src/tls_util.py +++ b/modules/test/tls/python/src/tls_util.py @@ -221,11 +221,11 @@ def verify_public_key(self, public_key): else: return False, 'Key is not RSA or EC type' - def validate_signature(self, host): + def validate_signature(self, host, port): # Reconnect to the device but with validate signature option # set to true which will check for proper cert chains # within the valid CA root certs stored on the server - if self.validate_trusted_ca_signature(host): + if self.validate_trusted_ca_signature(host, port): LOGGER.info('Authorized Certificate Authority signature confirmed') return True, 'Authorized Certificate Authority signature confirmed' else: @@ -261,13 +261,14 @@ def validate_local_ca_signature(self, device_cert_path): LOGGER.error(str(e)) return False, None - def validate_trusted_ca_signature(self, host): + def validate_trusted_ca_signature(self, host, port): # Reconnect to the device but with validate signature option # set to true which will check for proper cert chains # within the valid CA root certs stored on the server LOGGER.info( 'Checking for valid signature from authorized Certificate Authorities') - public_cert = self.get_public_certificate(host, + public_cert = self.get_public_certificate(host=host, + port=port, validate_cert=True, tls_version='1.2') if public_cert: @@ -377,34 +378,41 @@ def get_certificate(self, uri, timeout=10): LOGGER.error(f'Error fetching certificate from URI: {e}') return certificate - def process_tls_server_results(self, tls_1_2_results, tls_1_3_results): + def process_tls_server_results(self, tls_1_2_results, tls_1_3_results, port): results = '' if tls_1_2_results[0] is None and tls_1_3_results[0] is not None: # Validate only TLS 1.3 results - description = 'TLS 1.3' + (' not' if not tls_1_3_results[0] else - '') + ' validated: ' + tls_1_3_results[1] + description = (f"""TLS 1.3 {'' if tls_1_3_results[0] else 'not '}""" + f"""validated on port {port}: """ + f"""{tls_1_3_results[1]}""") results = tls_1_3_results[0], description elif tls_1_3_results[0] is None and tls_1_2_results[0] is not None: # Vaidate only TLS 1.2 results - description = 'TLS 1.2' + (' not' if not tls_1_2_results[0] else - '') + ' validated: ' + tls_1_2_results[1] + description = (f"""TLS 1.2 {'' if tls_1_2_results[0] else 'not '}""" + f"""validated on port {port}: """ + f"""{tls_1_2_results[1]}""") results = tls_1_2_results[0], description elif tls_1_3_results[0] is not None and tls_1_2_results[0] is not None: # Validate both results - description = 'TLS 1.2' + (' not' if not tls_1_2_results[0] else - '') + ' validated: ' + tls_1_2_results[1] - description += '\nTLS 1.3' + (' not' if not tls_1_3_results[0] else - '') + ' validated: ' + tls_1_3_results[1] + description = (f"""TLS 1.2 {'' if tls_1_2_results[0] else 'not '}""" + f"""validated on port {port}: """ + f"""{tls_1_2_results[1]}""") + description += '\n'+(f"""TLS 1.3 {'' if tls_1_3_results[0] else 'not '}""" + f"""validated on port {port}: """ + f"""{tls_1_3_results[1]}""") results = tls_1_2_results[0] or tls_1_3_results[0], description else: - description = f'TLS 1.2 not validated: {tls_1_2_results[1]}' - description += f'\nTLS 1.3 not validated: {tls_1_3_results[1]}' + description = (f"""TLS 1.2 not validated on port {port}: """ + f"""{tls_1_2_results[1]}""") + description += '\n'+(f"""TLS 1.3 not validated on port {port}: """ + f"""{tls_1_3_results[1]}""") results = None, description LOGGER.info('TLS server test results: ' + str(results)) return results - def validate_tls_server(self, host, tls_version): - cert_pem = self.get_public_certificate(host, + def validate_tls_server(self, host, tls_version, port=443): + cert_pem = self.get_public_certificate(host=host, + port=port, validate_cert=False, tls_version=tls_version) if cert_pem: @@ -430,13 +438,12 @@ def validate_tls_server(self, host, tls_version): else: key_valid = [0] - sig_valid = self.validate_signature(host) + sig_valid = self.validate_signature(host=host, port=port) # Check results cert_valid = tr_valid[0] and key_valid[0] and sig_valid[0] test_details = tr_valid[1] + '\n' + key_valid[1] + '\n' + sig_valid[1] LOGGER.info('Certificate validated: ' + str(cert_valid)) - LOGGER.info('Test details:\n' + test_details) return cert_valid, test_details else: LOGGER.info('Failed to resolve public certificate') @@ -632,7 +639,7 @@ def get_non_tls_client_connection_ips(self, client_mac, capture_files): # Packet is not ACK or SYN src_ip = ipaddress.ip_address( - packet['_source']['layers']['ip.src'][0]) + packet['_source']['layers']['ip.src'][0]) src_subnet = ipaddress.ip_network(src_ip, strict=False) subnet_with_mask = ipaddress.ip_network( src_subnet, strict=False).supernet(new_prefix=24) diff --git a/modules/test/tls/resources/report_template.jinja2 b/modules/test/tls/resources/report_template.jinja2 new file mode 100644 index 000000000..5680d9339 --- /dev/null +++ b/modules/test/tls/resources/report_template.jinja2 @@ -0,0 +1,90 @@ +{% extends base_template %} +{% block content %} +{% if cert_info_data and subject_data %} +
+
+
Certificate Information
+ + + + {% for header in cert_table_headers%} + + {% endfor %} + + + + {% for k,v in cert_info_data.items() %} + + + + + {% endfor %} + +
{{ header }}
{{ k }}{{ v }}
+
+
+
Subject Information
+ + + + {% for header in cert_table_headers%} + + {% endfor %} + + + + {% for k,v in subject_data.items() %} + + + + + {% endfor %} + +
{{ header }}
{{ k }}{{ v }}
+
+
+ {% if cert_ext %} +
Certificate Extensions
+ + + + + + + + + {% for k,v in cert_ext.items()%} + + + + + {% endfor %} + +
PropertyValue
{{ k }}{{ v }}
+ {% endif %} +{% elif ountbound_headers %} +

Outbound Connections

+ + + + {% for header in ountbound_headers%} + + {% endfor %} + + + + {% for ip, port in outbound_conns%} + + + + + {% endfor %} + +
{{ header }}
{{ip}}{{port}}
+{% else %} +
+
+ No TLS certificates found on the device +
+{% endif %} +{% endblock %} \ No newline at end of file diff --git a/modules/test/tls/tls.Dockerfile b/modules/test/tls/tls.Dockerfile index c448c8478..3d6d66544 100644 --- a/modules/test/tls/tls.Dockerfile +++ b/modules/test/tls/tls.Dockerfile @@ -51,3 +51,6 @@ RUN pip3 install -r /testrun/python/requirements-test.txt # Create a directory inside the container to store the root certificates RUN mkdir -p /testrun/root_certs + +# Copy Jinja template +COPY $MODULE_DIR/resources/report_template.jinja2 $REPORT_TEMPLATE_PATH/ diff --git a/modules/ui/angular.json b/modules/ui/angular.json index 28e0e9a36..d56b3b8ee 100644 --- a/modules/ui/angular.json +++ b/modules/ui/angular.json @@ -38,8 +38,8 @@ }, { "type": "anyComponentStyle", - "maximumWarning": "5kb", - "maximumError": "6kb" + "maximumWarning": "6kb", + "maximumError": "7kb" } ], "outputHashing": "all" diff --git a/modules/ui/package-lock.json b/modules/ui/package-lock.json index b0ec71c07..1e4edf90f 100644 --- a/modules/ui/package-lock.json +++ b/modules/ui/package-lock.json @@ -67,12 +67,12 @@ } }, "node_modules/@angular-devkit/architect": { - "version": "0.1802.6", - "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1802.6.tgz", - "integrity": "sha512-oF7cPFdTLxeuvXkK/opSdIxZ1E4LrBbmuytQ/nCoAGOaKBWdqvwagRZ6jVhaI0Gwu48rkcV7Zhesg/ESNnROdw==", + "version": "0.1802.14", + "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1802.14.tgz", + "integrity": "sha512-eplaGCXSlPwf1f4XwyzsYTd8/lJ0/Adm6XsODsBxvkZlIpLcps80/h2lH5MVJpoDREzIFu1BweDpYCoNK5yYZg==", "dev": true, "dependencies": { - "@angular-devkit/core": "18.2.6", + "@angular-devkit/core": "18.2.14", "rxjs": "7.8.1" }, "engines": { @@ -82,16 +82,16 @@ } }, "node_modules/@angular-devkit/build-angular": { - "version": "18.2.6", - "resolved": "https://registry.npmjs.org/@angular-devkit/build-angular/-/build-angular-18.2.6.tgz", - "integrity": "sha512-u12cJZttgs5j7gICHWSmcaTCu0EFXEzKqI8nkYCwq2MtuJlAXiMQSXYuEP9OU3Go4vMAPtQh2kShyOWCX5b4EQ==", + "version": "18.2.14", + "resolved": "https://registry.npmjs.org/@angular-devkit/build-angular/-/build-angular-18.2.14.tgz", + "integrity": "sha512-ycie4OhvNv8eNVqvq46pCIf6kB50xbMOdnAVqmlj+BaQjWbGjUQPjAmp4VGqeDZZ/lW82xkfTmJZxc6pYp7YdQ==", "dev": true, "dependencies": { "@ampproject/remapping": "2.3.0", - "@angular-devkit/architect": "0.1802.6", - "@angular-devkit/build-webpack": "0.1802.6", - "@angular-devkit/core": "18.2.6", - "@angular/build": "18.2.6", + "@angular-devkit/architect": "0.1802.14", + "@angular-devkit/build-webpack": "0.1802.14", + "@angular-devkit/core": "18.2.14", + "@angular/build": "18.2.14", "@babel/core": "7.25.2", "@babel/generator": "7.25.0", "@babel/helper-annotate-as-pure": "7.24.7", @@ -102,7 +102,7 @@ "@babel/preset-env": "7.25.3", "@babel/runtime": "7.25.0", "@discoveryjs/json-ext": "0.6.1", - "@ngtools/webpack": "18.2.6", + "@ngtools/webpack": "18.2.14", "@vitejs/plugin-basic-ssl": "1.1.0", "ansi-colors": "4.1.3", "autoprefixer": "10.4.20", @@ -113,7 +113,7 @@ "css-loader": "7.1.2", "esbuild-wasm": "0.23.0", "fast-glob": "3.3.2", - "http-proxy-middleware": "3.0.0", + "http-proxy-middleware": "3.0.3", "https-proxy-agent": "7.0.5", "istanbul-lib-instrument": "6.0.3", "jsonc-parser": "3.3.1", @@ -142,7 +142,6 @@ "terser": "5.31.6", "tree-kill": "1.2.2", "tslib": "2.6.3", - "vite": "5.4.6", "watchpack": "2.4.1", "webpack": "5.94.0", "webpack-dev-middleware": "7.4.2", @@ -216,12 +215,12 @@ "dev": true }, "node_modules/@angular-devkit/build-webpack": { - "version": "0.1802.6", - "resolved": "https://registry.npmjs.org/@angular-devkit/build-webpack/-/build-webpack-0.1802.6.tgz", - "integrity": "sha512-JMLcXFaitJplwZMKkqhbYirINCRD6eOPZuIGaIOVynXYGWgvJkLT9t5C2wm9HqSLtp1K7NcYG2Y7PtTVR4krnQ==", + "version": "0.1802.14", + "resolved": "https://registry.npmjs.org/@angular-devkit/build-webpack/-/build-webpack-0.1802.14.tgz", + "integrity": "sha512-cccne0SG4BaQHsKRRZCi/wMLJ7yFXrwvE8w+Kug3HdpJJoyH3FeG386EQuca/azslQlK+c5g4ywSZdXeNkGazA==", "dev": true, "dependencies": { - "@angular-devkit/architect": "0.1802.6", + "@angular-devkit/architect": "0.1802.14", "rxjs": "7.8.1" }, "engines": { @@ -235,9 +234,9 @@ } }, "node_modules/@angular-devkit/core": { - "version": "18.2.6", - "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-18.2.6.tgz", - "integrity": "sha512-la4CFvs5PcRWSkQ/H7TB5cPZirFVA9GoWk5LzIk8si6VjWBJRm8b3keKJoC9LlNeABRUIR5z0ocYkyQQUhdMfg==", + "version": "18.2.14", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-18.2.14.tgz", + "integrity": "sha512-UGIGOjXuOyCW+5S4tINu7e6LOu738CmTw3h7Ui1I8OzdTIYJcYJrei8sgrwDwOYADRal+p0MeMlnykH3TM5XBA==", "dev": true, "dependencies": { "ajv": "8.17.1", @@ -262,12 +261,12 @@ } }, "node_modules/@angular-devkit/schematics": { - "version": "18.2.6", - "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-18.2.6.tgz", - "integrity": "sha512-uIttrQ2cQ2PWAFFVPeCoNR8xvs7tPJ2i8gzqsIwYdge107xDC6u9CqfgmBqPDSFpWj+IiC2Jwcm8Z4HYKU4+7A==", + "version": "18.2.14", + "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-18.2.14.tgz", + "integrity": "sha512-mukjZIHHB7gWratq8fZwUq5WZ+1bF4feG/idXr1wgQ+/FqWjs2PP7HDesHVcPymmRulpTyCpB7TNB1O1fgnCpA==", "dev": true, "dependencies": { - "@angular-devkit/core": "18.2.6", + "@angular-devkit/core": "18.2.14", "jsonc-parser": "3.3.1", "magic-string": "0.30.11", "ora": "5.4.1", @@ -290,19 +289,19 @@ } }, "node_modules/@angular-eslint/bundled-angular-compiler": { - "version": "18.3.1", - "resolved": "https://registry.npmjs.org/@angular-eslint/bundled-angular-compiler/-/bundled-angular-compiler-18.3.1.tgz", - "integrity": "sha512-sikmkjfsXPpPTku1aQkQ1MNNEKGBgGGRvUN/WeNS9dhCJ4dxU3O7dZctt1aQWj+W3nbuUtDiimAWF5fZHGFE2Q==", + "version": "18.4.3", + "resolved": "https://registry.npmjs.org/@angular-eslint/bundled-angular-compiler/-/bundled-angular-compiler-18.4.3.tgz", + "integrity": "sha512-zdrA8mR98X+U4YgHzUKmivRU+PxzwOL/j8G7eTOvBuq8GPzsP+hvak+tyxlgeGm9HsvpFj9ERHLtJ0xDUPs8fg==", "dev": true }, "node_modules/@angular-eslint/eslint-plugin": { - "version": "18.3.1", - "resolved": "https://registry.npmjs.org/@angular-eslint/eslint-plugin/-/eslint-plugin-18.3.1.tgz", - "integrity": "sha512-MP4Nm+SHboF8KdnN0KpPEGAaTTzDLPm3+S/4W3Mg8onqWCyadyd4mActh9mK/pvCj8TVlb/SW1zeTtdMYhwonw==", + "version": "18.4.3", + "resolved": "https://registry.npmjs.org/@angular-eslint/eslint-plugin/-/eslint-plugin-18.4.3.tgz", + "integrity": "sha512-AyJbupiwTBR81P6T59v+aULEnPpZBCBxL2S5QFWfAhNCwWhcof4GihvdK2Z87yhvzDGeAzUFSWl/beJfeFa+PA==", "dev": true, "dependencies": { - "@angular-eslint/bundled-angular-compiler": "18.3.1", - "@angular-eslint/utils": "18.3.1" + "@angular-eslint/bundled-angular-compiler": "18.4.3", + "@angular-eslint/utils": "18.4.3" }, "peerDependencies": { "@typescript-eslint/utils": "^7.11.0 || ^8.0.0", @@ -311,37 +310,36 @@ } }, "node_modules/@angular-eslint/eslint-plugin-template": { - "version": "18.3.1", - "resolved": "https://registry.npmjs.org/@angular-eslint/eslint-plugin-template/-/eslint-plugin-template-18.3.1.tgz", - "integrity": "sha512-hBJ3+f7VSidvrtYaXH7Vp0sWvblA9jLK2c6uQzhYGWdEDUcTg7g7VI9ThW39WvMbHqkyzNE4PPOynK69cBEDGg==", + "version": "18.4.3", + "resolved": "https://registry.npmjs.org/@angular-eslint/eslint-plugin-template/-/eslint-plugin-template-18.4.3.tgz", + "integrity": "sha512-ijGlX2N01ayMXTpeQivOA31AszO8OEbu9ZQUCxnu9AyMMhxyi2q50bujRChAvN9YXQfdQtbxuajxV6+aiWb5BQ==", "dev": true, "dependencies": { - "@angular-eslint/bundled-angular-compiler": "18.3.1", - "@angular-eslint/utils": "18.3.1", - "aria-query": "5.3.0", + "@angular-eslint/bundled-angular-compiler": "18.4.3", + "@angular-eslint/utils": "18.4.3", + "aria-query": "5.3.2", "axobject-query": "4.1.0" }, "peerDependencies": { + "@typescript-eslint/types": "^7.11.0 || ^8.0.0", "@typescript-eslint/utils": "^7.11.0 || ^8.0.0", "eslint": "^8.57.0 || ^9.0.0", "typescript": "*" } }, "node_modules/@angular-eslint/schematics": { - "version": "18.3.1", - "resolved": "https://registry.npmjs.org/@angular-eslint/schematics/-/schematics-18.3.1.tgz", - "integrity": "sha512-BTsQHDu7LjvXannJTb5BqMPCFIHRNN94eRyb60VfjJxB/ZFtsbAQDFFOi5lEZsRsd4mBeUMuL9mW4IMcPtUQ9Q==", + "version": "18.4.3", + "resolved": "https://registry.npmjs.org/@angular-eslint/schematics/-/schematics-18.4.3.tgz", + "integrity": "sha512-D5maKn5e6n58+8n7jLFLD4g+RGPOPeDSsvPc1sqial5tEKLxAJQJS9WZ28oef3bhkob6C60D+1H0mMmEEVvyVA==", "dev": true, "dependencies": { - "@angular-eslint/eslint-plugin": "18.3.1", - "@angular-eslint/eslint-plugin-template": "18.3.1", - "ignore": "5.3.2", + "@angular-devkit/core": ">= 18.0.0 < 19.0.0", + "@angular-devkit/schematics": ">= 18.0.0 < 19.0.0", + "@angular-eslint/eslint-plugin": "18.4.3", + "@angular-eslint/eslint-plugin-template": "18.4.3", + "ignore": "6.0.2", "semver": "7.6.3", "strip-json-comments": "3.1.1" - }, - "peerDependencies": { - "@angular-devkit/core": ">= 18.0.0 < 19.0.0", - "@angular-devkit/schematics": ">= 18.0.0 < 19.0.0" } }, "node_modules/@angular-eslint/template-parser": { @@ -365,12 +363,12 @@ "dev": true }, "node_modules/@angular-eslint/utils": { - "version": "18.3.1", - "resolved": "https://registry.npmjs.org/@angular-eslint/utils/-/utils-18.3.1.tgz", - "integrity": "sha512-sd9niZI7h9H2FQ7OLiQsLFBhjhRQTASh+Q0+4+hyjv9idbSHBJli8Gsi2fqj9zhtMKpAZFTrWzuLUpubJ9UYbA==", + "version": "18.4.3", + "resolved": "https://registry.npmjs.org/@angular-eslint/utils/-/utils-18.4.3.tgz", + "integrity": "sha512-w0bJ9+ELAEiPBSTPPm9bvDngfu1d8JbzUhvs2vU+z7sIz/HMwUZT5S4naypj2kNN0gZYGYrW0lt+HIbW87zTAQ==", "dev": true, "dependencies": { - "@angular-eslint/bundled-angular-compiler": "18.3.1" + "@angular-eslint/bundled-angular-compiler": "18.4.3" }, "peerDependencies": { "@typescript-eslint/utils": "^7.11.0 || ^8.0.0", @@ -379,9 +377,9 @@ } }, "node_modules/@angular/animations": { - "version": "18.2.6", - "resolved": "https://registry.npmjs.org/@angular/animations/-/animations-18.2.6.tgz", - "integrity": "sha512-vy9wy+Q9beiRxkEO8wNxFQ63AqAujGvk8AUHepxxIT7QNNc512TNKz8uH+feWDPO38Dm2obwYQHMGzs3WO7pUA==", + "version": "18.2.13", + "resolved": "https://registry.npmjs.org/@angular/animations/-/animations-18.2.13.tgz", + "integrity": "sha512-rG5J5Ek5Hg+Tz2NjkNOaG6PupiNK/lPfophXpsR1t/nWujqnMWX2krahD/i6kgD+jNWNKCJCYSOVvCx/BHOtKA==", "dependencies": { "tslib": "^2.3.0" }, @@ -389,17 +387,17 @@ "node": "^18.19.1 || ^20.11.1 || >=22.0.0" }, "peerDependencies": { - "@angular/core": "18.2.6" + "@angular/core": "18.2.13" } }, "node_modules/@angular/build": { - "version": "18.2.6", - "resolved": "https://registry.npmjs.org/@angular/build/-/build-18.2.6.tgz", - "integrity": "sha512-TQzX6Mi7uXFvmz7+OVl4Za7WawYPcx+B5Ewm6IY/DdMyB9P/Z4tbKb1LO+ynWUXYwm7avXo6XQQ4m5ArDY5F/A==", + "version": "18.2.14", + "resolved": "https://registry.npmjs.org/@angular/build/-/build-18.2.14.tgz", + "integrity": "sha512-9g24Oe/ZLULacW3hEpRCjSZIJPJTzN5BeFbA27epSV5NsrQOoeUGsEpRs90Zmt6eReO0fW1BGshWRoZtpSedcw==", "dev": true, "dependencies": { "@ampproject/remapping": "2.3.0", - "@angular-devkit/architect": "0.1802.6", + "@angular-devkit/architect": "0.1802.14", "@babel/core": "7.25.2", "@babel/helper-annotate-as-pure": "7.24.7", "@babel/helper-split-export-declaration": "7.24.7", @@ -421,7 +419,7 @@ "rollup": "4.22.4", "sass": "1.77.6", "semver": "7.6.3", - "vite": "5.4.6", + "vite": "5.4.14", "watchpack": "2.4.1" }, "engines": { @@ -461,9 +459,9 @@ } }, "node_modules/@angular/cdk": { - "version": "18.2.6", - "resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-18.2.6.tgz", - "integrity": "sha512-Gfq/iv4zhlKYpdQkDaBRwxI71NHNUHM1Cs1XhnZ0/oFct5HXvSv1RHRGTKqBJLLACaAPzZKXJ/UglLoyO5CNiQ==", + "version": "18.2.14", + "resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-18.2.14.tgz", + "integrity": "sha512-vDyOh1lwjfVk9OqoroZAP8pf3xxKUvyl+TVR8nJxL4c5fOfUFkD7l94HaanqKSRwJcI2xiztuu92IVoHn8T33Q==", "dependencies": { "tslib": "^2.3.0" }, @@ -477,17 +475,17 @@ } }, "node_modules/@angular/cli": { - "version": "18.2.6", - "resolved": "https://registry.npmjs.org/@angular/cli/-/cli-18.2.6.tgz", - "integrity": "sha512-tdXsnV/w+Rgu8q0zFsLU5L9ImTVqrTol1vppHaQkJ/vuoHy+s8ZEbBqhVrO/ffosNb2xseUybGYvqMS4zkNQjg==", + "version": "18.2.14", + "resolved": "https://registry.npmjs.org/@angular/cli/-/cli-18.2.14.tgz", + "integrity": "sha512-kWgRRQtJPkr8iwN7DMbTi3sXOnv7H5QhbU/GgD3nNX3D8YCSPmnby4PAE/P3wn7FsIK9JsSchsCt7MZ37Urh9A==", "dev": true, "dependencies": { - "@angular-devkit/architect": "0.1802.6", - "@angular-devkit/core": "18.2.6", - "@angular-devkit/schematics": "18.2.6", + "@angular-devkit/architect": "0.1802.14", + "@angular-devkit/core": "18.2.14", + "@angular-devkit/schematics": "18.2.14", "@inquirer/prompts": "5.3.8", "@listr2/prompt-adapter-inquirer": "2.0.15", - "@schematics/angular": "18.2.6", + "@schematics/angular": "18.2.14", "@yarnpkg/lockfile": "1.1.0", "ini": "4.1.3", "jsonc-parser": "3.3.1", @@ -510,9 +508,9 @@ } }, "node_modules/@angular/common": { - "version": "18.2.6", - "resolved": "https://registry.npmjs.org/@angular/common/-/common-18.2.6.tgz", - "integrity": "sha512-89793ow+wrI1c7C6kyMbnweLNIZHzXthosxAEjipRZGBrqBYjvTtkE45Fl+5yBa3JO7bAhyGkUnEoyvWtZIAEA==", + "version": "18.2.13", + "resolved": "https://registry.npmjs.org/@angular/common/-/common-18.2.13.tgz", + "integrity": "sha512-4ZqrNp1PoZo7VNvW+sbSc2CB2axP1sCH2wXl8B0wdjsj8JY1hF1OhuugwhpAHtGxqewed2kCXayE+ZJqSTV4jw==", "dependencies": { "tslib": "^2.3.0" }, @@ -520,14 +518,14 @@ "node": "^18.19.1 || ^20.11.1 || >=22.0.0" }, "peerDependencies": { - "@angular/core": "18.2.6", + "@angular/core": "18.2.13", "rxjs": "^6.5.3 || ^7.4.0" } }, "node_modules/@angular/compiler": { - "version": "18.2.6", - "resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-18.2.6.tgz", - "integrity": "sha512-3tX2/Qw+bZ8XzKitviH8jzNGyY0uohhehhBB57OJOCc+yr4ojy/7SYFnun1lSsRnDztdCE461641X4iQLCQ94w==", + "version": "18.2.13", + "resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-18.2.13.tgz", + "integrity": "sha512-TzWcrkopyjFF+WeDr2cRe8CcHjU72KfYV3Sm2TkBkcXrkYX5sDjGWrBGrG3hRB4e4okqchrOCvm1MiTdy2vKMA==", "dependencies": { "tslib": "^2.3.0" }, @@ -535,7 +533,7 @@ "node": "^18.19.1 || ^20.11.1 || >=22.0.0" }, "peerDependencies": { - "@angular/core": "18.2.6" + "@angular/core": "18.2.13" }, "peerDependenciesMeta": { "@angular/core": { @@ -544,14 +542,14 @@ } }, "node_modules/@angular/compiler-cli": { - "version": "18.2.6", - "resolved": "https://registry.npmjs.org/@angular/compiler-cli/-/compiler-cli-18.2.6.tgz", - "integrity": "sha512-b5x9STfjNiNM/S0D+CnqRP9UOxPtSz1+RlCH5WdOMiW/p8j5p6dBix8YYgTe6Wg3OD7eItD2pnFQKgF/dWiopA==", + "version": "18.2.13", + "resolved": "https://registry.npmjs.org/@angular/compiler-cli/-/compiler-cli-18.2.13.tgz", + "integrity": "sha512-DBSh4AQwkiJDSiVvJATRmjxf6wyUs9pwQLgaFdSlfuTRO+sdb0J2z1r3BYm8t0IqdoyXzdZq2YCH43EmyvD71g==", "dev": true, "dependencies": { "@babel/core": "7.25.2", "@jridgewell/sourcemap-codec": "^1.4.14", - "chokidar": "^3.0.0", + "chokidar": "^4.0.0", "convert-source-map": "^1.5.1", "reflect-metadata": "^0.2.0", "semver": "^7.0.0", @@ -567,14 +565,14 @@ "node": "^18.19.1 || ^20.11.1 || >=22.0.0" }, "peerDependencies": { - "@angular/compiler": "18.2.6", + "@angular/compiler": "18.2.13", "typescript": ">=5.4 <5.6" } }, "node_modules/@angular/core": { - "version": "18.2.6", - "resolved": "https://registry.npmjs.org/@angular/core/-/core-18.2.6.tgz", - "integrity": "sha512-PjFad2j4YBwLVTw+0Te8CJCa/tV0W8caTHG8aOjj3ObdL6ihGI+FKnwerLc9RVzDFd14BOO4C6/+LbOQAh3Ltw==", + "version": "18.2.13", + "resolved": "https://registry.npmjs.org/@angular/core/-/core-18.2.13.tgz", + "integrity": "sha512-8mbWHMgO95OuFV1Ejy4oKmbe9NOJ3WazQf/f7wks8Bck7pcihd0IKhlPBNjFllbF5o+04EYSwFhEtvEgjMDClA==", "dependencies": { "tslib": "^2.3.0" }, @@ -587,9 +585,9 @@ } }, "node_modules/@angular/forms": { - "version": "18.2.6", - "resolved": "https://registry.npmjs.org/@angular/forms/-/forms-18.2.6.tgz", - "integrity": "sha512-quGkUqTxlBaLB8C/RnpfFG57fdmNF5RQ+368N89Ma++2lpIsVAHaGZZn4yOyo3wNYaM2jBxNqaYxOzZNUl5Tig==", + "version": "18.2.13", + "resolved": "https://registry.npmjs.org/@angular/forms/-/forms-18.2.13.tgz", + "integrity": "sha512-A67D867fu3DSBhdLWWZl/F5pr7v2+dRM2u3U7ZJ0ewh4a+sv+0yqWdJW+a8xIoiHxS+btGEJL2qAKJiH+MCFfg==", "dependencies": { "tslib": "^2.3.0" }, @@ -597,22 +595,22 @@ "node": "^18.19.1 || ^20.11.1 || >=22.0.0" }, "peerDependencies": { - "@angular/common": "18.2.6", - "@angular/core": "18.2.6", - "@angular/platform-browser": "18.2.6", + "@angular/common": "18.2.13", + "@angular/core": "18.2.13", + "@angular/platform-browser": "18.2.13", "rxjs": "^6.5.3 || ^7.4.0" } }, "node_modules/@angular/material": { - "version": "18.2.6", - "resolved": "https://registry.npmjs.org/@angular/material/-/material-18.2.6.tgz", - "integrity": "sha512-ObxC/vomSb9QF3vIztuiInQzws+D6u09Dhfx6uNFjtyICqxEFpF7+Qx7QVDWrsuXOgxZTKgacK8f46iV8hWUfg==", + "version": "18.2.14", + "resolved": "https://registry.npmjs.org/@angular/material/-/material-18.2.14.tgz", + "integrity": "sha512-28pxzJP49Mymt664WnCtPkKeg7kXUsQKTKGf/Kl95rNTEdTJLbnlcc8wV0rT0yQNR7kXgpfBnG7h0ETLv/iu5Q==", "dependencies": { "tslib": "^2.3.0" }, "peerDependencies": { "@angular/animations": "^18.0.0 || ^19.0.0", - "@angular/cdk": "18.2.6", + "@angular/cdk": "18.2.14", "@angular/common": "^18.0.0 || ^19.0.0", "@angular/core": "^18.0.0 || ^19.0.0", "@angular/forms": "^18.0.0 || ^19.0.0", @@ -621,9 +619,9 @@ } }, "node_modules/@angular/platform-browser": { - "version": "18.2.6", - "resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-18.2.6.tgz", - "integrity": "sha512-RA8UMiYNLga+QMwpKcDw1357gYPfPyY/rmLeezMak//BbsENFYQOJ4Z6DBOBNiPlHxmBsUJMGaKdlpQhfCROyQ==", + "version": "18.2.13", + "resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-18.2.13.tgz", + "integrity": "sha512-tu7ZzY6qD3ATdWFzcTcsAKe7M6cJeWbT/4/bF9unyGO3XBPcNYDKoiz10+7ap2PUd0fmPwvuvTvSNJiFEBnB8Q==", "dependencies": { "tslib": "^2.3.0" }, @@ -631,9 +629,9 @@ "node": "^18.19.1 || ^20.11.1 || >=22.0.0" }, "peerDependencies": { - "@angular/animations": "18.2.6", - "@angular/common": "18.2.6", - "@angular/core": "18.2.6" + "@angular/animations": "18.2.13", + "@angular/common": "18.2.13", + "@angular/core": "18.2.13" }, "peerDependenciesMeta": { "@angular/animations": { @@ -642,9 +640,9 @@ } }, "node_modules/@angular/platform-browser-dynamic": { - "version": "18.2.6", - "resolved": "https://registry.npmjs.org/@angular/platform-browser-dynamic/-/platform-browser-dynamic-18.2.6.tgz", - "integrity": "sha512-kGBU3FNc+DF9r33hwHZqiWoZgQbCDdEIucU0NCLCIg0Hw6/Q9Hr2ndjxQI+WynCPg0JeBn34jpouvpeJer3YDQ==", + "version": "18.2.13", + "resolved": "https://registry.npmjs.org/@angular/platform-browser-dynamic/-/platform-browser-dynamic-18.2.13.tgz", + "integrity": "sha512-kbQCf9+8EpuJC7buBxhSiwBtXvjAwAKh6MznD6zd2pyCYqfY6gfRCZQRtK59IfgVtKmEONWI9grEyNIRoTmqJg==", "dependencies": { "tslib": "^2.3.0" }, @@ -652,16 +650,16 @@ "node": "^18.19.1 || ^20.11.1 || >=22.0.0" }, "peerDependencies": { - "@angular/common": "18.2.6", - "@angular/compiler": "18.2.6", - "@angular/core": "18.2.6", - "@angular/platform-browser": "18.2.6" + "@angular/common": "18.2.13", + "@angular/compiler": "18.2.13", + "@angular/core": "18.2.13", + "@angular/platform-browser": "18.2.13" } }, "node_modules/@angular/router": { - "version": "18.2.6", - "resolved": "https://registry.npmjs.org/@angular/router/-/router-18.2.6.tgz", - "integrity": "sha512-t57Sqja8unHhZlPr+4CWnQacuox2M4p2pMHps+31wt337qH6mKf4jqDmK0dE/MFdRyKjT2a2E/2NwtxXxcWNuw==", + "version": "18.2.13", + "resolved": "https://registry.npmjs.org/@angular/router/-/router-18.2.13.tgz", + "integrity": "sha512-VKmfgi/r/CkyBq9nChQ/ptmfu0JT/8ONnLVJ5H+SkFLRYJcIRyHLKjRihMCyVm6xM5yktOdCaW73NTQrFz7+bg==", "dependencies": { "tslib": "^2.3.0" }, @@ -669,19 +667,20 @@ "node": "^18.19.1 || ^20.11.1 || >=22.0.0" }, "peerDependencies": { - "@angular/common": "18.2.6", - "@angular/core": "18.2.6", - "@angular/platform-browser": "18.2.6", + "@angular/common": "18.2.13", + "@angular/core": "18.2.13", + "@angular/platform-browser": "18.2.13", "rxjs": "^6.5.3 || ^7.4.0" } }, "node_modules/@babel/code-frame": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.7.tgz", - "integrity": "sha512-BcYH1CVJBO9tvyIZ2jVeXgSIMvGZ2FDRvDdOIVQyuklNKSsx+eppDEBq/g47Ayw+RqNFE+URvOShmf+f/qwAlA==", + "version": "7.26.2", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.26.2.tgz", + "integrity": "sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==", "dev": true, "dependencies": { - "@babel/highlight": "^7.24.7", + "@babel/helper-validator-identifier": "^7.25.9", + "js-tokens": "^4.0.0", "picocolors": "^1.0.0" }, "engines": { @@ -689,9 +688,9 @@ } }, "node_modules/@babel/compat-data": { - "version": "7.25.4", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.25.4.tgz", - "integrity": "sha512-+LGRog6RAsCJrrrg/IO6LGmpphNe5DiK30dGjCoxxeGv49B10/3XYGxPsAwrDlMFcFEvdAUavDT8r9k/hSyQqQ==", + "version": "7.26.5", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.26.5.tgz", + "integrity": "sha512-XvcZi1KWf88RVbF9wn8MN6tYFloU5qX8KjuF3E1PVBmJ9eypXfs4GRiJwLuTZL0iSnJUKn1BFPa5BPZZJyFzPg==", "dev": true, "engines": { "node": ">=6.9.0" @@ -769,28 +768,15 @@ "node": ">=6.9.0" } }, - "node_modules/@babel/helper-builder-binary-assignment-operator-visitor": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.24.7.tgz", - "integrity": "sha512-xZeCVVdwb4MsDBkkyZ64tReWYrLRHlMN72vP7Bdm3OUOuyFZExhsHUUnuWnm2/XOlAJzR0LfPpB56WXZn0X/lA==", - "dev": true, - "dependencies": { - "@babel/traverse": "^7.24.7", - "@babel/types": "^7.24.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, "node_modules/@babel/helper-compilation-targets": { - "version": "7.25.2", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.25.2.tgz", - "integrity": "sha512-U2U5LsSaZ7TAt3cfaymQ8WHh0pxvdHoEk6HVpaexxixjyEquMh0L0YNJNM6CTGKMXV1iksi0iZkGw4AcFkPaaw==", + "version": "7.26.5", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.26.5.tgz", + "integrity": "sha512-IXuyn5EkouFJscIDuFF5EsiSolseme1s0CZB+QxVugqJLYmKdxI1VfIBOst0SUu4rnk2Z7kqTwmoO1lp3HIfnA==", "dev": true, "dependencies": { - "@babel/compat-data": "^7.25.2", - "@babel/helper-validator-option": "^7.24.8", - "browserslist": "^4.23.1", + "@babel/compat-data": "^7.26.5", + "@babel/helper-validator-option": "^7.25.9", + "browserslist": "^4.24.0", "lru-cache": "^5.1.1", "semver": "^6.3.1" }, @@ -808,17 +794,17 @@ } }, "node_modules/@babel/helper-create-class-features-plugin": { - "version": "7.25.4", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.25.4.tgz", - "integrity": "sha512-ro/bFs3/84MDgDmMwbcHgDa8/E6J3QKNTk4xJJnVeFtGE+tL0K26E3pNxhYz2b67fJpt7Aphw5XcploKXuCvCQ==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.25.9.tgz", + "integrity": "sha512-UTZQMvt0d/rSz6KI+qdu7GQze5TIajwTS++GUozlw8VBJDEOAqSXwm1WvmYEZwqdqSGQshRocPDqrt4HBZB3fQ==", "dev": true, "dependencies": { - "@babel/helper-annotate-as-pure": "^7.24.7", - "@babel/helper-member-expression-to-functions": "^7.24.8", - "@babel/helper-optimise-call-expression": "^7.24.7", - "@babel/helper-replace-supers": "^7.25.0", - "@babel/helper-skip-transparent-expression-wrappers": "^7.24.7", - "@babel/traverse": "^7.25.4", + "@babel/helper-annotate-as-pure": "^7.25.9", + "@babel/helper-member-expression-to-functions": "^7.25.9", + "@babel/helper-optimise-call-expression": "^7.25.9", + "@babel/helper-replace-supers": "^7.25.9", + "@babel/helper-skip-transparent-expression-wrappers": "^7.25.9", + "@babel/traverse": "^7.25.9", "semver": "^6.3.1" }, "engines": { @@ -828,6 +814,18 @@ "@babel/core": "^7.0.0" } }, + "node_modules/@babel/helper-create-class-features-plugin/node_modules/@babel/helper-annotate-as-pure": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.25.9.tgz", + "integrity": "sha512-gv7320KBUFJz1RnylIg5WWYPRXKZ884AGkYpgpWW02TH66Dl+HaC1t1CKd0z3R4b6hdYEcmrNZHUmfCP+1u3/g==", + "dev": true, + "dependencies": { + "@babel/types": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@babel/helper-create-class-features-plugin/node_modules/semver": { "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", @@ -838,13 +836,13 @@ } }, "node_modules/@babel/helper-create-regexp-features-plugin": { - "version": "7.25.2", - "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.25.2.tgz", - "integrity": "sha512-+wqVGP+DFmqwFD3EH6TMTfUNeqDehV3E/dl+Sd54eaXqm17tEUNbEIn4sVivVowbvUpOtIGxdo3GoXyDH9N/9g==", + "version": "7.26.3", + "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.26.3.tgz", + "integrity": "sha512-G7ZRb40uUgdKOQqPLjfD12ZmGA54PzqDFUv2BKImnC9QIfGhIHKvVML0oN8IUiDq4iRqpq74ABpvOaerfWdong==", "dev": true, "dependencies": { - "@babel/helper-annotate-as-pure": "^7.24.7", - "regexpu-core": "^5.3.1", + "@babel/helper-annotate-as-pure": "^7.25.9", + "regexpu-core": "^6.2.0", "semver": "^6.3.1" }, "engines": { @@ -854,6 +852,18 @@ "@babel/core": "^7.0.0" } }, + "node_modules/@babel/helper-create-regexp-features-plugin/node_modules/@babel/helper-annotate-as-pure": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.25.9.tgz", + "integrity": "sha512-gv7320KBUFJz1RnylIg5WWYPRXKZ884AGkYpgpWW02TH66Dl+HaC1t1CKd0z3R4b6hdYEcmrNZHUmfCP+1u3/g==", + "dev": true, + "dependencies": { + "@babel/types": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@babel/helper-create-regexp-features-plugin/node_modules/semver": { "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", @@ -864,9 +874,9 @@ } }, "node_modules/@babel/helper-define-polyfill-provider": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.2.tgz", - "integrity": "sha512-LV76g+C502biUK6AyZ3LK10vDpDyCzZnhZFXkH1L75zHPj68+qc8Zfpx2th+gzwA2MzyK+1g/3EPl62yFnVttQ==", + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.3.tgz", + "integrity": "sha512-HK7Bi+Hj6H+VTHA3ZvBis7V/6hu9QuTrnMXNybfUf2iiuU/N97I8VjB+KbhFF8Rld/Lx5MzoCwPCpPjfK+n8Cg==", "dev": true, "dependencies": { "@babel/helper-compilation-targets": "^7.22.6", @@ -880,41 +890,40 @@ } }, "node_modules/@babel/helper-member-expression-to-functions": { - "version": "7.24.8", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.24.8.tgz", - "integrity": "sha512-LABppdt+Lp/RlBxqrh4qgf1oEH/WxdzQNDJIu5gC/W1GyvPVrOBiItmmM8wan2fm4oYqFuFfkXmlGpLQhPY8CA==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.25.9.tgz", + "integrity": "sha512-wbfdZ9w5vk0C0oyHqAJbc62+vet5prjj01jjJ8sKn3j9h3MQQlflEdXYvuqRWjHnM12coDEqiC1IRCi0U/EKwQ==", "dev": true, "dependencies": { - "@babel/traverse": "^7.24.8", - "@babel/types": "^7.24.8" + "@babel/traverse": "^7.25.9", + "@babel/types": "^7.25.9" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-module-imports": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.24.7.tgz", - "integrity": "sha512-8AyH3C+74cgCVVXow/myrynrAGv+nTVg5vKu2nZph9x7RcRwzmh0VFallJuFTZ9mx6u4eSdXZfcOzSqTUm0HCA==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.25.9.tgz", + "integrity": "sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw==", "dev": true, "dependencies": { - "@babel/traverse": "^7.24.7", - "@babel/types": "^7.24.7" + "@babel/traverse": "^7.25.9", + "@babel/types": "^7.25.9" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-module-transforms": { - "version": "7.25.2", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.25.2.tgz", - "integrity": "sha512-BjyRAbix6j/wv83ftcVJmBt72QtHI56C7JXZoG2xATiLpmoC7dpd8WnkikExHDVPpi/3qCmO6WY1EaXOluiecQ==", + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.26.0.tgz", + "integrity": "sha512-xO+xu6B5K2czEnQye6BHA7DolFFmS3LB7stHZFaOLb1pAwO1HWLS8fXA+eh0A2yIvltPVmx3eNNDBJA2SLHXFw==", "dev": true, "dependencies": { - "@babel/helper-module-imports": "^7.24.7", - "@babel/helper-simple-access": "^7.24.7", - "@babel/helper-validator-identifier": "^7.24.7", - "@babel/traverse": "^7.25.2" + "@babel/helper-module-imports": "^7.25.9", + "@babel/helper-validator-identifier": "^7.25.9", + "@babel/traverse": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -924,35 +933,35 @@ } }, "node_modules/@babel/helper-optimise-call-expression": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.24.7.tgz", - "integrity": "sha512-jKiTsW2xmWwxT1ixIdfXUZp+P5yURx2suzLZr5Hi64rURpDYdMW0pv+Uf17EYk2Rd428Lx4tLsnjGJzYKDM/6A==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.25.9.tgz", + "integrity": "sha512-FIpuNaz5ow8VyrYcnXQTDRGvV6tTjkNtCK/RYNDXGSLlUD6cBuQTSw43CShGxjvfBTfcUA/r6UhUCbtYqkhcuQ==", "dev": true, "dependencies": { - "@babel/types": "^7.24.7" + "@babel/types": "^7.25.9" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-plugin-utils": { - "version": "7.24.8", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.8.tgz", - "integrity": "sha512-FFWx5142D8h2Mgr/iPVGH5G7w6jDn4jUSpZTyDnQO0Yn7Ks2Kuz6Pci8H6MPCoUJegd/UZQ3tAvfLCxQSnWWwg==", + "version": "7.26.5", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.26.5.tgz", + "integrity": "sha512-RS+jZcRdZdRFzMyr+wcsaqOmld1/EqTghfaBGQQd/WnRdzdlvSZ//kF7U8VQTxf1ynZ4cjUcYgjVGx13ewNPMg==", "dev": true, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-remap-async-to-generator": { - "version": "7.25.0", - "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.25.0.tgz", - "integrity": "sha512-NhavI2eWEIz/H9dbrG0TuOicDhNexze43i5z7lEqwYm0WEZVTwnPpA0EafUTP7+6/W79HWIP2cTe3Z5NiSTVpw==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.25.9.tgz", + "integrity": "sha512-IZtukuUeBbhgOcaW2s06OXTzVNJR0ybm4W5xC1opWFFJMZbwRj5LCk+ByYH7WdZPZTt8KnFwA8pvjN2yqcPlgw==", "dev": true, "dependencies": { - "@babel/helper-annotate-as-pure": "^7.24.7", - "@babel/helper-wrap-function": "^7.25.0", - "@babel/traverse": "^7.25.0" + "@babel/helper-annotate-as-pure": "^7.25.9", + "@babel/helper-wrap-function": "^7.25.9", + "@babel/traverse": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -961,44 +970,43 @@ "@babel/core": "^7.0.0" } }, - "node_modules/@babel/helper-replace-supers": { - "version": "7.25.0", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.25.0.tgz", - "integrity": "sha512-q688zIvQVYtZu+i2PsdIu/uWGRpfxzr5WESsfpShfZECkO+d2o+WROWezCi/Q6kJ0tfPa5+pUGUlfx2HhrA3Bg==", + "node_modules/@babel/helper-remap-async-to-generator/node_modules/@babel/helper-annotate-as-pure": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.25.9.tgz", + "integrity": "sha512-gv7320KBUFJz1RnylIg5WWYPRXKZ884AGkYpgpWW02TH66Dl+HaC1t1CKd0z3R4b6hdYEcmrNZHUmfCP+1u3/g==", "dev": true, "dependencies": { - "@babel/helper-member-expression-to-functions": "^7.24.8", - "@babel/helper-optimise-call-expression": "^7.24.7", - "@babel/traverse": "^7.25.0" + "@babel/types": "^7.25.9" }, "engines": { "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" } }, - "node_modules/@babel/helper-simple-access": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.24.7.tgz", - "integrity": "sha512-zBAIvbCMh5Ts+b86r/CjU+4XGYIs+R1j951gxI3KmmxBMhCg4oQMsv6ZXQ64XOm/cvzfU1FmoCyt6+owc5QMYg==", + "node_modules/@babel/helper-replace-supers": { + "version": "7.26.5", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.26.5.tgz", + "integrity": "sha512-bJ6iIVdYX1YooY2X7w1q6VITt+LnUILtNk7zT78ykuwStx8BauCzxvFqFaHjOpW1bVnSUM1PN1f0p5P21wHxvg==", "dev": true, "dependencies": { - "@babel/traverse": "^7.24.7", - "@babel/types": "^7.24.7" + "@babel/helper-member-expression-to-functions": "^7.25.9", + "@babel/helper-optimise-call-expression": "^7.25.9", + "@babel/traverse": "^7.26.5" }, "engines": { "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" } }, "node_modules/@babel/helper-skip-transparent-expression-wrappers": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.24.7.tgz", - "integrity": "sha512-IO+DLT3LQUElMbpzlatRASEyQtfhSE0+m465v++3jyyXeBTBUjtVZg28/gHeV5mrTJqvEKhKroBGAvhW+qPHiQ==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.25.9.tgz", + "integrity": "sha512-K4Du3BFa3gvyhzgPcntrkDgZzQaq6uozzcpGbOO1OEJaI+EJdqWIMTLgFgQf6lrfiDFo5FU+BxKepI9RmZqahA==", "dev": true, "dependencies": { - "@babel/traverse": "^7.24.7", - "@babel/types": "^7.24.7" + "@babel/traverse": "^7.25.9", + "@babel/types": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1017,66 +1025,66 @@ } }, "node_modules/@babel/helper-string-parser": { - "version": "7.24.8", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.8.tgz", - "integrity": "sha512-pO9KhhRcuUyGnJWwyEgnRJTSIZHiT+vMD0kPeD+so0l7mxkMT19g3pjY9GTnHySck/hDzq+dtW/4VgnMkippsQ==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz", + "integrity": "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==", "dev": true, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.7.tgz", - "integrity": "sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz", + "integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==", "dev": true, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-option": { - "version": "7.24.8", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.24.8.tgz", - "integrity": "sha512-xb8t9tD1MHLungh/AIoWYN+gVHaB9kwlu8gffXGSt3FFEIT7RjS+xWbc2vUD1UTZdIpKj/ab3rdqJ7ufngyi2Q==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.25.9.tgz", + "integrity": "sha512-e/zv1co8pp55dNdEcCynfj9X7nyUKUXoUEwfXqaZt0omVOmDe9oOTdKStH4GmAw6zxMFs50ZayuMfHDKlO7Tfw==", "dev": true, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-wrap-function": { - "version": "7.25.0", - "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.25.0.tgz", - "integrity": "sha512-s6Q1ebqutSiZnEjaofc/UKDyC4SbzV5n5SrA2Gq8UawLycr3i04f1dX4OzoQVnexm6aOCh37SQNYlJ/8Ku+PMQ==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.25.9.tgz", + "integrity": "sha512-ETzz9UTjQSTmw39GboatdymDq4XIQbR8ySgVrylRhPOFpsd+JrKHIuF0de7GCWmem+T4uC5z7EZguod7Wj4A4g==", "dev": true, "dependencies": { - "@babel/template": "^7.25.0", - "@babel/traverse": "^7.25.0", - "@babel/types": "^7.25.0" + "@babel/template": "^7.25.9", + "@babel/traverse": "^7.25.9", + "@babel/types": "^7.25.9" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helpers": { - "version": "7.25.6", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.25.6.tgz", - "integrity": "sha512-Xg0tn4HcfTijTwfDwYlvVCl43V6h4KyVVX2aEm4qdO/PC6L2YvzLHFdmxhoeSA3eslcE6+ZVXHgWwopXYLNq4Q==", + "version": "7.26.7", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.26.7.tgz", + "integrity": "sha512-8NHiL98vsi0mbPQmYAGWwfcFaOy4j2HY49fXJCfuDcdE7fMIsH9a7GdaeXpIBsbT7307WU8KCMp5pUVDNL4f9A==", "dev": true, "dependencies": { - "@babel/template": "^7.25.0", - "@babel/types": "^7.25.6" + "@babel/template": "^7.25.9", + "@babel/types": "^7.26.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/highlight": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.7.tgz", - "integrity": "sha512-EStJpq4OuY8xYfhGVXngigBJRWxftKX9ksiGDnmlY3o7B/V7KIAc9X4oiK87uPJSc/vs5L869bem5fhZa8caZw==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.25.9.tgz", + "integrity": "sha512-llL88JShoCsth8fF8R4SJnIn+WLvR6ccFxu1H3FlMhDontdcmZWf2HgIZ7AIqV3Xcck1idlohrN4EUBQz6klbw==", "dev": true, "dependencies": { - "@babel/helper-validator-identifier": "^7.24.7", + "@babel/helper-validator-identifier": "^7.25.9", "chalk": "^2.4.2", "js-tokens": "^4.0.0", "picocolors": "^1.0.0" @@ -1085,13 +1093,84 @@ "node": ">=6.9.0" } }, + "node_modules/@babel/highlight/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/@babel/highlight/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true + }, + "node_modules/@babel/highlight/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/@babel/highlight/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/@babel/parser": { - "version": "7.25.6", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.25.6.tgz", - "integrity": "sha512-trGdfBdbD0l1ZPmcJ83eNxB9rbEax4ALFTF7fN386TMYbeCQbyme5cOEXQhbGXKebwGaB/J52w1mrklMcbgy6Q==", + "version": "7.26.7", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.26.7.tgz", + "integrity": "sha512-kEvgGGgEjRUutvdVvZhbn/BxVt+5VSpwXz1j3WYXQbXDo8KzFOPNG2GQbdAiNq8g6wn1yKk7C/qrke03a84V+w==", "dev": true, "dependencies": { - "@babel/types": "^7.25.6" + "@babel/types": "^7.26.7" }, "bin": { "parser": "bin/babel-parser.js" @@ -1101,13 +1180,13 @@ } }, "node_modules/@babel/plugin-bugfix-firefox-class-in-computed-class-key": { - "version": "7.25.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.25.3.tgz", - "integrity": "sha512-wUrcsxZg6rqBXG05HG1FPYgsP6EvwF4WpBbxIpWIIYnH8wG0gzx3yZY3dtEHas4sTAOGkbTsc9EGPxwff8lRoA==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.25.9.tgz", + "integrity": "sha512-ZkRyVkThtxQ/J6nv3JFYv1RYY+JT5BvU0y3k5bWrmuG4woXypRa4PXmm9RhOwodRkYFWqC0C0cqcJ4OqR7kW+g==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.24.8", - "@babel/traverse": "^7.25.3" + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/traverse": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1117,12 +1196,12 @@ } }, "node_modules/@babel/plugin-bugfix-safari-class-field-initializer-scope": { - "version": "7.25.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-class-field-initializer-scope/-/plugin-bugfix-safari-class-field-initializer-scope-7.25.0.tgz", - "integrity": "sha512-Bm4bH2qsX880b/3ziJ8KD711LT7z4u8CFudmjqle65AZj/HNUFhEf90dqYv6O86buWvSBmeQDjv0Tn2aF/bIBA==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-class-field-initializer-scope/-/plugin-bugfix-safari-class-field-initializer-scope-7.25.9.tgz", + "integrity": "sha512-MrGRLZxLD/Zjj0gdU15dfs+HH/OXvnw/U4jJD8vpcP2CJQapPEv1IWwjc/qMg7ItBlPwSv1hRBbb7LeuANdcnw==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.24.8" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1132,12 +1211,12 @@ } }, "node_modules/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { - "version": "7.25.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.25.0.tgz", - "integrity": "sha512-lXwdNZtTmeVOOFtwM/WDe7yg1PL8sYhRk/XH0FzbR2HDQ0xC+EnQ/JHeoMYSavtU115tnUk0q9CDyq8si+LMAA==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.25.9.tgz", + "integrity": "sha512-2qUwwfAFpJLZqxd02YW9btUCZHl+RFvdDkNfZwaIJrvB8Tesjsk8pEQkTvGwZXLqXUx/2oyY3ySRhm6HOXuCug==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.24.8" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1147,14 +1226,14 @@ } }, "node_modules/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.24.7.tgz", - "integrity": "sha512-+izXIbke1T33mY4MSNnrqhPXDz01WYhEf3yF5NbnUtkiNnm+XBZJl3kNfoK6NKmYlz/D07+l2GWVK/QfDkNCuQ==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.25.9.tgz", + "integrity": "sha512-6xWgLZTJXwilVjlnV7ospI3xi+sl8lN8rXXbBD6vYn3UYDlGsag8wrZkKcSI8G6KgqKP7vNFaDgeDnfAABq61g==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7", - "@babel/helper-skip-transparent-expression-wrappers": "^7.24.7", - "@babel/plugin-transform-optional-chaining": "^7.24.7" + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/helper-skip-transparent-expression-wrappers": "^7.25.9", + "@babel/plugin-transform-optional-chaining": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1164,13 +1243,13 @@ } }, "node_modules/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": { - "version": "7.25.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.25.0.tgz", - "integrity": "sha512-tggFrk1AIShG/RUQbEwt2Tr/E+ObkfwrPjR6BjbRvsx24+PSjK8zrq0GWPNCjo8qpRx4DuJzlcvWJqlm+0h3kw==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.25.9.tgz", + "integrity": "sha512-aLnMXYPnzwwqhYSCyXfKkIkYgJ8zv9RK+roo9DkTXz38ynIhd9XCbN08s3MGvqL2MYGVUGdRQLL/JqBIeJhJBg==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.24.8", - "@babel/traverse": "^7.25.0" + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/traverse": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1255,12 +1334,12 @@ } }, "node_modules/@babel/plugin-syntax-import-assertions": { - "version": "7.25.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.25.6.tgz", - "integrity": "sha512-aABl0jHw9bZ2karQ/uUD6XP4u0SG22SJrOHFoL6XB1R7dTovOP4TzTlsxOYC5yQ1pdscVK2JTUnF6QL3ARoAiQ==", + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.26.0.tgz", + "integrity": "sha512-QCWT5Hh830hK5EQa7XzuqIkQU9tT/whqbDz7kuaZMHFl1inRRg7JnuAEOQ0Ur0QUl0NufCk1msK2BeY79Aj/eg==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.24.8" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1427,12 +1506,12 @@ } }, "node_modules/@babel/plugin-transform-arrow-functions": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.24.7.tgz", - "integrity": "sha512-Dt9LQs6iEY++gXUwY03DNFat5C2NbO48jj+j/bSAz6b3HgPs39qcPiYt77fDObIcFwj3/C2ICX9YMwGflUoSHQ==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.25.9.tgz", + "integrity": "sha512-6jmooXYIwn9ca5/RylZADJ+EnSxVUS5sjeJ9UPk6RWRzXCmOJCy6dqItPJFpw2cuCangPK4OYr5uhGKcmrm5Qg==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1477,12 +1556,12 @@ } }, "node_modules/@babel/plugin-transform-block-scoped-functions": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.24.7.tgz", - "integrity": "sha512-yO7RAz6EsVQDaBH18IDJcMB1HnrUn2FJ/Jslc/WtPPWcjhpUJXU/rjbwmluzp7v/ZzWcEhTMXELnnsz8djWDwQ==", + "version": "7.26.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.26.5.tgz", + "integrity": "sha512-chuTSY+hq09+/f5lMj8ZSYgCFpppV2CbYrhNFJ1BFoXpiWPnnAb7R0MqrafCpN8E1+YRrtM1MXZHJdIx8B6rMQ==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7" + "@babel/helper-plugin-utils": "^7.26.5" }, "engines": { "node": ">=6.9.0" @@ -1492,12 +1571,12 @@ } }, "node_modules/@babel/plugin-transform-block-scoping": { - "version": "7.25.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.25.0.tgz", - "integrity": "sha512-yBQjYoOjXlFv9nlXb3f1casSHOZkWr29NX+zChVanLg5Nc157CrbEX9D7hxxtTpuFy7Q0YzmmWfJxzvps4kXrQ==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.25.9.tgz", + "integrity": "sha512-1F05O7AYjymAtqbsFETboN1NvBdcnzMerO+zlMyJBEz6WkMdejvGWw9p05iTSjC85RLlBseHHQpYaM4gzJkBGg==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.24.8" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1507,13 +1586,13 @@ } }, "node_modules/@babel/plugin-transform-class-properties": { - "version": "7.25.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.25.4.tgz", - "integrity": "sha512-nZeZHyCWPfjkdU5pA/uHiTaDAFUEqkpzf1YoQT2NeSynCGYq9rxfyI3XpQbfx/a0hSnFH6TGlEXvae5Vi7GD8g==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.25.9.tgz", + "integrity": "sha512-bbMAII8GRSkcd0h0b4X+36GksxuheLFjP65ul9w6C3KgAamI3JqErNgSrosX6ZPj+Mpim5VvEbawXxJCyEUV3Q==", "dev": true, "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.25.4", - "@babel/helper-plugin-utils": "^7.24.8" + "@babel/helper-create-class-features-plugin": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1523,14 +1602,13 @@ } }, "node_modules/@babel/plugin-transform-class-static-block": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.24.7.tgz", - "integrity": "sha512-HMXK3WbBPpZQufbMG4B46A90PkuuhN9vBCb5T8+VAHqvAqvcLi+2cKoukcpmUYkszLhScU3l1iudhrks3DggRQ==", + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.26.0.tgz", + "integrity": "sha512-6J2APTs7BDDm+UMqP1useWqhcRAXo0WIoVj26N7kPFB6S73Lgvyka4KTZYIxtgYXiN5HTyRObA72N2iu628iTQ==", "dev": true, "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.24.7", - "@babel/helper-plugin-utils": "^7.24.7", - "@babel/plugin-syntax-class-static-block": "^7.14.5" + "@babel/helper-create-class-features-plugin": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1540,16 +1618,16 @@ } }, "node_modules/@babel/plugin-transform-classes": { - "version": "7.25.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.25.4.tgz", - "integrity": "sha512-oexUfaQle2pF/b6E0dwsxQtAol9TLSO88kQvym6HHBWFliV2lGdrPieX+WgMRLSJDVzdYywk7jXbLPuO2KLTLg==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.25.9.tgz", + "integrity": "sha512-mD8APIXmseE7oZvZgGABDyM34GUmK45Um2TXiBUt7PnuAxrgoSVf123qUzPxEr/+/BHrRn5NMZCdE2m/1F8DGg==", "dev": true, "dependencies": { - "@babel/helper-annotate-as-pure": "^7.24.7", - "@babel/helper-compilation-targets": "^7.25.2", - "@babel/helper-plugin-utils": "^7.24.8", - "@babel/helper-replace-supers": "^7.25.0", - "@babel/traverse": "^7.25.4", + "@babel/helper-annotate-as-pure": "^7.25.9", + "@babel/helper-compilation-targets": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/helper-replace-supers": "^7.25.9", + "@babel/traverse": "^7.25.9", "globals": "^11.1.0" }, "engines": { @@ -1559,14 +1637,26 @@ "@babel/core": "^7.0.0-0" } }, + "node_modules/@babel/plugin-transform-classes/node_modules/@babel/helper-annotate-as-pure": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.25.9.tgz", + "integrity": "sha512-gv7320KBUFJz1RnylIg5WWYPRXKZ884AGkYpgpWW02TH66Dl+HaC1t1CKd0z3R4b6hdYEcmrNZHUmfCP+1u3/g==", + "dev": true, + "dependencies": { + "@babel/types": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@babel/plugin-transform-computed-properties": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.24.7.tgz", - "integrity": "sha512-25cS7v+707Gu6Ds2oY6tCkUwsJ9YIDbggd9+cu9jzzDgiNq7hR/8dkzxWfKWnTic26vsI3EsCXNd4iEB6e8esQ==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.25.9.tgz", + "integrity": "sha512-HnBegGqXZR12xbcTHlJ9HGxw1OniltT26J5YpfruGqtUHlz/xKf/G2ak9e+t0rVqrjXa9WOhvYPz1ERfMj23AA==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7", - "@babel/template": "^7.24.7" + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/template": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1576,12 +1666,12 @@ } }, "node_modules/@babel/plugin-transform-destructuring": { - "version": "7.24.8", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.24.8.tgz", - "integrity": "sha512-36e87mfY8TnRxc7yc6M9g9gOB7rKgSahqkIKwLpz4Ppk2+zC2Cy1is0uwtuSG6AE4zlTOUa+7JGz9jCJGLqQFQ==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.25.9.tgz", + "integrity": "sha512-WkCGb/3ZxXepmMiX101nnGiU+1CAdut8oHyEOHxkKuS1qKpU2SMXE2uSvfz8PBuLd49V6LEsbtyPhWC7fnkgvQ==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.24.8" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1591,13 +1681,13 @@ } }, "node_modules/@babel/plugin-transform-dotall-regex": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.24.7.tgz", - "integrity": "sha512-ZOA3W+1RRTSWvyqcMJDLqbchh7U4NRGqwRfFSVbOLS/ePIP4vHB5e8T8eXcuqyN1QkgKyj5wuW0lcS85v4CrSw==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.25.9.tgz", + "integrity": "sha512-t7ZQ7g5trIgSRYhI9pIJtRl64KHotutUJsh4Eze5l7olJv+mRSg4/MmbZ0tv1eeqRbdvo/+trvJD/Oc5DmW2cA==", "dev": true, "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.24.7", - "@babel/helper-plugin-utils": "^7.24.7" + "@babel/helper-create-regexp-features-plugin": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1607,12 +1697,12 @@ } }, "node_modules/@babel/plugin-transform-duplicate-keys": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.24.7.tgz", - "integrity": "sha512-JdYfXyCRihAe46jUIliuL2/s0x0wObgwwiGxw/UbgJBr20gQBThrokO4nYKgWkD7uBaqM7+9x5TU7NkExZJyzw==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.25.9.tgz", + "integrity": "sha512-LZxhJ6dvBb/f3x8xwWIuyiAHy56nrRG3PeYTpBkkzkYRRQ6tJLu68lEF5VIqMUZiAV7a8+Tb78nEoMCMcqjXBw==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1622,13 +1712,13 @@ } }, "node_modules/@babel/plugin-transform-duplicate-named-capturing-groups-regex": { - "version": "7.25.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-named-capturing-groups-regex/-/plugin-transform-duplicate-named-capturing-groups-regex-7.25.0.tgz", - "integrity": "sha512-YLpb4LlYSc3sCUa35un84poXoraOiQucUTTu8X1j18JV+gNa8E0nyUf/CjZ171IRGr4jEguF+vzJU66QZhn29g==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-named-capturing-groups-regex/-/plugin-transform-duplicate-named-capturing-groups-regex-7.25.9.tgz", + "integrity": "sha512-0UfuJS0EsXbRvKnwcLjFtJy/Sxc5J5jhLHnFhy7u4zih97Hz6tJkLU+O+FMMrNZrosUPxDi6sYxJ/EA8jDiAog==", "dev": true, "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.25.0", - "@babel/helper-plugin-utils": "^7.24.8" + "@babel/helper-create-regexp-features-plugin": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1638,13 +1728,12 @@ } }, "node_modules/@babel/plugin-transform-dynamic-import": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.24.7.tgz", - "integrity": "sha512-sc3X26PhZQDb3JhORmakcbvkeInvxz+A8oda99lj7J60QRuPZvNAk9wQlTBS1ZynelDrDmTU4pw1tyc5d5ZMUg==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.25.9.tgz", + "integrity": "sha512-GCggjexbmSLaFhqsojeugBpeaRIgWNTcgKVq/0qIteFEqY2A+b9QidYadrWlnbWQUrW5fn+mCvf3tr7OeBFTyg==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7", - "@babel/plugin-syntax-dynamic-import": "^7.8.3" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1654,13 +1743,12 @@ } }, "node_modules/@babel/plugin-transform-exponentiation-operator": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.24.7.tgz", - "integrity": "sha512-Rqe/vSc9OYgDajNIK35u7ot+KeCoetqQYFXM4Epf7M7ez3lWlOjrDjrwMei6caCVhfdw+mIKD4cgdGNy5JQotQ==", + "version": "7.26.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.26.3.tgz", + "integrity": "sha512-7CAHcQ58z2chuXPWblnn1K6rLDnDWieghSOEmqQsrBenH0P9InCUtOJYD89pvngljmZlJcz3fcmgYsXFNGa1ZQ==", "dev": true, "dependencies": { - "@babel/helper-builder-binary-assignment-operator-visitor": "^7.24.7", - "@babel/helper-plugin-utils": "^7.24.7" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1670,13 +1758,12 @@ } }, "node_modules/@babel/plugin-transform-export-namespace-from": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.24.7.tgz", - "integrity": "sha512-v0K9uNYsPL3oXZ/7F9NNIbAj2jv1whUEtyA6aujhekLs56R++JDQuzRcP2/z4WX5Vg/c5lE9uWZA0/iUoFhLTA==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.25.9.tgz", + "integrity": "sha512-2NsEz+CxzJIVOPx2o9UsW1rXLqtChtLoVnwYHHiB04wS5sgn7mrV45fWMBX0Kk+ub9uXytVYfNP2HjbVbCB3Ww==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7", - "@babel/plugin-syntax-export-namespace-from": "^7.8.3" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1686,13 +1773,13 @@ } }, "node_modules/@babel/plugin-transform-for-of": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.24.7.tgz", - "integrity": "sha512-wo9ogrDG1ITTTBsy46oGiN1dS9A7MROBTcYsfS8DtsImMkHk9JXJ3EWQM6X2SUw4x80uGPlwj0o00Uoc6nEE3g==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.25.9.tgz", + "integrity": "sha512-LqHxduHoaGELJl2uhImHwRQudhCM50pT46rIBNvtT/Oql3nqiS3wOwP+5ten7NpYSXrrVLgtZU3DZmPtWZo16A==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7", - "@babel/helper-skip-transparent-expression-wrappers": "^7.24.7" + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/helper-skip-transparent-expression-wrappers": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1702,14 +1789,14 @@ } }, "node_modules/@babel/plugin-transform-function-name": { - "version": "7.25.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.25.1.tgz", - "integrity": "sha512-TVVJVdW9RKMNgJJlLtHsKDTydjZAbwIsn6ySBPQaEAUU5+gVvlJt/9nRmqVbsV/IBanRjzWoaAQKLoamWVOUuA==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.25.9.tgz", + "integrity": "sha512-8lP+Yxjv14Vc5MuWBpJsoUCd3hD6V9DgBon2FVYL4jJgbnVQ9fTgYmonchzZJOVNgzEgbxp4OwAf6xz6M/14XA==", "dev": true, "dependencies": { - "@babel/helper-compilation-targets": "^7.24.8", - "@babel/helper-plugin-utils": "^7.24.8", - "@babel/traverse": "^7.25.1" + "@babel/helper-compilation-targets": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/traverse": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1719,13 +1806,12 @@ } }, "node_modules/@babel/plugin-transform-json-strings": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.24.7.tgz", - "integrity": "sha512-2yFnBGDvRuxAaE/f0vfBKvtnvvqU8tGpMHqMNpTN2oWMKIR3NqFkjaAgGwawhqK/pIN2T3XdjGPdaG0vDhOBGw==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.25.9.tgz", + "integrity": "sha512-xoTMk0WXceiiIvsaquQQUaLLXSW1KJ159KP87VilruQm0LNNGxWzahxSS6T6i4Zg3ezp4vA4zuwiNUR53qmQAw==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7", - "@babel/plugin-syntax-json-strings": "^7.8.3" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1735,12 +1821,12 @@ } }, "node_modules/@babel/plugin-transform-literals": { - "version": "7.25.2", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.25.2.tgz", - "integrity": "sha512-HQI+HcTbm9ur3Z2DkO+jgESMAMcYLuN/A7NRw9juzxAezN9AvqvUTnpKP/9kkYANz6u7dFlAyOu44ejuGySlfw==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.25.9.tgz", + "integrity": "sha512-9N7+2lFziW8W9pBl2TzaNht3+pgMIRP74zizeCSrtnSKVdUl8mAjjOP2OOVQAfZ881P2cNjDj1uAMEdeD50nuQ==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.24.8" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1750,13 +1836,12 @@ } }, "node_modules/@babel/plugin-transform-logical-assignment-operators": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.24.7.tgz", - "integrity": "sha512-4D2tpwlQ1odXmTEIFWy9ELJcZHqrStlzK/dAOWYyxX3zT0iXQB6banjgeOJQXzEc4S0E0a5A+hahxPaEFYftsw==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.25.9.tgz", + "integrity": "sha512-wI4wRAzGko551Y8eVf6iOY9EouIDTtPb0ByZx+ktDGHwv6bHFimrgJM/2T021txPZ2s4c7bqvHbd+vXG6K948Q==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7", - "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1766,12 +1851,12 @@ } }, "node_modules/@babel/plugin-transform-member-expression-literals": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.24.7.tgz", - "integrity": "sha512-T/hRC1uqrzXMKLQ6UCwMT85S3EvqaBXDGf0FaMf4446Qx9vKwlghvee0+uuZcDUCZU5RuNi4781UQ7R308zzBw==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.25.9.tgz", + "integrity": "sha512-PYazBVfofCQkkMzh2P6IdIUaCEWni3iYEerAsRWuVd8+jlM1S9S9cz1dF9hIzyoZ8IA3+OwVYIp9v9e+GbgZhA==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1781,13 +1866,13 @@ } }, "node_modules/@babel/plugin-transform-modules-amd": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.24.7.tgz", - "integrity": "sha512-9+pB1qxV3vs/8Hdmz/CulFB8w2tuu6EB94JZFsjdqxQokwGa9Unap7Bo2gGBGIvPmDIVvQrom7r5m/TCDMURhg==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.25.9.tgz", + "integrity": "sha512-g5T11tnI36jVClQlMlt4qKDLlWnG5pP9CSM4GhdRciTNMRgkfpo5cR6b4rGIOYPgRRuFAvwjPQ/Yk+ql4dyhbw==", "dev": true, "dependencies": { - "@babel/helper-module-transforms": "^7.24.7", - "@babel/helper-plugin-utils": "^7.24.7" + "@babel/helper-module-transforms": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1797,14 +1882,13 @@ } }, "node_modules/@babel/plugin-transform-modules-commonjs": { - "version": "7.24.8", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.24.8.tgz", - "integrity": "sha512-WHsk9H8XxRs3JXKWFiqtQebdh9b/pTk4EgueygFzYlTKAg0Ud985mSevdNjdXdFBATSKVJGQXP1tv6aGbssLKA==", + "version": "7.26.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.26.3.tgz", + "integrity": "sha512-MgR55l4q9KddUDITEzEFYn5ZsGDXMSsU9E+kh7fjRXTIC3RHqfCo8RPRbyReYJh44HQ/yomFkqbOFohXvDCiIQ==", "dev": true, "dependencies": { - "@babel/helper-module-transforms": "^7.24.8", - "@babel/helper-plugin-utils": "^7.24.8", - "@babel/helper-simple-access": "^7.24.7" + "@babel/helper-module-transforms": "^7.26.0", + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1814,15 +1898,15 @@ } }, "node_modules/@babel/plugin-transform-modules-systemjs": { - "version": "7.25.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.25.0.tgz", - "integrity": "sha512-YPJfjQPDXxyQWg/0+jHKj1llnY5f/R6a0p/vP4lPymxLu7Lvl4k2WMitqi08yxwQcCVUUdG9LCUj4TNEgAp3Jw==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.25.9.tgz", + "integrity": "sha512-hyss7iIlH/zLHaehT+xwiymtPOpsiwIIRlCAOwBB04ta5Tt+lNItADdlXw3jAWZ96VJ2jlhl/c+PNIQPKNfvcA==", "dev": true, "dependencies": { - "@babel/helper-module-transforms": "^7.25.0", - "@babel/helper-plugin-utils": "^7.24.8", - "@babel/helper-validator-identifier": "^7.24.7", - "@babel/traverse": "^7.25.0" + "@babel/helper-module-transforms": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/helper-validator-identifier": "^7.25.9", + "@babel/traverse": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1832,13 +1916,13 @@ } }, "node_modules/@babel/plugin-transform-modules-umd": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.24.7.tgz", - "integrity": "sha512-3aytQvqJ/h9z4g8AsKPLvD4Zqi2qT+L3j7XoFFu1XBlZWEl2/1kWnhmAbxpLgPrHSY0M6UA02jyTiwUVtiKR6A==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.25.9.tgz", + "integrity": "sha512-bS9MVObUgE7ww36HEfwe6g9WakQ0KF07mQF74uuXdkoziUPfKyu/nIm663kz//e5O1nPInPFx36z7WJmJ4yNEw==", "dev": true, "dependencies": { - "@babel/helper-module-transforms": "^7.24.7", - "@babel/helper-plugin-utils": "^7.24.7" + "@babel/helper-module-transforms": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1848,13 +1932,13 @@ } }, "node_modules/@babel/plugin-transform-named-capturing-groups-regex": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.24.7.tgz", - "integrity": "sha512-/jr7h/EWeJtk1U/uz2jlsCioHkZk1JJZVcc8oQsJ1dUlaJD83f4/6Zeh2aHt9BIFokHIsSeDfhUmju0+1GPd6g==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.25.9.tgz", + "integrity": "sha512-oqB6WHdKTGl3q/ItQhpLSnWWOpjUJLsOCLVyeFgeTktkBSCiurvPOsyt93gibI9CmuKvTUEtWmG5VhZD+5T/KA==", "dev": true, "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.24.7", - "@babel/helper-plugin-utils": "^7.24.7" + "@babel/helper-create-regexp-features-plugin": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1864,12 +1948,12 @@ } }, "node_modules/@babel/plugin-transform-new-target": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.24.7.tgz", - "integrity": "sha512-RNKwfRIXg4Ls/8mMTza5oPF5RkOW8Wy/WgMAp1/F1yZ8mMbtwXW+HDoJiOsagWrAhI5f57Vncrmr9XeT4CVapA==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.25.9.tgz", + "integrity": "sha512-U/3p8X1yCSoKyUj2eOBIx3FOn6pElFOKvAAGf8HTtItuPyB+ZeOqfn+mvTtg9ZlOAjsPdK3ayQEjqHjU/yLeVQ==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1879,13 +1963,12 @@ } }, "node_modules/@babel/plugin-transform-nullish-coalescing-operator": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.24.7.tgz", - "integrity": "sha512-Ts7xQVk1OEocqzm8rHMXHlxvsfZ0cEF2yomUqpKENHWMF4zKk175Y4q8H5knJes6PgYad50uuRmt3UJuhBw8pQ==", + "version": "7.26.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.26.6.tgz", + "integrity": "sha512-CKW8Vu+uUZneQCPtXmSBUC6NCAUdya26hWCElAWh5mVSlSRsmiCPUUDKb3Z0szng1hiAJa098Hkhg9o4SE35Qw==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3" + "@babel/helper-plugin-utils": "^7.26.5" }, "engines": { "node": ">=6.9.0" @@ -1895,13 +1978,12 @@ } }, "node_modules/@babel/plugin-transform-numeric-separator": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.24.7.tgz", - "integrity": "sha512-e6q1TiVUzvH9KRvicuxdBTUj4AdKSRwzIyFFnfnezpCfP2/7Qmbb8qbU2j7GODbl4JMkblitCQjKYUaX/qkkwA==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.25.9.tgz", + "integrity": "sha512-TlprrJ1GBZ3r6s96Yq8gEQv82s8/5HnCVHtEJScUj90thHQbwe+E5MLhi2bbNHBEJuzrvltXSru+BUxHDoog7Q==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7", - "@babel/plugin-syntax-numeric-separator": "^7.10.4" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1911,15 +1993,14 @@ } }, "node_modules/@babel/plugin-transform-object-rest-spread": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.24.7.tgz", - "integrity": "sha512-4QrHAr0aXQCEFni2q4DqKLD31n2DL+RxcwnNjDFkSG0eNQ/xCavnRkfCUjsyqGC2OviNJvZOF/mQqZBw7i2C5Q==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.25.9.tgz", + "integrity": "sha512-fSaXafEE9CVHPweLYw4J0emp1t8zYTXyzN3UuG+lylqkvYd7RMrsOQ8TYx5RF231be0vqtFC6jnx3UmpJmKBYg==", "dev": true, "dependencies": { - "@babel/helper-compilation-targets": "^7.24.7", - "@babel/helper-plugin-utils": "^7.24.7", - "@babel/plugin-syntax-object-rest-spread": "^7.8.3", - "@babel/plugin-transform-parameters": "^7.24.7" + "@babel/helper-compilation-targets": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/plugin-transform-parameters": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1929,13 +2010,13 @@ } }, "node_modules/@babel/plugin-transform-object-super": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.24.7.tgz", - "integrity": "sha512-A/vVLwN6lBrMFmMDmPPz0jnE6ZGx7Jq7d6sT/Ev4H65RER6pZ+kczlf1DthF5N0qaPHBsI7UXiE8Zy66nmAovg==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.25.9.tgz", + "integrity": "sha512-Kj/Gh+Rw2RNLbCK1VAWj2U48yxxqL2x0k10nPtSdRa0O2xnHXalD0s+o1A6a0W43gJ00ANo38jxkQreckOzv5A==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7", - "@babel/helper-replace-supers": "^7.24.7" + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/helper-replace-supers": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1945,13 +2026,12 @@ } }, "node_modules/@babel/plugin-transform-optional-catch-binding": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.24.7.tgz", - "integrity": "sha512-uLEndKqP5BfBbC/5jTwPxLh9kqPWWgzN/f8w6UwAIirAEqiIVJWWY312X72Eub09g5KF9+Zn7+hT7sDxmhRuKA==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.25.9.tgz", + "integrity": "sha512-qM/6m6hQZzDcZF3onzIhZeDHDO43bkNNlOX0i8n3lR6zLbu0GN2d8qfM/IERJZYauhAHSLHy39NF0Ctdvcid7g==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7", - "@babel/plugin-syntax-optional-catch-binding": "^7.8.3" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1961,14 +2041,13 @@ } }, "node_modules/@babel/plugin-transform-optional-chaining": { - "version": "7.24.8", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.24.8.tgz", - "integrity": "sha512-5cTOLSMs9eypEy8JUVvIKOu6NgvbJMnpG62VpIHrTmROdQ+L5mDAaI40g25k5vXti55JWNX5jCkq3HZxXBQANw==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.25.9.tgz", + "integrity": "sha512-6AvV0FsLULbpnXeBjrY4dmWF8F7gf8QnvTEoO/wX/5xm/xE1Xo8oPuD3MPS+KS9f9XBEAWN7X1aWr4z9HdOr7A==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.24.8", - "@babel/helper-skip-transparent-expression-wrappers": "^7.24.7", - "@babel/plugin-syntax-optional-chaining": "^7.8.3" + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/helper-skip-transparent-expression-wrappers": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1978,12 +2057,12 @@ } }, "node_modules/@babel/plugin-transform-parameters": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.24.7.tgz", - "integrity": "sha512-yGWW5Rr+sQOhK0Ot8hjDJuxU3XLRQGflvT4lhlSY0DFvdb3TwKaY26CJzHtYllU0vT9j58hc37ndFPsqT1SrzA==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.25.9.tgz", + "integrity": "sha512-wzz6MKwpnshBAiRmn4jR8LYz/g8Ksg0o80XmwZDlordjwEk9SxBzTWC7F5ef1jhbrbOW2DJ5J6ayRukrJmnr0g==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1993,13 +2072,13 @@ } }, "node_modules/@babel/plugin-transform-private-methods": { - "version": "7.25.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.25.4.tgz", - "integrity": "sha512-ao8BG7E2b/URaUQGqN3Tlsg+M3KlHY6rJ1O1gXAEUnZoyNQnvKyH87Kfg+FoxSeyWUB8ISZZsC91C44ZuBFytw==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.25.9.tgz", + "integrity": "sha512-D/JUozNpQLAPUVusvqMxyvjzllRaF8/nSrP1s2YGQT/W4LHK4xxsMcHjhOGTS01mp9Hda8nswb+FblLdJornQw==", "dev": true, "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.25.4", - "@babel/helper-plugin-utils": "^7.24.8" + "@babel/helper-create-class-features-plugin": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -2009,15 +2088,14 @@ } }, "node_modules/@babel/plugin-transform-private-property-in-object": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.24.7.tgz", - "integrity": "sha512-9z76mxwnwFxMyxZWEgdgECQglF2Q7cFLm0kMf8pGwt+GSJsY0cONKj/UuO4bOH0w/uAel3ekS4ra5CEAyJRmDA==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.25.9.tgz", + "integrity": "sha512-Evf3kcMqzXA3xfYJmZ9Pg1OvKdtqsDMSWBDzZOPLvHiTt36E75jLDQo5w1gtRU95Q4E5PDttrTf25Fw8d/uWLw==", "dev": true, "dependencies": { - "@babel/helper-annotate-as-pure": "^7.24.7", - "@babel/helper-create-class-features-plugin": "^7.24.7", - "@babel/helper-plugin-utils": "^7.24.7", - "@babel/plugin-syntax-private-property-in-object": "^7.14.5" + "@babel/helper-annotate-as-pure": "^7.25.9", + "@babel/helper-create-class-features-plugin": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -2026,13 +2104,25 @@ "@babel/core": "^7.0.0-0" } }, + "node_modules/@babel/plugin-transform-private-property-in-object/node_modules/@babel/helper-annotate-as-pure": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.25.9.tgz", + "integrity": "sha512-gv7320KBUFJz1RnylIg5WWYPRXKZ884AGkYpgpWW02TH66Dl+HaC1t1CKd0z3R4b6hdYEcmrNZHUmfCP+1u3/g==", + "dev": true, + "dependencies": { + "@babel/types": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@babel/plugin-transform-property-literals": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.24.7.tgz", - "integrity": "sha512-EMi4MLQSHfd2nrCqQEWxFdha2gBCqU4ZcCng4WBGZ5CJL4bBRW0ptdqqDdeirGZcpALazVVNJqRmsO8/+oNCBA==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.25.9.tgz", + "integrity": "sha512-IvIUeV5KrS/VPavfSM/Iu+RE6llrHrYIKY1yfCzyO/lMXHQ+p7uGhonmGVisv6tSBSVgWzMBohTcvkC9vQcQFA==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -2042,12 +2132,12 @@ } }, "node_modules/@babel/plugin-transform-regenerator": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.24.7.tgz", - "integrity": "sha512-lq3fvXPdimDrlg6LWBoqj+r/DEWgONuwjuOuQCSYgRroXDH/IdM1C0IZf59fL5cHLpjEH/O6opIRBbqv7ELnuA==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.25.9.tgz", + "integrity": "sha512-vwDcDNsgMPDGP0nMqzahDWE5/MLcX8sv96+wfX7as7LoF/kr97Bo/7fI00lXY4wUXYfVmwIIyG80fGZ1uvt2qg==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7", + "@babel/helper-plugin-utils": "^7.25.9", "regenerator-transform": "^0.15.2" }, "engines": { @@ -2058,12 +2148,12 @@ } }, "node_modules/@babel/plugin-transform-reserved-words": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.24.7.tgz", - "integrity": "sha512-0DUq0pHcPKbjFZCfTss/pGkYMfy3vFWydkUBd9r0GHpIyfs2eCDENvqadMycRS9wZCXR41wucAfJHJmwA0UmoQ==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.25.9.tgz", + "integrity": "sha512-7DL7DKYjn5Su++4RXu8puKZm2XBPHyjWLUidaPEkCUBbE7IPcsrkRHggAOOKydH1dASWdcUBxrkOGNxUv5P3Jg==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -2102,12 +2192,12 @@ } }, "node_modules/@babel/plugin-transform-shorthand-properties": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.24.7.tgz", - "integrity": "sha512-KsDsevZMDsigzbA09+vacnLpmPH4aWjcZjXdyFKGzpplxhbeB4wYtury3vglQkg6KM/xEPKt73eCjPPf1PgXBA==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.25.9.tgz", + "integrity": "sha512-MUv6t0FhO5qHnS/W8XCbHmiRWOphNufpE1IVxhK5kuN3Td9FT1x4rx4K42s3RYdMXCXpfWkGSbCSd0Z64xA7Ng==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -2117,13 +2207,13 @@ } }, "node_modules/@babel/plugin-transform-spread": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.24.7.tgz", - "integrity": "sha512-x96oO0I09dgMDxJaANcRyD4ellXFLLiWhuwDxKZX5g2rWP1bTPkBSwCYv96VDXVT1bD9aPj8tppr5ITIh8hBng==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.25.9.tgz", + "integrity": "sha512-oNknIB0TbURU5pqJFVbOOFspVlrpVwo2H1+HUIsVDvp5VauGGDP1ZEvO8Nn5xyMEs3dakajOxlmkNW7kNgSm6A==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7", - "@babel/helper-skip-transparent-expression-wrappers": "^7.24.7" + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/helper-skip-transparent-expression-wrappers": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -2133,12 +2223,12 @@ } }, "node_modules/@babel/plugin-transform-sticky-regex": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.24.7.tgz", - "integrity": "sha512-kHPSIJc9v24zEml5geKg9Mjx5ULpfncj0wRpYtxbvKyTtHCYDkVE3aHQ03FrpEo4gEe2vrJJS1Y9CJTaThA52g==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.25.9.tgz", + "integrity": "sha512-WqBUSgeVwucYDP9U/xNRQam7xV8W5Zf+6Eo7T2SRVUFlhRiMNFdFz58u0KZmCVVqs2i7SHgpRnAhzRNmKfi2uA==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -2148,12 +2238,12 @@ } }, "node_modules/@babel/plugin-transform-template-literals": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.24.7.tgz", - "integrity": "sha512-AfDTQmClklHCOLxtGoP7HkeMw56k1/bTQjwsfhL6pppo/M4TOBSq+jjBUBLmV/4oeFg4GWMavIl44ZeCtmmZTw==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.25.9.tgz", + "integrity": "sha512-o97AE4syN71M/lxrCtQByzphAdlYluKPDBzDVzMmfCobUjjhAryZV0AIpRPrxN0eAkxXO6ZLEScmt+PNhj2OTw==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -2163,12 +2253,12 @@ } }, "node_modules/@babel/plugin-transform-typeof-symbol": { - "version": "7.24.8", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.24.8.tgz", - "integrity": "sha512-adNTUpDCVnmAE58VEqKlAA6ZBlNkMnWD0ZcW76lyNFN3MJniyGFZfNwERVk8Ap56MCnXztmDr19T4mPTztcuaw==", + "version": "7.26.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.26.7.tgz", + "integrity": "sha512-jfoTXXZTgGg36BmhqT3cAYK5qkmqvJpvNrPhaK/52Vgjhw4Rq29s9UqpWWV0D6yuRmgiFH/BUVlkl96zJWqnaw==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.24.8" + "@babel/helper-plugin-utils": "^7.26.5" }, "engines": { "node": ">=6.9.0" @@ -2178,12 +2268,12 @@ } }, "node_modules/@babel/plugin-transform-unicode-escapes": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.24.7.tgz", - "integrity": "sha512-U3ap1gm5+4edc2Q/P+9VrBNhGkfnf+8ZqppY71Bo/pzZmXhhLdqgaUl6cuB07O1+AQJtCLfaOmswiNbSQ9ivhw==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.25.9.tgz", + "integrity": "sha512-s5EDrE6bW97LtxOcGj1Khcx5AaXwiMmi4toFWRDP9/y0Woo6pXC+iyPu/KuhKtfSrNFd7jJB+/fkOtZy6aIC6Q==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -2193,13 +2283,13 @@ } }, "node_modules/@babel/plugin-transform-unicode-property-regex": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.24.7.tgz", - "integrity": "sha512-uH2O4OV5M9FZYQrwc7NdVmMxQJOCCzFeYudlZSzUAHRFeOujQefa92E74TQDVskNHCzOXoigEuoyzHDhaEaK5w==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.25.9.tgz", + "integrity": "sha512-Jt2d8Ga+QwRluxRQ307Vlxa6dMrYEMZCgGxoPR8V52rxPyldHu3hdlHspxaqYmE7oID5+kB+UKUB/eWS+DkkWg==", "dev": true, "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.24.7", - "@babel/helper-plugin-utils": "^7.24.7" + "@babel/helper-create-regexp-features-plugin": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -2209,13 +2299,13 @@ } }, "node_modules/@babel/plugin-transform-unicode-regex": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.24.7.tgz", - "integrity": "sha512-hlQ96MBZSAXUq7ltkjtu3FJCCSMx/j629ns3hA3pXnBXjanNP0LHi+JpPeA81zaWgVK1VGH95Xuy7u0RyQ8kMg==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.25.9.tgz", + "integrity": "sha512-yoxstj7Rg9dlNn9UQxzk4fcNivwv4nUYz7fYXBaKxvw/lnmPuOm/ikoELygbYq68Bls3D/D+NBPHiLwZdZZ4HA==", "dev": true, "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.24.7", - "@babel/helper-plugin-utils": "^7.24.7" + "@babel/helper-create-regexp-features-plugin": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -2225,13 +2315,13 @@ } }, "node_modules/@babel/plugin-transform-unicode-sets-regex": { - "version": "7.25.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.25.4.tgz", - "integrity": "sha512-qesBxiWkgN1Q+31xUE9RcMk79eOXXDCv6tfyGMRSs4RGlioSg2WVyQAm07k726cSE56pa+Kb0y9epX2qaXzTvA==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.25.9.tgz", + "integrity": "sha512-8BYqO3GeVNHtx69fdPshN3fnzUNLrWdHhk/icSwigksJGczKSizZ+Z6SBCxTs723Fr5VSNorTIK7a+R2tISvwQ==", "dev": true, "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.25.2", - "@babel/helper-plugin-utils": "^7.24.8" + "@babel/helper-create-regexp-features-plugin": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -2360,12 +2450,6 @@ "@babel/core": "^7.0.0-0 || ^8.0.0-0 <8.0.0" } }, - "node_modules/@babel/regjsgen": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/@babel/regjsgen/-/regjsgen-0.8.0.tgz", - "integrity": "sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA==", - "dev": true - }, "node_modules/@babel/runtime": { "version": "7.25.0", "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.25.0.tgz", @@ -2379,30 +2463,30 @@ } }, "node_modules/@babel/template": { - "version": "7.25.0", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.25.0.tgz", - "integrity": "sha512-aOOgh1/5XzKvg1jvVz7AVrx2piJ2XBi227DHmbY6y+bM9H2FlN+IfecYu4Xl0cNiiVejlsCri89LUsbj8vJD9Q==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.25.9.tgz", + "integrity": "sha512-9DGttpmPvIxBb/2uwpVo3dqJ+O6RooAFOS+lB+xDqoE2PVCE8nfoHMdZLpfCQRLwvohzXISPZcgxt80xLfsuwg==", "dev": true, "dependencies": { - "@babel/code-frame": "^7.24.7", - "@babel/parser": "^7.25.0", - "@babel/types": "^7.25.0" + "@babel/code-frame": "^7.25.9", + "@babel/parser": "^7.25.9", + "@babel/types": "^7.25.9" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/traverse": { - "version": "7.25.6", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.25.6.tgz", - "integrity": "sha512-9Vrcx5ZW6UwK5tvqsj0nGpp/XzqthkT0dqIc9g1AdtygFToNtTF67XzYS//dm+SAK9cp3B9R4ZO/46p63SCjlQ==", + "version": "7.26.7", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.26.7.tgz", + "integrity": "sha512-1x1sgeyRLC3r5fQOM0/xtQKsYjyxmFjaOrLJNtZ81inNjyJHGIolTULPiSc/2qe1/qfpFLisLQYFnnZl7QoedA==", "dev": true, "dependencies": { - "@babel/code-frame": "^7.24.7", - "@babel/generator": "^7.25.6", - "@babel/parser": "^7.25.6", - "@babel/template": "^7.25.0", - "@babel/types": "^7.25.6", + "@babel/code-frame": "^7.26.2", + "@babel/generator": "^7.26.5", + "@babel/parser": "^7.26.7", + "@babel/template": "^7.25.9", + "@babel/types": "^7.26.7", "debug": "^4.3.1", "globals": "^11.1.0" }, @@ -2411,29 +2495,41 @@ } }, "node_modules/@babel/traverse/node_modules/@babel/generator": { - "version": "7.25.6", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.25.6.tgz", - "integrity": "sha512-VPC82gr1seXOpkjAAKoLhP50vx4vGNlF4msF64dSFq1P8RfB+QAuJWGHPXXPc8QyfVWwwB/TNNU4+ayZmHNbZw==", + "version": "7.26.5", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.26.5.tgz", + "integrity": "sha512-2caSP6fN9I7HOe6nqhtft7V4g7/V/gfDsC3Ag4W7kEzzvRGKqiv0pu0HogPiZ3KaVSoNDhUws6IJjDjpfmYIXw==", "dev": true, "dependencies": { - "@babel/types": "^7.25.6", + "@babel/parser": "^7.26.5", + "@babel/types": "^7.26.5", "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.25", - "jsesc": "^2.5.1" + "jsesc": "^3.0.2" }, "engines": { "node": ">=6.9.0" } }, + "node_modules/@babel/traverse/node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/@babel/types": { - "version": "7.25.6", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.25.6.tgz", - "integrity": "sha512-/l42B1qxpG6RdfYf343Uw1vmDjeNhneUXtzhojE7pDgfpEypmRhI6j1kr17XCVv4Cgl9HdAiQY2x0GwKm7rWCw==", + "version": "7.26.7", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.26.7.tgz", + "integrity": "sha512-t8kDRGrKXyp6+tjUh7hw2RLyclsW4TRoRvRHtSyAX9Bb5ldlFh+90YAYY6awRXrlB4G5G2izNeGySpATlFzmOg==", "dev": true, "dependencies": { - "@babel/helper-string-parser": "^7.24.8", - "@babel/helper-validator-identifier": "^7.24.7", - "to-fast-properties": "^2.0.0" + "@babel/helper-string-parser": "^7.25.9", + "@babel/helper-validator-identifier": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -2842,24 +2938,27 @@ } }, "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", - "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.1.tgz", + "integrity": "sha512-s3O3waFUrMV8P/XaF/+ZTp1X9XBZW1a4B97ZnjQF2KYWaFD2A8KyFBsrsfSjEmjn3RGWAIuvlneuZm3CUK3jbA==", "dev": true, "dependencies": { - "eslint-visitor-keys": "^3.3.0" + "eslint-visitor-keys": "^3.4.3" }, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, + "funding": { + "url": "https://opencollective.com/eslint" + }, "peerDependencies": { "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, "node_modules/@eslint-community/regexpp": { - "version": "4.11.1", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.11.1.tgz", - "integrity": "sha512-m4DVN9ZqskZoLU5GlWZadwDnYo3vAEydiUayB9widCl9ffWx2IvPnp6n3on5rJmziJSw9Bv+Z3ChDVdMwXCY8Q==", + "version": "4.12.1", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz", + "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==", "dev": true, "engines": { "node": "^12.0.0 || ^14.0.0 || >=16.0.0" @@ -2929,10 +3028,19 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@eslint/eslintrc/node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "node_modules/@eslint/eslintrc/node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/@eslint/eslintrc/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", "dev": true }, "node_modules/@eslint/eslintrc/node_modules/minimatch": { @@ -3118,9 +3226,9 @@ } }, "node_modules/@inquirer/figures": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/@inquirer/figures/-/figures-1.0.6.tgz", - "integrity": "sha512-yfZzps3Cso2UbM7WlxKwZQh2Hs6plrbjs1QnzQDZhK2DgyCo6D8AaHps9olkNcUFlcYERMqU3uJSp1gmy3s/qQ==", + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/@inquirer/figures/-/figures-1.0.10.tgz", + "integrity": "sha512-Ey6176gZmeqZuY/W/nZiUyvmb1/qInjcpiZjXWi6nON+nxJpD1bxtSoBxNliGISae32n6OwbY+TSXPZ1CfS4bw==", "dev": true, "engines": { "node": ">=18" @@ -3350,9 +3458,9 @@ } }, "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", - "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==", + "version": "0.3.8", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz", + "integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==", "dev": true, "dependencies": { "@jridgewell/set-array": "^1.2.1", @@ -3424,9 +3532,9 @@ } }, "node_modules/@jsonjoy.com/json-pack": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@jsonjoy.com/json-pack/-/json-pack-1.1.0.tgz", - "integrity": "sha512-zlQONA+msXPPwHWZMKFVS78ewFczIll5lXiVPwFPCZUsrOKdxc2AvxU1HoNBmMRhqDZUR9HkC3UOm+6pME6Xsg==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/json-pack/-/json-pack-1.1.1.tgz", + "integrity": "sha512-osjeBqMJ2lb/j/M8NCPjs1ylqWIcTRTycIhVB5pt6LgzgeRSb0YRZ7j9RfA8wIUrsr/medIuhVyonXRZWLyfdw==", "dev": true, "dependencies": { "@jsonjoy.com/base64": "^1.1.1", @@ -3446,9 +3554,9 @@ } }, "node_modules/@jsonjoy.com/util": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@jsonjoy.com/util/-/util-1.3.0.tgz", - "integrity": "sha512-Cebt4Vk7k1xHy87kHY7KSPLT77A7Ev7IfOblyLZhtYEhrdQ6fX4EoLq3xOQ3O/DRMEh2ok5nyC180E+ABS8Wmw==", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/util/-/util-1.5.0.tgz", + "integrity": "sha512-ojoNsrIuPI9g6o8UxhraZQSyF2ByJanAY4cTFbc8Mf2AXEF4aQRGY1dJxyJpuyav8r9FGflEt/Ff3u5Nt6YMPA==", "dev": true, "engines": { "node": ">=10.0" @@ -3639,11 +3747,10 @@ ] }, "node_modules/@ngrx/component-store": { - "version": "18.0.2", - "resolved": "https://registry.npmjs.org/@ngrx/component-store/-/component-store-18.0.2.tgz", - "integrity": "sha512-IB7ZKFqjDt4duQbfYqXxAOKf9Si9O1HFodqbNCSgi7gnovK/frf/H429a+lYOyItPcpno3ECom6/1k8pE8fWlg==", + "version": "18.1.1", + "resolved": "https://registry.npmjs.org/@ngrx/component-store/-/component-store-18.1.1.tgz", + "integrity": "sha512-+FDd44D+unx/eE/7qCyK1IDsTu8503JOnj3lEhL9f9TDmE4arDNqeNx/8Sh3V3HDkH7mVC9iV0c538ACGlvWIA==", "dependencies": { - "@ngrx/operators": "18.0.1", "tslib": "^2.0.0" }, "peerDependencies": { @@ -3652,34 +3759,22 @@ } }, "node_modules/@ngrx/effects": { - "version": "18.0.2", - "resolved": "https://registry.npmjs.org/@ngrx/effects/-/effects-18.0.2.tgz", - "integrity": "sha512-YojXcOD9Lsq4kl2HCjENccyUM/mOlgBdtddsg9j/ojzSUgu3ZuBVKLN3atrL2TJYkbMX1MN0RzafSkL3TPGFIA==", + "version": "18.1.1", + "resolved": "https://registry.npmjs.org/@ngrx/effects/-/effects-18.1.1.tgz", + "integrity": "sha512-XXob8kYEvYMaZwgHtrrTW0XZargbu5PloEpNHLnzB8jPk0yWEw6keryxaF09Ylws1779MWvMmF/YP2rPl04nHQ==", "dependencies": { - "@ngrx/operators": "18.0.1", "tslib": "^2.0.0" }, "peerDependencies": { "@angular/core": "^18.0.0", - "@ngrx/store": "18.0.2", + "@ngrx/store": "18.1.1", "rxjs": "^6.5.3 || ^7.5.0" } }, - "node_modules/@ngrx/operators": { - "version": "18.0.1", - "resolved": "https://registry.npmjs.org/@ngrx/operators/-/operators-18.0.1.tgz", - "integrity": "sha512-M+QMrHNKgcuiLaRGZxJ4aQi5/OCRfKC4+T/63dsHyLFZ53/FFpF6a/ytSO1Q+tzOplZ5o99S+i8FVaZqNQ3LmQ==", - "dependencies": { - "tslib": "^2.3.0" - }, - "peerDependencies": { - "rxjs": "^6.5.3 || ^7.4.0" - } - }, "node_modules/@ngrx/store": { - "version": "18.0.2", - "resolved": "https://registry.npmjs.org/@ngrx/store/-/store-18.0.2.tgz", - "integrity": "sha512-ajwv0+njsO4vzArp9esnFvs1wyUb1U1W8E8LSCKrcW2hWWo9o1Pezj+JRsdQwatxHfrrPFuTDyajsl6GQM/JSA==", + "version": "18.1.1", + "resolved": "https://registry.npmjs.org/@ngrx/store/-/store-18.1.1.tgz", + "integrity": "sha512-K0v1akJ2sEnIeb1AUA064+ksgRgbMgVG9HbSsLBxENbFjK2ZvKRxo1bpOw6WHW9+hyDTlhZGl7+gUtjmo3497g==", "dependencies": { "tslib": "^2.0.0" }, @@ -3689,9 +3784,9 @@ } }, "node_modules/@ngtools/webpack": { - "version": "18.2.6", - "resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-18.2.6.tgz", - "integrity": "sha512-7HwOPE1EOgcHnpt4brSiT8G2CcXB50G0+CbCBaKGy4LYCG3Y3mrlzF5Fup9HvMJ6Tzqd62RqzpKKYBiGUT7hxg==", + "version": "18.2.14", + "resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-18.2.14.tgz", + "integrity": "sha512-rT+Y4WR8QTVsijtb+YRqHcPTpd1ZiwRbklQXRTxU0YGFHpxpi+bhjmY8FjpPoAtdPO1Lg3l3KIZPZa0thG0FNg==", "dev": true, "engines": { "node": "^18.19.1 || ^20.11.1 || >=22.0.0", @@ -4203,13 +4298,13 @@ ] }, "node_modules/@schematics/angular": { - "version": "18.2.6", - "resolved": "https://registry.npmjs.org/@schematics/angular/-/angular-18.2.6.tgz", - "integrity": "sha512-Y988EoOEQDLEyHu3414T6AeVUyx21AexBHQNbUNQkK8cxlxyB6m1eH1cx6vFgLRFUTsLVv+C6Ln/ICNTfLcG4A==", + "version": "18.2.14", + "resolved": "https://registry.npmjs.org/@schematics/angular/-/angular-18.2.14.tgz", + "integrity": "sha512-CHh6ew2Az71UlvVcnYeuMEwjwkZqR7y/9ebLzFRvczC71ZL8qPVBpBTVGbCpGBd54VEbCZVWRxBQoZZ5LP/aBw==", "dev": true, "dependencies": { - "@angular-devkit/core": "18.2.6", - "@angular-devkit/schematics": "18.2.6", + "@angular-devkit/core": "18.2.14", + "@angular-devkit/schematics": "18.2.14", "jsonc-parser": "3.3.1" }, "engines": { @@ -4240,12 +4335,12 @@ } }, "node_modules/@sigstore/protobuf-specs": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@sigstore/protobuf-specs/-/protobuf-specs-0.3.2.tgz", - "integrity": "sha512-c6B0ehIWxMI8wiS/bj6rHMPqeFvngFV7cDU/MY+B16P9Z3Mp9k8L93eYZ7BYzSickzuqAQqAq0V956b3Ju6mLw==", + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/@sigstore/protobuf-specs/-/protobuf-specs-0.3.3.tgz", + "integrity": "sha512-RpacQhBlwpBWd7KEJsRKcBQalbV28fvkxwTOJIqhIuDysMMaJW47V4OqW30iJB9uRpqOSxxEAQFdr8tTattReQ==", "dev": true, "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": "^18.17.0 || >=20.5.0" } }, "node_modules/@sigstore/sign": { @@ -4370,12 +4465,6 @@ "@types/node": "*" } }, - "node_modules/@types/cookie": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.4.1.tgz", - "integrity": "sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q==", - "dev": true - }, "node_modules/@types/cors": { "version": "2.8.17", "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.17.tgz", @@ -4410,9 +4499,9 @@ } }, "node_modules/@types/express-serve-static-core": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-5.0.0.tgz", - "integrity": "sha512-AbXMTZGt40T+KON9/Fdxx0B2WK5hsgxcfXJLr5bFpZ7b4JCex2WyQPTEKdXqfHiY5nKKBScZ7yCoO6Pvgxfvnw==", + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-5.0.6.tgz", + "integrity": "sha512-3xhRnjJPkULekpSzgtoNYYcTWgEZkp4myc+Saevii5JPnHNvHMRlBSHDbs7Bh1iPPoVTERHEZXyhyLbMEsExsA==", "dev": true, "dependencies": { "@types/node": "*", @@ -4476,12 +4565,12 @@ } }, "node_modules/@types/node": { - "version": "22.7.4", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.7.4.tgz", - "integrity": "sha512-y+NPi1rFzDs1NdQHHToqeiX2TIS79SWEAw9GYhkkx8bD0ChpfqC+n2j5OXOCpzfojBEBt6DnEnnG9MY0zk1XLg==", + "version": "22.12.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.12.0.tgz", + "integrity": "sha512-Fll2FZ1riMjNmlmJOdAyY5pUbkftXslB5DgEzlIuNaiWhXd00FhWxVC/r4yV/4wBb9JfImTu+jiSvXTkJ7F/gA==", "dev": true, "dependencies": { - "undici-types": "~6.19.2" + "undici-types": "~6.20.0" } }, "node_modules/@types/node-forge": { @@ -4494,9 +4583,9 @@ } }, "node_modules/@types/qs": { - "version": "6.9.16", - "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.16.tgz", - "integrity": "sha512-7i+zxXdPD0T4cKDuxCUXJ4wHcsJLwENa6Z3dCu8cfCK743OGy5Nu1RmAGqDPsoTDINVEcdXKRvR/zre+P2Ku1A==", + "version": "6.9.18", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.18.tgz", + "integrity": "sha512-kK7dgTYDyGqS+e2Q4aK9X3D7q234CIZ1Bv0q/7Z5IwRDoADNU81xXJK/YVyLbLTZCoIwUoDoffFeF+p/eIklAA==", "dev": true }, "node_modules/@types/range-parser": { @@ -4557,29 +4646,29 @@ "dev": true }, "node_modules/@types/ws": { - "version": "8.5.12", - "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.12.tgz", - "integrity": "sha512-3tPRkv1EtkDpzlgyKyI8pGsGZAGPEaXeu0DOj5DI25Ja91bdAYddYHbADRYVrZMRbfW+1l5YwXVDKohDJNQxkQ==", + "version": "8.5.14", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.14.tgz", + "integrity": "sha512-bd/YFLW+URhBzMXurx7lWByOu+xzU9+kb3RboOteXYDfW+tr+JZa99OyNmPINEGB/ahzKrEuc8rcv4gnpJmxTw==", "dev": true, "dependencies": { "@types/node": "*" } }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.8.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.8.0.tgz", - "integrity": "sha512-wORFWjU30B2WJ/aXBfOm1LX9v9nyt9D3jsSOxC3cCaTQGCW5k4jNpmjFv3U7p/7s4yvdjHzwtv2Sd2dOyhjS0A==", + "version": "8.22.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.22.0.tgz", + "integrity": "sha512-4Uta6REnz/xEJMvwf72wdUnC3rr4jAQf5jnTkeRQ9b6soxLxhDEbS/pfMPoJLDfFPNVRdryqWUIV/2GZzDJFZw==", "dev": true, "dependencies": { "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "8.8.0", - "@typescript-eslint/type-utils": "8.8.0", - "@typescript-eslint/utils": "8.8.0", - "@typescript-eslint/visitor-keys": "8.8.0", + "@typescript-eslint/scope-manager": "8.22.0", + "@typescript-eslint/type-utils": "8.22.0", + "@typescript-eslint/utils": "8.22.0", + "@typescript-eslint/visitor-keys": "8.22.0", "graphemer": "^1.4.0", "ignore": "^5.3.1", "natural-compare": "^1.4.0", - "ts-api-utils": "^1.3.0" + "ts-api-utils": "^2.0.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -4590,12 +4679,17 @@ }, "peerDependencies": { "@typescript-eslint/parser": "^8.0.0 || ^8.0.0-alpha.0", - "eslint": "^8.57.0 || ^9.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.8.0" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "engines": { + "node": ">= 4" } }, "node_modules/@typescript-eslint/experimental-utils": { @@ -4710,15 +4804,15 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "8.8.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.8.0.tgz", - "integrity": "sha512-uEFUsgR+tl8GmzmLjRqz+VrDv4eoaMqMXW7ruXfgThaAShO9JTciKpEsB+TvnfFfbg5IpujgMXVV36gOJRLtZg==", + "version": "8.22.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.22.0.tgz", + "integrity": "sha512-MqtmbdNEdoNxTPzpWiWnqNac54h8JDAmkWtJExBVVnSrSmi9z+sZUt0LfKqk9rjqmKOIeRhO4fHHJ1nQIjduIQ==", "dev": true, "dependencies": { - "@typescript-eslint/scope-manager": "8.8.0", - "@typescript-eslint/types": "8.8.0", - "@typescript-eslint/typescript-estree": "8.8.0", - "@typescript-eslint/visitor-keys": "8.8.0", + "@typescript-eslint/scope-manager": "8.22.0", + "@typescript-eslint/types": "8.22.0", + "@typescript-eslint/typescript-estree": "8.22.0", + "@typescript-eslint/visitor-keys": "8.22.0", "debug": "^4.3.4" }, "engines": { @@ -4729,22 +4823,18 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.8.0" } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.8.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.8.0.tgz", - "integrity": "sha512-EL8eaGC6gx3jDd8GwEFEV091210U97J0jeEHrAYvIYosmEGet4wJ+g0SYmLu+oRiAwbSA5AVrt6DxLHfdd+bUg==", + "version": "8.22.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.22.0.tgz", + "integrity": "sha512-/lwVV0UYgkj7wPSw0o8URy6YI64QmcOdwHuGuxWIYznO6d45ER0wXUbksr9pYdViAofpUCNJx/tAzNukgvaaiQ==", "dev": true, "dependencies": { - "@typescript-eslint/types": "8.8.0", - "@typescript-eslint/visitor-keys": "8.8.0" + "@typescript-eslint/types": "8.22.0", + "@typescript-eslint/visitor-keys": "8.22.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -4755,15 +4845,15 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "8.8.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.8.0.tgz", - "integrity": "sha512-IKwJSS7bCqyCeG4NVGxnOP6lLT9Okc3Zj8hLO96bpMkJab+10HIfJbMouLrlpyOr3yrQ1cA413YPFiGd1mW9/Q==", + "version": "8.22.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.22.0.tgz", + "integrity": "sha512-NzE3aB62fDEaGjaAYZE4LH7I1MUwHooQ98Byq0G0y3kkibPJQIXVUspzlFOmOfHhiDLwKzMlWxaNv+/qcZurJA==", "dev": true, "dependencies": { - "@typescript-eslint/typescript-estree": "8.8.0", - "@typescript-eslint/utils": "8.8.0", + "@typescript-eslint/typescript-estree": "8.22.0", + "@typescript-eslint/utils": "8.22.0", "debug": "^4.3.4", - "ts-api-utils": "^1.3.0" + "ts-api-utils": "^2.0.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -4772,16 +4862,15 @@ "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.8.0" } }, "node_modules/@typescript-eslint/types": { - "version": "8.8.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.8.0.tgz", - "integrity": "sha512-QJwc50hRCgBd/k12sTykOJbESe1RrzmX6COk8Y525C9l7oweZ+1lw9JiU56im7Amm8swlz00DRIlxMYLizr2Vw==", + "version": "8.22.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.22.0.tgz", + "integrity": "sha512-0S4M4baNzp612zwpD4YOieP3VowOARgK2EkN/GBn95hpyF8E2fbMT55sRHWBq+Huaqk3b3XK+rxxlM8sPgGM6A==", "dev": true, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -4792,19 +4881,19 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.8.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.8.0.tgz", - "integrity": "sha512-ZaMJwc/0ckLz5DaAZ+pNLmHv8AMVGtfWxZe/x2JVEkD5LnmhWiQMMcYT7IY7gkdJuzJ9P14fRy28lUrlDSWYdw==", + "version": "8.22.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.22.0.tgz", + "integrity": "sha512-SJX99NAS2ugGOzpyhMza/tX+zDwjvwAtQFLsBo3GQxiGcvaKlqGBkmZ+Y1IdiSi9h4Q0Lr5ey+Cp9CGWNY/F/w==", "dev": true, "dependencies": { - "@typescript-eslint/types": "8.8.0", - "@typescript-eslint/visitor-keys": "8.8.0", + "@typescript-eslint/types": "8.22.0", + "@typescript-eslint/visitor-keys": "8.22.0", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", "minimatch": "^9.0.4", "semver": "^7.6.0", - "ts-api-utils": "^1.3.0" + "ts-api-utils": "^2.0.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -4813,22 +4902,20 @@ "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "peerDependencies": { + "typescript": ">=4.8.4 <5.8.0" } }, "node_modules/@typescript-eslint/utils": { - "version": "8.8.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.8.0.tgz", - "integrity": "sha512-QE2MgfOTem00qrlPgyByaCHay9yb1+9BjnMFnSFkUKQfu7adBXDTnCAivURnuPPAG/qiB+kzKkZKmKfaMT0zVg==", + "version": "8.22.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.22.0.tgz", + "integrity": "sha512-T8oc1MbF8L+Bk2msAvCUzjxVB2Z2f+vXYfcucE2wOmYs7ZUwco5Ep0fYZw8quNwOiw9K8GYVL+Kgc2pETNTLOg==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", - "@typescript-eslint/scope-manager": "8.8.0", - "@typescript-eslint/types": "8.8.0", - "@typescript-eslint/typescript-estree": "8.8.0" + "@typescript-eslint/scope-manager": "8.22.0", + "@typescript-eslint/types": "8.22.0", + "@typescript-eslint/typescript-estree": "8.22.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -4838,17 +4925,18 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0" + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.8.0" } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.8.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.8.0.tgz", - "integrity": "sha512-8mq51Lx6Hpmd7HnA2fcHQo3YgfX1qbccxQOgZcb4tvasu//zXRaA1j5ZRFeCw/VRAdFi4mRM9DnZw0Nu0Q2d1g==", + "version": "8.22.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.22.0.tgz", + "integrity": "sha512-AWpYAXnUgvLNabGTy3uBylkgZoosva/miNd1I8Bz3SjotmQPbVqhO4Cczo8AsZ44XVErEBPr/CRSgaj8sG7g0w==", "dev": true, "dependencies": { - "@typescript-eslint/types": "8.8.0", - "eslint-visitor-keys": "^3.4.3" + "@typescript-eslint/types": "8.22.0", + "eslint-visitor-keys": "^4.2.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -4858,10 +4946,22 @@ "url": "https://opencollective.com/typescript-eslint" } }, + "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", + "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", + "dev": true, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, "node_modules/@ungap/structured-clone": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz", - "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", + "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==", "dev": true }, "node_modules/@vitejs/plugin-basic-ssl": { @@ -4877,148 +4977,148 @@ } }, "node_modules/@webassemblyjs/ast": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.12.1.tgz", - "integrity": "sha512-EKfMUOPRRUTy5UII4qJDGPpqfwjOmZ5jeGFwid9mnoqIFK+e0vqoi1qH56JpmZSzEL53jKnNzScdmftJyG5xWg==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.14.1.tgz", + "integrity": "sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==", "dev": true, "dependencies": { - "@webassemblyjs/helper-numbers": "1.11.6", - "@webassemblyjs/helper-wasm-bytecode": "1.11.6" + "@webassemblyjs/helper-numbers": "1.13.2", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2" } }, "node_modules/@webassemblyjs/floating-point-hex-parser": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.6.tgz", - "integrity": "sha512-ejAj9hfRJ2XMsNHk/v6Fu2dGS+i4UaXBXGemOfQ/JfQ6mdQg/WXtwleQRLLS4OvfDhv8rYnVwH27YJLMyYsxhw==", + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.13.2.tgz", + "integrity": "sha512-6oXyTOzbKxGH4steLbLNOu71Oj+C8Lg34n6CqRvqfS2O71BxY6ByfMDRhBytzknj9yGUPVJ1qIKhRlAwO1AovA==", "dev": true }, "node_modules/@webassemblyjs/helper-api-error": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.6.tgz", - "integrity": "sha512-o0YkoP4pVu4rN8aTJgAyj9hC2Sv5UlkzCHhxqWj8butaLvnpdc2jOwh4ewE6CX0txSfLn/UYaV/pheS2Txg//Q==", + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.13.2.tgz", + "integrity": "sha512-U56GMYxy4ZQCbDZd6JuvvNV/WFildOjsaWD3Tzzvmw/mas3cXzRJPMjP83JqEsgSbyrmaGjBfDtV7KDXV9UzFQ==", "dev": true }, "node_modules/@webassemblyjs/helper-buffer": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.12.1.tgz", - "integrity": "sha512-nzJwQw99DNDKr9BVCOZcLuJJUlqkJh+kVzVl6Fmq/tI5ZtEyWT1KZMyOXltXLZJmDtvLCDgwsyrkohEtopTXCw==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.14.1.tgz", + "integrity": "sha512-jyH7wtcHiKssDtFPRB+iQdxlDf96m0E39yb0k5uJVhFGleZFoNw1c4aeIcVUPPbXUVJ94wwnMOAqUHyzoEPVMA==", "dev": true }, "node_modules/@webassemblyjs/helper-numbers": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.6.tgz", - "integrity": "sha512-vUIhZ8LZoIWHBohiEObxVm6hwP034jwmc9kuq5GdHZH0wiLVLIPcMCdpJzG4C11cHoQ25TFIQj9kaVADVX7N3g==", + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.13.2.tgz", + "integrity": "sha512-FE8aCmS5Q6eQYcV3gI35O4J789wlQA+7JrqTTpJqn5emA4U2hvwJmvFRC0HODS+3Ye6WioDklgd6scJ3+PLnEA==", "dev": true, "dependencies": { - "@webassemblyjs/floating-point-hex-parser": "1.11.6", - "@webassemblyjs/helper-api-error": "1.11.6", + "@webassemblyjs/floating-point-hex-parser": "1.13.2", + "@webassemblyjs/helper-api-error": "1.13.2", "@xtuc/long": "4.2.2" } }, "node_modules/@webassemblyjs/helper-wasm-bytecode": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.6.tgz", - "integrity": "sha512-sFFHKwcmBprO9e7Icf0+gddyWYDViL8bpPjJJl0WHxCdETktXdmtWLGVzoHbqUcY4Be1LkNfwTmXOJUFZYSJdA==", + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.13.2.tgz", + "integrity": "sha512-3QbLKy93F0EAIXLh0ogEVR6rOubA9AoZ+WRYhNbFyuB70j3dRdwH9g+qXhLAO0kiYGlg3TxDV+I4rQTr/YNXkA==", "dev": true }, "node_modules/@webassemblyjs/helper-wasm-section": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.12.1.tgz", - "integrity": "sha512-Jif4vfB6FJlUlSbgEMHUyk1j234GTNG9dBJ4XJdOySoj518Xj0oGsNi59cUQF4RRMS9ouBUxDDdyBVfPTypa5g==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.14.1.tgz", + "integrity": "sha512-ds5mXEqTJ6oxRoqjhWDU83OgzAYjwsCV8Lo/N+oRsNDmx/ZDpqalmrtgOMkHwxsG0iI//3BwWAErYRHtgn0dZw==", "dev": true, "dependencies": { - "@webassemblyjs/ast": "1.12.1", - "@webassemblyjs/helper-buffer": "1.12.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.6", - "@webassemblyjs/wasm-gen": "1.12.1" + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/wasm-gen": "1.14.1" } }, "node_modules/@webassemblyjs/ieee754": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.6.tgz", - "integrity": "sha512-LM4p2csPNvbij6U1f19v6WR56QZ8JcHg3QIJTlSwzFcmx6WSORicYj6I63f9yU1kEUtrpG+kjkiIAkevHpDXrg==", + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.13.2.tgz", + "integrity": "sha512-4LtOzh58S/5lX4ITKxnAK2USuNEvpdVV9AlgGQb8rJDHaLeHciwG4zlGr0j/SNWlr7x3vO1lDEsuePvtcDNCkw==", "dev": true, "dependencies": { "@xtuc/ieee754": "^1.2.0" } }, "node_modules/@webassemblyjs/leb128": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.6.tgz", - "integrity": "sha512-m7a0FhE67DQXgouf1tbN5XQcdWoNgaAuoULHIfGFIEVKA6tu/edls6XnIlkmS6FrXAquJRPni3ZZKjw6FSPjPQ==", + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.13.2.tgz", + "integrity": "sha512-Lde1oNoIdzVzdkNEAWZ1dZ5orIbff80YPdHx20mrHwHrVNNTjNr8E3xz9BdpcGqRQbAEa+fkrCb+fRFTl/6sQw==", "dev": true, "dependencies": { "@xtuc/long": "4.2.2" } }, "node_modules/@webassemblyjs/utf8": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.11.6.tgz", - "integrity": "sha512-vtXf2wTQ3+up9Zsg8sa2yWiQpzSsMyXj0qViVP6xKGCUT8p8YJ6HqI7l5eCnWx1T/FYdsv07HQs2wTFbbof/RA==", + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.13.2.tgz", + "integrity": "sha512-3NQWGjKTASY1xV5m7Hr0iPeXD9+RDobLll3T9d2AO+g3my8xy5peVyjSag4I50mR1bBSN/Ct12lo+R9tJk0NZQ==", "dev": true }, "node_modules/@webassemblyjs/wasm-edit": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.12.1.tgz", - "integrity": "sha512-1DuwbVvADvS5mGnXbE+c9NfA8QRcZ6iKquqjjmR10k6o+zzsRVesil54DKexiowcFCPdr/Q0qaMgB01+SQ1u6g==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.14.1.tgz", + "integrity": "sha512-RNJUIQH/J8iA/1NzlE4N7KtyZNHi3w7at7hDjvRNm5rcUXa00z1vRz3glZoULfJ5mpvYhLybmVcwcjGrC1pRrQ==", "dev": true, "dependencies": { - "@webassemblyjs/ast": "1.12.1", - "@webassemblyjs/helper-buffer": "1.12.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.6", - "@webassemblyjs/helper-wasm-section": "1.12.1", - "@webassemblyjs/wasm-gen": "1.12.1", - "@webassemblyjs/wasm-opt": "1.12.1", - "@webassemblyjs/wasm-parser": "1.12.1", - "@webassemblyjs/wast-printer": "1.12.1" + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/helper-wasm-section": "1.14.1", + "@webassemblyjs/wasm-gen": "1.14.1", + "@webassemblyjs/wasm-opt": "1.14.1", + "@webassemblyjs/wasm-parser": "1.14.1", + "@webassemblyjs/wast-printer": "1.14.1" } }, "node_modules/@webassemblyjs/wasm-gen": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.12.1.tgz", - "integrity": "sha512-TDq4Ojh9fcohAw6OIMXqiIcTq5KUXTGRkVxbSo1hQnSy6lAM5GSdfwWeSxpAo0YzgsgF182E/U0mDNhuA0tW7w==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.14.1.tgz", + "integrity": "sha512-AmomSIjP8ZbfGQhumkNvgC33AY7qtMCXnN6bL2u2Js4gVCg8fp735aEiMSBbDR7UQIj90n4wKAFUSEd0QN2Ukg==", "dev": true, "dependencies": { - "@webassemblyjs/ast": "1.12.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.6", - "@webassemblyjs/ieee754": "1.11.6", - "@webassemblyjs/leb128": "1.11.6", - "@webassemblyjs/utf8": "1.11.6" + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/ieee754": "1.13.2", + "@webassemblyjs/leb128": "1.13.2", + "@webassemblyjs/utf8": "1.13.2" } }, "node_modules/@webassemblyjs/wasm-opt": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.12.1.tgz", - "integrity": "sha512-Jg99j/2gG2iaz3hijw857AVYekZe2SAskcqlWIZXjji5WStnOpVoat3gQfT/Q5tb2djnCjBtMocY/Su1GfxPBg==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.14.1.tgz", + "integrity": "sha512-PTcKLUNvBqnY2U6E5bdOQcSM+oVP/PmrDY9NzowJjislEjwP/C4an2303MCVS2Mg9d3AJpIGdUFIQQWbPds0Sw==", "dev": true, "dependencies": { - "@webassemblyjs/ast": "1.12.1", - "@webassemblyjs/helper-buffer": "1.12.1", - "@webassemblyjs/wasm-gen": "1.12.1", - "@webassemblyjs/wasm-parser": "1.12.1" + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/wasm-gen": "1.14.1", + "@webassemblyjs/wasm-parser": "1.14.1" } }, "node_modules/@webassemblyjs/wasm-parser": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.12.1.tgz", - "integrity": "sha512-xikIi7c2FHXysxXe3COrVUPSheuBtpcfhbpFj4gmu7KRLYOzANztwUU0IbsqvMqzuNK2+glRGWCEqZo1WCLyAQ==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.14.1.tgz", + "integrity": "sha512-JLBl+KZ0R5qB7mCnud/yyX08jWFw5MsoalJ1pQ4EdFlgj9VdXKGuENGsiCIjegI1W7p91rUlcB/LB5yRJKNTcQ==", "dev": true, "dependencies": { - "@webassemblyjs/ast": "1.12.1", - "@webassemblyjs/helper-api-error": "1.11.6", - "@webassemblyjs/helper-wasm-bytecode": "1.11.6", - "@webassemblyjs/ieee754": "1.11.6", - "@webassemblyjs/leb128": "1.11.6", - "@webassemblyjs/utf8": "1.11.6" + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-api-error": "1.13.2", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/ieee754": "1.13.2", + "@webassemblyjs/leb128": "1.13.2", + "@webassemblyjs/utf8": "1.13.2" } }, "node_modules/@webassemblyjs/wast-printer": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.12.1.tgz", - "integrity": "sha512-+X4WAlOisVWQMikjbcvY2e0rwPsKQ9F688lksZhBcPycBBuii3O7m8FACbDMWDojpAqvjIncrG8J0XHKyQfVeA==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.14.1.tgz", + "integrity": "sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw==", "dev": true, "dependencies": { - "@webassemblyjs/ast": "1.12.1", + "@webassemblyjs/ast": "1.14.1", "@xtuc/long": "4.2.2" } }, @@ -5062,10 +5162,19 @@ "node": ">= 0.6" } }, + "node_modules/accepts/node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/acorn": { - "version": "8.12.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.1.tgz", - "integrity": "sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==", + "version": "8.14.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz", + "integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==", "dev": true, "bin": { "acorn": "bin/acorn" @@ -5120,13 +5229,10 @@ } }, "node_modules/agent-base": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz", - "integrity": "sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==", + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.3.tgz", + "integrity": "sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==", "dev": true, - "dependencies": { - "debug": "^4.3.4" - }, "engines": { "node": ">= 14" } @@ -5235,15 +5341,18 @@ } }, "node_modules/ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, "dependencies": { - "color-convert": "^1.9.0" + "color-convert": "^2.0.1" }, "engines": { - "node": ">=4" + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, "node_modules/anymatch": { @@ -5278,12 +5387,12 @@ "dev": true }, "node_modules/aria-query": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz", - "integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==", + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.2.tgz", + "integrity": "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==", "dev": true, - "dependencies": { - "dequal": "^2.0.3" + "engines": { + "node": ">= 0.4" } }, "node_modules/array-flatten": { @@ -5365,13 +5474,13 @@ } }, "node_modules/babel-plugin-polyfill-corejs2": { - "version": "0.4.11", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.11.tgz", - "integrity": "sha512-sMEJ27L0gRHShOh5G54uAAPaiCOygY/5ratXuiyb2G46FmlSpc9eFCzYVyDiPxfNbwzA7mYahmjQc5q+CZQ09Q==", + "version": "0.4.12", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.12.tgz", + "integrity": "sha512-CPWT6BwvhrTO2d8QVorhTCQw9Y43zOu7G9HigcfxvepOU6b8o3tcWad6oVgZIsZCTt42FFv97aA7ZJsbM4+8og==", "dev": true, "dependencies": { "@babel/compat-data": "^7.22.6", - "@babel/helper-define-polyfill-provider": "^0.6.2", + "@babel/helper-define-polyfill-provider": "^0.6.3", "semver": "^6.3.1" }, "peerDependencies": { @@ -5401,12 +5510,12 @@ } }, "node_modules/babel-plugin-polyfill-regenerator": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.2.tgz", - "integrity": "sha512-2R25rQZWP63nGwaAswvDazbPXfrM3HwVoBXK6HcqeKrSrL/JqcC/rDcf95l4r7LXLyxDXc8uQDa064GubtCABg==", + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.3.tgz", + "integrity": "sha512-LiWSbl4CRSIa5x/JAU6jZiG9eit9w6mz+yVMFwDE83LAWvt0AfGBoZ7HS/mkhrKuh2ZlzfVZYKoLjXdqw6Yt7Q==", "dev": true, "dependencies": { - "@babel/helper-define-polyfill-provider": "^0.6.2" + "@babel/helper-define-polyfill-provider": "^0.6.3" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" @@ -5522,9 +5631,9 @@ "dev": true }, "node_modules/bonjour-service": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/bonjour-service/-/bonjour-service-1.2.1.tgz", - "integrity": "sha512-oSzCS2zV14bh2kji6vNe7vrpJYCHGvcZnlffFQ1MEoX/WOeQ/teD8SYWKR942OI3INjq8OMNJlbPK5LLLUxFDw==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/bonjour-service/-/bonjour-service-1.3.0.tgz", + "integrity": "sha512-3YuAUiSkWykd+2Azjgyxei8OWf8thdn8AITIog2M4UICzoqfjlqr64WIjEXZllf/W6vK1goqleSR6brGomxQqA==", "dev": true, "dependencies": { "fast-deep-equal": "^3.1.3", @@ -5559,9 +5668,9 @@ } }, "node_modules/browserslist": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.0.tgz", - "integrity": "sha512-Rmb62sR1Zpjql25eSanFGEhAxcFwfA1K0GuQcLoaJBAcENegrQut3hYdhXFF1obQfiDyqIW/cLM5HSJ/9k884A==", + "version": "4.24.4", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.4.tgz", + "integrity": "sha512-KDi1Ny1gSePi1vm0q4oxSF8b4DR44GF4BbmS2YdhPLOEqd8pDviZOGH/GsmRwoWJ2+5Lr085X7naowMwKHDG1A==", "dev": true, "funding": [ { @@ -5578,10 +5687,10 @@ } ], "dependencies": { - "caniuse-lite": "^1.0.30001663", - "electron-to-chromium": "^1.5.28", - "node-releases": "^2.0.18", - "update-browserslist-db": "^1.1.0" + "caniuse-lite": "^1.0.30001688", + "electron-to-chromium": "^1.5.73", + "node-releases": "^2.0.19", + "update-browserslist-db": "^1.1.1" }, "bin": { "browserslist": "cli.js" @@ -5691,17 +5800,27 @@ "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", "dev": true }, - "node_modules/call-bind": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", - "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", + "node_modules/call-bind-apply-helpers": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.1.tgz", + "integrity": "sha512-BhYE+WDaywFg2TBWYNXAE+8B1ATnThNBqXHP5nQu0jWJdVvY2hvkpyB3qOmtmDePiS5/BDQ8wASEWGMWRG148g==", "dev": true, "dependencies": { - "es-define-property": "^1.0.0", "es-errors": "^1.3.0", - "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.4", - "set-function-length": "^1.2.1" + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.3.tgz", + "integrity": "sha512-YTd+6wGlNlPxSuri7Y6X8tY2dmm12UMH66RpKMhiX6rsk5wXXnYgbUcOt8kiS31/AjfoTOvCsE+w8nZQLQnzHA==", + "dev": true, + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "get-intrinsic": "^1.2.6" }, "engines": { "node": ">= 0.4" @@ -5720,9 +5839,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001664", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001664.tgz", - "integrity": "sha512-AmE7k4dXiNKQipgn7a2xg558IRqPN3jMQY/rOsbxDhrd0tyChwbITBfiwtnqz8bi2M5mIWbxAYBvk7W7QBUS2g==", + "version": "1.0.30001696", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001696.tgz", + "integrity": "sha512-pDCPkvzfa39ehJtJ+OwGT/2yvT2SbjfHhiIW2LWOAcMQ7BzwxT/XuyUp4OTOd0XFWA6BKw0JalnBHgSi5DGJBQ==", "dev": true, "funding": [ { @@ -5740,17 +5859,19 @@ ] }, "node_modules/chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" }, "engines": { - "node": ">=4" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" } }, "node_modules/chardet": { @@ -5760,27 +5881,18 @@ "dev": true }, "node_modules/chokidar": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", - "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", "dev": true, "dependencies": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" + "readdirp": "^4.0.1" }, "engines": { - "node": ">= 8.10.0" + "node": ">= 14.16.0" }, "funding": { "url": "https://paulmillr.com/funding/" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" } }, "node_modules/chownr": { @@ -5876,39 +5988,6 @@ "node": ">=12" } }, - "node_modules/cliui/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/cliui/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/cliui/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, "node_modules/cliui/node_modules/emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", @@ -5978,19 +6057,34 @@ "node": ">=6" } }, + "node_modules/clone-deep/node_modules/is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "dev": true, + "dependencies": { + "isobject": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, "dependencies": { - "color-name": "1.1.3" + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" } }, "node_modules/color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, "node_modules/colorette": { @@ -6042,32 +6136,23 @@ } }, "node_modules/compression": { - "version": "1.7.4", - "resolved": "https://registry.npmjs.org/compression/-/compression-1.7.4.tgz", - "integrity": "sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ==", + "version": "1.7.5", + "resolved": "https://registry.npmjs.org/compression/-/compression-1.7.5.tgz", + "integrity": "sha512-bQJ0YRck5ak3LgtnpKkiabX5pNF7tMUh1BSy2ZBOTh0Dim0BUu6aPPwByIns6/A5Prh8PufSPerMDUklpzes2Q==", "dev": true, "dependencies": { - "accepts": "~1.3.5", - "bytes": "3.0.0", - "compressible": "~2.0.16", + "bytes": "3.1.2", + "compressible": "~2.0.18", "debug": "2.6.9", + "negotiator": "~0.6.4", "on-headers": "~1.0.2", - "safe-buffer": "5.1.2", + "safe-buffer": "5.2.1", "vary": "~1.1.2" }, "engines": { "node": ">= 0.8.0" } }, - "node_modules/compression/node_modules/bytes": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", - "integrity": "sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw==", - "dev": true, - "engines": { - "node": ">= 0.8" - } - }, "node_modules/compression/node_modules/debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", @@ -6083,12 +6168,6 @@ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", "dev": true }, - "node_modules/compression/node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true - }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -6225,25 +6304,13 @@ "webpack": "^5.1.0" } }, - "node_modules/copy-webpack-plugin/node_modules/glob-parent": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", - "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", - "dev": true, - "dependencies": { - "is-glob": "^4.0.3" - }, - "engines": { - "node": ">=10.13.0" - } - }, "node_modules/core-js-compat": { - "version": "3.38.1", - "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.38.1.tgz", - "integrity": "sha512-JRH6gfXxGmrzF3tZ57lFx97YARxCXPaMzPo6jELZhv88pBH5VXpQ+y0znKGlFnzuaihqhLbefxSJxWJMPtfDzw==", + "version": "3.40.0", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.40.0.tgz", + "integrity": "sha512-0XEDpr5y5mijvw8Lbc6E5AkjrHfp7eEoPlu36SWeAbcL8fn1G1ANe8DBlo2XoNN89oVpxWwOjYIPVzR4ZvsKCQ==", "dev": true, "dependencies": { - "browserslist": "^4.23.3" + "browserslist": "^4.24.3" }, "funding": { "type": "opencollective", @@ -6299,6 +6366,7 @@ "version": "0.0.24", "resolved": "https://registry.npmjs.org/critters/-/critters-0.0.24.tgz", "integrity": "sha512-Oyqew0FGM0wYUSNqR0L6AteO5MpMoUU0rhKRieXeiKs+PmRTxiJMyaunYB2KF6fQ3dzChXKCpbFOEJx3OQ1v/Q==", + "deprecated": "Ownership of Critters has moved to the Nuxt team, who will be maintaining the project going forward. If you'd like to keep using Critters, please switch to the actively-maintained fork at https://github.com/danielroe/beasties", "dev": true, "dependencies": { "chalk": "^4.1.0", @@ -6310,85 +6378,15 @@ "postcss-media-query-parser": "^0.2.3" } }, - "node_modules/critters/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", "dev": true, "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/critters/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/critters/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/critters/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/critters/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/critters/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", - "dev": true, - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" }, "engines": { "node": ">= 8" @@ -6485,9 +6483,9 @@ } }, "node_modules/debug": { - "version": "4.3.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", - "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", + "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", "dependencies": { "ms": "^2.1.3" }, @@ -6558,23 +6556,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/define-data-property": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", - "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", - "dev": true, - "dependencies": { - "es-define-property": "^1.0.0", - "es-errors": "^1.3.0", - "gopd": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/define-lazy-prop": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz", @@ -6596,15 +6577,6 @@ "node": ">= 0.8" } }, - "node_modules/dequal": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", - "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", - "dev": true, - "engines": { - "node": ">=6" - } - }, "node_modules/destroy": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", @@ -6720,9 +6692,9 @@ } }, "node_modules/domutils": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.1.0.tgz", - "integrity": "sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA==", + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz", + "integrity": "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==", "dev": true, "dependencies": { "dom-serializer": "^2.0.0", @@ -6733,6 +6705,20 @@ "url": "https://github.com/fb55/domutils?sponsor=1" } }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "dev": true, + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/duplexify": { "version": "4.1.3", "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-4.1.3.tgz", @@ -6757,9 +6743,9 @@ "dev": true }, "node_modules/electron-to-chromium": { - "version": "1.5.30", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.30.tgz", - "integrity": "sha512-sXI35EBN4lYxzc/pIGorlymYNzDBOqkSlVRe6MkgBsW/hW1tpC/HDJ2fjG7XnjakzfLEuvdmux0Mjs6jHq4UOA==", + "version": "1.5.90", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.90.tgz", + "integrity": "sha512-C3PN4aydfW91Natdyd449Kw+BzhLmof6tzy5W1pFC5SpQxVXT+oyiyOG9AgYYSN9OdA/ik3YkCrpwqI8ug5Tug==", "dev": true }, "node_modules/emoji-regex": { @@ -6818,12 +6804,11 @@ } }, "node_modules/engine.io": { - "version": "6.6.2", - "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.6.2.tgz", - "integrity": "sha512-gmNvsYi9C8iErnZdVcJnvCpSKbWTt1E8+JZo8b+daLninywUWi5NQ5STSHZ9rFjFO7imNcvb8Pc5pe/wMR5xEw==", + "version": "6.6.4", + "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.6.4.tgz", + "integrity": "sha512-ZCkIjSYNDyGn0R6ewHDtXgns/Zre/NT6Agvq1/WobF7JXgFff4SeDroKiCO3fNJreU9YG429Sc81o4w5ok/W5g==", "dev": true, "dependencies": { - "@types/cookie": "^0.4.1", "@types/cors": "^2.8.12", "@types/node": ">=10.0.0", "accepts": "~1.3.4", @@ -6847,6 +6832,23 @@ "node": ">=10.0.0" } }, + "node_modules/engine.io/node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, "node_modules/engine.io/node_modules/ws": { "version": "8.17.1", "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", @@ -6869,9 +6871,9 @@ } }, "node_modules/enhanced-resolve": { - "version": "5.17.1", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.17.1.tgz", - "integrity": "sha512-LMHl3dXhTcfv8gM4kEzIUeTQ+7fpdA0l2tUf34BddXPkz2A5xJ5L/Pchd5BL6rdccM9QGvu0sWZzK1Z1t4wwyg==", + "version": "5.18.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.0.tgz", + "integrity": "sha512-0/r0MySGYG8YqlayBZ6MuCfECmHFdJ5qyPh8s8wa5Hnm6SaFLSK1VYCbj+NKp090Nm1caZhD+QTnmxO7esYGyQ==", "dev": true, "dependencies": { "graceful-fs": "^4.2.4", @@ -6895,12 +6897,15 @@ } }, "node_modules/ent": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/ent/-/ent-2.2.1.tgz", - "integrity": "sha512-QHuXVeZx9d+tIQAz/XztU0ZwZf2Agg9CcXcgE1rurqvdBeDBrpSwjl8/6XUqMg7tw2Y7uAdKb2sRv+bSEFqQ5A==", + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/ent/-/ent-2.2.2.tgz", + "integrity": "sha512-kKvD1tO6BM+oK9HzCPpUdRb4vKFQY/FPTFmurMvh6LlN68VMrdj77w8yp51/kDbpkFOS9J8w5W6zIzgM2H8/hw==", "dev": true, "dependencies": { - "punycode": "^1.4.1" + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "punycode": "^1.4.1", + "safe-regex-test": "^1.1.0" }, "engines": { "node": ">= 0.4" @@ -6968,13 +6973,10 @@ } }, "node_modules/es-define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", - "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", "dev": true, - "dependencies": { - "get-intrinsic": "^1.2.4" - }, "engines": { "node": ">= 0.4" } @@ -6989,11 +6991,23 @@ } }, "node_modules/es-module-lexer": { - "version": "1.5.4", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.5.4.tgz", - "integrity": "sha512-MVNK56NiMrOwitFB7cqDwq0CQutbw+0BvLshJSse0MUNU+y1FC3bUS/AQg7oUng+/wKrrki7JfmwtVHkVfPLlw==", + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.6.0.tgz", + "integrity": "sha512-qqnD1yMU6tk/jnaMosogGySTZP8YtUgAffA9nMN+E/rjxcfRQ6IEk7IiozUjgxKoFHBGjTLnrHB/YC45r/59EQ==", "dev": true }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "dev": true, + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/esbuild": { "version": "0.23.0", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.23.0.tgz", @@ -7061,18 +7075,22 @@ "dev": true }, "node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", "dev": true, "engines": { - "node": ">=0.8.0" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/eslint": { "version": "8.57.1", "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.1.tgz", "integrity": "sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==", + "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", @@ -7137,9 +7155,9 @@ } }, "node_modules/eslint-plugin-prettier": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.2.1.tgz", - "integrity": "sha512-gH3iR3g4JfF+yYPaJYkN7jEl9QbweL/YfkoRlNnuIEHEz1vHVlCmWOS+eGGiRuzHQXdJFCOTxRgvju9b8VUmrw==", + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.2.3.tgz", + "integrity": "sha512-qJ+y0FfCp/mQYQ/vWQ3s7eUlFEL4PyKfAJxsnYTJ4YT73nsJBWqmEpFryxV9OeUiqmsTsYJ5Y+KDNaeP31wrRw==", "dev": true, "dependencies": { "prettier-linter-helpers": "^1.0.0", @@ -7167,9 +7185,9 @@ } }, "node_modules/eslint-scope": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.1.0.tgz", - "integrity": "sha512-14dSvlhaVhKKsa9Fx1l8A17s7ah7Ef7wCakJ10LYk6+GYmP9yDti2oq2SEwcyndt6knfcZyhyxwY3i9yL78EQw==", + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.2.0.tgz", + "integrity": "sha512-PHlWUfG6lvPc3yvP5A4PNyBL1W8fkDUccmI21JUu/+GKZBoH/W5u6usENXUrWFRsyoW5ACUjFGgAFQp5gUlb/A==", "dev": true, "dependencies": { "esrecurse": "^4.3.0", @@ -7234,21 +7252,6 @@ "url": "https://github.com/sponsors/epoberezkin" } }, - "node_modules/eslint/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, "node_modules/eslint/node_modules/brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -7259,52 +7262,6 @@ "concat-map": "0.0.1" } }, - "node_modules/eslint/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/eslint/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/eslint/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/eslint/node_modules/escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/eslint/node_modules/eslint-scope": { "version": "7.2.2", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", @@ -7321,18 +7278,6 @@ "url": "https://opencollective.com/eslint" } }, - "node_modules/eslint/node_modules/glob-parent": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", - "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", - "dev": true, - "dependencies": { - "is-glob": "^4.0.3" - }, - "engines": { - "node": ">=10.13.0" - } - }, "node_modules/eslint/node_modules/globals": { "version": "13.24.0", "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", @@ -7348,13 +7293,13 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/eslint/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "node_modules/eslint/node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", "dev": true, "engines": { - "node": ">=8" + "node": ">= 4" } }, "node_modules/eslint/node_modules/json-schema-traverse": { @@ -7375,18 +7320,6 @@ "node": "*" } }, - "node_modules/eslint/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/eslint/node_modules/type-fest": { "version": "0.20.2", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", @@ -7546,9 +7479,9 @@ "dev": true }, "node_modules/express": { - "version": "4.21.1", - "resolved": "https://registry.npmjs.org/express/-/express-4.21.1.tgz", - "integrity": "sha512-YSFlK1Ee0/GC8QaO91tHcDxJiE/X4FbpAyQWkxAvG6AXCuR65YzK8ua6D9hvi/TzUfZMpc+BwuM1IPw8fmQBiQ==", + "version": "4.21.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", + "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", "dev": true, "dependencies": { "accepts": "~1.3.8", @@ -7570,7 +7503,7 @@ "methods": "~1.1.2", "on-finished": "2.4.1", "parseurl": "~1.3.3", - "path-to-regexp": "0.1.10", + "path-to-regexp": "0.1.12", "proxy-addr": "~2.0.7", "qs": "6.13.0", "range-parser": "~1.2.1", @@ -7585,6 +7518,10 @@ }, "engines": { "node": ">= 0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/express/node_modules/cookie": { @@ -7695,6 +7632,18 @@ "node": ">=8.6.0" } }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/fast-json-stable-stringify": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", @@ -7708,15 +7657,25 @@ "dev": true }, "node_modules/fast-uri": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.0.2.tgz", - "integrity": "sha512-GR6f0hD7XXyNJa25Tb9BuIdN0tdr+0BMi6/CJPH3wJO1JjNG3n/VsSw38AwRdKZABm8lGbPfakLRkYzx2V9row==", - "dev": true + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.0.6.tgz", + "integrity": "sha512-Atfo14OibSv5wAp4VWNsFYE1AchQRTv9cBGWET4pZWHzYshFSS9NQI6I57rdKn9croWVMbYFbLhJ+yJvmZIIHw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ] }, "node_modules/fastq": { - "version": "1.17.1", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz", - "integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==", + "version": "1.19.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.0.tgz", + "integrity": "sha512-7SFSRCNjBQIZH/xZR3iy5iQYR8aGBE0h3VG6/cwlbrpdciNYBMotQav8c1XI3HjHH+NikUpP53nPdlZSdWmFzA==", "dev": true, "dependencies": { "reusify": "^1.0.4" @@ -7859,9 +7818,9 @@ } }, "node_modules/flatted": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.1.tgz", - "integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==", + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.2.tgz", + "integrity": "sha512-AiwGJM8YcNOaobumgtng+6NHuOqC3A7MixFeDafM3X9cIUM+xUXoS5Vfgf+OihAYe20fxqNM9yPBXJzRtZ/4eA==", "dev": true }, "node_modules/follow-redirects": { @@ -8010,9 +7969,9 @@ } }, "node_modules/get-east-asian-width": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.2.0.tgz", - "integrity": "sha512-2nk+7SIVb14QrgXFHcm84tD4bKQz0RxPuMT8Ag5KPOq7J5fEmAg0UbXdTOSHqNuHSU28k55qnceesxXRZGzKWA==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.3.0.tgz", + "integrity": "sha512-vpeMIQKxczTD/0s2CdEWHcb0eeJe6TFjxb+J5xgX7hScxqrGuyjmv4c1D4A/gelKfyox0gJJwIHF+fLjeaM8kQ==", "dev": true, "engines": { "node": ">=18" @@ -8022,16 +7981,21 @@ } }, "node_modules/get-intrinsic": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", - "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.7.tgz", + "integrity": "sha512-VW6Pxhsrk0KAOqs3WEd0klDiF/+V7gQOpAvY1jVU/LHmaD/kQO4523aiJuikX/QAKYiW6x8Jh+RJej1almdtCA==", "dev": true, "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-define-property": "^1.0.1", "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", "function-bind": "^1.1.2", - "has-proto": "^1.0.1", - "has-symbols": "^1.0.3", - "hasown": "^2.0.0" + "get-proto": "^1.0.0", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" }, "engines": { "node": ">= 0.4" @@ -8040,6 +8004,19 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "dev": true, + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/get-stream": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", @@ -8073,15 +8050,15 @@ } }, "node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", "dev": true, "dependencies": { - "is-glob": "^4.0.1" + "is-glob": "^4.0.3" }, "engines": { - "node": ">= 6" + "node": ">=10.13.0" } }, "node_modules/glob-to-regexp": { @@ -8139,13 +8116,22 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/globby/node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, "node_modules/gopd": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", - "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", "dev": true, - "dependencies": { - "get-intrinsic": "^1.1.3" + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -8191,30 +8177,18 @@ } }, "node_modules/has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true, "engines": { - "node": ">=4" - } - }, - "node_modules/has-property-descriptors": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", - "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", - "dev": true, - "dependencies": { - "es-define-property": "^1.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">=8" } }, - "node_modules/has-proto": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz", - "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==", + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", "dev": true, "engines": { "node": ">= 0.4" @@ -8223,11 +8197,14 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/has-symbols": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", - "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", "dev": true, + "dependencies": { + "has-symbols": "^1.0.3" + }, "engines": { "node": ">= 0.4" }, @@ -8395,9 +8372,9 @@ } }, "node_modules/http-parser-js": { - "version": "0.5.8", - "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.8.tgz", - "integrity": "sha512-SGeBX54F94Wgu5RH3X5jsDtf4eHyRogWX1XGT3b4HuW3tQPM4AaBzoUji/4AAJNXCEOWZ5O0DgZmJw1947gD5Q==", + "version": "0.5.9", + "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.9.tgz", + "integrity": "sha512-n1XsPy3rXVxlqxVioEWdC+0+M+SQw0DpJynwtOPo1X+ZlvdzTLtDBIJJlDQTnwZIFJrZSzSGmIOUdP8tu+SgLw==", "dev": true }, "node_modules/http-proxy": { @@ -8428,17 +8405,17 @@ } }, "node_modules/http-proxy-middleware": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-3.0.0.tgz", - "integrity": "sha512-36AV1fIaI2cWRzHo+rbcxhe3M3jUDCNzc4D5zRl57sEWRAxdXYtw7FSQKYY6PDKssiAKjLYypbssHk+xs/kMXw==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-3.0.3.tgz", + "integrity": "sha512-usY0HG5nyDUwtqpiZdETNbmKtw3QQ1jwYFZ9wi5iHzX2BcILwQKtYDJPo7XHTsu5Z0B2Hj3W9NNnbd+AjFWjqg==", "dev": true, "dependencies": { - "@types/http-proxy": "^1.17.10", - "debug": "^4.3.4", + "@types/http-proxy": "^1.17.15", + "debug": "^4.3.6", "http-proxy": "^1.18.1", - "is-glob": "^4.0.1", - "is-plain-obj": "^3.0.0", - "micromatch": "^4.0.5" + "is-glob": "^4.0.3", + "is-plain-object": "^5.0.0", + "micromatch": "^4.0.8" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" @@ -8519,9 +8496,9 @@ ] }, "node_modules/ignore": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", - "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-6.0.2.tgz", + "integrity": "sha512-InwqeHHN2XpumIkMvpl/DCJVrAHgCsG5+cn1XlnLWGwtZBm8QJfSusItfrwx81CTp5agNZqpKU2J/ccC5nGT4A==", "dev": true, "engines": { "node": ">= 4" @@ -8657,9 +8634,9 @@ } }, "node_modules/is-core-module": { - "version": "2.15.1", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.15.1.tgz", - "integrity": "sha512-z0vtXSwucUJtANQWldhbtbt7BnL0vxiFjIdDLAatwhDYty2bad6s+rijD6Ri4YuYJubLzIJLUidCh09e1djEVQ==", + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", "dev": true, "dependencies": { "hasown": "^2.0.2" @@ -8795,15 +8772,30 @@ } }, "node_modules/is-plain-object": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", - "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz", + "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-regex": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", + "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==", "dev": true, "dependencies": { - "isobject": "^3.0.1" + "call-bound": "^1.0.2", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" }, "engines": { - "node": ">=0.10.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, "node_modules/is-stream": { @@ -8923,27 +8915,6 @@ "node": ">=10" } }, - "node_modules/istanbul-lib-report/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/istanbul-lib-report/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/istanbul-lib-source-maps": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", @@ -9015,15 +8986,6 @@ "node": ">= 10.13.0" } }, - "node_modules/jest-worker/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/jest-worker/node_modules/supports-color": { "version": "8.1.1", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", @@ -9040,9 +9002,9 @@ } }, "node_modules/jiti": { - "version": "1.21.6", - "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.6.tgz", - "integrity": "sha512-2yTgeWTWzMWkHu6Jp9NKgePDaYHbntiwvYuuJLbbN9vl7DC9DvXKOB2BC3ZZ92D3cvV/aflH0osDfwpHepQ53w==", + "version": "1.21.7", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.7.tgz", + "integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==", "dev": true, "bin": { "jiti": "bin/jiti.js" @@ -9314,21 +9276,6 @@ "source-map-support": "^0.5.5" } }, - "node_modules/karma/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, "node_modules/karma/node_modules/brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -9339,6 +9286,30 @@ "concat-map": "0.0.1" } }, + "node_modules/karma/node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, "node_modules/karma/node_modules/cliui": { "version": "7.0.4", "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", @@ -9350,23 +9321,32 @@ "wrap-ansi": "^7.0.0" } }, - "node_modules/karma/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "node_modules/karma/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/karma/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", "dev": true, "dependencies": { - "color-name": "~1.1.4" + "is-glob": "^4.0.1" }, "engines": { - "node": ">=7.0.0" + "node": ">= 6" } }, - "node_modules/karma/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true + "node_modules/karma/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } }, "node_modules/karma/node_modules/emoji-regex": { "version": "8.0.0", @@ -9395,6 +9375,30 @@ "node": "*" } }, + "node_modules/karma/node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/karma/node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, "node_modules/karma/node_modules/source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", @@ -9819,74 +9823,125 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/log-symbols/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "node_modules/log-update": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/log-update/-/log-update-6.1.0.tgz", + "integrity": "sha512-9ie8ItPR6tjY5uYJh8K/Zrv/RMZ5VOlOWvtZdEHYSTFKZfIBPQa9tOAEeAWhd+AnIneLJ22w5fjOYtoutpWq5w==", "dev": true, "dependencies": { - "color-convert": "^2.0.1" + "ansi-escapes": "^7.0.0", + "cli-cursor": "^5.0.0", + "slice-ansi": "^7.1.0", + "strip-ansi": "^7.1.0", + "wrap-ansi": "^9.0.0" }, "engines": { - "node": ">=8" + "node": ">=18" }, "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/log-symbols/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "node_modules/log-update/node_modules/ansi-escapes": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-7.0.0.tgz", + "integrity": "sha512-GdYO7a61mR0fOlAsvC9/rIHf7L96sBc6dEWzeOu+KAea5bZyQRPIpojrVoI4AXGJS/ycu/fBTdLrUkA4ODrvjw==", "dev": true, "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" + "environment": "^1.0.0" }, "engines": { - "node": ">=10" + "node": ">=18" }, "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/log-symbols/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "node_modules/log-update/node_modules/ansi-regex": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", + "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, "engines": { - "node": ">=7.0.0" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" } }, - "node_modules/log-symbols/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/log-symbols/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, + "node_modules/log-update/node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "dev": true, "engines": { - "node": ">=8" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/log-symbols/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "node_modules/log-update/node_modules/is-fullwidth-code-point": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-5.0.0.tgz", + "integrity": "sha512-OVa3u9kkBbw7b8Xw5F9P+D/T9X+Z4+JruYVNapTjPYZYUznQ5YfWeFkOj606XYYW8yugTfC8Pj0hYqvi4ryAhA==", "dev": true, "dependencies": { - "has-flag": "^4.0.0" + "get-east-asian-width": "^1.0.0" }, "engines": { - "node": ">=8" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-update/node_modules/slice-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-7.1.0.tgz", + "integrity": "sha512-bSiSngZ/jWeX93BqeIAbImyTbEihizcwNjFoRUIY/T1wWQsfsm2Vw1agPKylXvQTU7iASGdHhyqRlqQzfz+Htg==", + "dev": true, + "dependencies": { + "ansi-styles": "^6.2.1", + "is-fullwidth-code-point": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, + "node_modules/log-update/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/log-update/node_modules/wrap-ansi": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.0.tgz", + "integrity": "sha512-G8ura3S+3Z2G+mkgNRq8dqaFZAuxfsxpBB8OCTGRTCtp+l/v9nbFNmCUP1BZMts3G1142MsZfn6eeUKrr4PD1Q==", + "dev": true, + "dependencies": { + "ansi-styles": "^6.2.1", + "string-width": "^7.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, "node_modules/log-update": { @@ -10083,6 +10138,15 @@ "node": ">=0.10.0" } }, + "node_modules/loglevel-colored-level-prefix/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, "node_modules/loglevel-colored-level-prefix/node_modules/strip-ansi": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", @@ -10160,6 +10224,15 @@ "node": "^16.14.0 || >=18.0.0" } }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", @@ -10170,9 +10243,9 @@ } }, "node_modules/memfs": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/memfs/-/memfs-4.12.0.tgz", - "integrity": "sha512-74wDsex5tQDSClVkeK1vtxqYCAgCoXxx+K4NSHzgU/muYVYByFqa+0RnrPO9NM6naWm1+G9JmZ0p6QHhXmeYfA==", + "version": "4.17.0", + "resolved": "https://registry.npmjs.org/memfs/-/memfs-4.17.0.tgz", + "integrity": "sha512-4eirfZ7thblFmqFjywlTmuWVSvccHAJbn1r8qQLzmTO11qcqpohOjmY2mFce6x7x7WtskzRqApPD0hv+Oa74jg==", "dev": true, "dependencies": { "@jsonjoy.com/json-pack": "^1.0.3", @@ -10601,9 +10674,9 @@ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" }, "node_modules/msgpackr": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/msgpackr/-/msgpackr-1.11.0.tgz", - "integrity": "sha512-I8qXuuALqJe5laEBYoFykChhSXLikZmUhccjGsPuSJ/7uPip2TJ7lwdIQwWSAi0jGZDXv4WOP8Qg65QZRuXxXw==", + "version": "1.11.2", + "resolved": "https://registry.npmjs.org/msgpackr/-/msgpackr-1.11.2.tgz", + "integrity": "sha512-F9UngXRlPyWCDEASDpTf6c9uNhGPTqnTeLVt7bN+bU1eajoR/8V9ys2BRaV5C/e5ihE6sJ9uPIKaYt6bFuO32g==", "dev": true, "optionalDependencies": { "msgpackr-extract": "^3.0.2" @@ -10654,9 +10727,9 @@ } }, "node_modules/nanoid": { - "version": "3.3.7", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", - "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", + "version": "3.3.8", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz", + "integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==", "dev": true, "funding": [ { @@ -10708,9 +10781,9 @@ } }, "node_modules/negotiator": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", - "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.4.tgz", + "integrity": "sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==", "dev": true, "engines": { "node": ">= 0.6" @@ -10787,9 +10860,9 @@ } }, "node_modules/node-gyp": { - "version": "10.2.0", - "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-10.2.0.tgz", - "integrity": "sha512-sp3FonBAaFe4aYTcFdZUn2NYkbP7xroPGYvQmP4Nl5PxamznItBnNCgjrVTKrEfQynInMsJvZrdmqUnysCJ8rw==", + "version": "10.3.1", + "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-10.3.1.tgz", + "integrity": "sha512-Pp3nFHBThHzVtNY7U6JfPjvT/DTE8+o/4xKsLQtBoU+j2HLsGlhcfzflAoUreaJbNmYnX+LlLi0qjV8kpyO6xQ==", "dev": true, "dependencies": { "env-paths": "^2.2.0", @@ -10811,9 +10884,9 @@ } }, "node_modules/node-gyp-build": { - "version": "4.8.2", - "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.2.tgz", - "integrity": "sha512-IRUxE4BVsHWXkV/SFOut4qTlagw2aM8T5/vnTsmrHJvVoKueJHRc/JaFND7QDDc61kLYUJ6qlZM3sqTSyx2dTw==", + "version": "4.8.4", + "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.4.tgz", + "integrity": "sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==", "dev": true, "optional": true, "bin": { @@ -10881,9 +10954,9 @@ } }, "node_modules/node-releases": { - "version": "2.0.18", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.18.tgz", - "integrity": "sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==", + "version": "2.0.19", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", + "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==", "dev": true }, "node_modules/nopt": { @@ -11070,9 +11143,9 @@ } }, "node_modules/object-inspect": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.2.tgz", - "integrity": "sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==", + "version": "1.13.3", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.3.tgz", + "integrity": "sha512-kDCGIbxkDSXE3euJZZXzc6to7fCrKHNI/hSRQnRuQ+BWjFNzZwiFF8fj/6o2t2G9/jTj8PSIYTfCLelLZEeRpA==", "dev": true, "engines": { "node": ">= 0.4" @@ -11189,37 +11262,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/ora/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/ora/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, "node_modules/ora/node_modules/cli-cursor": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", @@ -11232,33 +11274,6 @@ "node": ">=8" } }, - "node_modules/ora/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/ora/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/ora/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/ora/node_modules/onetime": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", @@ -11293,17 +11308,11 @@ "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", "dev": true }, - "node_modules/ora/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } + "node_modules/ordered-binary": { + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/ordered-binary/-/ordered-binary-1.5.3.tgz", + "integrity": "sha512-oGFr3T+pYdTGJ+YFEILMpS3es+GiIbs9h/XQrclBXUtd44ey7XwfsMzM31f64I1SQOawDoDr/D823kNCADI8TA==", + "dev": true }, "node_modules/ordered-binary": { "version": "1.5.2", @@ -11366,9 +11375,9 @@ } }, "node_modules/p-retry": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-6.2.0.tgz", - "integrity": "sha512-JA6nkq6hKyWLLasXQXUrO4z8BUZGUt/LjlJxx8Gb2+2ntodU/SS63YZ8b0LUTbQ8ZB9iwOfhEPhg4ykKnn2KsA==", + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-6.2.1.tgz", + "integrity": "sha512-hEt02O4hUct5wtwg4H4KcWgDdm+l1bOaEy/hWzd8xtXB9BqxTWBBhb+2ImAtH4Cv4rPjV76xN3Zumqk3k3AhhQ==", "dev": true, "dependencies": { "@types/retry": "0.12.2", @@ -11474,12 +11483,12 @@ } }, "node_modules/parse5": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.1.2.tgz", - "integrity": "sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==", + "version": "7.2.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.2.1.tgz", + "integrity": "sha512-BuBYQYlv1ckiPdQi/ohiivi9Sagc9JG+Ozs0r7b/0iK3sKmrb0b9FdWdBbOdx6hBCM/F9Ir82ofnBhtZOjCRPQ==", "devOptional": true, "dependencies": { - "entities": "^4.4.0" + "entities": "^4.5.0" }, "funding": { "url": "https://github.com/inikulin/parse5?sponsor=1" @@ -11575,9 +11584,9 @@ "dev": true }, "node_modules/path-to-regexp": { - "version": "0.1.10", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.10.tgz", - "integrity": "sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w==", + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", + "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", "dev": true }, "node_modules/path-type": { @@ -11593,9 +11602,9 @@ } }, "node_modules/picocolors": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.0.tgz", - "integrity": "sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", "dev": true }, "node_modules/picomatch": { @@ -11804,13 +11813,13 @@ } }, "node_modules/postcss-modules-local-by-default": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.5.tgz", - "integrity": "sha512-6MieY7sIfTK0hYfafw1OMEG+2bg8Q1ocHCpoWLqOKj3JXlKu4G7btkmM/B7lFubYkYWmRSPLZi5chid63ZaZYw==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.2.0.tgz", + "integrity": "sha512-5kcJm/zk+GJDSfw+V/42fJ5fhjL5YbFDl8nVdXkJPLLW+Vf9mTD5Xe0wqIaDnLuL2U6cDNpTr+UQ+v2HWIBhzw==", "dev": true, "dependencies": { "icss-utils": "^5.0.0", - "postcss-selector-parser": "^6.0.2", + "postcss-selector-parser": "^7.0.0", "postcss-value-parser": "^4.1.0" }, "engines": { @@ -11821,12 +11830,12 @@ } }, "node_modules/postcss-modules-scope": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-3.2.0.tgz", - "integrity": "sha512-oq+g1ssrsZOsx9M96c5w8laRmvEu9C3adDSjI8oTcbfkrTE8hx/zfyobUoWIxaKPO8bt6S62kxpw5GqypEw1QQ==", + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-3.2.1.tgz", + "integrity": "sha512-m9jZstCVaqGjTAuny8MdgE88scJnCiQSlSrOWcTQgM2t32UBe+MUmFSO5t7VMSfAf/FJKImAxBav8ooCHJXCJA==", "dev": true, "dependencies": { - "postcss-selector-parser": "^6.0.4" + "postcss-selector-parser": "^7.0.0" }, "engines": { "node": "^10 || ^12 || >= 14" @@ -11851,9 +11860,9 @@ } }, "node_modules/postcss-selector-parser": { - "version": "6.1.2", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz", - "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.0.0.tgz", + "integrity": "sha512-9RbEr1Y7FFfptd/1eEdntyjMwLeghW1bHX9GWjXo19vx4ytPQhANltvVxDggzJl7mnWM+dX28kb6cyS/4iQjlQ==", "dev": true, "dependencies": { "cssesc": "^3.0.0", @@ -11879,9 +11888,9 @@ } }, "node_modules/prettier": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.3.3.tgz", - "integrity": "sha512-i2tDNA0O5IrMO757lfrdQZCc2jPNDVntV0m/+4whiDfWaTKfMNgR7Qz0NAeGz/nRqF4m5/6CLzbP4/liHt12Ew==", + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.4.2.tgz", + "integrity": "sha512-e9MewbtFo+Fevyuxn/4rrcDAaq0IYxPGLvObpQjiZBMAzB9IGmzlnG9RZy3FFas+eBMu2vA0CszMeduow5dIuQ==", "dev": true, "bin": { "prettier": "bin/prettier.cjs" @@ -12080,21 +12089,6 @@ "url": "https://github.com/sponsors/epoberezkin" } }, - "node_modules/prettier-eslint/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, "node_modules/prettier-eslint/node_modules/argparse": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", @@ -12114,56 +12108,11 @@ "concat-map": "0.0.1" } }, - "node_modules/prettier-eslint/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/prettier-eslint/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/prettier-eslint/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/prettier-eslint/node_modules/escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/prettier-eslint/node_modules/eslint": { "version": "7.32.0", "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.32.0.tgz", "integrity": "sha512-VHZ8gX+EDfz+97jGcgyGCyRia/dPOd6Xh9yPv8Bl1+SoaIwD+a/vlrOmGRUyOYu7MwUhc7CxqeaDZU13S4+EpA==", + "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", "dev": true, "dependencies": { "@babel/code-frame": "7.12.11", @@ -12271,6 +12220,18 @@ "node": ">=4.0" } }, + "node_modules/prettier-eslint/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/prettier-eslint/node_modules/globals": { "version": "13.24.0", "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", @@ -12286,15 +12247,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/prettier-eslint/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/prettier-eslint/node_modules/ignore": { "version": "4.0.6", "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", @@ -12356,18 +12308,6 @@ "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", "dev": true }, - "node_modules/prettier-eslint/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/prettier-eslint/node_modules/type-fest": { "version": "0.20.2", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", @@ -12415,6 +12355,33 @@ "ansi-styles": "^3.2.0" } }, + "node_modules/pretty-format/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/pretty-format/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/pretty-format/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true + }, "node_modules/proc-log": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-4.2.0.tgz", @@ -12592,27 +12559,16 @@ } }, "node_modules/readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "dev": true, - "dependencies": { - "picomatch": "^2.2.1" - }, - "engines": { - "node": ">=8.10.0" - } - }, - "node_modules/readdirp/node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.1.tgz", + "integrity": "sha512-h80JrZu/MHUZCyHu5ciuoI0+WxsCxzxJTILn6Fs8rxSnFPh+UVHYfeIxK1nVGugMqkfC4vJcBOYbkfkwYK0+gw==", "dev": true, "engines": { - "node": ">=8.6" + "node": ">= 14.18.0" }, "funding": { - "url": "https://github.com/sponsors/jonschlinkert" + "type": "individual", + "url": "https://paulmillr.com/funding/" } }, "node_modules/reflect-metadata": { @@ -12673,15 +12629,15 @@ } }, "node_modules/regexpu-core": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-5.3.2.tgz", - "integrity": "sha512-RAM5FlZz+Lhmo7db9L298p2vHP5ZywrVXmVXpmAD9GuL5MPH6t9ROw1iA/wfHkQ76Qe7AaPF0nGuim96/IrQMQ==", + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-6.2.0.tgz", + "integrity": "sha512-H66BPQMrv+V16t8xtmq+UC0CBpiTBA60V8ibS1QVReIp8T1z8hwFxqcGzm9K6lgsN7sB5edVH8a+ze6Fqm4weA==", "dev": true, "dependencies": { - "@babel/regjsgen": "^0.8.0", "regenerate": "^1.4.2", - "regenerate-unicode-properties": "^10.1.0", - "regjsparser": "^0.9.1", + "regenerate-unicode-properties": "^10.2.0", + "regjsgen": "^0.8.0", + "regjsparser": "^0.12.0", "unicode-match-property-ecmascript": "^2.0.0", "unicode-match-property-value-ecmascript": "^2.1.0" }, @@ -12689,25 +12645,34 @@ "node": ">=4" } }, + "node_modules/regjsgen": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.8.0.tgz", + "integrity": "sha512-RvwtGe3d7LvWiDQXeQw8p5asZUmfU1G/l6WbUXeHta7Y2PEIvBTwH6E2EfmYUK8pxcxEdEmaomqyp0vZZ7C+3Q==", + "dev": true + }, "node_modules/regjsparser": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.9.1.tgz", - "integrity": "sha512-dQUtn90WanSNl+7mQKcXAgZxvUe7Z0SqXlgzv0za4LwiUhyzBC58yQO3liFoUgu8GiJVInAhJjkj1N0EtQ5nkQ==", + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.12.0.tgz", + "integrity": "sha512-cnE+y8bz4NhMjISKbgeVJtqNbtf5QpjZP+Bslo+UqkIt9QPnX9q095eiRRASJG1/tz6dlNr6Z5NsBiWYokp6EQ==", "dev": true, "dependencies": { - "jsesc": "~0.5.0" + "jsesc": "~3.0.2" }, "bin": { "regjsparser": "bin/parser" } }, "node_modules/regjsparser/node_modules/jsesc": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", - "integrity": "sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.0.2.tgz", + "integrity": "sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g==", "dev": true, "bin": { "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" } }, "node_modules/reinterval": { @@ -12963,6 +12928,23 @@ } ] }, + "node_modules/safe-regex-test": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.1.0.tgz", + "integrity": "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==", + "dev": true, + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-regex": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", @@ -13026,6 +13008,66 @@ } } }, + "node_modules/sass/node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/sass/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/sass/node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/sass/node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, "node_modules/sax": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/sax/-/sax-1.4.1.tgz", @@ -13034,9 +13076,9 @@ "optional": true }, "node_modules/schema-utils": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.2.0.tgz", - "integrity": "sha512-L0jRsrPpjdckP3oPug3/VxNKt2trR8TcabrM6FOAAlvC/9Phcmm+cuAgTlxBqdBR1WJx7Naj9WHw+aOmheSVbw==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.0.tgz", + "integrity": "sha512-Gf9qqc58SpCA/xdziiHz35F4GNIWYWZrEshUc/G/r5BnLph6xpKuLeoJoQuj5WfBIx/eQLf+hmVPYHaxJu7V2g==", "dev": true, "dependencies": { "@types/json-schema": "^7.0.9", @@ -13045,7 +13087,7 @@ "ajv-keywords": "^5.1.0" }, "engines": { - "node": ">= 12.13.0" + "node": ">= 10.13.0" }, "funding": { "type": "opencollective", @@ -13262,23 +13304,6 @@ "node": ">= 0.8" } }, - "node_modules/set-function-length": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", - "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", - "dev": true, - "dependencies": { - "define-data-property": "^1.1.4", - "es-errors": "^1.3.0", - "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.4", - "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - } - }, "node_modules/setprototypeof": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", @@ -13319,24 +13344,81 @@ } }, "node_modules/shell-quote": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.1.tgz", - "integrity": "sha512-6j1W9l1iAs/4xYBI1SYOVZyFcCis9b4KCLQ8fgAGG07QvzaRLVVRQvAy85yNmmZSjYjg4MWh4gNvlPujU/5LpA==", + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.2.tgz", + "integrity": "sha512-AzqKpGKjrj7EM6rKVQEPpB288oCfnrEIuyoT9cyF4nmGa7V8Zk6f7RRqYisX8X9m+Q7bd632aZW4ky7EhbQztA==", "dev": true, + "engines": { + "node": ">= 0.4" + }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/side-channel": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz", - "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "dev": true, + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", "dev": true, "dependencies": { - "call-bind": "^1.0.7", "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.4", - "object-inspect": "^1.13.1" + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "dev": true, + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "dev": true, + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" }, "engines": { "node": ">= 0.4" @@ -13425,9 +13507,9 @@ } }, "node_modules/socket.io": { - "version": "4.8.0", - "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.8.0.tgz", - "integrity": "sha512-8U6BEgGjQOfGz3HHTYaC/L1GaxDCJ/KM0XTkJly0EhZ5U/du9uNEZy4ZgYzEzIqlx2CMm25CrCqr1ck899eLNA==", + "version": "4.8.1", + "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.8.1.tgz", + "integrity": "sha512-oZ7iUCxph8WYRHHcjBEc9unw3adt5CmSNlppj/5Q4k2RIrhl8Z5yY2Xr4j9zj0+wzVZ0bxmYoGSzKJnRl6A4yg==", "dev": true, "dependencies": { "accepts": "~1.3.4", @@ -13452,6 +13534,23 @@ "ws": "~8.17.1" } }, + "node_modules/socket.io-adapter/node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, "node_modules/socket.io-adapter/node_modules/ws": { "version": "8.17.1", "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", @@ -13486,6 +13585,40 @@ "node": ">=10.0.0" } }, + "node_modules/socket.io-parser/node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/socket.io/node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, "node_modules/sockjs": { "version": "0.3.24", "resolved": "https://registry.npmjs.org/sockjs/-/sockjs-0.3.24.tgz", @@ -13512,12 +13645,12 @@ } }, "node_modules/socks-proxy-agent": { - "version": "8.0.4", - "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.4.tgz", - "integrity": "sha512-GNAq/eg8Udq2x0eNiFkr9gRg5bA7PXEWagQdeRX4cPSG+X/8V38v637gim9bjFptMk1QWsCTr0ttrJEiXbNnRw==", + "version": "8.0.5", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.5.tgz", + "integrity": "sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw==", "dev": true, "dependencies": { - "agent-base": "^7.1.1", + "agent-base": "^7.1.2", "debug": "^4.3.4", "socks": "^2.8.3" }, @@ -13621,9 +13754,9 @@ } }, "node_modules/spdx-license-ids": { - "version": "3.0.20", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.20.tgz", - "integrity": "sha512-jg25NiDV/1fLtSgEgyvVyDunvaNHbuwF9lfNV17gSmPFAlYzdfNBlLtLzXTevwkPj7DhGbmN9VnmJIgLnhvaBw==", + "version": "3.0.21", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.21.tgz", + "integrity": "sha512-Bvg/8F5XephndSK3JffaRqdT+gyhfqIPwDHpX80tJrF8QQRYMo8sNMeaZ2Dp5+jhwKnUmIOyFFQfHRkjJm5nXg==", "dev": true }, "node_modules/spdy": { @@ -13857,15 +13990,15 @@ } }, "node_modules/supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, "dependencies": { - "has-flag": "^3.0.0" + "has-flag": "^4.0.0" }, "engines": { - "node": ">=4" + "node": ">=8" } }, "node_modules/supports-preserve-symlinks-flag": { @@ -13890,9 +14023,9 @@ } }, "node_modules/synckit": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.9.1.tgz", - "integrity": "sha512-7gr8p9TQP6RAHusBOSLs46F4564ZrjV8xFmw5zCmgmhGUcw2hxsShhJ6CEiHQMgPDwAQ1fWHPM0ypc4RMAig4A==", + "version": "0.9.2", + "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.9.2.tgz", + "integrity": "sha512-vrozgXDQwYO72vHjUb/HnFbQx1exDjoKzqx23aXEg2a9VIg2TSFZ8FmeZpTjUCFMYw7mpX4BE2SFu8wI7asYsw==", "dev": true, "dependencies": { "@pkgr/core": "^0.1.0", @@ -13906,9 +14039,9 @@ } }, "node_modules/table": { - "version": "6.8.2", - "resolved": "https://registry.npmjs.org/table/-/table-6.8.2.tgz", - "integrity": "sha512-w2sfv80nrAh2VCbqR5AK27wswXhqcck2AhfnNW76beQXskGZ1V12GwS//yYVa3d3fcvAip2OUnbDAjW2k3v9fA==", + "version": "6.9.0", + "resolved": "https://registry.npmjs.org/table/-/table-6.9.0.tgz", + "integrity": "sha512-9kY+CygyYM6j02t5YFHbNz2FN5QmYGv9zAjVp4lCDjlCw7amdckXlEt/bjMhUIfj4ThGRE4gCUH5+yGnNuPo5A==", "dev": true, "dependencies": { "ajv": "^8.0.1", @@ -13921,39 +14054,6 @@ "node": ">=10.0.0" } }, - "node_modules/table/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/table/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/table/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, "node_modules/table/node_modules/emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", @@ -14096,16 +14196,16 @@ } }, "node_modules/terser-webpack-plugin": { - "version": "5.3.10", - "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.10.tgz", - "integrity": "sha512-BKFPWlPDndPs+NGGCr1U59t0XScL5317Y0UReNrHaw9/FwhPENlq6bfgs+4yPfyP51vqC1bQ4rp1EfXW5ZSH9w==", + "version": "5.3.11", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.11.tgz", + "integrity": "sha512-RVCsMfuD0+cTt3EwX8hSl2Ks56EbFHWmhluwcqoPKtBnfjiT6olaq7PRIRfhyU8nnC2MrnDrBLfrD/RGE+cVXQ==", "dev": true, "dependencies": { - "@jridgewell/trace-mapping": "^0.3.20", + "@jridgewell/trace-mapping": "^0.3.25", "jest-worker": "^27.4.5", - "schema-utils": "^3.1.1", - "serialize-javascript": "^6.0.1", - "terser": "^5.26.0" + "schema-utils": "^4.3.0", + "serialize-javascript": "^6.0.2", + "terser": "^5.31.1" }, "engines": { "node": ">= 10.13.0" @@ -14129,55 +14229,6 @@ } } }, - "node_modules/terser-webpack-plugin/node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/terser-webpack-plugin/node_modules/ajv-keywords": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", - "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", - "dev": true, - "peerDependencies": { - "ajv": "^6.9.1" - } - }, - "node_modules/terser-webpack-plugin/node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true - }, - "node_modules/terser-webpack-plugin/node_modules/schema-utils": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", - "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", - "dev": true, - "dependencies": { - "@types/json-schema": "^7.0.8", - "ajv": "^6.12.5", - "ajv-keywords": "^3.5.2" - }, - "engines": { - "node": ">= 10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - } - }, "node_modules/text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", @@ -14214,15 +14265,6 @@ "node": ">=0.6.0" } }, - "node_modules/to-fast-properties": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", - "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", - "dev": true, - "engines": { - "node": ">=4" - } - }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -14270,21 +14312,21 @@ } }, "node_modules/ts-api-utils": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.3.0.tgz", - "integrity": "sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.0.0.tgz", + "integrity": "sha512-xCt/TOAc+EOHS1XPnijD3/yzpH6qg2xppZO1YDqGoVsNXfQfzHpOdNuXwrwOU8u4ITXJyDCTyt8w5g1sZv9ynQ==", "dev": true, "engines": { - "node": ">=16" + "node": ">=18.12" }, "peerDependencies": { - "typescript": ">=4.2.0" + "typescript": ">=4.8.4" } }, "node_modules/tslib": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", - "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" }, "node_modules/tsutils": { "version": "3.21.0", @@ -14383,9 +14425,9 @@ } }, "node_modules/ua-parser-js": { - "version": "0.7.39", - "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.39.tgz", - "integrity": "sha512-IZ6acm6RhQHNibSt7+c09hhvsKy9WUr4DVbeq9U8o71qxyYtJpQeDxQnMrVqnIFMLcQjHO0I9wgfO2vIahht4w==", + "version": "0.7.40", + "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.40.tgz", + "integrity": "sha512-us1E3K+3jJppDBa3Tl0L3MOJiGhe1C6P0+nIvQAFYbxlMAx0h81eOwLmU57xgqToduDDPx3y5QsdjPfDu+FgOQ==", "dev": true, "funding": [ { @@ -14409,9 +14451,9 @@ } }, "node_modules/undici-types": { - "version": "6.19.8", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", - "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==", + "version": "6.20.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz", + "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==", "dev": true }, "node_modules/unicode-canonical-property-names-ecmascript": { @@ -14509,9 +14551,9 @@ } }, "node_modules/update-browserslist-db": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.1.tgz", - "integrity": "sha512-R8UzCaa9Az+38REPiJ1tXlImTJXlVfgHZsglwBD/k6nj76ctsH1E3q4doGrukiLQd3sGQYu56r5+lo5r94l29A==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.2.tgz", + "integrity": "sha512-PPypAm5qvlD7XMZC3BujecnaOxwhrtoFR+Dqkk5Aa/6DssiH0ibKoketaj9w8LP7Bont1rYeoV5plxD7RTEPRg==", "dev": true, "funding": [ { @@ -14529,7 +14571,7 @@ ], "dependencies": { "escalade": "^3.2.0", - "picocolors": "^1.1.0" + "picocolors": "^1.1.1" }, "bin": { "update-browserslist-db": "cli.js" @@ -14614,9 +14656,9 @@ } }, "node_modules/vite": { - "version": "5.4.6", - "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.6.tgz", - "integrity": "sha512-IeL5f8OO5nylsgzd9tq4qD2QqI0k2CQLGrWD0rCN0EQJZpBK5vJAx0I+GDkMOXxQX/OfFHMuLIx6ddAxGX/k+Q==", + "version": "5.4.14", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.14.tgz", + "integrity": "sha512-EK5cY7Q1D8JNhSaPKVK4pwBFvaTmZxEnoKXLG/U9gmdDcihQGNzFlgIvaxezFR4glP1LsuiedwMBqCXH3wZccA==", "dev": true, "dependencies": { "esbuild": "^0.21.3", @@ -15079,9 +15121,9 @@ } }, "node_modules/vite/node_modules/postcss": { - "version": "8.4.47", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.47.tgz", - "integrity": "sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ==", + "version": "8.5.1", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.1.tgz", + "integrity": "sha512-6oz2beyjc5VMn/KV1pPw8fliQkhBXrVn1Z3TVyqZxU8kZpzEKhBdmCFqI6ZbmGtamQvQGuU1sgPTk8ZrXDD7jQ==", "dev": true, "funding": [ { @@ -15098,8 +15140,8 @@ } ], "dependencies": { - "nanoid": "^3.3.7", - "picocolors": "^1.1.0", + "nanoid": "^3.3.8", + "picocolors": "^1.1.1", "source-map-js": "^1.2.1" }, "engines": { @@ -15366,6 +15408,30 @@ } } }, + "node_modules/webpack-dev-server/node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, "node_modules/webpack-dev-server/node_modules/glob": { "version": "10.4.5", "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", @@ -15386,10 +15452,22 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/webpack-dev-server/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/webpack-dev-server/node_modules/http-proxy-middleware": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.6.tgz", - "integrity": "sha512-ya/UeJ6HVBYxrgYotAZo1KvPWlgB48kUJLDePFeneHsVujFaW5WNj2NgWCAE//B1Dl02BIfYlpNgBy8Kf8Rjmw==", + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.7.tgz", + "integrity": "sha512-fgVY8AV7qU7z/MmXJ/rxwbrtQH4jBQ9m7kp3llF0liB7glmFeVZFBepQb32T3y8n8k2+AEYuMPCpinYW+/CuRA==", "dev": true, "dependencies": { "@types/http-proxy": "^1.17.8", @@ -15410,6 +15488,30 @@ } } }, + "node_modules/webpack-dev-server/node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/webpack-dev-server/node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, "node_modules/webpack-dev-server/node_modules/rimraf": { "version": "5.0.10", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-5.0.10.tgz", @@ -15652,39 +15754,6 @@ "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, - "node_modules/wrap-ansi-cjs/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/wrap-ansi-cjs/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/wrap-ansi-cjs/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", @@ -15714,39 +15783,6 @@ "node": ">=8" } }, - "node_modules/wrap-ansi/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/wrap-ansi/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/wrap-ansi/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, "node_modules/wrap-ansi/node_modules/emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", diff --git a/modules/ui/src/app/app.store.spec.ts b/modules/ui/src/app/app.store.spec.ts index 300a250fd..40b2d26dc 100644 --- a/modules/ui/src/app/app.store.spec.ts +++ b/modules/ui/src/app/app.store.spec.ts @@ -53,6 +53,7 @@ import { FocusManagerService } from './services/focus-manager.service'; import { TestRunMqttService } from './services/test-run-mqtt.service'; import { MOCK_ADAPTERS } from './mocks/settings.mock'; import { TestingType } from './model/device'; +import { ResultOfTestrun, StatusOfTestrun } from './model/testrun-status'; const mock = (() => { let store: { [key: string]: string } = {}; @@ -499,7 +500,8 @@ describe('AppStore', () => { store.overrideSelector(selectIsTestingComplete, true); store.overrideSelector(selectSystemStatus, { - status: 'Compliant', + result: ResultOfTestrun.Compliant, + status: StatusOfTestrun.Complete, mac_addr: '00:1e:42:35:73:c4', device: { manufacturer: 'Delta', diff --git a/modules/ui/src/app/app.store.ts b/modules/ui/src/app/app.store.ts index a12a536a3..9b07fddf7 100644 --- a/modules/ui/src/app/app.store.ts +++ b/modules/ui/src/app/app.store.ts @@ -53,7 +53,7 @@ import { setTestModules, updateAdapters, } from './store/actions'; -import { StatusOfTestrun, TestrunStatus } from './model/testrun-status'; +import { ResultOfTestrun, TestrunStatus } from './model/testrun-status'; import { Adapters, SettingMissedError, @@ -333,7 +333,7 @@ export class AppStore extends ComponentStore { filter(([isTestingComplete]) => isTestingComplete === true), filter( ([, testrunStatus]) => - testrunStatus?.status === StatusOfTestrun.Compliant && + testrunStatus?.result === ResultOfTestrun.Compliant && testrunStatus?.device.test_pack === TestingType.Pilot ), tap(() => { diff --git a/modules/ui/src/app/components/download-report/download-report.component.ts b/modules/ui/src/app/components/download-report/download-report.component.ts index dcabc2281..8184ef6fc 100644 --- a/modules/ui/src/app/components/download-report/download-report.component.ts +++ b/modules/ui/src/app/components/download-report/download-report.component.ts @@ -14,7 +14,7 @@ * limitations under the License. */ import { ChangeDetectionStrategy, Component, Input } from '@angular/core'; -import { StatusOfTestrun, TestrunStatus } from '../../model/testrun-status'; +import { ResultOfTestrun, TestrunStatus } from '../../model/testrun-status'; import { CommonModule, DatePipe } from '@angular/common'; import { MatTooltipModule } from '@angular/material/tooltip'; import { ReportActionComponent } from '../report-action/report-action.component'; @@ -40,16 +40,16 @@ export class DownloadReportComponent extends ReportActionComponent { } return `${data.device.manufacturer} ${data.device.model} ${ data.device.firmware - } ${data.status} ${this.getFormattedDateString(data.started)}` + } ${data.result ? data.result : data.status} ${this.getFormattedDateString(data.started)}` .replace(/ /g, '_') .toLowerCase(); } getClass(data: TestrunStatus) { - if (data.status === StatusOfTestrun.Compliant) { + if (data.result === ResultOfTestrun.Compliant) { return `${this.class}-compliant`; } - if (data.status === StatusOfTestrun.NonCompliant) { + if (data.result === ResultOfTestrun.NonCompliant) { return `${this.class}-non-compliant`; } return this.class; diff --git a/modules/ui/src/app/components/download-zip-modal/download-zip-modal.component.html b/modules/ui/src/app/components/download-zip-modal/download-zip-modal.component.html index f949fb279..61ba85614 100644 --- a/modules/ui/src/app/components/download-zip-modal/download-zip-modal.component.html +++ b/modules/ui/src/app/components/download-zip-modal/download-zip-modal.component.html @@ -17,9 +17,8 @@

{{ data.testrunStatus.device.manufacturer }} - {{ data.testrunStatus.device.model }} v{{ - data.testrunStatus.device.firmware - }} + {{ data.testrunStatus.device.model }} + {{ data.testrunStatus.device.firmware }}

{{ data.testrunStatus.device.test_pack }} testing has just finished @@ -29,11 +28,15 @@ class="testing-result" id="testing-result-main-info" [class]=" - data.testrunStatus.status === StatusOfTestrun.Compliant + (data.testrunStatus.result === ResultOfTestrun.Compliant && + data.testrunStatus.status === StatusOfTestrun.Complete) || + data.testrunStatus.status === StatusOfTestrun.Proceed ? 'success-result' : 'failed-result' "> -

{{ data.testrunStatus.status }}

+

+ {{ getTestingResult(data.testrunStatus) }} +

{{ data.testrunStatus.description }}

diff --git a/modules/ui/src/app/components/download-zip-modal/download-zip-modal.component.ts b/modules/ui/src/app/components/download-zip-modal/download-zip-modal.component.ts index 199d89bf5..bb4c54b62 100644 --- a/modules/ui/src/app/components/download-zip-modal/download-zip-modal.component.ts +++ b/modules/ui/src/app/components/download-zip-modal/download-zip-modal.component.ts @@ -25,7 +25,11 @@ import { MatOptionModule } from '@angular/material/core'; import { TestRunService } from '../../services/test-run.service'; import { Routes } from '../../model/routes'; import { Router, RouterLink } from '@angular/router'; -import { TestrunStatus, StatusOfTestrun } from '../../model/testrun-status'; +import { + TestrunStatus, + StatusOfTestrun, + ResultOfTestrun, +} from '../../model/testrun-status'; import { DownloadReportComponent } from '../download-report/download-report.component'; import { Subject, takeUntil, timer } from 'rxjs'; import { FocusManagerService } from '../../services/focus-manager.service'; @@ -80,6 +84,7 @@ export class DownloadZipModalComponent } as Profile; public readonly Routes = Routes; public readonly StatusOfTestrun = StatusOfTestrun; + public readonly ResultOfTestrun = ResultOfTestrun; profiles: Profile[] = []; selectedProfile: Profile; constructor( @@ -165,6 +170,13 @@ export class DownloadZipModalComponent return this.testRunService.getRiskClass(riskResult); } + public getTestingResult(data: TestrunStatus): string { + if (data.status === StatusOfTestrun.Complete && data.result) { + return data.result; + } + return data.status; + } + private getZipLink(reportURL: string): string { return reportURL.replace('report', 'export'); } diff --git a/modules/ui/src/app/mocks/reports.mock.ts b/modules/ui/src/app/mocks/reports.mock.ts index 2889b0571..0c127b18c 100644 --- a/modules/ui/src/app/mocks/reports.mock.ts +++ b/modules/ui/src/app/mocks/reports.mock.ts @@ -5,7 +5,8 @@ import { DeviceStatus, TestingType } from '../model/device'; export const HISTORY = [ { mac_addr: '01:02:03:04:05:06', - status: 'compliant', + status: 'Complete', + result: 'Compliant', device: { status: DeviceStatus.VALID, manufacturer: 'Delta', @@ -20,7 +21,8 @@ export const HISTORY = [ finished: '2023-06-23T10:17:10.123Z', }, { - status: 'compliant', + status: 'Complete', + result: 'Compliant', mac_addr: '01:02:03:04:05:07', device: { status: DeviceStatus.VALID, @@ -37,7 +39,8 @@ export const HISTORY = [ }, { mac_addr: null, - status: 'compliant', + status: 'Complete', + result: 'Compliant', device: { status: DeviceStatus.VALID, manufacturer: 'Delta', @@ -56,7 +59,8 @@ export const HISTORY = [ export const HISTORY_AFTER_REMOVE = [ { mac_addr: '01:02:03:04:05:06', - status: 'compliant', + status: 'Complete', + result: 'Compliant', device: { status: DeviceStatus.VALID, manufacturer: 'Delta', @@ -72,7 +76,8 @@ export const HISTORY_AFTER_REMOVE = [ }, { mac_addr: null, - status: 'compliant', + status: 'Complete', + result: 'Compliant', device: { status: DeviceStatus.VALID, manufacturer: 'Delta', @@ -86,11 +91,12 @@ export const HISTORY_AFTER_REMOVE = [ started: '2023-06-23T10:11:00.123Z', finished: '2023-06-23T10:17:10.123Z', }, -]; +] as TestrunStatus[]; export const FORMATTED_HISTORY = [ { - status: 'compliant', + status: 'Complete', + result: 'Compliant', mac_addr: '01:02:03:04:05:06', device: { status: DeviceStatus.VALID, @@ -106,11 +112,13 @@ export const FORMATTED_HISTORY = [ finished: '2023-06-23T10:17:10.123Z', deviceFirmware: '1.2.2', deviceInfo: 'Delta 03-DIN-SRC', + testResult: 'Compliant', duration: '06m 10s', program: 'Device Qualification', }, { - status: 'compliant', + status: 'Complete', + result: 'Compliant', mac_addr: '01:02:03:04:05:07', device: { status: DeviceStatus.VALID, @@ -126,12 +134,14 @@ export const FORMATTED_HISTORY = [ finished: '2023-07-23T10:17:10.123Z', deviceFirmware: '1.2.3', deviceInfo: 'Delta 03-DIN-SRC', + testResult: 'Compliant', duration: '06m 10s', program: 'Device Qualification', }, { mac_addr: null, - status: 'compliant', + status: 'Complete', + result: 'Compliant', device: { status: DeviceStatus.VALID, manufacturer: 'Delta', @@ -146,10 +156,11 @@ export const FORMATTED_HISTORY = [ finished: '2023-06-23T10:17:10.123Z', deviceFirmware: '1.2.2', deviceInfo: 'Delta 03-DIN-SRC', + testResult: 'Compliant', duration: '06m 10s', program: 'Device Qualification', }, -]; +] as HistoryTestrun[]; export const FILTERS = { deviceInfo: 'test', diff --git a/modules/ui/src/app/mocks/testrun.mock.ts b/modules/ui/src/app/mocks/testrun.mock.ts index 3decf9973..48321f2d5 100644 --- a/modules/ui/src/app/mocks/testrun.mock.ts +++ b/modules/ui/src/app/mocks/testrun.mock.ts @@ -15,6 +15,8 @@ */ import { IResult, + RequiredResult, + ResultOfTestrun, StatusOfTestrun, TestrunStatus, TestsData, @@ -26,17 +28,20 @@ export const TEST_DATA_RESULT: IResult[] = [ name: 'dns.network.hostname_resolution', description: 'The device should resolve hostnames', result: 'Compliant', + required_result: RequiredResult.Required, }, { name: 'dns.network.from_dhcp', description: 'The device should use the DNS server provided by the DHCP server', result: 'Non-Compliant', + required_result: RequiredResult.Informational, }, { name: 'dns.mdns', description: 'Does the device has MDNS (or any kind of IP multicast)', result: 'Not Started', + required_result: RequiredResult.RequiredIfApplicable, }, ]; @@ -50,6 +55,7 @@ export const TEST_DATA_RESULT_WITH_RECOMMENDATIONS: IResult[] = [ 'An example of a step to resolve', 'Disable any running NTP server', ], + required_result: RequiredResult.Required, }, ]; @@ -58,17 +64,20 @@ export const TEST_DATA_RESULT_WITH_ERROR: IResult[] = [ name: 'dns.network.hostname_resolution', description: 'The device should resolve hostnames', result: 'Compliant', + required_result: RequiredResult.Required, }, { name: 'dns.network.from_dhcp', description: 'The device should use the DNS server provided by the DHCP server', result: 'Error', + required_result: RequiredResult.Required, }, { name: 'dns.mdns', description: 'Does the device has MDNS (or any kind of IP multicast)', result: 'Not Started', + required_result: RequiredResult.Required, }, ]; @@ -87,12 +96,13 @@ export const TEST_DATA: TestsData = { }; const PROGRESS_DATA_RESPONSE = ( - status: string, + status: StatusOfTestrun, finished: string | null, tests: TestsData | IResult[], - report: string = '' + report: string = '', + result?: ResultOfTestrun ) => { - return { + const response = { status, mac_addr: '01:02:03:04:05:06', device: { @@ -107,7 +117,11 @@ const PROGRESS_DATA_RESPONSE = ( tests, report, tags: ['VSA', 'Other tag', 'And one more'], - }; + } as TestrunStatus; + if (result) { + response.result = result; + } + return response; }; export const MOCK_PROGRESS_DATA_CANCELLING: TestrunStatus = @@ -118,18 +132,20 @@ export const MOCK_PROGRESS_DATA_IN_PROGRESS_EMPTY: TestrunStatus = PROGRESS_DATA_RESPONSE(StatusOfTestrun.InProgress, null, []); export const MOCK_PROGRESS_DATA_COMPLIANT: TestrunStatus = PROGRESS_DATA_RESPONSE( - StatusOfTestrun.Compliant, + StatusOfTestrun.Complete, '2023-06-22T09:20:00.123Z', TEST_DATA_RESULT, - 'https://api.testrun.io/report.pdf' + 'https://api.testrun.io/report.pdf', + ResultOfTestrun.Compliant ); export const MOCK_PROGRESS_DATA_NON_COMPLIANT: TestrunStatus = PROGRESS_DATA_RESPONSE( - StatusOfTestrun.NonCompliant, + StatusOfTestrun.Complete, '2023-06-22T09:20:00.123Z', TEST_DATA_RESULT, - 'https://api.testrun.io/report.pdf' + 'https://api.testrun.io/report.pdf', + ResultOfTestrun.NonCompliant ); export const MOCK_PROGRESS_DATA_CANCELLED: TestrunStatus = @@ -159,6 +175,12 @@ export const MOCK_PROGRESS_DATA_WAITING_FOR_DEVICE: TestrunStatus = { started: null, }; +export const MOCK_PROGRESS_DATA_STARTING: TestrunStatus = { + ...MOCK_PROGRESS_DATA_IN_PROGRESS, + status: StatusOfTestrun.Starting, + started: null, +}; + export const MOCK_PROGRESS_DATA_VALIDATING: TestrunStatus = { ...MOCK_PROGRESS_DATA_IN_PROGRESS, status: StatusOfTestrun.Validating, diff --git a/modules/ui/src/app/model/testrun-status.ts b/modules/ui/src/app/model/testrun-status.ts index f14dce652..04596d5e4 100644 --- a/modules/ui/src/app/model/testrun-status.ts +++ b/modules/ui/src/app/model/testrun-status.ts @@ -17,7 +17,8 @@ import { Device } from './device'; export interface TestrunStatus { mac_addr: string | null; - status: string; + status: StatusOfTestrun; + result?: ResultOfTestrun; description?: string; device: IDevice; started: string | null; @@ -30,6 +31,7 @@ export interface TestrunStatus { export interface HistoryTestrun extends TestrunStatus { deviceFirmware: string; deviceInfo: string; + testResult: string; program: string; duration: string; } @@ -50,6 +52,18 @@ export interface IResult { description: string; result: string; recommendations?: string[]; + required_result: RequiredResult; +} + +export enum RequiredResult { + Informational = 'Informational', + Required = 'Required', + RequiredIfApplicable = 'Required if Applicable', +} + +export enum ResultOfTestrun { + Compliant = 'Compliant', // used for Completed + NonCompliant = 'Non-Compliant', // used for Completed } export enum StatusOfTestrun { @@ -58,15 +72,17 @@ export enum StatusOfTestrun { Cancelled = 'Cancelled', Cancelling = 'Cancelling', Failed = 'Failed', - Compliant = 'Compliant', // used for Completed CompliantLimited = 'Compliant (Limited)', CompliantHigh = 'Compliant (High)', - NonCompliant = 'Non-Compliant', // used for Completed - SmartReady = 'Smart Ready', // used for Completed + SmartReady = 'Smart Ready', Idle = 'Idle', Monitoring = 'Monitoring', + Starting = 'Starting', Error = 'Error', Validating = 'Validating Network', + Complete = 'Complete', // device qualification + Proceed = 'Proceed', // pilot assessment + DoNotProceed = 'Do Not Proceed', // pilot assessment } export enum StatusOfTestResult { diff --git a/modules/ui/src/app/pages/devices/components/device-qualification-from/device-qualification-from.component.scss b/modules/ui/src/app/pages/devices/components/device-qualification-from/device-qualification-from.component.scss index c2088c67b..902986f90 100644 --- a/modules/ui/src/app/pages/devices/components/device-qualification-from/device-qualification-from.component.scss +++ b/modules/ui/src/app/pages/devices/components/device-qualification-from/device-qualification-from.component.scss @@ -22,7 +22,6 @@ $form-min-width: 732px; :host { container-type: size; container-name: qualification-form; - display: grid; grid-template-rows: 1fr; overflow: auto; diff --git a/modules/ui/src/app/pages/reports/components/filter-dialog/filter-dialog.component.ts b/modules/ui/src/app/pages/reports/components/filter-dialog/filter-dialog.component.ts index 3d8ac231a..6dd78f401 100644 --- a/modules/ui/src/app/pages/reports/components/filter-dialog/filter-dialog.component.ts +++ b/modules/ui/src/app/pages/reports/components/filter-dialog/filter-dialog.component.ts @@ -58,7 +58,10 @@ import { DateRange as LocalDateRange, } from '../../../../model/filters'; import { EscapableDialogComponent } from '../../../../components/escapable-dialog/escapable-dialog.component'; -import { StatusOfTestResult } from '../../../../model/testrun-status'; +import { + ResultOfTestrun, + StatusOfTestrun, +} from '../../../../model/testrun-status'; import { DeviceValidators } from '../../../devices/components/device-form/device.validators'; interface DialogData { @@ -97,8 +100,10 @@ export class FilterDialogComponent implements OnInit { resultList = [ - { value: StatusOfTestResult.Compliant, enabled: false }, - { value: StatusOfTestResult.NonCompliant, enabled: false }, + { value: ResultOfTestrun.Compliant, enabled: false }, + { value: ResultOfTestrun.NonCompliant, enabled: false }, + { value: StatusOfTestrun.Proceed, enabled: false }, + { value: StatusOfTestrun.DoNotProceed, enabled: false }, ]; filterForm!: FormGroup; selectedRangeValue!: DateRange | undefined; diff --git a/modules/ui/src/app/pages/reports/reports.component.html b/modules/ui/src/app/pages/reports/reports.component.html index beb538149..c187a6c27 100644 --- a/modules/ui/src/app/pages/reports/reports.component.html +++ b/modules/ui/src/app/pages/reports/reports.component.html @@ -147,9 +147,9 @@

Reports

- {{ data.status }} + {{ data.testResult }} diff --git a/modules/ui/src/app/pages/reports/reports.store.ts b/modules/ui/src/app/pages/reports/reports.store.ts index 7b13bf5e9..de0bf20c3 100644 --- a/modules/ui/src/app/pages/reports/reports.store.ts +++ b/modules/ui/src/app/pages/reports/reports.store.ts @@ -12,6 +12,7 @@ import { selectReports, selectRiskProfiles } from '../../store/selectors'; import { Store } from '@ngrx/store'; import { AppState } from '../../store/state'; import { fetchReports, setReports } from '../../store/actions'; +import { TestingType } from '../../model/device'; export interface ReportsComponentState { displayedColumns: string[]; @@ -252,12 +253,24 @@ export class ReportsStore extends ComponentStore { ...item, deviceFirmware: item.device.firmware, deviceInfo: item.device.manufacturer + ' ' + item.device.model, + testResult: this.getTestResult(item), duration: this.getDuration(item.started, item.finished), program: item.device.test_pack ?? '', }; }); } + private getTestResult(item: TestrunStatus): string { + let result = ''; + if (item.device.test_pack === TestingType.Qualification) { + result = item.result ? item.result : item.status; + } + if (item.device.test_pack === TestingType.Pilot) { + result = item.status; + } + return result; + } + private getDuration(started: string | null, finished: string | null): string { if (!started || !finished) { return ''; @@ -290,7 +303,7 @@ export class ReportsStore extends ComponentStore { const isIncludeStatus = searchString.results?.length === 0 || - searchString.results?.includes(data.status); + searchString.results?.includes(data.testResult); const isIncludeStartedDate = this.filterStartedDateRange( data.started, searchString diff --git a/modules/ui/src/app/pages/risk-assessment/profile-form/profile-form.component.spec.ts b/modules/ui/src/app/pages/risk-assessment/profile-form/profile-form.component.spec.ts index d279a1f14..12ae7457e 100644 --- a/modules/ui/src/app/pages/risk-assessment/profile-form/profile-form.component.spec.ts +++ b/modules/ui/src/app/pages/risk-assessment/profile-form/profile-form.component.spec.ts @@ -89,6 +89,9 @@ describe('ProfileFormComponent', () => { [ 'very long value very long value very long value very long value very long value very long value very long value', 'as&@3$', + 'test/', + 'test[', + ':test', ].forEach(value => { const name: HTMLInputElement = compiled.querySelector( '.form-name' diff --git a/modules/ui/src/app/pages/risk-assessment/profile-form/profile-form.component.ts b/modules/ui/src/app/pages/risk-assessment/profile-form/profile-form.component.ts index 2656221cd..cc6a18e8d 100644 --- a/modules/ui/src/app/pages/risk-assessment/profile-form/profile-form.component.ts +++ b/modules/ui/src/app/pages/risk-assessment/profile-form/profile-form.component.ts @@ -156,7 +156,7 @@ export class ProfileFormComponent implements OnInit, AfterViewInit { group['name'] = new FormControl('', [ this.profileValidators.textRequired(), - this.deviceValidators.deviceStringFormat(), + this.profileValidators.profileNameFormat(), this.nameValidator, ]); diff --git a/modules/ui/src/app/pages/risk-assessment/profile-form/profile.validators.ts b/modules/ui/src/app/pages/risk-assessment/profile-form/profile.validators.ts index 10280586d..d3847345d 100644 --- a/modules/ui/src/app/pages/risk-assessment/profile-form/profile.validators.ts +++ b/modules/ui/src/app/pages/risk-assessment/profile-form/profile.validators.ts @@ -31,6 +31,12 @@ export class ProfileValidators { readonly STRING_FORMAT_REGEXP = new RegExp('^[^"\\\\]*$', 'u'); + // Not allowed symbols: <>?/:;@'"][=^!\#$%&*+{}|() + readonly PROFILE_NAME_FORMAT_REGEXP = new RegExp( + '^([^<>?:;@\'\\\\"\\[\\]=^!/,.#$%&*+{}|()]{1,28})$', + 'u' + ); + public differentProfileName( profiles: Profile[], profile: Profile | null @@ -60,6 +66,10 @@ export class ProfileValidators { }; } + public profileNameFormat(): ValidatorFn { + return this.stringFormat(this.PROFILE_NAME_FORMAT_REGEXP); + } + public multiSelectRequired(g: FormGroup) { if (Object.values(g.value).every(value => value === false)) { return { required: true }; diff --git a/modules/ui/src/app/pages/testrun/components/download-options/download-options.component.ts b/modules/ui/src/app/pages/testrun/components/download-options/download-options.component.ts index 81b4a1fee..814584eb0 100644 --- a/modules/ui/src/app/pages/testrun/components/download-options/download-options.component.ts +++ b/modules/ui/src/app/pages/testrun/components/download-options/download-options.component.ts @@ -26,7 +26,7 @@ import { MatSelectModule } from '@angular/material/select'; import { CommonModule, DatePipe } from '@angular/common'; import { MatIconModule } from '@angular/material/icon'; import { - StatusOfTestrun, + ResultOfTestrun, TestrunStatus, } from '../../../../model/testrun-status'; import { MatOptionSelectionChange } from '@angular/material/core'; @@ -113,9 +113,9 @@ export class DownloadOptionsComponent { sendGAEvent(data: TestrunStatus, type: string) { let event = `download_report_${type === DownloadOption.PDF ? 'pdf' : 'zip'}`; - if (data.status === StatusOfTestrun.Compliant) { + if (data.result === ResultOfTestrun.Compliant) { event += '_compliant'; - } else if (data.status === StatusOfTestrun.NonCompliant) { + } else if (data.result === ResultOfTestrun.NonCompliant) { event += '_non_compliant'; } // @ts-expect-error data layer is not null diff --git a/modules/ui/src/app/pages/testrun/components/testrun-initiate-form/testrun-initiate-form.component.ts b/modules/ui/src/app/pages/testrun/components/testrun-initiate-form/testrun-initiate-form.component.ts index cbec35a0a..0fddce670 100644 --- a/modules/ui/src/app/pages/testrun/components/testrun-initiate-form/testrun-initiate-form.component.ts +++ b/modules/ui/src/app/pages/testrun/components/testrun-initiate-form/testrun-initiate-form.component.ts @@ -59,6 +59,7 @@ export class TestrunInitiateFormComponent extends EscapableDialogComponent implements OnInit, AfterViewChecked { + private startRequestSent = new BehaviorSubject(false); @ViewChild('firmwareInput') firmwareInput!: ElementRef; initiateForm!: FormGroup; devices$ = this.store.select(selectDevices); @@ -165,7 +166,8 @@ export class TestrunInitiateFormComponent } ); - if (this.selectedDevice) { + if (this.selectedDevice && !this.startRequestSent.value) { + this.startRequestSent.next(true); this.testRunService.fetchVersion(); this.testRunService .startTestrun({ @@ -174,8 +176,13 @@ export class TestrunInitiateFormComponent test_modules: testModules, }) .pipe(take(1)) - .subscribe(status => { - this.cancel(status); + .subscribe({ + next: status => { + this.cancel(status); + }, + error: () => { + this.startRequestSent.next(false); + }, }); } } diff --git a/modules/ui/src/app/pages/testrun/components/testrun-status-card/testrun-status-card.component.html b/modules/ui/src/app/pages/testrun/components/testrun-status-card/testrun-status-card.component.html index 9ca1d80b2..ca8009264 100644 --- a/modules/ui/src/app/pages/testrun/components/testrun-status-card/testrun-status-card.component.html +++ b/modules/ui/src/app/pages/testrun/components/testrun-status-card/testrun-status-card.component.html @@ -16,7 +16,8 @@

- - - - - - - - - - - - - - -
TCP ports openUDP ports openTotal ports open
000
- -
-
- No open ports detected -
\ No newline at end of file + +
+
+
+ {# Badge #} +

+ + {{ name }} +

+ {{ title }} +
+ + {{ device['manufacturer'] }} + {{ device['model']}} + + Testrun +
+
+

Services Module

+ + + + + + + + + + + + + + + +
TCP ports openUDP ports openTotal ports open
000
+
+
+ No open ports detected +
+
+ + +
+
diff --git a/testing/unit/services/reports/services_report_local.html b/testing/unit/services/reports/services_report_local.html index ce601cfc0..786a98f1d 100644 --- a/testing/unit/services/reports/services_report_local.html +++ b/testing/unit/services/reports/services_report_local.html @@ -1,48 +1,73 @@ -

Services Module

- - - - - - - - - - - - - - -
TCP ports openUDP ports openTotal ports open
303
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
PortStateServiceVersion
22/tcpopenssh8.8 protocol 2.0
443/tcpopenhttp
502/tcpopenmbap
- \ No newline at end of file + +
+
+
+ {# Badge #} +

+ + {{ name }} +

+ {{ title }} +
+ + {{ device['manufacturer'] }} + {{ device['model']}} + + Testrun +
+
+

Services Module

+ + + + + + + + + + + + + + + +
TCP ports openUDP ports openTotal ports open
303
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
PortStateServiceVersion
22/tcpopenssh8.8 protocol 2.0
443/tcpopenhttp
502/tcpopenmbap
+
+ + +
+
diff --git a/testing/unit/tls/reports/tls_report_ext_local.html b/testing/unit/tls/reports/tls_report_ext_local.html index df1ebada2..e9e3b1930 100644 --- a/testing/unit/tls/reports/tls_report_ext_local.html +++ b/testing/unit/tls/reports/tls_report_ext_local.html @@ -1,88 +1,95 @@ -

TLS Module

- - - +
+
+
+ {# Badge #} +

+ + {{ name }} +

+ {{ title }} +
+ + {{ device['manufacturer'] }} + {{ device['model']}} + + Testrun +
+
+

TLS Module

+
+ + + + + + + + + + + + + + + + + + +
ExpiryLengthTypePort numberSigned by
2027-07-25 15:33:09888EC443Sub CA
+
+
+
Certificate Information
+ + + + + + + + - - - - - + + - - - - - - - - + + - -
PropertyValue
ExpiryLengthTypePort numberSigned byVersion3 (0x2)
2027-07-25 15:33:09888EC443Sub CASignature Alg.sha256WithRSAEncryption
- -
-
-
Certificate Information
- - - - - + + - - - - - - - - - - - - - - - - - - - -
PropertyValueValidity from2022-07-26 15:33:09
Version3 (0x2)
Signature Alg.sha256WithRSAEncryption
Validity from2022-07-26 15:33:09
Valid to2027-07-25 15:33:09
- -
-
-
Subject Information
- - - - - + + - - - - - - - + +
PropertyValueValid to2027-07-25 15:33:09
CUS
+
+
+
Subject Information
+ + - - + + - + + + + + + + + + +
CNapc27D605.nam.gad.schneider-electric.comPropertyValue
CUS
CNapc27D605.nam.gad.schneider-electric.com
- - -
Certificate Extensions
+
Certificate Extensions
@@ -91,24 +98,17 @@
Certificate Extensions
- - - - - - -
subjectAltNameap9643_qa1941270129.nam.gad.schneider-electric.com
-

Outbound Connections

- - - - - - - - - - - -
Destination IPPort
- \ No newline at end of file + + subjectAltName + ap9643_qa1941270129.nam.gad.schneider-electric.com + + + +
+ + +
+
diff --git a/testing/unit/tls/reports/tls_report_local.html b/testing/unit/tls/reports/tls_report_local.html index 610381444..3100aaf69 100644 --- a/testing/unit/tls/reports/tls_report_local.html +++ b/testing/unit/tls/reports/tls_report_local.html @@ -1,103 +1,107 @@ -

TLS Module

- - - +
+
+
+ {# Badge #} +

+ + {{ name }} +

+ {{ title }} +
+ + {{ device['manufacturer'] }} + {{ device['model']}} + + Testrun +
+
+

TLS Module

+
+ + + + + + + + + + + + + + + + + + +
ExpiryLengthTypePort numberSigned by
2049-12-31 23:59:59779EC35288None
+
+
+
Certificate Information
+ + + + + + + + - - - - - + + - - - - - - - - + + - -
PropertyValue
ExpiryLengthTypePort numberSigned byVersion3 (0x2)
2049-12-31 23:59:59779EC35288NoneSignature Alg.sha256WithRSAEncryption
- -
-
-
Certificate Information
- - - - - + + - - - - - - - - - - - - - - - - - - - -
PropertyValueValidity from2023-03-29 18:37:51
Version3 (0x2)
Signature Alg.sha256WithRSAEncryption
Validity from2023-03-29 18:37:51
Valid to2049-12-31 23:59:59
- -
-
-
Subject Information
- - - - - + + - - - - - - - - - - - - - - - - - - - - - - + +
PropertyValueValid to2049-12-31 23:59:59
CUS
STPennsylvania
LCoopersburg
OLutron Electronics Co.\, Inc.
+
+
+
Subject Information
+ + - - + + - + + + + + + + + + + + + + + + + + + + + + +
CNathena04E580B9PropertyValue
CUS
STPennsylvania
LCoopersburg
OLutron Electronics Co.\, Inc.
CNathena04E580B9
- - -
Certificate Extensions
+
Certificate Extensions
@@ -106,128 +110,135 @@
Certificate Extensions
- - - - - - - - - - - - - - - - - - - - - -
authorityKeyIdentifierkey_identifier=accca4f9bd2a47dae81a8f4c87ed2c8edcfd07bf, authority_cert_issuer=None, authority_cert_serial_number=None
subjectKeyIdentifierdigest=37d90a274635e963081520f98411bda240d30252
basicConstraintsca=False, path_length=None
keyUsagedigital_signature=True, key_cert_sign=False, key_encipherment=False, crl_sign=False
- + + authorityKeyIdentifier + key_identifier=accca4f9bd2a47dae81a8f4c87ed2c8edcfd07bf, authority_cert_issuer=None, authority_cert_serial_number=None + + + subjectKeyIdentifier + digest=37d90a274635e963081520f98411bda240d30252 + + + basicConstraints + ca=False, path_length=None + + + keyUsage + digital_signature=True, key_cert_sign=False, key_encipherment=False, crl_sign=False + + + +
+ +
+
- - +
+
+
+ {# Badge #} +

+ + {{ name }} +

+ {{ title }} +
+ + {{ device['manufacturer'] }} + {{ device['model']}} + + Testrun +
+
+
+ + + + + + + + + + + + + + + + + + +
ExpiryLengthTypePort numberSigned by
2119-02-05 00:00:00619EC443AthenaProcessor685E1CCB6ECB
+
+
+
Certificate Information
+ + + + + + + + - - - - - + + - - - - - - - - + + - -
PropertyValue
ExpiryLengthTypePort numberSigned byVersion3 (0x2)
2119-02-05 00:00:00619EC443AthenaProcessor685E1CCB6ECBSignature Alg.ecdsa-with-SHA256
- -
-
-
Certificate Information
- - - - - + + - - - - - - - - - - - - - - - - - - - -
PropertyValueValidity from2019-03-01 00:00:00
Version3 (0x2)
Signature Alg.ecdsa-with-SHA256
Validity from2019-03-01 00:00:00
Valid to2119-02-05 00:00:00
- -
-
-
Subject Information
- - - - - + + - - - - - - - - - - - - - - - - - - - - - - + +
PropertyValueValid to2119-02-05 00:00:00
CUS
STPennsylvania
LCoopersburg
OLutron Electronics Co.\, Inc.
+
+
+
Subject Information
+ + - - + + - + + + + + + + + + + + + + + + + + + + + + +
CNIPLServer4E580B9PropertyValue
CUS
STPennsylvania
LCoopersburg
OLutron Electronics Co.\, Inc.
CNIPLServer4E580B9
- - -
Certificate Extensions
+
Certificate Extensions
@@ -236,128 +247,135 @@
Certificate Extensions
- - - - - - - - - - - - - - - - - - - - - -
keyUsagedigital_signature=True, key_cert_sign=False, key_encipherment=True, crl_sign=False
extendedKeyUsageserverAuth, Unknown OID
authorityKeyIdentifierkey_identifier=dff100033b0ab36497bbcd2f3e0515ea7b2f7ea0, authority_cert_issuer=None, authority_cert_serial_number=None
subjectAltNameIPLServer4E580B9
- + + keyUsage + digital_signature=True, key_cert_sign=False, key_encipherment=True, crl_sign=False + + + extendedKeyUsage + serverAuth, Unknown OID + + + authorityKeyIdentifier + key_identifier=dff100033b0ab36497bbcd2f3e0515ea7b2f7ea0, authority_cert_issuer=None, authority_cert_serial_number=None + + + subjectAltName + IPLServer4E580B9 + + + +
+ +
+
- - +
+
+
+ {# Badge #} +

+ + {{ name }} +

+ {{ title }} +
+ + {{ device['manufacturer'] }} + {{ device['model']}} + + Testrun +
+
+
+ + + + + + + + + + + + + + + + + + +
ExpiryLengthTypePort numberSigned by
2049-12-31 23:59:59779EC47188None
+
+
+
Certificate Information
+ + + + + + + + - - - - - + + - - - - - - - - + + - -
PropertyValue
ExpiryLengthTypePort numberSigned byVersion3 (0x2)
2049-12-31 23:59:59779EC47188NoneSignature Alg.sha256WithRSAEncryption
- -
-
-
Certificate Information
- - - - - + + - - - - - - - - - - - - - - - - - - - -
PropertyValueValidity from2023-03-29 18:37:51
Version3 (0x2)
Signature Alg.sha256WithRSAEncryption
Validity from2023-03-29 18:37:51
Valid to2049-12-31 23:59:59
- -
-
-
Subject Information
- - - - - + + - - - - - - - - - - - - - - - - - - - - - - + +
PropertyValueValid to2049-12-31 23:59:59
CUS
STPennsylvania
LCoopersburg
OLutron Electronics Co.\, Inc.
+
+
+
Subject Information
+ + - - + + - + + + + + + + + + + + + + + + + + + + + + +
CNathena04E580B9PropertyValue
CUS
STPennsylvania
LCoopersburg
OLutron Electronics Co.\, Inc.
CNathena04E580B9
- - -
Certificate Extensions
+
Certificate Extensions
@@ -366,45 +384,94 @@
Certificate Extensions
- - - - - - - - - - - - - - - - - - - - - -
authorityKeyIdentifierkey_identifier=accca4f9bd2a47dae81a8f4c87ed2c8edcfd07bf, authority_cert_issuer=None, authority_cert_serial_number=None
subjectKeyIdentifierdigest=37d90a274635e963081520f98411bda240d30252
basicConstraintsca=False, path_length=None
keyUsagedigital_signature=True, key_cert_sign=False, key_encipherment=False, crl_sign=False
-

Outbound Connections

+ + authorityKeyIdentifier + key_identifier=accca4f9bd2a47dae81a8f4c87ed2c8edcfd07bf, authority_cert_issuer=None, authority_cert_serial_number=None + + + subjectKeyIdentifier + digest=37d90a274635e963081520f98411bda240d30252 + + + basicConstraints + ca=False, path_length=None + + + keyUsage + digital_signature=True, key_cert_sign=False, key_encipherment=False, crl_sign=False + + + +
+ + +
+
+ +
+
+
+ {# Badge #} +

+ + {{ name }} +

+ {{ title }} +
+ + {{ device['manufacturer'] }} + {{ device['model']}} + + Testrun +
+
+

Outbound Connections

- - + + - - - - - - - - - - -
Destination IPPortDestination IPPort
224.0.0.2515353
209.244.0.3Unknown
3.227.250.136443
3.227.203.88443
34.226.101.2528883
3.227.250.208443
52.94.225.110443
- \ No newline at end of file + + + 224.0.0.251 + 5353 + + + 209.244.0.3 + Unknown + + + 3.227.250.136 + 443 + + + 3.227.203.88 + 443 + + + 34.226.101.252 + 8883 + + + 3.227.250.208 + 443 + + + 52.94.225.110 + 443 + + + +
+ + +
+
diff --git a/testing/unit/tls/reports/tls_report_no_cert_local.html b/testing/unit/tls/reports/tls_report_no_cert_local.html index c025ee9e8..c641abbf0 100644 --- a/testing/unit/tls/reports/tls_report_no_cert_local.html +++ b/testing/unit/tls/reports/tls_report_no_cert_local.html @@ -1,5 +1,31 @@ -

TLS Module

-
-
- No TLS certificates found on the device -
\ No newline at end of file + +
+
+
+ {# Badge #} +

+ + {{ name }} +

+ {{ title }} +
+ + {{ device['manufacturer'] }} + {{ device['model']}} + + Testrun +
+
+

TLS Module

+
+
+ No TLS certificates found on the device +
+
+ + +
+
diff --git a/testing/unit/tls/reports/tls_report_single.html b/testing/unit/tls/reports/tls_report_single.html index 6106068a6..4fb2be16d 100644 --- a/testing/unit/tls/reports/tls_report_single.html +++ b/testing/unit/tls/reports/tls_report_single.html @@ -1,118 +1,115 @@ -

TLS Module

- - - +
+
+
+ {# Badge #} +

+ + {{ name }} +

+ {{ title }} +
+ + {{ device['manufacturer'] }} + {{ device['model']}} + + Testrun +
+
+

TLS Module

+
+ + + + + + + + + + + + + + + + + + +
ExpiryLengthTypePort numberSigned by
2027-09-21 19:57:57901RSA443BuildingsIoT RSA Signing CA
+
+
+
Certificate Information
+ + + + + + + + - - - - - + + - - - - - - - - + + - -
PropertyValue
ExpiryLengthTypePort numberSigned byVersion1 (0x0)
2027-09-21 19:57:57901RSA443BuildingsIoT RSA Signing CASignature Alg.sha256WithRSAEncryption
- -
-
-
Certificate Information
- - - - - + + - - - - - - - - - - - - - - - - - - - -
PropertyValueValidity from2022-09-21 19:57:57
Version1 (0x0)
Signature Alg.sha256WithRSAEncryption
Validity from2022-09-21 19:57:57
Valid to2027-09-21 19:57:57
- -
-
-
Subject Information
- - - - - + + - - - - - - - - - - - - - - - - - - - - - - - - - - - + +
PropertyValueValid to2027-09-21 19:57:57
CUS
STCalifornia
LConcord
OBuildingsIoT
OUSoftware
+
+
+
Subject Information
+ + - - + + - + + + + + + + + + + + + + + + + + + + + + + + + + +
CNEasyIO_FS-32PropertyValue
CUS
STCalifornia
LConcord
OBuildingsIoT
OUSoftware
CNEasyIO_FS-32
+
- -

Outbound Connections

- - - - - - - - - - - -
Destination IPPort
- \ No newline at end of file + +
+
diff --git a/testing/unit/tls/tls_module_test.py b/testing/unit/tls/tls_module_test.py index f42c7e9d4..205280329 100644 --- a/testing/unit/tls/tls_module_test.py +++ b/testing/unit/tls/tls_module_test.py @@ -71,7 +71,7 @@ def security_tls_v1_2_server_test(self): tls_version='1.2') tls_1_3_results = None, 'No TLS 1.3' test_results = TLS_UTIL.process_tls_server_results(tls_1_2_results, - tls_1_3_results) + tls_1_3_results,port=443) self.assertTrue(test_results[0]) # Test 1.2 server when 1.3 connection is established @@ -80,7 +80,7 @@ def security_tls_v1_2_for_1_3_server_test(self): tls_1_3_results = TLS_UTIL.validate_tls_server('google.com', tls_version='1.3') test_results = TLS_UTIL.process_tls_server_results(tls_1_2_results, - tls_1_3_results) + tls_1_3_results,port=443) self.assertTrue(test_results[0]) # Test 1.2 server when 1.2 and 1.3 connection is established @@ -90,7 +90,7 @@ def security_tls_v1_2_for_1_2_and_1_3_server_test(self): tls_1_3_results = TLS_UTIL.validate_tls_server('google.com', tls_version='1.3') test_results = TLS_UTIL.process_tls_server_results(tls_1_2_results, - tls_1_3_results) + tls_1_3_results,port=443) self.assertTrue(test_results[0]) # Test 1.2 server when 1.2 and failed 1.3 connection is established @@ -99,7 +99,7 @@ def security_tls_v1_2_for_1_2_and_1_3_fail_server_test(self): tls_version='1.2') tls_1_3_results = False, 'Signature faild' test_results = TLS_UTIL.process_tls_server_results(tls_1_2_results, - tls_1_3_results) + tls_1_3_results,port=443) self.assertTrue(test_results[0]) # Test 1.2 server when 1.3 and failed 1.2 connection is established @@ -108,10 +108,10 @@ def security_tls_v1_2_for_1_3_and_1_2_fail_server_test(self): tls_version='1.3') tls_1_2_results = False, 'Signature faild' test_results = TLS_UTIL.process_tls_server_results(tls_1_2_results, - tls_1_3_results) + tls_1_3_results,port=443) self.assertTrue(test_results[0]) - def security_tls_server_results_test(self, ): + def security_tls_server_results_test(self): # Generic messages to test they are passing through # to the results as expected fail_message = 'Certificate not validated' @@ -121,74 +121,75 @@ def security_tls_server_results_test(self, ): # Both None tls_1_2_results = None, none_message tls_1_3_results = None, none_message - expected = None, (f'TLS 1.2 not validated: {none_message}\n' - f'TLS 1.3 not validated: {none_message}') + expected = None, (f'TLS 1.2 not validated on port 443: {none_message}\n' + f'TLS 1.3 not validated on port 443: {none_message}') result = TLS_UTIL.process_tls_server_results(tls_1_2_results, - tls_1_3_results) + tls_1_3_results,port=443) self.assertEqual(result, expected) # TLS 1.2 Pass and TLS 1.3 None tls_1_2_results = True, success_message - expected = True, f'TLS 1.2 validated: {success_message}' + expected = True, f'TLS 1.2 validated on port 443: {success_message}' result = TLS_UTIL.process_tls_server_results(tls_1_2_results, - tls_1_3_results) + tls_1_3_results,port=443) self.assertEqual(result, expected) # TLS 1.2 Fail and TLS 1.3 None tls_1_2_results = False, fail_message - expected = False, f'TLS 1.2 not validated: {fail_message}' + expected = False, f'TLS 1.2 not validated on port 443: {fail_message}' result = TLS_UTIL.process_tls_server_results(tls_1_2_results, - tls_1_3_results) + tls_1_3_results,port=443) self.assertEqual(result, expected) # TLS 1.3 Pass and TLS 1.2 None tls_1_2_results = None, fail_message tls_1_3_results = True, success_message - expected = True, f'TLS 1.3 validated: {success_message}' + expected = True, f'TLS 1.3 validated on port 443: {success_message}' result = TLS_UTIL.process_tls_server_results(tls_1_2_results, - tls_1_3_results) + tls_1_3_results,port=443) self.assertEqual(result, expected) # TLS 1.3 Fail and TLS 1.2 None tls_1_3_results = False, fail_message - expected = False, f'TLS 1.3 not validated: {fail_message}' + expected = False, f'TLS 1.3 not validated on port 443: {fail_message}' result = TLS_UTIL.process_tls_server_results(tls_1_2_results, - tls_1_3_results) + tls_1_3_results,port=443) self.assertEqual(result, expected) # TLS 1.2 Pass and TLS 1.3 Pass tls_1_2_results = True, success_message tls_1_3_results = True, success_message - expected = True, (f'TLS 1.2 validated: {success_message}\n' - f'TLS 1.3 validated: {success_message}') + expected = True, (f'TLS 1.2 validated on port 443: {success_message}\n' + f'TLS 1.3 validated on port 443: {success_message}') result = TLS_UTIL.process_tls_server_results(tls_1_2_results, - tls_1_3_results) + tls_1_3_results,port=443) + self.assertEqual(result, expected) # TLS 1.2 Pass and TLS 1.3 Fail tls_1_2_results = True, success_message tls_1_3_results = False, fail_message - expected = True, (f'TLS 1.2 validated: {success_message}\n' - f'TLS 1.3 not validated: {fail_message}') + expected = True, (f'TLS 1.2 validated on port 443: {success_message}\n' + f'TLS 1.3 not validated on port 443: {fail_message}') result = TLS_UTIL.process_tls_server_results(tls_1_2_results, - tls_1_3_results) + tls_1_3_results,port=443) self.assertEqual(result, expected) # TLS 1.2 Fail and TLS 1.2 Pass tls_1_2_results = False, fail_message tls_1_3_results = True, success_message - expected = True, (f'TLS 1.2 not validated: {fail_message}\n' - f'TLS 1.3 validated: {success_message}') + expected = True, (f'TLS 1.2 not validated on port 443: {fail_message}\n' + f'TLS 1.3 validated on port 443: {success_message}') result = TLS_UTIL.process_tls_server_results(tls_1_2_results, - tls_1_3_results) + tls_1_3_results,port=443) self.assertEqual(result, expected) # TLS 1.2 Fail and TLS 1.2 Fail tls_1_3_results = False, fail_message - expected = False, (f'TLS 1.2 not validated: {fail_message}\n' - f'TLS 1.3 not validated: {fail_message}') + expected = False, (f'TLS 1.2 not validated on port 443: {fail_message}\n' + f'TLS 1.3 not validated on port 443: {fail_message}') result = TLS_UTIL.process_tls_server_results(tls_1_2_results, - tls_1_3_results) + tls_1_3_results,port=443) self.assertEqual(result, expected) # Test 1.2 server when 1.3 and 1.2 failed connection is established @@ -196,7 +197,7 @@ def security_tls_v1_2_fail_server_test(self): tls_1_2_results = False, 'Signature faild' tls_1_3_results = False, 'Signature faild' test_results = TLS_UTIL.process_tls_server_results(tls_1_2_results, - tls_1_3_results) + tls_1_3_results,port=443) self.assertFalse(test_results[0]) # Test 1.2 server when 1.3 and 1.2 failed connection is established @@ -205,7 +206,7 @@ def security_tls_v1_2_none_server_test(self): tls_1_2_results = None, 'No cert' tls_1_3_results = None, 'No cert' test_results = TLS_UTIL.process_tls_server_results(tls_1_2_results, - tls_1_3_results) + tls_1_3_results,port=443) self.assertIsNone(test_results[0]) def security_tls_v1_3_server_test(self): @@ -608,7 +609,7 @@ def download_public_cert(self, hostname, port=443): suite.addTest(TLSModuleTest('security_tls_v1_2_fail_server_test')) suite.addTest(TLSModuleTest('security_tls_v1_2_none_server_test')) - # # TLS 1.3 server tests + # TLS 1.3 server tests suite.addTest(TLSModuleTest('security_tls_v1_3_server_test')) # TLS client tests From 6d11ca7abff34ae4c6642eb47fc49f7d63fb3d1b Mon Sep 17 00:00:00 2001 From: Sofia Kurilova Date: Tue, 29 Apr 2025 15:51:37 +0000 Subject: [PATCH 6/9] Merge release v2.2 into main MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * No update from BE of test status results during testrun (#761) * Fix host; fix status update * Fix css property name * Fix text for High risk profile save window (#762) * Focus selected item on save (#754) * Focus first interactive element on redirect to risk assessment (#763) * Adds dynamic top property to callouts (#765) * Added test for 'system/testpacks' endpoint, fixed typing error in api.py (#767) * 362959152: (feat) add testing complete modal (#770) * Listen callout resize event, not window resize event (#769) * Add dhcp disconnect tests to qual test pack (#771) Add test descriptions to test results * Move bottom callouts to top when callout is destroyed; fix css selector (#772) * Filter devices to open device dialog if all devices are outdated (#774) * Rename column "Program" to "Assessment type" (#775) * Hide "Create" text in edit mode (#773) * Prevent default event (#777) * Show success dialog only for valid risk profile (#779) * Filter Not Started tests from test count (#778) * Change version label (#780) * Display tags for Testrun progress page (#781) * Add tags on test run page * 365728028: (feat) improve callouts (#782) * 367673910: (fix) prevent download PDF report button focused twice on completed testing modal (#785) * Fix tags (#786) * Console errors on starting the test attempt (#784) * Fix error "cannot read property of null" in console --------- Signed-off-by: Sofia Kurilova * 366014294: (feat) update callouts logic (#787) * Destroy "waiting for device" snackbar when status changed (#788) * Mark dirty only multi select control when tab navigation (#789) * Adds focus flow after closing download zip/testing complete dialog (#794) * Remove tooltip from link (#793) * Adds aria label; code refactoring (#790) * Add mac address filter for the device interface to single ip test (#791) * Add mac address filter for the device interface to single ip test * pylint --------- Co-authored-by: J Boddey * Do not show "Save changes" dialog when there is no changes in profile (#799) * Risk profile completed modal: the redundant information is communicated by Screen reader (#800) * Clear announcement message on form close * Adds timeout to allow screen reader to detect changes and pronounce the changes on page * Adds button action on space click * Adds focus trap * Fix tests * 369106935: (fix) [a11y] make focused element announced (#807) * Focus with delay to allow screen reader detect changes * Focus risk assessment link when appears * 369314564: (fix) set focus after callout dismissed (#812) * 369312866: (fix) set focus after welcome modal is closed (#813) * Fix profiles page style to fit the screen * Improve style for buttons * Improve style for buttons * 358071738: (feat) prevent listening mgtt internet connect event in single port mode (#815) * remove singl_intf parameter from start_ui * single_intf config parameter * added test_get_devices_format (#809) Co-authored-by: J Boddey * Fix condition to show dialog on name change * Fix tests * 365978129: (feat) add PDF report templates (#801) * 365978129: (feat) add PDF report templates * 365978129: (feat) keep the current report as is and create new one with updates --------- Co-authored-by: J Boddey * 369538601: (fix) set focus to first interactive elem after stop test attempt (#818) * Remove steps to resolve from information tests (#817) * 369327155: (fix) set focus to selected risk profile if no changes on edit (#819) * Prevent default event * Adds simple dialog announcement * Fix tests * Return focus on previous active element * remove vsa tag (#823) * Fix baseline tests (#821) * Active delete button on space and enter * 369779748: (fix) add field name to validation rule for announce by Screen Reader (#826) * Add delay and fix dialog open configuration * Fix tests * 367979183: (fix) announce the result and description on completed test modal (#829) * Add optional recommendations for informational tests on pilot devices (#824) * Informational tests now produce optional recommendations * Remove debugging lines * select row on enter and space * 369542969: (fix) announce completed test modal (#832) * fixed _ load_version (#828) * Feature/tls 1.0 (#783) * Add TLS 1.0 client test * Update tls 1.0 test to account for higher versions Add tls 1.0 and 1.1 versions to binaries * pylint * Fix final result calculation * pylint * Prevent duplicate tags * Fix feature not detected result * fix device disconnected whilst monitoring (#830) * 369538601: (fix) focus first interactive elem on start or stop testrun (#837) * Adds deactivate guard for devices page * Fix audit * 369788198: (fix) validation message alignment on create device (#842) * 370729271: (fix) remove asterisks on the questions in the device flow (#843) * framework/unit_tests (#841) * Break unit tests into their own steps Update cert chain to use upgraded packages Download google public cert every time to prevent expiration * pylint * security updates * Add risk profile tests Disable report tests * Handle failed unit test result * update exit code handling * Handle failed test modules * pylint * update unit test risk profiles * 370711565: (fix) changes for informational test result (#844) * 370711565: (fix) changes for informational test result * Update informational color in report --------- Co-authored-by: Jacob Boddey * Add role and aria-live for screen reader * 370923173: (fix) remove close button from the certificates panel (#847) * Change text in risk dialog * Revised device qualification PDF report and Pilot PDF report. The report styles will be reviewed and corrected separately. (#840) * 365978129: (feat) add PDF report templates * 365978129: (feat) keep the current report as is and create new one with updates * badges * device profile * device profile page * steps to resolve pilot * steps to resolve pilot placeholder * header macros * device profile macros header * rename header macros * pilot optional steps to resolve * remove unused files * pylint * html reports examples * Switch test modules base image to pytho:3.10-slim image (#833) Increase NTP module timeout * Change description for NTP test failure (#835) * 370012527: (feat) update PDF reports styles (#850) * 370012527: (feat) update PDF reports styles * 370012527: (feat) update PDF reports styles - update * remove unused prints * delete html examples --------- Co-authored-by: Aliaksandr Nikitsin * Update docs (#839) * Update docs * Update links and icons * Add header images * Update docs * Update links * Perform some refactoring (#846) * Perform some refactoring * Remove print line * Don't load template network module * Refactoring * Re-enable tests * Fix report_test.py tests (#848) * Break unit tests into their own steps Update cert chain to use upgraded packages Download google public cert every time to prevent expiration * pylint * security updates * Add risk profile tests Disable report tests * Handle failed unit test result * update exit code handling * Handle failed test modules * pylint * update unit test risk profiles * changed testing.yml * added the jinja formatting to html file for modules * fix pylint * updated testing.yml * use regex to extract the head from existing html file * removed double curly braces from body * create artifact with html reports * 369538601: (fix) set focus to first interactive elem after stop test attempt (#852) * 371506031: (fix) make the welcome modal be on top completed test modal (#853) * 366151318: (fix) add communicate steps on create device (#851) * 370941517: (fix) return focus to initial btn after cancel download zip (#856) * 371177676: (fix) set focus to the initial device if cancel on edit device (#858) * 371525395: (fix) exclude error status from count of completed test result (#863) * 371948760: (fix) set focus to first interactive elem after start testrun from device (#864) * feature/dns_module_resolved_ips (#862) * Add resolved ip addresses from dns queries to dns module report * update dns report * stop monitoring when test run is cancelled (#855) * session/remove_invalid_questions (#834) * updated _load_profiles to remove all unrecognised risk profiles questions * added the method '_remove_invalid_questions' to remove unrecognised profile questions * API tests (#766) * updated delete device endpoint to respond 404 if report is not found * fix test_delete_report_no_report * fixed failing tests * fixed json formatting * updated the profile endpoints * updates * fixed device json * 362646053: (feat) add program icons and change borders in tiles (#745) * Adds dialog if profile is successfully saved (#746) * Adds dialog if profile is successfully saved * 364545807: (fix) changes to prevent the second callout is hidden (#748) * Change dialog height on resize (#749) * When canceling deletion, Create device modal opens (#751) * Open Edit device window when delete device is cancelled * 364828484: (fix) [GAR 1.1] change program icon aria-label (#752) * fixed device json * fixed failing tests * fixed errors * fixed device json files * updates on the code * updates on devices tests * Update testing.yml Signed-off-by: Marius <86727846+MariusBaldovin@users.noreply.github.com> * added comments to testrun fixture * fixed pylint * reverted testing.yml back to original state * updated profiles directory, small updates on code * updates * 371525174: (fix) update ui on PDF report (#866) * 371525174: (fix) update ui on PDF report * 371525174: (fix) update steps to resolve on PDF report * Fix navigation structure in PDF report (#867) * Fix navigation structure in PDF report * Fix navigation --------- Signed-off-by: J Boddey * copy a device profile questionnaire to risk profile * device risk to risk_profile risk * page counter * bold table headers * test report duration format * 372610099: (fix) update the text for Risk assessment complete modal (#873) * fix sensitive info risk * Remove global app component store * 372804973: (fix) make risk profile names not case sensitive (#882) * 370942396: (fix) change create device errors and program styles for improve view in the helperbird (#874) Co-authored-by: J Boddey * Adds action value instead of null, undefined, string; move common functionality in component * 369806347: (fix) improve unit tests (#887) * Send event to GA when test run is completed for Pilot program * 373617780: (fix) change to allow MAC address error text move on to next line (#889) * Answers per page calculation refactor (#884) * Send event to GA when Pilot zip is downloaded * Fix tests * 373855404: (fix) allow to change case of letters for existing profile name (#895) Co-authored-by: Sofia Kurilova * Device endpoints (#872) * Profiles endpoint (#870) * added add_profile fixture * updated profiles endpoints * comment update * fixed failing test * Reports endpoint (#868) * added get_timestamp to extract the 'started' field value to sue it as timestamp, updated create_report_folder * updated test_delete_report * Certificates endpoint (#861) * updated certs endpoint to copy the certs locally rather than using the API * test name update * update * updated the comments * changed the certs fixture and use parametrize to pass the cert * removed duplicate comment * Remove v from firmware version in UI (#897) * 372768360: (feat) [Settings] only device port is enabled in no internet mode (#899) * Change error message * Fix tests * Update statuses.md (#894) * Add release 2.0 fixes to dev (#879) * Fix spelling mistake in report (#902) * Hide complete dialog if test run is missing in reports * 374055268: (fix) prevent overwrite copied profile (#909) * 374055268: (fix) prevent overwrite copied profile * 374055268: (fix) prevent overwrite copied profile * Update dependencies (#903) * Update dependencies * Pin ws server --------- Co-authored-by: jhughesbiot * Merge hotfix into dev (#911) * The link provided in the risk profile answers is not clickable (#908) * Adds clickable link in risk assessment questionnaire * Fix options in dropdown * Catch invalid device config nicely (#912) * Check if dhcp-1.pcap exists (#913) Co-authored-by: jhughesbiot * Skip first default values to display error correctly * updated package.yml to start testrun and check if successfully started (#907) * Add better error handling to port stat tests (#914) * Add extra handling for port tests * Add error checking for port tests * Normalize return value behavior --------- Co-authored-by: jhughesbiot * 374258745: (fix) [PDF report] wrap long words into the next line to prevent cutting content (#917) * Adds separate method for value * 375367968: (fix) [Setting panel] remove callout about two ports warning (#919) * 374084009: Fix error on restoring DHCP server configuration if device has no DHCP lease (#896) * Fix error with DHCP server restore * Remove log dir for test modules * Change get leases method * Revert removal of error print * updated readme.md (#926) * Upload cert - error handling (#924) * Feature/cli no UI (#916) * Add target and firmware args for no-ui option * pylint fixes * Add network only filter * Add new vars to baseline test * Cleanup arg error message * Update bad target error messaging * converted bin/testrun back to unix format (#928) * Connection/README.md (#937) * updated Readme.md for connection module * removed spaces * updated * Disable MQTT log (#930) * corrected mistake on vnc (#940) * remove dublicate tests from the TLS test module (#942) * tls/README.md (#941) * updated tls/README.md * Update README.md * updated protocol/README.md (#939) * update readme.mf for dns module (#938) * updated postman file (#865) * Scapy update to 2.6.0 (#943) * added procps package in base container (#945) * remove the mqtt debug line (#944) * Unit tests (#946) * Fix security.services.vnc test (#921) * Change test in Delete Report Dialog * Fix style * added error handling if 200~dhcp-1.pcap is empty (#949) * Add communication network type test (#950) * Add communication network type test * fix type-o --------- Co-authored-by: J Boddey * Adds tooltip for title * Fix is based on console error * Feature/sys test timeout (#951) * Add option to override test module timeouts in system config * Add docs for new feature * pylint --------- Co-authored-by: J Boddey * 374289627: (fix) change the report url from static to dynamic (#954) * Pin python requirements (#764) * Semi-auto pin and update of python requirements in test modules * Semi-auto pin and update of python requirements in network modules * Convert line endings * Comment out binary used to generate pinned versions * Update requirements in feature request (#958) * Update requirements and run command * Update requirements again * typo error (#961) * Conn/module_config typo errors (#959) * fix typo error * fix more typo error --------- Co-authored-by: jhughesbiot * conn/ipv6_slaac (#962) * if dhcp-1.pcap is empty or not found it should check monitor and startup pcap files for ipv6 address * update --------- Co-authored-by: jhughesbiot * Fix tls error result (#963) * Add BACnet test and extend port scan (#957) * Add BACnet test and extend port scan * Add bacnet test to module * Change log to info --------- Co-authored-by: jhughesbiot * Error handling for connection module (#960) * added error handling if GRPC server can't start due to port 5001 is blocked by UFW firewall * fixed typo error * update * removed firewall info --------- Co-authored-by: jhughesbiot * Adds coverage check * Adds coverage check * Adds coverage check * Fix floating test issue * Fix permissions of resources folder during install (#974) * Only run tests if they are included in the device's test pack (#956) * Check if test is in test pack * First commit * Fix disabling tests * Pass test pack with env var for unit tests * changed the logic to display the description when test is non compliant (#983) * fix chrony install error (#979) Co-authored-by: jhughesbiot * Error handling when lease is None (#981) * added try catch if lese is None type * added spaces * Bacnet traceback error (#989) * added conditionn to check if validate_bacnet_source * added conditionn to check if validate_bacnet_source * remove empty space * Feature/tls module report (#871) * Initialize listener every time (#990) * Merge release changes into dev (#1030) * Update Crypto and Jinja libraries (#1038) * Added debug message in case users enters an incorrect device mac address (#1044) * added debug message in case users enters an incorrect device mac address * changed from debug to info * Allow NTPv3 packets (#1048) * Re-render device profile (#1050) * Handling error if profile name contains special characters (#1043) * 389661817: (feat) change profile name validation (#1057) * Add column; change pilot text; change order of columns * 389657377: (feat) add status Starting to UI (#1062) * Add full port scan to determine any HTTPS/TLS server running on non-s… (#1055) * Remove 'v' from testing complete modal (#1063) * 384421113: Add starting status (#1032) * Pin download artifact actions (#1065) * Adds statuses for Testing complete modal * Profile export endpoint (#1052) * Fix protocol.services.bacnet (#1082) * added protocol.services.bacnet in test_packs * changed services bacnet to Informational * tr-ws/tr-ui containers (#1089) * made wifi interface available * added error handling if the ui or ws containers are started without auto-remove flag * corrections * corrections * Generating module reports using Jinja2 (#1073) * Update dependencies; increase budget * Add a disclaimer about tests affected by single interface mode (#1056) * Update TLS client description (#1097) * Set test description for module timeout (#1098) * Fix services resolving wrong ip (#1096) * 384032243: Rename test result to pilot recommendation (#1031) * fixed reports unittest (#1110) * prevent mqtt sending messages when test is not running * Fix bacnet version for devices that do not report IP address in their discovery response (#1139) * added logger if no bacnet packets are found for the object_id (#1152) * Merge release changes into dev (#1154) * fix pilot report steps to resolve * fix presence of empty steps to resolve * fix no type attribute error (#1164) * Add the "export" field to the MQTT message * "export" field to the /reports api endpoint * fix API unit tests * handle the dns.pcap missing (#1166) * DNS module bug (#1165) * fix no type attribute error * Added more dns unittests and fixed the tcpdum command in dns_module * renamed test * fix pylint --------- Co-authored-by: J Boddey * fix the duration in pilot and qualification (#1167) Co-authored-by: J Boddey * Use field "export" for zip download, or make url from "report" field if "export" is missing * Change host for "export" field * Fix test * Fix spelling of occurred (#1170) * Catch OS error in TLS server protocol test (#1171) * Increase timeout value for /export request * Use base64 icons instead of font for proper rendering without internet connection (#1184) * Fix html to not display font icons * Update DNS from DHCP test description (#1192) * Update DNS test description * Fix pylint issue * Update Jinja2 version * fix unit test --------- Co-authored-by: MariusBaldovin * fix key service not found error (#1194) * Test tests (#1177) * changed test_tests, created 2 images one for compliant on efor non compliant * updates * updates * changes * added all modules * fixes * updated package.yml to start testrun after the package has been installed * fixed test_tests workflow error * updated package.yml * updated package.yml * updates * changed package.yml in separate jobs start and verify if started * removed comments * updated package.yml * enable onlye services for testr1 and tester2 * updated package.yml * reverted package.yml to its original state * added comments * updated comments * removed push from testing.yml * updates on docker images * updated testing.yml * added tests for DNS Module * updates * Updated the scrip for starting testrun with no ui Signed-off-by: Marius <86727846+MariusBaldovin@users.noreply.github.com> * Merge release changes into dev (#1030) * Update Crypto and Jinja libraries (#1038) * Added debug message in case users enters an incorrect device mac address (#1044) * added debug message in case users enters an incorrect device mac address * changed from debug to info * Allow NTPv3 packets (#1048) * Re-render device profile (#1050) * Handling error if profile name contains special characters (#1043) * 389661817: (feat) change profile name validation (#1057) * Add column; change pilot text; change order of columns * 389657377: (feat) add status Starting to UI (#1062) * Add full port scan to determine any HTTPS/TLS server running on non-s… (#1055) * Remove 'v' from testing complete modal (#1063) * 384421113: Add starting status (#1032) * Pin download artifact actions (#1065) * Adds statuses for Testing complete modal * Profile export endpoint (#1052) * Fix protocol.services.bacnet (#1082) * added protocol.services.bacnet in test_packs * changed services bacnet to Informational * tr-ws/tr-ui containers (#1089) * made wifi interface available * added error handling if the ui or ws containers are started without auto-remove flag * corrections * corrections * Generating module reports using Jinja2 (#1073) * Update dependencies; increase budget * Add a disclaimer about tests affected by single interface mode (#1056) * Update TLS client description (#1097) * Set test description for module timeout (#1098) * Fix services resolving wrong ip (#1096) * 384032243: Rename test result to pilot recommendation (#1031) * fixed reports unittest (#1110) * prevent mqtt sending messages when test is not running * added avirtual bacnet device * changes * changes * updates * remove logger * each test module will have its separate container * fix typo error * fix maxc address for services non compliant * save the tets tests reports into github * test1 * test2 * test3 * test4 * test5 * test6 * test7 * test8 * test9 * test10 * test11 * remove on push from workflows * renamed the files saved on github actions * fix upload test reports job * grant write permission to local/devices * remove the upload job * added for loop to build the test containers * fix for loop --------- Signed-off-by: Marius <86727846+MariusBaldovin@users.noreply.github.com> Signed-off-by: J Boddey Co-authored-by: jhughesbiot Co-authored-by: J Boddey Co-authored-by: Olga Mardvilko Co-authored-by: kurilova Co-authored-by: Aleksander * Resolve and use device address for reads (#1215) * Fix missing bracket (#1217) * Removed Ubuntu 20.04 runner (#1243) * remove ubuntu 20.04 runner * remove 20.04 support from documentation * bug/ethtool (#1242) * Add ifconfig option for resolving port stats Add unit tests * pylint fixes * pylint fixes * fix unit testing action --------- Co-authored-by: J Boddey * feature/allow_monitor_disconnect (#1249) * Add option to allow devices to disconnect during monitor period * update example config * Merge google material 3 changes into dev * Migrate to material 3 * Adds copyright * Apply new styles for reports table * Fix color * 378065444: (feat) apply gm3 for header (#965) * Increase budget * Adds color palette * Fix lint issues * Make palette private * Remove unused import * Fix sorting icon on hover * Adds aria-label and hover styles * Change import from 'src/theming/...' to '...' * Fix linter issues * Fix imports * Fix tests * 378065443: (feat) add gm3 for nav bar (#971) * 378065443: (feat) add gm3 for nav bar * 378065443: (feat) add gm3 for nav bar * 379246727: (fix) changes for gm3 header (#973) * 379246727: (fix) change certificate button to filled view (#980) * Adds style for focused logo * Adds style for better spacing between elements * Fix focus style; adds focus after redirect * Update version * Update version * Fix tests * Adds style for sorting; adds filter header component * 380254684: (feat) apply gm3 for filtering report (#996) * Migrate code to new angular features: signals and injection * Change budget * 382457675: (fix) changes for calendar filter issues (#1002) * Fixed issues: Buttons order Focus border Sorting button color Default sorting indicator * Fix tests * 382461850: (fix) changes for result filter (#1004) * Use signal store for certificates * Fix logic * Update dependencies * Fix error * Adds styles for snackbar * Fix liner issues * 382095919: (feat) add settings basic page * Adds Certificates page * Change empty state for reports page * 382095919: (feat) add general settings page (#1018) * Adds style for callouts; update component with signals; adds action in callout * Remove comment in file * Change style for delete certificate dialog * Fix tests * Change empty state for Devices page * Adds Copyright * 380231252: (fix) save button becomes active when any setting changes (#1029) * 382109519: (feat) apply gm3 for welcome modal (#1033) * Adds layout with devices list; adds state with no device selected * 385323565: (feat) disable general settings when testing in progress (#1035) * 385715473: (feat) add TLS callout to certificates tab (#1036) * Request settings when app is opened * Change border color for warning callout; change icon for error callouts * 386919467: (fix) make internet port disable in single mode after enable form (#1040) * 387242682: (fix) make save button available on the settings page with callouts (#1042) * 387242169: (fix) change availability of certificate page (#1045) * 387242169: (fix) change scroll on settings (#1049) * 387242169: (fix) change scroll on settings (#1049) * Device form (#1046) * BE: Changes format for devices/format FE: Form has no pages; form opens on button click. Device actions is out of scope. * Fix tests * Fix tests * 387461943: (fix) remove double scroll on device page (#1051) * 385169773: (feat) add devices input form (#1054) * Reset form values for new device * Reset form to default values * Fix test * Disable 3 dots menu for device under test; adds status for device under test * Disable save button when devices has no changes or invalid; disable cancel button when device has no changes; disable delete button for new device * Adds dialog when switching the device and there are unsaved changes * Adds tests * Change dialog width * 388235982: (fix) setting and callout fixes (#1070) * 385169778: (feat) apply search for devices (#1072) * Adds title for device under test; fix background for Outdated device * Fix tests * Fix dialog width * Fix tests * 392037156: (fix) [GM3] close form after delete device (#1077) * 392033964: (fix) [GM3] change cancel to discard on devices (#1078) * Show Discard dialog when changing the tab; show Discard dialog when clicking the Create new device * 391367079: (feat) [GM3] [Risk profiles] add empty state (#1090) * Adds layout for Risk profiles page * Disable create button while creating new device * Fix merge issues * 391367086: (feat) [GM3] add new risk profile form (#1099) * 391372198: (feat) [GM3] add risk profile search (#1100) * Update dependencies * Adds copy/delete functionality for Risk Profiles * Fix error when discard acts like cancel * 391372721: (feat) add risk profile sticky menu (#1108) * Show discard dialog when leaving the Risk Profile page; show discard dialog on profile change * 395664115: (fix) [RA] make discard disable until no changes (#1112) * Optimize changes detection * 396366298: (fix) [GM3] prevent corrupting profile if it has long name (#1121) * 396579770: (fix) set focus to correct interactive elements after delete discard profile (#1122) * 395677144: (fix) [GM3] A11Y: move button on the next line to prevent corrupting sticky menu (#1125) * Remove copy of profile on discard * 396664128: (feat) [GM3] add empty state for testing tab (#1128) * 395677144: (fix) change style to prevent hide content under sticky menu (#1129) * Adds new design for test run page for statuses before "In progress" * Clear form when Create new profile click and then Discard changes click * Fix test * 396664082: (feat) [GM3] apply changes to start testing modal (#1140) * Adds new style for Stop button; fix style for Stop test run dialog * Fix styles for tile * Improve method to check is profile is filled * Fix tests * Fix empty state for testing page * 398167706: (fix) [GM3] [Testing] change text mistake in the empty state (#1159) * Delete copy of unsaved profile without backend call * 396665821: (feat) [GM3] add testing table (#1161) * Fix bacnet version for devices that do not report IP address in their discovery response (#1139) * added logger if no bacnet packets are found for the object_id (#1152) * Merge release changes into dev (#1154) * Update design of the Stop Testrun modal * 396677540: (feat) [GM3] add steps to resolve modal (#1169) * 396676848: (feat) [GM3] add testing done (#1172) * 400924741: (fix) [GM3] [Testing] change security tag design (#1174) * Adds side button with menu * Adds tests * Change style * 396678430: (feat) [GM3] add download result options (#1180) * Change buttons text and add aria-label for "+" button * 403525535: (feat) add help tips (#1186) * 403525535: (feat) add help tips * Partially fix tests * 403525535: (feat) add unit test * 403525535: (fix) remove unused data --------- Co-authored-by: kurilova * 405967754: (fix) [a11y] changes for focus indicator issues (#1187) * 406180159: (fix) [a11y] change page title for testing page (#1188) * Focus Version button on Welcome modal close * Use hash location strategy * Change tabindex * Mask form pristine after settings saved * Disable side bar closing * Hide non-interactive element * Enable save button when optout field has changes * Fix tests * Remove focus trap on certificate page; remove ng-template from menu; adds tooltip on + button * Revert "Remove focus trap on certificate page; remove ng-template from menu; adds tooltip on + button" This reverts commit 82c534b08293710209ec89e84af2877ada5b3f7a. * Remove focus trap on certificate page; remove ng-template from menu; adds tooltip on + button * Change link style * Fix a11y issues on Risk Profiles page * Fix selector * Fix selector * Fix tests * Scroll only entities list; focus selected device after save; unselect device after discard * Focus first element in page content after callouts after redirect * Focus tab when general settings are saved * Change element for Clear all button * 406770411: (feat) add start testing message (#1207) * 406769857: (feat) add risk assessment message (#1209) * Disable copy of copy * 407724424: (fix) changes for risk profile message to be displayed on all pages (#1211) * 406699210: (fix) change styles for risk assessment form (#1212) * Change error status background * Fix tests * Adds Status for Pilot Assessment; make progress bar width as 100% * Fix deleting of profile * Fix tests * 407737058: (fix) hide help tip when the actions menu opens (#1216) * 407943943: (fix) open profile form when redirect from profile help tip (#1218) * 408378150: (fix) changes for broken download pdf link (#1219) * Hide Tip button when no tips available * 406766143: (feat) [Testing] show explanation for test result (#1222) * Fix side button display * Change layout * 409247193: (fix) [a11y] change focus when cancelling the start testrun (#1225) * Fix paddings; change label to placeholder * 406210966: (fix) make save button enable if any setting form field changed (#1227) * Fix style for status card, change style for Cancelling status * Fix tests * Filter navigation buttons to remove "mouse-only" style; clear search input field when user input only spaces * Change background color for cancelling status * 409241692: (fix) [a11Y] prevent opening the testing modal with selected device (#1234) * 409766744: (feat) change title for testing empty state (#1236) * Sort profiles list by date * Change layout for status card; fix progress bar * Add new device as the first element; fix focus after adding the new RA profile * Remove console.log; change the container for proper focus * 409764406: (feat) changes to show next tip step on first time user flow (#1238) * 410518000: (fix) [Settings] change showing two ports value by default (#1240) * Redirect user to settings page if no settings saved * Fix tests * 410755779: (fix) changes to move focus to the first interactive elem on opened page (#1244) * 410765201: (fix) make focus visible after the new device form was discarded (#1246) * Use simple dialog for shutdown * Fix lint issue * Do not show loading when cancelling * 410553758: (fix) move focus to the actions button after test attempt was cancelled (#1250) * Provide proper url for report download * 409765887: (feat) remove redundant copy and delete on risk assessment form (#1253) * Save consent value * Do not show error when only spaces entered * 409765421: (feat) [Testing tab] adjust spacing between columns (#1256) * Scroll to new added profile; remove console.log * 411077761: (fix) [a11Y] changes for focus on the settings page (#1258) * Close menu and focus menu button on Esc and Tab * Change label for download button * 411390117: (fix) [a11Y]: changes for zoom in issues (#1261) * Prevent default event when open filter * small update to trigger actions (#1263) * 412959654: (fix) make test modules visible on zoom in (#1264) * 412968690: (fix) change delete report popup to be the same as other delete popups (#1265) * The team of angular material recommends to use one-line hints, so for this case it's better to use approach with script as css changes can break whole component * Fix test coverage * 412962437: (fix) change testrun table view to improve on zoom in (#1268) --------- Co-authored-by: Olga Mardvilko Co-authored-by: jhughesbiot Co-authored-by: Marius <86727846+MariusBaldovin@users.noreply.github.com> Co-authored-by: J Boddey * fix the tcp/udp scans to target the correct ip address (#1269) Co-authored-by: J Boddey * PDF report bug on device profile update (#1267) * reload the jinja templates into TestReport class when the report is re-renedered * fix pylint --------- Co-authored-by: J Boddey * Feature/module logging (#1135) * Add log level configurations to test modules * Update docs * Update docs * pylint fix * update docs * fix doc link --------- Signed-off-by: J Boddey Co-authored-by: J Boddey * Merge branch 'main' into release/v2.2 # Conflicts: # .github/workflows/package.yml # .github/workflows/testing.yml # README.md # cmd/build_ui # docs/dev/mockoon.json # docs/dev/postman.json # docs/get_started.md # docs/virtual_machine.md # framework/python/src/api/api.py # framework/python/src/common/risk_profile.py # framework/python/src/core/session.py # framework/python/src/core/testrun.py # framework/python/src/net_orc/ip_control.py # framework/python/src/net_orc/network_orchestrator.py # framework/requirements.txt # local/system.json.example # modules/test/base/python/requirements.txt # modules/test/base/python/src/test_module.py # modules/test/conn/python/src/port_stats_util.py # modules/test/dns/python/src/dns_module.py # modules/test/services/python/src/services_module.py # modules/ui/angular.json # modules/ui/package-lock.json # modules/ui/package.json # modules/ui/src/app/app.component.html # modules/ui/src/app/app.component.scss # modules/ui/src/app/app.component.spec.ts # modules/ui/src/app/app.component.ts # modules/ui/src/app/app.module.ts # modules/ui/src/app/app.store.spec.ts # modules/ui/src/app/app.store.ts # modules/ui/src/app/components/callout/callout.component.html # modules/ui/src/app/components/callout/callout.component.scss # modules/ui/src/app/components/callout/callout.component.spec.ts # modules/ui/src/app/components/callout/callout.component.ts # modules/ui/src/app/components/device-item/device-item.component.html # modules/ui/src/app/components/device-item/device-item.component.scss # modules/ui/src/app/components/device-item/device-item.component.spec.ts # modules/ui/src/app/components/device-item/device-item.component.ts # modules/ui/src/app/components/download-report-zip/download-report-zip.component.spec.ts # modules/ui/src/app/components/download-report-zip/download-report-zip.component.ts # modules/ui/src/app/components/download-report/download-report.component.scss # modules/ui/src/app/components/download-zip-modal/download-zip-modal.component.scss # modules/ui/src/app/components/download-zip-modal/download-zip-modal.component.spec.ts # modules/ui/src/app/components/download-zip-modal/download-zip-modal.component.ts # modules/ui/src/app/components/dynamic-form/dynamic-form.component.html # modules/ui/src/app/components/dynamic-form/dynamic-form.component.scss # modules/ui/src/app/components/dynamic-form/dynamic-form.component.spec.ts # modules/ui/src/app/components/dynamic-form/dynamic-form.component.ts # modules/ui/src/app/components/program-type-icon/program-type-icon.component.ts # modules/ui/src/app/components/simple-dialog/simple-dialog.component.scss # modules/ui/src/app/components/simple-dialog/simple-dialog.component.ts # modules/ui/src/app/components/spinner/spinner.component.scss # modules/ui/src/app/components/stepper/stepper.component.scss # modules/ui/src/app/components/stepper/stepper.component.spec.ts # modules/ui/src/app/components/stepper/stepper.component.ts # modules/ui/src/app/components/testing-complete/testing-complete.component.spec.ts # modules/ui/src/app/components/testing-complete/testing-complete.component.ts # modules/ui/src/app/components/version/consent-dialog/consent-dialog.component.html # modules/ui/src/app/components/version/consent-dialog/consent-dialog.component.spec.ts # modules/ui/src/app/components/version/consent-dialog/consent-dialog.component.ts # modules/ui/src/app/components/version/version.component.ts # modules/ui/src/app/components/wifi/wifi.component.html # modules/ui/src/app/components/wifi/wifi.component.ts # modules/ui/src/app/mocks/device.mock.ts # modules/ui/src/app/mocks/profile.mock.ts # modules/ui/src/app/mocks/reports.mock.ts # modules/ui/src/app/mocks/testrun.mock.ts # modules/ui/src/app/model/device.ts # modules/ui/src/app/model/entity-action.ts # modules/ui/src/app/model/layout-type.ts # modules/ui/src/app/model/testrun-status.ts # modules/ui/src/app/pages/certificates/certificates.component.html # modules/ui/src/app/pages/certificates/certificates.component.spec.ts # modules/ui/src/app/pages/certificates/certificates.component.ts # modules/ui/src/app/pages/certificates/certificates.store.spec.ts # modules/ui/src/app/pages/certificates/certificates.store.ts # modules/ui/src/app/pages/devices/components/device-form/device.validators.ts # modules/ui/src/app/pages/devices/components/device-qualification-from/device-qualification-from.component.html # modules/ui/src/app/pages/devices/components/device-qualification-from/device-qualification-from.component.scss # modules/ui/src/app/pages/devices/components/device-qualification-from/device-qualification-from.component.spec.ts # modules/ui/src/app/pages/devices/components/device-qualification-from/device-qualification-from.component.ts # modules/ui/src/app/pages/devices/devices.component.html # modules/ui/src/app/pages/devices/devices.component.scss # modules/ui/src/app/pages/devices/devices.component.spec.ts # modules/ui/src/app/pages/devices/devices.component.ts # modules/ui/src/app/pages/devices/devices.module.ts # modules/ui/src/app/pages/devices/devices.store.spec.ts # modules/ui/src/app/pages/devices/devices.store.ts # modules/ui/src/app/pages/general-settings/general-settings.store.spec.ts # modules/ui/src/app/pages/general-settings/general-settings.store.ts # modules/ui/src/app/pages/reports/components/delete-report/delete-report.component.html # modules/ui/src/app/pages/reports/components/delete-report/delete-report.component.spec.ts # modules/ui/src/app/pages/reports/components/filter-dialog/filter-dialog.component.scss # modules/ui/src/app/pages/reports/reports.component.html # modules/ui/src/app/pages/reports/reports.component.scss # modules/ui/src/app/pages/reports/reports.component.spec.ts # modules/ui/src/app/pages/reports/reports.component.ts # modules/ui/src/app/pages/reports/reports.module.ts # modules/ui/src/app/pages/risk-assessment/components/success-dialog/success-dialog.component.html # modules/ui/src/app/pages/risk-assessment/components/success-dialog/success-dialog.component.scss # modules/ui/src/app/pages/risk-assessment/components/success-dialog/success-dialog.component.ts # modules/ui/src/app/pages/risk-assessment/profile-form/profile-form.component.html # modules/ui/src/app/pages/risk-assessment/profile-form/profile-form.component.scss # modules/ui/src/app/pages/risk-assessment/profile-form/profile-form.component.spec.ts # modules/ui/src/app/pages/risk-assessment/profile-form/profile-form.component.ts # modules/ui/src/app/pages/risk-assessment/profile-form/profile.validators.ts # modules/ui/src/app/pages/risk-assessment/profile-item/profile-item.component.html # modules/ui/src/app/pages/risk-assessment/profile-item/profile-item.component.scss # modules/ui/src/app/pages/risk-assessment/profile-item/profile-item.component.spec.ts # modules/ui/src/app/pages/risk-assessment/profile-item/profile-item.component.ts # modules/ui/src/app/pages/risk-assessment/risk-assessment.component.html # modules/ui/src/app/pages/risk-assessment/risk-assessment.component.scss # modules/ui/src/app/pages/risk-assessment/risk-assessment.component.spec.ts # modules/ui/src/app/pages/risk-assessment/risk-assessment.component.ts # modules/ui/src/app/pages/risk-assessment/risk-assessment.store.spec.ts # modules/ui/src/app/pages/risk-assessment/risk-assessment.store.ts # modules/ui/src/app/pages/settings/components/settings-dropdown/settings-dropdown.component.scss # modules/ui/src/app/pages/settings/settings.component.html # modules/ui/src/app/pages/settings/settings.component.spec.ts # modules/ui/src/app/pages/settings/settings.component.ts # modules/ui/src/app/pages/testrun/components/download-options/download-options.component.scss # modules/ui/src/app/pages/testrun/components/download-options/download-options.component.ts # modules/ui/src/app/pages/testrun/components/testrun-initiate-form/testrun-initiate-form.component.ts # modules/ui/src/app/pages/testrun/components/testrun-status-card/testrun-status-card.component.html # modules/ui/src/app/pages/testrun/components/testrun-status-card/testrun-status-card.component.scss # modules/ui/src/app/pages/testrun/components/testrun-status-card/testrun-status-card.component.spec.ts # modules/ui/src/app/pages/testrun/components/testrun-status-card/testrun-status-card.component.ts # modules/ui/src/app/pages/testrun/components/testrun-table/testrun-table.component.html # modules/ui/src/app/pages/testrun/components/testrun-table/testrun-table.component.scss # modules/ui/src/app/pages/testrun/components/testrun-table/testrun-table.component.spec.ts # modules/ui/src/app/pages/testrun/components/testrun-table/testrun-table.component.ts # modules/ui/src/app/pages/testrun/testrun.component.html # modules/ui/src/app/pages/testrun/testrun.component.scss # modules/ui/src/app/pages/testrun/testrun.component.spec.ts # modules/ui/src/app/pages/testrun/testrun.component.ts # modules/ui/src/app/pages/testrun/testrun.module.ts # modules/ui/src/app/pages/testrun/testrun.store.spec.ts # modules/ui/src/app/pages/testrun/testrun.store.ts # modules/ui/src/app/services/test-run-mqtt.service.spec.ts # modules/ui/src/app/services/test-run-mqtt.service.ts # modules/ui/src/app/services/test-run.service.ts # modules/ui/src/app/store/actions.ts # modules/ui/src/app/store/effects.spec.ts # modules/ui/src/app/store/effects.ts # modules/ui/src/app/store/reducers.ts # modules/ui/src/app/store/selectors.spec.ts # modules/ui/src/app/store/selectors.ts # modules/ui/src/app/store/state.ts # modules/ui/src/index.html # modules/ui/src/styles.scss # modules/ui/src/theming/colors.scss # modules/ui/src/theming/theme.scss # modules/ui/src/theming/variables.scss # resources/devices/device_profile.json # resources/report/test_report_styles.css # resources/test_packs/pilot/report_templates/results.jinja # resources/test_packs/pilot/report_templates/summary.jinja # resources/test_packs/pilot/test_pack.py # resources/test_packs/qualification/report_templates/results.jinja # resources/test_packs/qualification/report_templates/summary.jinja # resources/test_packs/qualification/test_pack.py # testing/api/reports/report.json # testing/api/test_api.py # testing/device_configs/tester1/device_config.json # testing/device_configs/tester2/device_config.json # testing/device_configs/tester3/device_config.json # testing/tests/test_tests.py # testing/unit/conn/conn_module_test.py # testing/unit/dns/dns_module_test.py * Delete redundant changes * Bump version number * docs: update get started guide (#1277) * docs: update get started guide * Update get started guide * Update roadmap --------- Co-authored-by: Jacob Boddey --------- Signed-off-by: Sofia Kurilova Signed-off-by: J Boddey Signed-off-by: Marius <86727846+MariusBaldovin@users.noreply.github.com> Co-authored-by: Marius <86727846+MariusBaldovin@users.noreply.github.com> Co-authored-by: Olga Mardvilko Co-authored-by: jhughesbiot Co-authored-by: J Boddey Co-authored-by: Aliaksandr Nikitsin Co-authored-by: MariusBaldovin --- .github/workflows/package.yml | 34 - .github/workflows/testing.yml | 23 +- README.md | 2 +- cmd/build | 10 +- cmd/build_ui | 2 +- docs/additional_config.md | 121 + docs/dev/mockoon.json | 18 +- docs/dev/postman.json | 2 +- docs/get_started.md | 141 +- docs/roadmap.pdf | Bin 0 -> 456579 bytes docs/rooadmap.pdf | Bin 828501 -> 0 bytes docs/ui/getstarted--7cfvdpdnc5o.png | Bin 17881 -> 0 bytes docs/ui/getstarted--j21skepmx1.png | Bin 28185 -> 0 bytes docs/ui/getstarted--m4si1otdu5d.png | Bin 13909 -> 0 bytes docs/ui/getstarted--q5uw26tfod.png | Bin 13929 -> 0 bytes docs/ui/getstarted--w09wecsry3.png | Bin 14001 -> 0 bytes docs/ui/getstarted-actions-device.png | Bin 0 -> 108601 bytes docs/ui/getstarted-actions-testing.png | Bin 0 -> 115595 bytes docs/ui/getstarted-add-device.png | Bin 0 -> 90633 bytes docs/ui/getstarted-certificates-menu.png | Bin 0 -> 105854 bytes docs/ui/getstarted-device-repository.png | Bin 0 -> 108666 bytes docs/ui/getstarted-reports.png | Bin 0 -> 65294 bytes docs/ui/getstarted-risk-assessment.png | Bin 0 -> 107548 bytes docs/ui/getstarted-settings-menu.png | Bin 0 -> 160225 bytes docs/ui/getstarted-testing.png | Bin 0 -> 92544 bytes docs/ui/getstarted-waiting-for-device.png | Bin 0 -> 65953 bytes docs/virtual_machine.md | 2 +- framework/python/src/api/api.py | 13 +- framework/python/src/common/risk_profile.py | 3 +- framework/python/src/common/testreport.py | 7 + .../python/src/core/docker/docker_module.py | 27 +- .../src/core/docker/network_docker_module.py | 3 +- .../src/core/docker/test_docker_module.py | 4 +- framework/python/src/core/session.py | 37 +- framework/python/src/core/testrun.py | 6 +- framework/python/src/net_orc/ip_control.py | 10 +- .../src/net_orc/network_orchestrator.py | 65 +- .../python/src/test_orc/test_orchestrator.py | 42 +- framework/requirements.txt | 2 +- local/system.json.example | 1 + make/DEBIAN/control | 2 +- modules/test/base/python/requirements.txt | 2 +- modules/test/base/python/src/test_module.py | 7 +- .../test/conn/python/src/port_stats_util.py | 71 +- modules/test/dns/python/src/dns_module.py | 56 +- .../test/protocol/bin/get_bacnet_packets.sh | 0 .../protocol/python/src/protocol_bacnet.py | 14 +- .../protocol/python/src/protocol_module.py | 17 +- .../services/python/src/services_module.py | 20 +- modules/test/tls/python/src/tls_util.py | 3 + modules/ui/angular.json | 42 +- modules/ui/package-lock.json | 7181 ++++++++++------- modules/ui/package.json | 46 +- modules/ui/src/app/app-routing.module.ts | 64 +- modules/ui/src/app/app.component.html | 478 +- modules/ui/src/app/app.component.scss | 235 +- modules/ui/src/app/app.component.spec.ts | 541 +- modules/ui/src/app/app.component.ts | 319 +- modules/ui/src/app/app.module.ts | 113 - modules/ui/src/app/app.store.spec.ts | 23 +- modules/ui/src/app/app.store.ts | 115 +- .../bypass/bypass.component.spec.ts | 1 + .../app/components/bypass/bypass.component.ts | 7 +- .../components/callout/callout.component.html | 40 +- .../components/callout/callout.component.scss | 126 +- .../callout/callout.component.spec.ts | 25 +- .../components/callout/callout.component.ts | 17 +- .../device-item/device-item.component.html | 120 +- .../device-item/device-item.component.scss | 226 +- .../device-item/device-item.component.spec.ts | 31 +- .../device-item/device-item.component.ts | 6 +- .../device-tests/device-tests.component.scss | 22 +- .../device-tests.component.spec.ts | 4 +- .../device-tests/device-tests.component.ts | 15 +- .../download-report-pdf.component.ts | 5 +- .../download-report-zip.component.spec.ts | 6 +- .../download-report-zip.component.ts | 20 +- .../download-report.component.html | 1 + .../download-report.component.scss | 10 +- .../download-report.component.ts | 2 +- .../download-zip-modal.component.scss | 50 +- .../download-zip-modal.component.spec.ts | 9 +- .../download-zip-modal.component.ts | 43 +- .../dynamic-form/dynamic-form.component.html | 12 +- .../dynamic-form/dynamic-form.component.scss | 26 +- .../dynamic-form.component.spec.ts | 23 +- .../dynamic-form/dynamic-form.component.ts | 45 +- .../empty-message.component.html | 28 + .../empty-message.component.scss | 73 + .../empty-message.component.spec.ts | 60 + .../empty-message.component.ts} | 41 +- .../empty-page.component.html} | 31 +- .../empty-page/empty-page.component.spec.ts | 37 + .../empty-page/empty-page.component.ts} | 21 +- .../escapable-dialog.component.ts | 10 +- .../help-tip/help-tip.component.html | 49 + .../help-tip/help-tip.component.scss | 111 + .../help-tip/help-tip.component.spec.ts | 134 + .../components/help-tip/help-tip.component.ts | 122 + .../list-item/list-item.component.html | 41 + .../list-item.component.scss} | 44 +- .../list-item/list-item.component.spec.ts | 107 + .../list-item/list-item.component.ts | 117 + .../list-layout/list-layout.component.html | 87 + .../list-layout/list-layout.component.scss | 153 + .../list-layout/list-layout.component.spec.ts | 150 + .../list-layout/list-layout.component.ts | 141 + .../no-entity-selected.component.html | 21 + .../no-entity-selected.component.scss} | 45 +- .../no-entity-selected.component.ts} | 22 +- .../program-type-icon.component.ts | 5 +- .../report-action/report-action.component.ts | 12 +- .../shutdown-app.component.spec.ts | 6 +- .../shutdown-app/shutdown-app.component.ts | 27 +- .../side-button-menu.component.html | 56 + .../side-button-menu.component.scss | 77 + .../side-button-menu.component.spec.ts | 126 + .../side-button-menu.component.ts | 36 + .../simple-dialog.component.html | 9 +- .../simple-dialog.component.scss | 32 +- .../simple-dialog.component.spec.ts | 7 + .../simple-dialog/simple-dialog.component.ts | 31 +- .../snack-bar/snack-bar.component.scss | 8 +- .../snack-bar/snack-bar.component.ts | 5 +- .../components/spinner/spinner.component.scss | 37 +- .../components/spinner/spinner.component.ts | 8 +- .../components/stepper/stepper.component.scss | 24 +- .../stepper/stepper.component.spec.ts | 12 +- .../components/stepper/stepper.component.ts | 4 +- .../testing-complete.component.spec.ts | 3 +- .../testing-complete.component.ts | 13 +- .../consent-dialog.component.html | 13 +- .../consent-dialog.component.scss | 107 +- .../consent-dialog.component.spec.ts | 27 +- .../consent-dialog.component.ts | 23 +- .../components/version/version.component.scss | 9 +- .../components/version/version.component.ts | 19 +- .../app/components/wifi/wifi.component.html | 2 +- .../src/app/components/wifi/wifi.component.ts | 11 +- .../src/app/guards/can-activate.guard.spec.ts | 100 + .../ui/src/app/guards/can-activate.guard.ts | 50 + .../interceptors/error-handler.interceptor.ts | 13 +- .../src/app/interceptors/error.interceptor.ts | 19 +- .../app/interceptors/loading.interceptor.ts | 10 +- modules/ui/src/app/mocks/device.mock.ts | 78 +- modules/ui/src/app/mocks/profile.mock.ts | 39 +- modules/ui/src/app/mocks/reports.mock.ts | 8 + modules/ui/src/app/mocks/testrun.mock.ts | 14 +- modules/ui/src/app/model/callout-type.ts | 3 +- modules/ui/src/app/model/certificate.ts | 2 +- modules/ui/src/app/model/device.ts | 17 +- .../entity-action.ts} | 20 +- modules/ui/src/app/model/filters.ts | 7 + .../layout-type.ts} | 14 +- modules/ui/src/app/model/profile.ts | 6 + modules/ui/src/app/model/routes.ts | 3 + modules/ui/src/app/model/testrun-status.ts | 2 + modules/ui/src/app/model/tip-config.ts | 56 + .../certificate-item.component.html | 39 - .../certificate-item.component.scss | 94 - .../certificate-item.component.ts | 29 - .../certificate-upload-button.component.scss | 14 - .../certificates/certificates.component.html | 41 +- .../certificates/certificates.component.scss | 70 +- .../certificates.component.spec.ts | 31 +- .../certificates/certificates.component.ts | 48 +- .../certificates/certificates.store.spec.ts | 259 +- .../pages/certificates/certificates.store.ts | 282 +- .../certificate-upload-button.component.html | 3 +- .../certificate-upload-button.component.scss | 19 + ...ertificate-upload-button.component.spec.ts | 0 .../certificate-upload-button.component.ts | 2 +- .../certificates-table.component.html | 127 + .../certificates-table.component.scss | 127 + .../certificates-table.component.spec.ts} | 48 +- .../certificates-table.component.ts | 34 + .../device-form/device.validators.ts | 5 +- .../device-qualification-from.component.html | 488 +- .../device-qualification-from.component.scss | 285 +- ...evice-qualification-from.component.spec.ts | 582 +- .../device-qualification-from.component.ts | 640 +- .../app/pages/devices/devices.component.html | 78 +- .../app/pages/devices/devices.component.scss | 35 +- .../pages/devices/devices.component.spec.ts | 226 +- .../app/pages/devices/devices.component.ts | 354 +- .../src/app/pages/devices/devices.module.ts | 59 - .../app/pages/devices/devices.store.spec.ts | 8 + .../ui/src/app/pages/devices/devices.store.ts | 79 +- .../settings-dropdown.component.html | 26 +- .../settings-dropdown.component.scss | 104 + .../settings-dropdown.component.spec.ts | 7 +- .../settings-dropdown.component.ts | 2 +- .../general-settings.component.html | 210 + .../general-settings.component.scss | 127 + .../general-settings.component.spec.ts | 343 + .../general-settings.component.ts | 297 + .../general-settings.store.spec.ts} | 140 +- .../general-settings.store.ts} | 86 +- .../only-different-values.validator.ts | 6 +- .../delete-report.component.html | 1 + .../delete-report.component.scss | 13 +- .../delete-report.component.spec.ts | 2 +- .../delete-report/delete-report.component.ts | 14 +- .../filter-chips/filter-chips.component.html | 23 +- .../filter-chips/filter-chips.component.scss | 78 +- .../filter-chips.component.spec.ts | 4 +- .../filter-chips/filter-chips.component.ts | 11 +- .../filter-dialog.component.html | 37 +- .../filter-dialog.component.scss | 108 +- .../filter-dialog.component.spec.ts | 9 +- .../filter-dialog/filter-dialog.component.ts | 88 +- .../filter-header.component.html | 38 + .../filter-header.component.scss | 43 + .../filter-header.component.spec.ts | 94 + .../filter-header/filter-header.component.ts | 51 + .../app/pages/reports/reports.component.html | 416 +- .../app/pages/reports/reports.component.scss | 59 +- .../pages/reports/reports.component.spec.ts | 48 +- .../app/pages/reports/reports.component.ts | 77 +- .../src/app/pages/reports/reports.module.ts | 49 - .../ui/src/app/pages/reports/reports.store.ts | 12 +- .../success-dialog.component.html | 10 +- .../success-dialog.component.scss | 43 +- .../success-dialog.component.ts | 20 +- .../profile-form/profile-form.component.html | 53 +- .../profile-form/profile-form.component.scss | 33 +- .../profile-form.component.spec.ts | 51 +- .../profile-form/profile-form.component.ts | 237 +- .../profile-form/profile.validators.ts | 16 +- .../profile-item/profile-item.component.html | 34 +- .../profile-item/profile-item.component.scss | 86 +- .../profile-item.component.spec.ts | 27 +- .../profile-item/profile-item.component.ts | 31 +- .../risk-assessment.component.html | 110 +- .../risk-assessment.component.scss | 73 +- .../risk-assessment.component.spec.ts | 195 +- .../risk-assessment.component.ts | 284 +- .../risk-assessment/risk-assessment.module.ts | 57 - .../risk-assessment.store.spec.ts | 29 +- .../risk-assessment/risk-assessment.store.ts | 62 +- .../settings-dropdown.component.scss | 74 - .../pages/settings/settings.component.html | 115 +- .../pages/settings/settings.component.scss | 143 +- .../pages/settings/settings.component.spec.ts | 347 +- .../app/pages/settings/settings.component.ts | 210 +- .../download-options.component.html | 84 +- .../download-options.component.scss | 121 +- .../download-options.component.spec.ts | 91 +- .../download-options.component.ts | 55 +- .../test-result-dialog.component.html | 46 + .../test-result-dialog.component.scss | 109 + .../test-result-dialog.component.spec.ts} | 58 +- .../test-result-dialog.component.ts} | 38 +- .../testrun-initiate-form.component.html | 12 +- .../testrun-initiate-form.component.scss | 80 +- .../testrun-initiate-form.component.spec.ts | 4 +- .../testrun-initiate-form.component.ts | 69 +- .../testrun-status-card.component.html | 168 +- .../testrun-status-card.component.scss | 175 +- .../testrun-status-card.component.spec.ts | 119 +- .../testrun-status-card.component.ts | 46 +- .../testrun-table.component.html | 144 +- .../testrun-table.component.scss | 220 +- .../testrun-table.component.spec.ts | 78 +- .../testrun-table/testrun-table.component.ts | 68 +- .../app/pages/testrun/testrun.component.html | 78 +- .../app/pages/testrun/testrun.component.scss | 135 +- .../pages/testrun/testrun.component.spec.ts | 89 +- .../app/pages/testrun/testrun.component.ts | 66 +- .../src/app/pages/testrun/testrun.module.ts | 67 - .../app/pages/testrun/testrun.store.spec.ts | 22 +- .../ui/src/app/pages/testrun/testrun.store.ts | 35 +- .../services/focus-manager.service.spec.ts | 1 + modules/ui/src/app/services/loader.service.ts | 11 +- .../services/local-storage.service.spec.ts | 84 + .../src/app/services/local-storage.service.ts | 25 + .../src/app/services/notification.service.ts | 14 +- .../services/test-run-mqtt.service.spec.ts | 4 +- .../src/app/services/test-run-mqtt.service.ts | 9 +- .../src/app/services/test-run.service.spec.ts | 7 +- .../ui/src/app/services/test-run.service.ts | 37 +- modules/ui/src/app/store/actions.ts | 9 + modules/ui/src/app/store/effects.spec.ts | 26 + modules/ui/src/app/store/effects.ts | 43 +- modules/ui/src/app/store/reducers.ts | 8 +- modules/ui/src/app/store/selectors.spec.ts | 1 + modules/ui/src/app/store/selectors.ts | 5 + modules/ui/src/app/store/state.ts | 2 + modules/ui/src/assets/icons/cornerstone.svg | 9 + modules/ui/src/assets/icons/desktop-new.svg | 27 + modules/ui/src/assets/icons/devices.svg | 5 - modules/ui/src/assets/icons/dog.svg | 15 + modules/ui/src/assets/icons/draft.svg | 16 +- modules/ui/src/assets/icons/empty-devices.svg | 37 + .../ui/src/assets/icons/empty-profiles.svg | 52 + modules/ui/src/assets/icons/empty-testrun.svg | 66 + modules/ui/src/assets/icons/reports.svg | 5 - .../ui/src/assets/icons/risk-assessment.svg | 10 - modules/ui/src/assets/icons/score.svg | 25 + modules/ui/src/assets/icons/switch.svg | 30 + modules/ui/src/index.html | 6 +- modules/ui/src/main.ts | 57 +- modules/ui/src/styles.scss | 407 +- modules/ui/src/theming/colors.scss | 214 +- modules/ui/src/theming/m3-theme.scss | 165 + modules/ui/src/theming/mixins.scss | 128 + modules/ui/src/theming/theme.scss | 77 - modules/ui/src/theming/variables.scss | 14 +- resources/devices/device_profile.json | 774 +- resources/report/header_macros.jinja | 43 - resources/report/pilot-icon.png | Bin 536 -> 0 bytes resources/report/qualification-icon.png | Bin 428 -> 0 bytes resources/report/test_report_styles.css | 65 +- resources/report/test_report_template.html | 241 - .../pilot/report_templates/results.jinja | 10 +- .../pilot/report_templates/summary.jinja | 2 +- resources/test_packs/pilot/test_pack.py | 7 +- .../report_templates/results.jinja | 10 +- .../report_templates/summary.jinja | 2 +- .../test_packs/qualification/test_pack.py | 5 +- testing/api/reports/report.json | 4 +- testing/api/test_api.py | 24 +- .../bacnet_compliant/device_config.json | 54 + .../dns_compliant/device_config.json | 54 + .../dns_non_compliant/device_config.json | 54 + .../services_compliant/device_config.json | 54 + .../services_non_compliant/device_config.json | 54 + .../device_configs/tester1/device_config.json | 57 - .../device_configs/tester2/device_config.json | 57 - .../device_configs/tester3/device_config.json | 51 - .../bacnet_compliant/Dockerfile | 24 + .../bacnet_compliant/entrypoint.py | 26 + .../bacnet_compliant/entrypoint.sh | 28 + .../ci_test_device1/dns_compliant/Dockerfile | 19 + .../dns_compliant/entrypoint.sh | 33 + .../dns_non_compliant/Dockerfile | 19 + .../dns_non_compliant/entrypoint.sh | 36 + .../services_compliant/Dockerfile | 19 + .../services_compliant/entrypoint.sh | 31 + .../services_non_compliant/Dockerfile | 19 + .../services_non_compliant/entrypoint.sh | 70 + testing/tests/test_tests | 69 +- testing/tests/test_tests.json | 69 +- testing/tests/test_tests.py | 127 +- testing/unit/conn/conn_module_test.py | 36 + .../ifconfig_port_stats_post_monitor.txt | 8 + ...g_port_stats_post_noncompliant_monitor.txt | 8 + .../ifconfig_port_stats_pre_monitor.txt | 8 + .../captures/{ => dns_dhcp_server}/dns.pcap | Bin .../{ => dns_dhcp_server}/monitor.pcap | Bin .../{ => dns_dhcp_server}/startup.pcap | Bin testing/unit/dns/captures/dns_no_dns/dns.pcap | Bin 0 -> 18335 bytes .../unit/dns/captures/dns_no_dns/monitor.pcap | Bin 0 -> 163095 bytes .../unit/dns/captures/dns_no_dns/startup.pcap | Bin 0 -> 1238 bytes .../dns/captures/dns_non_dhcp_server/dns.pcap | Bin 0 -> 2244 bytes .../captures/dns_non_dhcp_server/monitor.pcap | Bin 0 -> 1703 bytes .../captures/dns_non_dhcp_server/startup.pcap | Bin 0 -> 1596 bytes testing/unit/dns/dns_module_test.py | 170 +- 358 files changed, 17243 insertions(+), 11866 deletions(-) create mode 100644 docs/additional_config.md create mode 100644 docs/roadmap.pdf delete mode 100644 docs/rooadmap.pdf delete mode 100644 docs/ui/getstarted--7cfvdpdnc5o.png delete mode 100644 docs/ui/getstarted--j21skepmx1.png delete mode 100644 docs/ui/getstarted--m4si1otdu5d.png delete mode 100644 docs/ui/getstarted--q5uw26tfod.png delete mode 100644 docs/ui/getstarted--w09wecsry3.png create mode 100644 docs/ui/getstarted-actions-device.png create mode 100644 docs/ui/getstarted-actions-testing.png create mode 100644 docs/ui/getstarted-add-device.png create mode 100644 docs/ui/getstarted-certificates-menu.png create mode 100644 docs/ui/getstarted-device-repository.png create mode 100644 docs/ui/getstarted-reports.png create mode 100644 docs/ui/getstarted-risk-assessment.png create mode 100644 docs/ui/getstarted-settings-menu.png create mode 100644 docs/ui/getstarted-testing.png create mode 100644 docs/ui/getstarted-waiting-for-device.png mode change 100644 => 100755 modules/test/protocol/bin/get_bacnet_packets.sh delete mode 100644 modules/ui/src/app/app.module.ts create mode 100644 modules/ui/src/app/components/empty-message/empty-message.component.html create mode 100644 modules/ui/src/app/components/empty-message/empty-message.component.scss create mode 100644 modules/ui/src/app/components/empty-message/empty-message.component.spec.ts rename modules/ui/src/app/components/{shutdown-app-modal/shutdown-app-modal.component.scss => empty-message/empty-message.component.ts} (56%) rename modules/ui/src/app/components/{shutdown-app-modal/shutdown-app-modal.component.html => empty-page/empty-page.component.html} (54%) create mode 100644 modules/ui/src/app/components/empty-page/empty-page.component.spec.ts rename modules/ui/src/app/{pages/testrun/testrun-routing.module.ts => components/empty-page/empty-page.component.ts} (59%) create mode 100644 modules/ui/src/app/components/help-tip/help-tip.component.html create mode 100644 modules/ui/src/app/components/help-tip/help-tip.component.scss create mode 100644 modules/ui/src/app/components/help-tip/help-tip.component.spec.ts create mode 100644 modules/ui/src/app/components/help-tip/help-tip.component.ts create mode 100644 modules/ui/src/app/components/list-item/list-item.component.html rename modules/ui/src/app/components/{wifi/wifi.component.scss => list-item/list-item.component.scss} (55%) create mode 100644 modules/ui/src/app/components/list-item/list-item.component.spec.ts create mode 100644 modules/ui/src/app/components/list-item/list-item.component.ts create mode 100644 modules/ui/src/app/components/list-layout/list-layout.component.html create mode 100644 modules/ui/src/app/components/list-layout/list-layout.component.scss create mode 100644 modules/ui/src/app/components/list-layout/list-layout.component.spec.ts create mode 100644 modules/ui/src/app/components/list-layout/list-layout.component.ts create mode 100644 modules/ui/src/app/components/no-entity-selected/no-entity-selected.component.html rename modules/ui/src/app/{pages/devices/devices-routing.module.ts => components/no-entity-selected/no-entity-selected.component.scss} (52%) rename modules/ui/src/app/{pages/risk-assessment/risk-assessment-routing.module.ts => components/no-entity-selected/no-entity-selected.component.ts} (57%) create mode 100644 modules/ui/src/app/components/side-button-menu/side-button-menu.component.html create mode 100644 modules/ui/src/app/components/side-button-menu/side-button-menu.component.scss create mode 100644 modules/ui/src/app/components/side-button-menu/side-button-menu.component.spec.ts create mode 100644 modules/ui/src/app/components/side-button-menu/side-button-menu.component.ts create mode 100644 modules/ui/src/app/guards/can-activate.guard.spec.ts create mode 100644 modules/ui/src/app/guards/can-activate.guard.ts rename modules/ui/src/app/{components/shutdown-app/shutdown-app.component.scss => model/entity-action.ts} (69%) rename modules/ui/src/app/{pages/reports/reports-routing.module.ts => model/layout-type.ts} (63%) create mode 100644 modules/ui/src/app/model/tip-config.ts delete mode 100644 modules/ui/src/app/pages/certificates/certificate-item/certificate-item.component.html delete mode 100644 modules/ui/src/app/pages/certificates/certificate-item/certificate-item.component.scss delete mode 100644 modules/ui/src/app/pages/certificates/certificate-item/certificate-item.component.ts delete mode 100644 modules/ui/src/app/pages/certificates/certificate-upload-button/certificate-upload-button.component.scss rename modules/ui/src/app/pages/certificates/{ => components}/certificate-upload-button/certificate-upload-button.component.html (86%) create mode 100644 modules/ui/src/app/pages/certificates/components/certificate-upload-button/certificate-upload-button.component.scss rename modules/ui/src/app/pages/certificates/{ => components}/certificate-upload-button/certificate-upload-button.component.spec.ts (100%) rename modules/ui/src/app/pages/certificates/{ => components}/certificate-upload-button/certificate-upload-button.component.ts (97%) create mode 100644 modules/ui/src/app/pages/certificates/components/certificates-table/certificates-table.component.html create mode 100644 modules/ui/src/app/pages/certificates/components/certificates-table/certificates-table.component.scss rename modules/ui/src/app/pages/certificates/{certificate-item/certificate-item.component.spec.ts => components/certificates-table/certificates-table.component.spec.ts} (65%) create mode 100644 modules/ui/src/app/pages/certificates/components/certificates-table/certificates-table.component.ts delete mode 100644 modules/ui/src/app/pages/devices/devices.module.ts rename modules/ui/src/app/pages/{settings => general-settings}/components/settings-dropdown/settings-dropdown.component.html (73%) create mode 100644 modules/ui/src/app/pages/general-settings/components/settings-dropdown/settings-dropdown.component.scss rename modules/ui/src/app/pages/{settings => general-settings}/components/settings-dropdown/settings-dropdown.component.spec.ts (88%) rename modules/ui/src/app/pages/{settings => general-settings}/components/settings-dropdown/settings-dropdown.component.ts (98%) create mode 100644 modules/ui/src/app/pages/general-settings/general-settings.component.html create mode 100644 modules/ui/src/app/pages/general-settings/general-settings.component.scss create mode 100644 modules/ui/src/app/pages/general-settings/general-settings.component.spec.ts create mode 100644 modules/ui/src/app/pages/general-settings/general-settings.component.ts rename modules/ui/src/app/pages/{settings/settings.store.spec.ts => general-settings/general-settings.store.spec.ts} (70%) rename modules/ui/src/app/pages/{settings/settings.store.ts => general-settings/general-settings.store.ts} (84%) rename modules/ui/src/app/pages/{settings => general-settings}/only-different-values.validator.ts (90%) create mode 100644 modules/ui/src/app/pages/reports/components/filter-header/filter-header.component.html create mode 100644 modules/ui/src/app/pages/reports/components/filter-header/filter-header.component.scss create mode 100644 modules/ui/src/app/pages/reports/components/filter-header/filter-header.component.spec.ts create mode 100644 modules/ui/src/app/pages/reports/components/filter-header/filter-header.component.ts delete mode 100644 modules/ui/src/app/pages/reports/reports.module.ts delete mode 100644 modules/ui/src/app/pages/risk-assessment/risk-assessment.module.ts delete mode 100644 modules/ui/src/app/pages/settings/components/settings-dropdown/settings-dropdown.component.scss create mode 100644 modules/ui/src/app/pages/testrun/components/test-result-dialog/test-result-dialog.component.html create mode 100644 modules/ui/src/app/pages/testrun/components/test-result-dialog/test-result-dialog.component.scss rename modules/ui/src/app/{components/shutdown-app-modal/shutdown-app-modal.component.spec.ts => pages/testrun/components/test-result-dialog/test-result-dialog.component.spec.ts} (50%) rename modules/ui/src/app/{components/shutdown-app-modal/shutdown-app-modal.component.ts => pages/testrun/components/test-result-dialog/test-result-dialog.component.ts} (51%) delete mode 100644 modules/ui/src/app/pages/testrun/testrun.module.ts create mode 100644 modules/ui/src/app/services/local-storage.service.spec.ts create mode 100644 modules/ui/src/app/services/local-storage.service.ts create mode 100644 modules/ui/src/assets/icons/cornerstone.svg create mode 100644 modules/ui/src/assets/icons/desktop-new.svg delete mode 100644 modules/ui/src/assets/icons/devices.svg create mode 100644 modules/ui/src/assets/icons/dog.svg create mode 100644 modules/ui/src/assets/icons/empty-devices.svg create mode 100644 modules/ui/src/assets/icons/empty-profiles.svg create mode 100644 modules/ui/src/assets/icons/empty-testrun.svg delete mode 100644 modules/ui/src/assets/icons/reports.svg delete mode 100644 modules/ui/src/assets/icons/risk-assessment.svg create mode 100644 modules/ui/src/assets/icons/score.svg create mode 100644 modules/ui/src/assets/icons/switch.svg create mode 100644 modules/ui/src/theming/m3-theme.scss create mode 100644 modules/ui/src/theming/mixins.scss delete mode 100644 modules/ui/src/theming/theme.scss delete mode 100644 resources/report/header_macros.jinja delete mode 100644 resources/report/pilot-icon.png delete mode 100644 resources/report/qualification-icon.png delete mode 100644 resources/report/test_report_template.html create mode 100644 testing/device_configs/bacnet_compliant/device_config.json create mode 100644 testing/device_configs/dns_compliant/device_config.json create mode 100644 testing/device_configs/dns_non_compliant/device_config.json create mode 100644 testing/device_configs/services_compliant/device_config.json create mode 100644 testing/device_configs/services_non_compliant/device_config.json delete mode 100644 testing/device_configs/tester1/device_config.json delete mode 100644 testing/device_configs/tester2/device_config.json delete mode 100644 testing/device_configs/tester3/device_config.json create mode 100644 testing/docker/ci_test_device1/bacnet_compliant/Dockerfile create mode 100644 testing/docker/ci_test_device1/bacnet_compliant/entrypoint.py create mode 100755 testing/docker/ci_test_device1/bacnet_compliant/entrypoint.sh create mode 100644 testing/docker/ci_test_device1/dns_compliant/Dockerfile create mode 100755 testing/docker/ci_test_device1/dns_compliant/entrypoint.sh create mode 100644 testing/docker/ci_test_device1/dns_non_compliant/Dockerfile create mode 100755 testing/docker/ci_test_device1/dns_non_compliant/entrypoint.sh create mode 100644 testing/docker/ci_test_device1/services_compliant/Dockerfile create mode 100755 testing/docker/ci_test_device1/services_compliant/entrypoint.sh create mode 100644 testing/docker/ci_test_device1/services_non_compliant/Dockerfile create mode 100755 testing/docker/ci_test_device1/services_non_compliant/entrypoint.sh create mode 100644 testing/unit/conn/ifconfig/ifconfig_port_stats_post_monitor.txt create mode 100644 testing/unit/conn/ifconfig/ifconfig_port_stats_post_noncompliant_monitor.txt create mode 100644 testing/unit/conn/ifconfig/ifconfig_port_stats_pre_monitor.txt rename testing/unit/dns/captures/{ => dns_dhcp_server}/dns.pcap (100%) rename testing/unit/dns/captures/{ => dns_dhcp_server}/monitor.pcap (100%) rename testing/unit/dns/captures/{ => dns_dhcp_server}/startup.pcap (100%) create mode 100644 testing/unit/dns/captures/dns_no_dns/dns.pcap create mode 100644 testing/unit/dns/captures/dns_no_dns/monitor.pcap create mode 100644 testing/unit/dns/captures/dns_no_dns/startup.pcap create mode 100644 testing/unit/dns/captures/dns_non_dhcp_server/dns.pcap create mode 100644 testing/unit/dns/captures/dns_non_dhcp_server/monitor.pcap create mode 100644 testing/unit/dns/captures/dns_non_dhcp_server/startup.pcap diff --git a/.github/workflows/package.yml b/.github/workflows/package.yml index 1a2e219bb..9ede3a352 100644 --- a/.github/workflows/package.yml +++ b/.github/workflows/package.yml @@ -30,40 +30,6 @@ jobs: name: testrun_package path: testrun*.deb - install_package_20: - permissions: {} - needs: create_package - name: Install on Ubuntu 20.04 - runs-on: ubuntu-20.04 - timeout-minutes: 15 - steps: - - name: Checkout source - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - - name: Download package - uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8 - with: - name: testrun_package - - name: Install dependencies - shell: bash {0} - run: sudo cmd/prepare - - name: Install package - shell: bash {0} - run: sudo apt install ./testrun*.deb - - name: Start testrun - shell: bash {0} - run: sudo testrun > >(tee testrun_output.log) 2>&1 & - - name: Verify testrun started - shell: bash {0} - run: | - sleep 5 - if grep -q "API waiting for requests" testrun_output.log; then - echo "Testrun started successfully." - else - echo "Testrun did not start correctly." - cat testrun_output.log - exit 1 - fi - install_package_22: permissions: {} needs: create_package diff --git a/.github/workflows/testing.yml b/.github/workflows/testing.yml index 4e78f18b5..d8da3ada0 100644 --- a/.github/workflows/testing.yml +++ b/.github/workflows/testing.yml @@ -7,6 +7,25 @@ on: - cron: '0 13 * * *' jobs: + testrun_tests: + permissions: {} + name: Tests + runs-on: ubuntu-22.04 + timeout-minutes: 30 + steps: + - name: Checkout source + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + - name: Install dependencies + shell: bash {0} + run: cmd/prepare + - name: Install Testrun + shell: bash {0} + run: TESTRUN_DIR=. cmd/install + timeout-minutes: 30 + - name: Run tests + shell: bash {0} + run: testing/tests/test_tests + testrun_baseline: permissions: {} name: Baseline @@ -71,7 +90,7 @@ jobs: run: cmd/install -l - name: Run tests for conn module shell: bash {0} - run: bash testing/unit/run_test_module.sh conn captures ethtool output + run: bash testing/unit/run_test_module.sh conn captures ethtool ifconfig output - name: Run tests for dns module shell: bash {0} run: bash testing/unit/run_test_module.sh dns captures reports output @@ -98,7 +117,7 @@ jobs: if: ${{ always() }} with: if-no-files-found: error - name: reports_${{ github.run_id }} + name: unit_reports_${{ github.run_id }} path: testing/unit/report/output pylint: diff --git a/README.md b/README.md index 8f1739031..e6cfc8943 100644 --- a/README.md +++ b/README.md @@ -34,7 +34,7 @@ Testrun provides the network and assistive tools for engineers when manual testi ## Hardware -- PC running Ubuntu LTS 20.04, 22.04, or 24.04 (laptop or desktop) +- PC running Ubuntu LTS 22.04 or 24.04 (laptop or desktop) - 2x ethernet ports (USB ethernet adapters work too) - Internet connection diff --git a/cmd/build b/cmd/build index 8ecccb5ef..5d04e049b 100755 --- a/cmd/build +++ b/cmd/build @@ -45,7 +45,7 @@ fi if docker build -t testrun/ui -f modules/ui/ui.Dockerfile . ; then echo Successully built the user interface else - echo An error occured whilst building the user interface + echo An error occurred whilst building the user interface exit 1 fi @@ -54,7 +54,7 @@ echo Building websockets server if docker build -t testrun/ws -f modules/ws/ws.Dockerfile . ; then echo Successully built the web sockets server else - echo An error occured whilst building the websockets server + echo An error occurred whilst building the websockets server exit 1 fi @@ -66,7 +66,7 @@ for dir in modules/network/* ; do if docker build -f modules/network/$module/$module.Dockerfile -t testrun/$module . ; then echo Successfully built container for network $module else - echo An error occured whilst building container for network module $module + echo An error occurred whilst building container for network module $module exit 1 fi done @@ -79,7 +79,7 @@ for dir in modules/devices/* ; do if docker build -f modules/devices/$module/$module.Dockerfile -t testrun/$module . ; then echo Successfully built container for device module $module else - echo An error occured whilst building container for device module $module + echo An error occurred whilst building container for device module $module exit 1 fi done @@ -92,7 +92,7 @@ for dir in modules/test/* ; do if docker build -f modules/test/$module/$module.Dockerfile -t testrun/$module-test . ; then echo Successfully built container for test module $module else - echo An error occured whilst building container for test module $module + echo An error occurred whilst building container for test module $module exit 1 fi done diff --git a/cmd/build_ui b/cmd/build_ui index 17ccf0c3c..bbfc53764 100755 --- a/cmd/build_ui +++ b/cmd/build_ui @@ -21,7 +21,7 @@ echo Building the ui builder if docker build -t testrun/build-ui -f modules/ui/build.Dockerfile . ; then echo Successully built the ui builder else - echo An error occured whilst building the ui builder + echo An error occurred whilst building the ui builder exit 1 fi diff --git a/docs/additional_config.md b/docs/additional_config.md new file mode 100644 index 000000000..ea9aaaf51 --- /dev/null +++ b/docs/additional_config.md @@ -0,0 +1,121 @@ +# Additional Configuration Options + +Some configuration options are available but not exposed through the user interface and requires direct access. +Modification of various configuration files is necessary to access these options. + +## Override test module timeout at the system level + +Testrun attempts to set reasonable timeouts for test modules to prevent overly long test times but sometimes +a device or series of device may require longer than these default values. These can be overridden at +the test module configuration level but is not preferred since these changes will be undone during every +version upgrade. To modify these values: + +1. Navigate to the testrun installation directory. By default, this will be at: + `/usr/local/testrun` + +2. Open the system.json file and add the following section: + `"test_modules":{}` + +3. Add the module name(s) and timeout property into this test_modules section you wish to +set the timeout property for: + ``` + "test_modules":{ + "connection":{ + "timeout": 500 + } + } + ``` + +Before timeout options: +``` +{ + "network": { + "device_intf": "ens0", + "internet_intf": "ens1" + }, + "log_level": "DEBUG", + "startup_timeout": 60, + "monitor_period": 60, + "max_device_reports": 5, + "org_name": "", + "single_intf": false + } +``` + +After timeout options: +``` +{ + "network": { + "device_intf": "ens0", + "internet_intf": "ens1" + }, + "log_level": "DEBUG", + "startup_timeout": 60, + "monitor_period": 60, + "max_device_reports": 5, + "org_name": "", + "single_intf": false, + "test_modules":{ + "connection":{ + "timeout": 500 + } + } +} +``` + +## Override test module log level at the system level + +Test modules default to the log level info to prevent unecessary logging. These can be overridden at the test module configuration level but is not preferred since these changes will be undone during every version upgrade. To modify these values: + +1. Navigate to the testrun installation directory. By default, this will be at: + `/usr/local/testrun` + +2. Open the system.json file and add the following section: + `"test_modules":{}` + +3. Add the module name(s) and log_level property into this test_modules section you wish to +set the log_level property for: + ``` + "test_modules":{ + "connection":{ + "log_level": "DEGUG" + } + } + ``` +Valid options for modifying the log level are: INFO, DEBUG, WARNING, ERROR. + +Before log_level options: +``` +{ + "network": { + "device_intf": "ens0", + "internet_intf": "ens1" + }, + "log_level": "DEBUG", + "startup_timeout": 60, + "monitor_period": 60, + "max_device_reports": 5, + "org_name": "", + "single_intf": false + } +``` + +After log_level options: +``` +{ + "network": { + "device_intf": "ens0", + "internet_intf": "ens1" + }, + "log_level": "DEBUG", + "startup_timeout": 60, + "monitor_period": 60, + "max_device_reports": 5, + "org_name": "", + "single_intf": false, + "test_modules":{ + "connection":{ + "log_level": "DEBUG" + } + } +``` \ No newline at end of file diff --git a/docs/dev/mockoon.json b/docs/dev/mockoon.json index 394800402..8e9590e76 100644 --- a/docs/dev/mockoon.json +++ b/docs/dev/mockoon.json @@ -604,8 +604,8 @@ "endpoint": "reports", "responses": [ { - "uuid": "9536ff4c-f97f-4880-b9fc-f477686ad6b8", - "body": "[\n {\n \"mac_addr\": \"00:1e:42:35:73:c6\",\n \"device\": {\n \"mac_addr\": \"00:1e:42:35:73:c4\",\n \"manufacturer\": \"Teltonika\",\n \"model\": \"TRB140\",\n \"firmware\": \"1.2.3\",\n \"test_modules\": {\n \"connection\": {\n \"enabled\": false\n },\n \"ntp\": {\n \"enabled\": true\n },\n \"dns\": {\n \"enabled\": true\n },\n \"services\": {\n \"enabled\": true\n },\n \"tls\": {\n \"enabled\": true\n },\n \"protocol\": {\n \"enabled\": true\n }\n }\n },\n \"status\": \"Non-Compliant\",\n \"started\": \"2024-05-03 12:09:59\",\n \"finished\": \"2024-05-03 12:15:51\",\n \"tests\": {\n \"total\": 20,\n \"results\": [\n {\n \"name\": \"protocol.valid_bacnet\",\n \"description\": \"BACnet discovery could not resolve any devices\",\n \"expected_behavior\": \"BACnet traffic can be seen on the network and packets are valid and not malformed\",\n \"required_result\": \"Recommended\",\n \"result\": \"Skipped\"\n },\n {\n \"name\": \"protocol.bacnet.version\",\n \"description\": \"No BACnet devices discovered.\",\n \"expected_behavior\": \"The BACnet client implements an up to date version of BACnet\",\n \"required_result\": \"Recommended\",\n \"result\": \"Skipped\"\n },\n {\n \"name\": \"protocol.valid_modbus\",\n \"description\": \"Failed to establish Modbus connection to device\",\n \"expected_behavior\": \"Any Modbus functionality works as expected and valid Modbus traffic can be observed\",\n \"required_result\": \"Recommended\",\n \"result\": \"Non-Compliant\"\n },\n {\n \"name\": \"ntp.network.ntp_support\",\n \"description\": \"Device sent NTPv3 packets. NTPv3 is not allowed.\",\n \"expected_behavior\": \"The device sends an NTPv4 request to the configured NTP server.\",\n \"required_result\": \"Required\",\n \"result\": \"Non-Compliant\"\n },\n {\n \"name\": \"ntp.network.ntp_dhcp\",\n \"description\": \"Device sent NTP request to non-DHCP provided server\",\n \"expected_behavior\": \"Device can accept NTP server address, provided by the DHCP server (DHCP OFFER PACKET)\",\n \"required_result\": \"Roadmap\",\n \"result\": \"Non-Compliant\"\n },\n {\n \"name\": \"security.services.ftp\",\n \"description\": \"No FTP server found\",\n \"expected_behavior\": \"There is no FTP service running on any port\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"security.ssh.version\",\n \"description\": \"SSH server found running protocol 2.0\",\n \"expected_behavior\": \"SSH server is not running or server is SSHv2\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"security.services.telnet\",\n \"description\": \"No telnet server found\",\n \"expected_behavior\": \"There is no Telnet service running on any port\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"security.services.smtp\",\n \"description\": \"No SMTP server found\",\n \"expected_behavior\": \"There is no SMTP service running on any port\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"security.services.http\",\n \"description\": \"Found HTTP server running on port 80/tcp\",\n \"expected_behavior\": \"Device is unreachable on port 80 (or any other port) and only responds to HTTPS requests on port 443 (or any other port if HTTP is used at all)\",\n \"required_result\": \"Required\",\n \"result\": \"Non-Compliant\"\n },\n {\n \"name\": \"security.services.pop\",\n \"description\": \"No POP server found\",\n \"expected_behavior\": \"There is no POP service running on any port\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"security.services.imap\",\n \"description\": \"No IMAP server found\",\n \"expected_behavior\": \"There is no IMAP service running on any port\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"security.services.snmpv3\",\n \"description\": \"No SNMP server found\",\n \"expected_behavior\": \"Device is unreachable on port 161 (or any other port) and device is unreachable on port 162 (or any other port) unless SNMP is essential in which case it is SNMPv3 is used.\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"security.services.vnc\",\n \"description\": \"No VNC server found\",\n \"expected_behavior\": \"Device cannot be accessed / connected to via VNC on any port\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"security.services.tftp\",\n \"description\": \"No TFTP server found\",\n \"expected_behavior\": \"There is no TFTP service running on any port\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"ntp.network.ntp_server\",\n \"description\": \"No NTP server found\",\n \"expected_behavior\": \"The device does not respond to NTP requests when it's IP is set as the NTP server on another device\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"dns.network.hostname_resolution\",\n \"description\": \"DNS traffic detected from device\",\n \"expected_behavior\": \"The device sends DNS requests.\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"dns.network.from_dhcp\",\n \"description\": \"DNS traffic detected only to DHCP provided server\",\n \"expected_behavior\": \"The device sends DNS requests to the DNS server provided by the DHCP server\",\n \"required_result\": \"Roadmap\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"security.tls.v1_2_server\",\n \"description\": \"TLS 1.2 not validated: Certificate has expired\\nEC key length passed: 256 >= 224\\nDevice certificate has not been signed\\nTLS 1.3 not validated: Certificate has expired\\nEC key length passed: 256 >= 224\\nDevice certificate has not been signed\",\n \"expected_behavior\": \"TLS 1.2 certificate is issued to the web browser client when accessed\",\n \"required_result\": \"Required\",\n \"result\": \"Non-Compliant\"\n },\n {\n \"name\": \"security.tls.v1_2_client\",\n \"description\": \"No outbound TLS connections were found.\",\n \"expected_behavior\": \"The packet indicates a TLS connection with at least TLS 1.2 and support for ECDH and ECDSA ciphers\",\n \"required_result\": \"Required\",\n \"result\": \"Skipped\"\n }\n ]\n },\n \"report\": \"http://localhost:8000/report/123 123/2024-05-03T12:09:59\"\n }\n]", + "uuid": "6adc954a-55c9-40ed-8f49-cf38f659d883", + "body": "[\n {\n \"mac_addr\": \"00:1e:42:35:73:c6\",\n \"device\": {\n \"mac_addr\": \"00:1e:42:35:73:c4\",\n \"manufacturer\": \"Teltonika\",\n \"model\": \"TRB140\",\n \"firmware\": \"1.2.3\",\n \"test_modules\": {\n \"connection\": {\n \"enabled\": false\n },\n \"ntp\": {\n \"enabled\": true\n },\n \"dns\": {\n \"enabled\": true\n },\n \"services\": {\n \"enabled\": true\n },\n \"tls\": {\n \"enabled\": true\n },\n \"protocol\": {\n \"enabled\": true\n }\n }\n },\n \"status\": \"Non-Compliant\",\n \"started\": \"2024-05-03 12:09:59\",\n \"finished\": \"2024-05-03 12:15:51\",\n \"tests\": {\n \"total\": 20,\n \"results\": [\n {\n \"name\": \"protocol.valid_bacnet\",\n \"description\": \"BACnet discovery could not resolve any devices\",\n \"expected_behavior\": \"BACnet traffic can be seen on the network and packets are valid and not malformed\",\n \"required_result\": \"Recommended\",\n \"result\": \"Skipped\"\n },\n {\n \"name\": \"protocol.bacnet.version\",\n \"description\": \"No BACnet devices discovered.\",\n \"expected_behavior\": \"The BACnet client implements an up to date version of BACnet\",\n \"required_result\": \"Recommended\",\n \"result\": \"Skipped\"\n },\n {\n \"name\": \"protocol.valid_modbus\",\n \"description\": \"Failed to establish Modbus connection to device\",\n \"expected_behavior\": \"Any Modbus functionality works as expected and valid Modbus traffic can be observed\",\n \"required_result\": \"Recommended\",\n \"result\": \"Non-Compliant\"\n },\n {\n \"name\": \"ntp.network.ntp_support\",\n \"description\": \"Device sent NTPv3 packets. NTPv3 is not allowed.\",\n \"expected_behavior\": \"The device sends an NTPv4 request to the configured NTP server.\",\n \"required_result\": \"Required\",\n \"result\": \"Non-Compliant\"\n },\n {\n \"name\": \"ntp.network.ntp_dhcp\",\n \"description\": \"Device sent NTP request to non-DHCP provided server\",\n \"expected_behavior\": \"Device can accept NTP server address, provided by the DHCP server (DHCP OFFER PACKET)\",\n \"required_result\": \"Roadmap\",\n \"result\": \"Non-Compliant\"\n },\n {\n \"name\": \"security.services.ftp\",\n \"description\": \"No FTP server found\",\n \"expected_behavior\": \"There is no FTP service running on any port\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"security.ssh.version\",\n \"description\": \"SSH server found running protocol 2.0\",\n \"expected_behavior\": \"SSH server is not running or server is SSHv2\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"security.services.telnet\",\n \"description\": \"No telnet server found\",\n \"expected_behavior\": \"There is no Telnet service running on any port\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"security.services.smtp\",\n \"description\": \"No SMTP server found\",\n \"expected_behavior\": \"There is no SMTP service running on any port\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"security.services.http\",\n \"description\": \"Found HTTP server running on port 80/tcp\",\n \"expected_behavior\": \"Device is unreachable on port 80 (or any other port) and only responds to HTTPS requests on port 443 (or any other port if HTTP is used at all)\",\n \"required_result\": \"Required\",\n \"result\": \"Non-Compliant\"\n },\n {\n \"name\": \"security.services.pop\",\n \"description\": \"No POP server found\",\n \"expected_behavior\": \"There is no POP service running on any port\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"security.services.imap\",\n \"description\": \"No IMAP server found\",\n \"expected_behavior\": \"There is no IMAP service running on any port\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"security.services.snmpv3\",\n \"description\": \"No SNMP server found\",\n \"expected_behavior\": \"Device is unreachable on port 161 (or any other port) and device is unreachable on port 162 (or any other port) unless SNMP is essential in which case it is SNMPv3 is used.\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"security.services.vnc\",\n \"description\": \"No VNC server found\",\n \"expected_behavior\": \"Device cannot be accessed / connected to via VNC on any port\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"security.services.tftp\",\n \"description\": \"No TFTP server found\",\n \"expected_behavior\": \"There is no TFTP service running on any port\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"ntp.network.ntp_server\",\n \"description\": \"No NTP server found\",\n \"expected_behavior\": \"The device does not respond to NTP requests when it's IP is set as the NTP server on another device\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"dns.network.hostname_resolution\",\n \"description\": \"DNS traffic detected from device\",\n \"expected_behavior\": \"The device sends DNS requests.\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"dns.network.from_dhcp\",\n \"description\": \"DNS traffic detected only to DHCP provided server\",\n \"expected_behavior\": \"The device sends DNS requests to the DNS server provided by the DHCP server\",\n \"required_result\": \"Roadmap\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"security.tls.v1_2_server\",\n \"description\": \"TLS 1.2 not validated: Certificate has expired\\nEC key length passed: 256 >= 224\\nDevice certificate has not been signed\\nTLS 1.3 not validated: Certificate has expired\\nEC key length passed: 256 >= 224\\nDevice certificate has not been signed\",\n \"expected_behavior\": \"TLS 1.2 certificate is issued to the web browser client when accessed\",\n \"required_result\": \"Required\",\n \"result\": \"Non-Compliant\"\n },\n {\n \"name\": \"security.tls.v1_2_client\",\n \"description\": \"No outbound TLS connections were found.\",\n \"expected_behavior\": \"The packet indicates a TLS connection with at least TLS 1.2 and support for ECDH and ECDSA ciphers\",\n \"required_result\": \"Required\",\n \"result\": \"Skipped\"\n }\n ]\n },\n \"report\": \"http://localhost:8000/report/123 123/2024-05-03T12:09:59\",\n \"export\": \"http://localhost:8000/export/123 123/2024-05-03T12:09:59\"\n \n }\n]", "latency": 0, "statusCode": 200, "label": "", @@ -885,10 +885,10 @@ }, { "uuid": "220e4ba9-6463-4dc3-b714-f77643706b7d", - "body": "{\n \"error\": \"An error occured whilst deleting the report\"\n}", + "body": "{\n \"error\": \"An error occurred whilst deleting the report\"\n}", "latency": 0, "statusCode": 500, - "label": "Error occured", + "label": "Error occurred", "headers": [], "bodyType": "INLINE", "filePath": "", @@ -971,10 +971,10 @@ }, { "uuid": "a7fbb2c8-81dc-4a40-80d6-482119314086", - "body": "{\n \"error\": \"An error occured whilst getting the report\"\n}", + "body": "{\n \"error\": \"An error occurred whilst getting the report\"\n}", "latency": 0, "statusCode": 500, - "label": "Error occured", + "label": "Error occurred", "headers": [], "bodyType": "INLINE", "filePath": "", @@ -1151,10 +1151,10 @@ }, { "uuid": "3bcb2d6d-3290-43bb-8392-7bfffda4feae", - "body": "{\n \"error\": \"An error occured whilst getting the test attempt\"\n}", + "body": "{\n \"error\": \"An error occurred whilst getting the test attempt\"\n}", "latency": 0, "statusCode": 500, - "label": "Error occured", + "label": "Error occurred", "headers": [], "bodyType": "INLINE", "filePath": "", @@ -1691,7 +1691,7 @@ "body": "{\n \"error\": \"Error retrieving the profile PDF\"\n}", "latency": 0, "statusCode": 500, - "label": "Error occured", + "label": "Error occurred", "headers": [], "bodyType": "INLINE", "filePath": "", diff --git a/docs/dev/postman.json b/docs/dev/postman.json index 1e8a9cafb..08369ac55 100644 --- a/docs/dev/postman.json +++ b/docs/dev/postman.json @@ -1068,7 +1068,7 @@ } ], "cookie": [], - "body": "[\n {\n \"testrun\": {\n \"version\": \"2.0\"\n },\n \"mac_addr\": \"00:1e:42:28:9e:4a\",\n \"device\": {\n \"mac_addr\": \"00:1e:42:28:9e:4a\",\n \"manufacturer\": \"Teltonika\",\n \"model\": \"TRB140\",\n \"firmware\": \"test\",\n \"test_modules\": {\n \"protocol\": {\n \"enabled\": true\n },\n \"services\": {\n \"enabled\": true\n },\n \"ntp\": {\n \"enabled\": true\n },\n \"tls\": {\n \"enabled\": true\n },\n \"connection\": {\n \"enabled\": true\n },\n \"dns\": {\n \"enabled\": true\n }\n }\n },\n \"status\": \"Non-Compliant\",\n \"started\": \"2000-01-01 00:00:00\",\n \"finished\": \"2000-01-01 00:30:00\",\n \"tests\": {\n \"total\": 40,\n \"results\": [\n {\n \"name\": \"protocol.valid_bacnet\",\n \"description\": \"BACnet device could not be discovered\",\n \"expected_behavior\": \"BACnet traffic can be seen on the network and packets are valid and not malformed\",\n \"required_result\": \"Recommended\",\n \"result\": \"Feature Not Detected\"\n },\n {\n \"name\": \"protocol.bacnet.version\",\n \"description\": \"Device did not respond to BACnet discovery\",\n \"expected_behavior\": \"The BACnet client implements an up to date version of BACnet\",\n \"required_result\": \"Recommended\",\n \"result\": \"Feature Not Detected\"\n },\n {\n \"name\": \"protocol.valid_modbus\",\n \"description\": \"Device did not respond to Modbus connection\",\n \"expected_behavior\": \"Any Modbus functionality works as expected and valid Modbus traffic can be observed\",\n \"required_result\": \"Recommended\",\n \"result\": \"Feature Not Detected\"\n },\n {\n \"name\": \"security.services.ftp\",\n \"description\": \"No FTP server found\",\n \"expected_behavior\": \"There is no FTP service running on any port\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"security.ssh.version\",\n \"description\": \"SSH server found running protocol 2.0\",\n \"expected_behavior\": \"SSH server is not running or server is SSHv2\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"security.services.telnet\",\n \"description\": \"No telnet server found\",\n \"expected_behavior\": \"There is no Telnet service running on any port\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"security.services.smtp\",\n \"description\": \"No SMTP server found\",\n \"expected_behavior\": \"There is no SMTP service running on any port\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"security.services.http\",\n \"description\": \"Found HTTP server running on port 80/tcp\",\n \"expected_behavior\": \"Device is unreachable on port 80 (or any other port) and only responds to HTTPS requests on port 443 (or any other port if HTTP is used at all)\",\n \"required_result\": \"Required\",\n \"result\": \"Non-Compliant\",\n \"recommendations\": [\n \"Disable all unsecure HTTP servers\",\n \"Setup TLS on the web server\"\n ]\n },\n {\n \"name\": \"security.services.pop\",\n \"description\": \"No POP server found\",\n \"expected_behavior\": \"There is no POP service running on any port\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"security.services.imap\",\n \"description\": \"No IMAP server found\",\n \"expected_behavior\": \"There is no IMAP service running on any port\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"security.services.snmpv3\",\n \"description\": \"No SNMP server found\",\n \"expected_behavior\": \"Device is unreachable on port 161 (or any other port) and device is unreachable on port 162 (or any other port) unless SNMP is essential in which case it is SNMPv3 is used.\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"security.services.vnc\",\n \"description\": \"No VNC server found\",\n \"expected_behavior\": \"Device cannot be accessed / connected to via VNC on any port\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"security.services.tftp\",\n \"description\": \"No TFTP server found\",\n \"expected_behavior\": \"There is no TFTP service running on any port\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"ntp.network.ntp_server\",\n \"description\": \"No NTP server found\",\n \"expected_behavior\": \"The device does not respond to NTP requests when it's IP is set as the NTP server on another device\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"connection.port_link\",\n \"description\": \"No port errors detected\",\n \"expected_behavior\": \"When the etherent cable is connected to the port, the device triggers the port to its enabled \\\"Link UP\\\" (LEDs illuminate on device and switch ports if present) state, and the switch shows no errors with the LEDs and when interrogated with a \\\"show interface\\\" command on most network switches.\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"connection.port_speed\",\n \"description\": \"Succesfully auto-negotiated speeds above 10 Mbps\",\n \"expected_behavior\": \"When the ethernet cable is connected to the port, the device autonegotiates a speed that can be checked with the \\\"show interface\\\" command on most network switches. The output of this command must also show that the \\\"configured speed\\\" is set to \\\"auto\\\".\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"connection.port_duplex\",\n \"description\": \"Succesfully auto-negotiated full duplex\",\n \"expected_behavior\": \"When the ethernet cable is connected to the port, the device autonegotiates a full-duplex connection.\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"connection.switch.arp_inspection\",\n \"description\": \"Device uses ARP\",\n \"expected_behavior\": \"Device continues to operate correctly when ARP inspection is enabled on the switch. No functionality is lost with ARP inspection enabled.\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"connection.switch.dhcp_snooping\",\n \"description\": \"Device does not act as a DHCP server\",\n \"expected_behavior\": \"Device continues to operate correctly when DHCP snooping is enabled on the switch.\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"connection.dhcp_address\",\n \"description\": \"Device responded to leased ip address\",\n \"expected_behavior\": \"The device is not setup with a static IP address. The device accepts an IP address from a DHCP server (RFC 2131) and responds succesfully to an ICMP echo (ping) request.\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"connection.mac_address\",\n \"description\": \"MAC address found: 00:1e:42:28:9e:4a\",\n \"expected_behavior\": \"N/A\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"connection.mac_oui\",\n \"description\": \"OUI Manufacturer found: Teltonika\",\n \"expected_behavior\": \"The MAC address prefix is registered in the IEEE Organizationally Unique Identifier database.\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"connection.private_address\",\n \"description\": \"All subnets are supported\",\n \"expected_behavior\": \"The device under test accepts IP addresses within all ranges specified in RFC 1918 and communicates using these addresses. The Internet Assigned Numbers Authority (IANA) has reserved the following three blocks of the IP address space for private internets. 10.0.0.0 - 10.255.255.255.255 (10/8 prefix). 172.16.0.0 - 172.31.255.255 (172.16/12 prefix). 192.168.0.0 - 192.168.255.255 (192.168/16 prefix)\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"connection.shared_address\",\n \"description\": \"All subnets are supported\",\n \"expected_behavior\": \"The device under test accepts IP addresses within the ranges specified in RFC 6598 and communicates using these addresses\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"connection.dhcp_disconnect\",\n \"description\": \"An error occured whilst running this test\",\n \"expected_behavior\": \"A client SHOULD use DHCP to reacquire or verify its IP address and network parameters whenever the local network parameters may have changed; e.g., at system boot time or after a disconnection from the local network, as the local network configuration may change without the client's or user's knowledge. If a client has knowledge ofa previous network address and is unable to contact a local DHCP server, the client may continue to use the previous network addres until the lease for that address expires. If the lease expires before the client can contact a DHCP server, the client must immediately discontinue use of the previous network address and may inform local users of the problem.\",\n \"required_result\": \"Required\",\n \"result\": \"Error\"\n },\n {\n \"name\": \"connection.single_ip\",\n \"description\": \"Device is using multiple IP addresses\",\n \"expected_behavior\": \"The device under test does not behave as a network switch and only requets one IP address. This test is to avoid that devices implement network switches that allow connecting strings of daisy chained devices to one single network port, as this would not make 802.1x port based authentication possible.\",\n \"required_result\": \"Required\",\n \"result\": \"Non-Compliant\",\n \"recommendations\": [\n \"Ensure that all ports on the device are isolated\",\n \"Ensure only one DHCP client is running\"\n ]\n },\n {\n \"name\": \"connection.target_ping\",\n \"description\": \"Device responds to ping\",\n \"expected_behavior\": \"The device under test responds to an ICMP echo (ping) request.\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"connection.ipaddr.ip_change\",\n \"description\": \"Device has accepted an IP address change\",\n \"expected_behavior\": \"If the lease expires before the client receiveds a DHCPACK, the client moves to INIT state, MUST immediately stop any other network processing and requires network initialization parameters as if the client were uninitialized. If the client then receives a DHCPACK allocating the client its previous network addres, the client SHOULD continue network processing. If the client is given a new network address, it MUST NOT continue using the previous network address and SHOULD notify the local users of the problem.\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"connection.ipaddr.dhcp_failover\",\n \"description\": \"Secondary DHCP server lease confirmed active in device\",\n \"expected_behavior\": \"\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"connection.ipv6_slaac\",\n \"description\": \"Device does not support IPv6 SLAAC\",\n \"expected_behavior\": \"The device under test complies with RFC4862 and forms a valid IPv6 SLAAC address\",\n \"required_result\": \"Required\",\n \"result\": \"Non-Compliant\",\n \"recommendations\": [\n \"Install a network manager that supports IPv6\",\n \"Disable DHCPv6\"\n ]\n },\n {\n \"name\": \"connection.ipv6_ping\",\n \"description\": \"No IPv6 SLAAC address found. Cannot ping\",\n \"expected_behavior\": \"The device responds to the ping as per RFC4443\",\n \"required_result\": \"Required\",\n \"result\": \"Non-Compliant\",\n \"recommendations\": [\n \"Enable ping response to IPv6 ICMP requests in network manager settings\",\n \"Create a firewall exception to allow ICMPv6 via LAN\"\n ]\n },\n {\n \"name\": \"security.tls.v1_2_server\",\n \"description\": \"TLS 1.2 certificate is invalid\",\n \"expected_behavior\": \"TLS 1.2 certificate is issued to the web browser client when accessed\",\n \"required_result\": \"Required if Applicable\",\n \"result\": \"Non-Compliant\",\n \"recommendations\": [\n \"Enable TLS 1.2 support in the web server configuration\",\n \"Disable TLS 1.0 and 1.1\",\n \"Sign the certificate used by the web server\"\n ]\n },\n {\n \"name\": \"security.tls.v1_2_client\",\n \"description\": \"No outbound TLS connections were found\",\n \"expected_behavior\": \"The packet indicates a TLS connection with at least TLS 1.2 and support for ECDH and ECDSA ciphers\",\n \"required_result\": \"Required if Applicable\",\n \"result\": \"Feature Not Detected\"\n },\n {\n \"name\": \"security.tls.v1_3_server\",\n \"description\": \"TLS 1.3 certificate is invalid\",\n \"expected_behavior\": \"TLS 1.3 certificate is issued to the web browser client when accessed\",\n \"required_result\": \"Informational\",\n \"result\": \"Informational\"\n },\n {\n \"name\": \"security.tls.v1_3_client\",\n \"description\": \"No outbound TLS connections were found\",\n \"expected_behavior\": \"The packet indicates a TLS connection with at least TLS 1.3\",\n \"required_result\": \"Informational\",\n \"result\": \"Informational\"\n },\n {\n \"name\": \"ntp.network.ntp_support\",\n \"description\": \"Device has not sent any NTP requests\",\n \"expected_behavior\": \"The device sends an NTPv4 request to the configured NTP server.\",\n \"required_result\": \"Required\",\n \"result\": \"Non-Compliant\",\n \"recommendations\": [\n \"Set the NTP version to v4 in the NTP client\",\n \"Install an NTP client that supports NTPv4\"\n ]\n },\n {\n \"name\": \"ntp.network.ntp_dhcp\",\n \"description\": \"Device has not sent any NTP requests\",\n \"expected_behavior\": \"Device can accept NTP server address, provided by the DHCP server (DHCP OFFER PACKET)\",\n \"required_result\": \"Roadmap\",\n \"result\": \"Feature Not Detected\"\n },\n {\n \"name\": \"dns.network.hostname_resolution\",\n \"description\": \"DNS traffic detected from device\",\n \"expected_behavior\": \"The device sends DNS requests.\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"dns.network.from_dhcp\",\n \"description\": \"DNS traffic detected only to DHCP provided server\",\n \"expected_behavior\": \"The device sends DNS requests to the DNS server provided by the DHCP server\",\n \"required_result\": \"Informational\",\n \"result\": \"Informational\"\n },\n {\n \"name\": \"dns.mdns\",\n \"description\": \"No MDNS traffic detected from the device\",\n \"expected_behavior\": \"Device may send MDNS requests\",\n \"required_result\": \"Informational\",\n \"result\": \"Informational\"\n }\n ]\n },\n \"report\": \"http://localhost:8000/report/Teltonika TRB140/2024-09-10T13:19:24\"\n }\n]" + "body": "[\n {\n \"testrun\": {\n \"version\": \"2.0\"\n },\n \"mac_addr\": \"00:1e:42:28:9e:4a\",\n \"device\": {\n \"mac_addr\": \"00:1e:42:28:9e:4a\",\n \"manufacturer\": \"Teltonika\",\n \"model\": \"TRB140\",\n \"firmware\": \"test\",\n \"test_modules\": {\n \"protocol\": {\n \"enabled\": true\n },\n \"services\": {\n \"enabled\": true\n },\n \"ntp\": {\n \"enabled\": true\n },\n \"tls\": {\n \"enabled\": true\n },\n \"connection\": {\n \"enabled\": true\n },\n \"dns\": {\n \"enabled\": true\n }\n }\n },\n \"status\": \"Non-Compliant\",\n \"started\": \"2000-01-01 00:00:00\",\n \"finished\": \"2000-01-01 00:30:00\",\n \"tests\": {\n \"total\": 40,\n \"results\": [\n {\n \"name\": \"protocol.valid_bacnet\",\n \"description\": \"BACnet device could not be discovered\",\n \"expected_behavior\": \"BACnet traffic can be seen on the network and packets are valid and not malformed\",\n \"required_result\": \"Recommended\",\n \"result\": \"Feature Not Detected\"\n },\n {\n \"name\": \"protocol.bacnet.version\",\n \"description\": \"Device did not respond to BACnet discovery\",\n \"expected_behavior\": \"The BACnet client implements an up to date version of BACnet\",\n \"required_result\": \"Recommended\",\n \"result\": \"Feature Not Detected\"\n },\n {\n \"name\": \"protocol.valid_modbus\",\n \"description\": \"Device did not respond to Modbus connection\",\n \"expected_behavior\": \"Any Modbus functionality works as expected and valid Modbus traffic can be observed\",\n \"required_result\": \"Recommended\",\n \"result\": \"Feature Not Detected\"\n },\n {\n \"name\": \"security.services.ftp\",\n \"description\": \"No FTP server found\",\n \"expected_behavior\": \"There is no FTP service running on any port\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"security.ssh.version\",\n \"description\": \"SSH server found running protocol 2.0\",\n \"expected_behavior\": \"SSH server is not running or server is SSHv2\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"security.services.telnet\",\n \"description\": \"No telnet server found\",\n \"expected_behavior\": \"There is no Telnet service running on any port\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"security.services.smtp\",\n \"description\": \"No SMTP server found\",\n \"expected_behavior\": \"There is no SMTP service running on any port\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"security.services.http\",\n \"description\": \"Found HTTP server running on port 80/tcp\",\n \"expected_behavior\": \"Device is unreachable on port 80 (or any other port) and only responds to HTTPS requests on port 443 (or any other port if HTTP is used at all)\",\n \"required_result\": \"Required\",\n \"result\": \"Non-Compliant\",\n \"recommendations\": [\n \"Disable all unsecure HTTP servers\",\n \"Setup TLS on the web server\"\n ]\n },\n {\n \"name\": \"security.services.pop\",\n \"description\": \"No POP server found\",\n \"expected_behavior\": \"There is no POP service running on any port\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"security.services.imap\",\n \"description\": \"No IMAP server found\",\n \"expected_behavior\": \"There is no IMAP service running on any port\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"security.services.snmpv3\",\n \"description\": \"No SNMP server found\",\n \"expected_behavior\": \"Device is unreachable on port 161 (or any other port) and device is unreachable on port 162 (or any other port) unless SNMP is essential in which case it is SNMPv3 is used.\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"security.services.vnc\",\n \"description\": \"No VNC server found\",\n \"expected_behavior\": \"Device cannot be accessed / connected to via VNC on any port\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"security.services.tftp\",\n \"description\": \"No TFTP server found\",\n \"expected_behavior\": \"There is no TFTP service running on any port\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"ntp.network.ntp_server\",\n \"description\": \"No NTP server found\",\n \"expected_behavior\": \"The device does not respond to NTP requests when it's IP is set as the NTP server on another device\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"connection.port_link\",\n \"description\": \"No port errors detected\",\n \"expected_behavior\": \"When the etherent cable is connected to the port, the device triggers the port to its enabled \\\"Link UP\\\" (LEDs illuminate on device and switch ports if present) state, and the switch shows no errors with the LEDs and when interrogated with a \\\"show interface\\\" command on most network switches.\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"connection.port_speed\",\n \"description\": \"Succesfully auto-negotiated speeds above 10 Mbps\",\n \"expected_behavior\": \"When the ethernet cable is connected to the port, the device autonegotiates a speed that can be checked with the \\\"show interface\\\" command on most network switches. The output of this command must also show that the \\\"configured speed\\\" is set to \\\"auto\\\".\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"connection.port_duplex\",\n \"description\": \"Succesfully auto-negotiated full duplex\",\n \"expected_behavior\": \"When the ethernet cable is connected to the port, the device autonegotiates a full-duplex connection.\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"connection.switch.arp_inspection\",\n \"description\": \"Device uses ARP\",\n \"expected_behavior\": \"Device continues to operate correctly when ARP inspection is enabled on the switch. No functionality is lost with ARP inspection enabled.\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"connection.switch.dhcp_snooping\",\n \"description\": \"Device does not act as a DHCP server\",\n \"expected_behavior\": \"Device continues to operate correctly when DHCP snooping is enabled on the switch.\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"connection.dhcp_address\",\n \"description\": \"Device responded to leased ip address\",\n \"expected_behavior\": \"The device is not setup with a static IP address. The device accepts an IP address from a DHCP server (RFC 2131) and responds succesfully to an ICMP echo (ping) request.\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"connection.mac_address\",\n \"description\": \"MAC address found: 00:1e:42:28:9e:4a\",\n \"expected_behavior\": \"N/A\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"connection.mac_oui\",\n \"description\": \"OUI Manufacturer found: Teltonika\",\n \"expected_behavior\": \"The MAC address prefix is registered in the IEEE Organizationally Unique Identifier database.\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"connection.private_address\",\n \"description\": \"All subnets are supported\",\n \"expected_behavior\": \"The device under test accepts IP addresses within all ranges specified in RFC 1918 and communicates using these addresses. The Internet Assigned Numbers Authority (IANA) has reserved the following three blocks of the IP address space for private internets. 10.0.0.0 - 10.255.255.255.255 (10/8 prefix). 172.16.0.0 - 172.31.255.255 (172.16/12 prefix). 192.168.0.0 - 192.168.255.255 (192.168/16 prefix)\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"connection.shared_address\",\n \"description\": \"All subnets are supported\",\n \"expected_behavior\": \"The device under test accepts IP addresses within the ranges specified in RFC 6598 and communicates using these addresses\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"connection.dhcp_disconnect\",\n \"description\": \"An error occurred whilst running this test\",\n \"expected_behavior\": \"A client SHOULD use DHCP to reacquire or verify its IP address and network parameters whenever the local network parameters may have changed; e.g., at system boot time or after a disconnection from the local network, as the local network configuration may change without the client's or user's knowledge. If a client has knowledge ofa previous network address and is unable to contact a local DHCP server, the client may continue to use the previous network addres until the lease for that address expires. If the lease expires before the client can contact a DHCP server, the client must immediately discontinue use of the previous network address and may inform local users of the problem.\",\n \"required_result\": \"Required\",\n \"result\": \"Error\"\n },\n {\n \"name\": \"connection.single_ip\",\n \"description\": \"Device is using multiple IP addresses\",\n \"expected_behavior\": \"The device under test does not behave as a network switch and only requets one IP address. This test is to avoid that devices implement network switches that allow connecting strings of daisy chained devices to one single network port, as this would not make 802.1x port based authentication possible.\",\n \"required_result\": \"Required\",\n \"result\": \"Non-Compliant\",\n \"recommendations\": [\n \"Ensure that all ports on the device are isolated\",\n \"Ensure only one DHCP client is running\"\n ]\n },\n {\n \"name\": \"connection.target_ping\",\n \"description\": \"Device responds to ping\",\n \"expected_behavior\": \"The device under test responds to an ICMP echo (ping) request.\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"connection.ipaddr.ip_change\",\n \"description\": \"Device has accepted an IP address change\",\n \"expected_behavior\": \"If the lease expires before the client receiveds a DHCPACK, the client moves to INIT state, MUST immediately stop any other network processing and requires network initialization parameters as if the client were uninitialized. If the client then receives a DHCPACK allocating the client its previous network addres, the client SHOULD continue network processing. If the client is given a new network address, it MUST NOT continue using the previous network address and SHOULD notify the local users of the problem.\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"connection.ipaddr.dhcp_failover\",\n \"description\": \"Secondary DHCP server lease confirmed active in device\",\n \"expected_behavior\": \"\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"connection.ipv6_slaac\",\n \"description\": \"Device does not support IPv6 SLAAC\",\n \"expected_behavior\": \"The device under test complies with RFC4862 and forms a valid IPv6 SLAAC address\",\n \"required_result\": \"Required\",\n \"result\": \"Non-Compliant\",\n \"recommendations\": [\n \"Install a network manager that supports IPv6\",\n \"Disable DHCPv6\"\n ]\n },\n {\n \"name\": \"connection.ipv6_ping\",\n \"description\": \"No IPv6 SLAAC address found. Cannot ping\",\n \"expected_behavior\": \"The device responds to the ping as per RFC4443\",\n \"required_result\": \"Required\",\n \"result\": \"Non-Compliant\",\n \"recommendations\": [\n \"Enable ping response to IPv6 ICMP requests in network manager settings\",\n \"Create a firewall exception to allow ICMPv6 via LAN\"\n ]\n },\n {\n \"name\": \"security.tls.v1_2_server\",\n \"description\": \"TLS 1.2 certificate is invalid\",\n \"expected_behavior\": \"TLS 1.2 certificate is issued to the web browser client when accessed\",\n \"required_result\": \"Required if Applicable\",\n \"result\": \"Non-Compliant\",\n \"recommendations\": [\n \"Enable TLS 1.2 support in the web server configuration\",\n \"Disable TLS 1.0 and 1.1\",\n \"Sign the certificate used by the web server\"\n ]\n },\n {\n \"name\": \"security.tls.v1_2_client\",\n \"description\": \"No outbound TLS connections were found\",\n \"expected_behavior\": \"The packet indicates a TLS connection with at least TLS 1.2 and support for ECDH and ECDSA ciphers\",\n \"required_result\": \"Required if Applicable\",\n \"result\": \"Feature Not Detected\"\n },\n {\n \"name\": \"security.tls.v1_3_server\",\n \"description\": \"TLS 1.3 certificate is invalid\",\n \"expected_behavior\": \"TLS 1.3 certificate is issued to the web browser client when accessed\",\n \"required_result\": \"Informational\",\n \"result\": \"Informational\"\n },\n {\n \"name\": \"security.tls.v1_3_client\",\n \"description\": \"No outbound TLS connections were found\",\n \"expected_behavior\": \"The packet indicates a TLS connection with at least TLS 1.3\",\n \"required_result\": \"Informational\",\n \"result\": \"Informational\"\n },\n {\n \"name\": \"ntp.network.ntp_support\",\n \"description\": \"Device has not sent any NTP requests\",\n \"expected_behavior\": \"The device sends an NTPv4 request to the configured NTP server.\",\n \"required_result\": \"Required\",\n \"result\": \"Non-Compliant\",\n \"recommendations\": [\n \"Set the NTP version to v4 in the NTP client\",\n \"Install an NTP client that supports NTPv4\"\n ]\n },\n {\n \"name\": \"ntp.network.ntp_dhcp\",\n \"description\": \"Device has not sent any NTP requests\",\n \"expected_behavior\": \"Device can accept NTP server address, provided by the DHCP server (DHCP OFFER PACKET)\",\n \"required_result\": \"Roadmap\",\n \"result\": \"Feature Not Detected\"\n },\n {\n \"name\": \"dns.network.hostname_resolution\",\n \"description\": \"DNS traffic detected from device\",\n \"expected_behavior\": \"The device sends DNS requests.\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"dns.network.from_dhcp\",\n \"description\": \"DNS traffic detected only to DHCP provided server\",\n \"expected_behavior\": \"The device sends DNS requests to the DNS server provided by the DHCP server\",\n \"required_result\": \"Informational\",\n \"result\": \"Informational\"\n },\n {\n \"name\": \"dns.mdns\",\n \"description\": \"No MDNS traffic detected from the device\",\n \"expected_behavior\": \"Device may send MDNS requests\",\n \"required_result\": \"Informational\",\n \"result\": \"Informational\"\n }\n ]\n },\n \"report\": \"http://localhost:8000/report/Teltonika TRB140/2024-09-10T13:19:24\",\n \"export\": \"http://localhost:8000/export/Teltonika TRB140/2024-09-10T13:19:24\"\n }\n]" }, { "name": "No Test Reports (200)", diff --git a/docs/get_started.md b/docs/get_started.md index f0c39c284..41a1d13f8 100644 --- a/docs/get_started.md +++ b/docs/get_started.md @@ -4,23 +4,29 @@ This page covers the following topics: -- [Prerequisites](#prerequisites) -- [Installation](#installation) -- [Testing](#testing) -- [Additional Configuration Options](#additional-configuration-options) -- [Troubleshooting](#troubleshooting) -- [Review the report](#review-the-report) -- [Uninstall](#uninstall) +- [Get started](#get-started) +- [Prerequisites](#prerequisites) + - [Hardware](#hardware) + - [Software](#software) + - [Device](#device) +- [Installation](#installation) +- [Testing](#testing) + - [Start Testrun](#start-testrun) + - [Test your device](#test-your-device) +- [Additional Configuration Options](/docs/additional_config.md) +- [Troubleshooting](#troubleshooting) +- [Review the report](#review-the-report) +- [Uninstall](#uninstall) # Prerequisites -We recommend that you run Testrun on a stand-alone machine that has a fresh install of Ubuntu 20.04, 22.04, or 24.04 LTS (laptop or desktop). +We recommend that you run Testrun on a stand-alone machine that has a fresh install of Ubuntu 22.04 or 24.04 LTS (laptop or desktop). ## Hardware Before you start, ensure you have the following hardware: -- PC running Ubuntu LTS (laptop or desktop) +- PC running Ubuntu 22.04 or 24.04 LTS (laptop or desktop) - 2x ethernet ports (USB ethernet adapters work too) - Internet connection @@ -68,7 +74,7 @@ Follow these steps to start Testrun: Notes: - Disable both adapters in the host system (IPv4, IPv6, and general) by opening **Settings**, then **Network**. - - Keep the DUT powered off until prompted. + - Keep the DUT powered off until prompted. Otherwise, Testrun will not be able to fully capture the device behavior during startup - resulting in inaccurate test results. 1. Start Testrun with the command `sudo testrun` - To run Testrun in network-only mode (without running any tests), use the `--net-only` option. @@ -81,89 +87,42 @@ Follow these steps to start Testrun: Follow these steps to test your IoT device: 1. Open Testrun by navigating to [http://localhost:8080](http://localhost:8080/) in your browser. -2. Select the **Settings** menu in the top-right corner, then select your network interfaces. You can change the settings at any time. - ![Settings menu button](/docs/ui/getstarted--7cfvdpdnc5o.png) -3. Select the **Certificates** menu in the top-right corner, then upload your local CA certificates for TLS server testing. - ![Certificates menu button](/docs/ui/getstarted--j21skepmx1.png) -5. Select the **Device Repository** icon on the left panel to add a new device for testing. - ![Device repository button](/docs/ui/getstarted--q5uw26tfod.png) -6. Select the **Add Device** button. -7. Enter the MAC address, manufacturer name, and model number. +2. Select the **Settings** menu in the top-right corner. It will open the **General** tab by default. Then select your network interfaces. You can change the settings at any time. + ![Settings menu button](/docs/ui/getstarted-settings-menu.png) +3. Select the **Certificates** tab (Settings menu), then upload your local CA certificates for TLS server testing. + ![Certificates menu button](/docs/ui/getstarted-certificates-menu.png) +4. Select the **Device Repository** icon on the left panel to add a new device for testing. + ![Device repository button](/docs/ui/getstarted-device-repository.png) + + Or + + Click the **Actions** button to add a new device for testing. + ![Actions button](/docs/ui/getstarted-actions-device.png) +5. Select the **Add Device** button on the Device repository. + ![Add device](/docs/ui/getstarted-add-device.png) +6. Enter the MAC address, manufacturer name, and model number. +7. Select Qualification or Pilot program. 8. Select the test modules you want to enable for this device. Note: For qualification purposes, you must select all. -9. Select **Save**. -10. Select the Testrun progress icon, then select the **Testing** button.![Testing button](/docs/ui/getstarted--w09wecsry3.png) - -11. Select the device you want to test. -12. Enter the version number of the firmware running on the device. -13. Select **Start Testrun**. -- If you need to stop Testrun during testing, select **Stop** next to the test name. -12. Once the Waiting for Device notification appears, power on the device under test. A report appears under the Reports icon once the test sequence is complete. - ![Reports button](/docs/ui/getstarted--m4si1otdu5d.png) - -# Additional Configuration Options - -Some configuration options are available but not exposed through the user interface and requires direct access. -Modification of various configuration files is necessary to access these options. - -## Override test module timeout at the system level - -Testrun attempts to set reasonable timeouts for test modules to prevent overly long test times but sometimes -a device or series of device may require longer than these default values. These can be overridden at -the test module configuration level but is not preferred since these changes will be undone during every -version upgrade. To modify these values: - -1. Navigate to the testrun installation directory. By default, this will be at: - `/usr/local/testrun` - -2. Open the system.json file and add the following section: - `"test_modules":{}` - -3. Add the module name(s) and timeout property into this test_modules section you wish to -set the timeout property for: - ``` - "test_modules":{ - "connection":{ - "timeout": 500 - } - } - ``` - -Before timeout options: -``` -{ - "network": { - "device_intf": "ens0", - "internet_intf": "ens1" - }, - "log_level": "DEBUG", - "startup_timeout": 60, - "monitor_period": 60, - "max_device_reports": 5, - "org_name": "", - "single_intf": false - } -``` - -After timeout options: -``` -{ - "network": { - "device_intf": "ens0", - "internet_intf": "ens1" - }, - "log_level": "DEBUG", - "startup_timeout": 60, - "monitor_period": 60, - "max_device_reports": 5, - "org_name": "", - "single_intf": false, - "test_modules":{ - "connection":{ - "timeout": 500 - } - } -``` +9. Answer a few questions about your device. +10. Select **Save**. +11. Select the Testrun progress icon, then select the **Testing** button.![Testing button](/docs/ui/getstarted-testing.png) + + Or + + Click the **Actions** button, then select the **Start Testing** button. + ![Actions button](/docs/ui/getstarted-actions-testing.png) + +12. Select the device you want to test. +13. Enter the version number of the firmware running on the device. +14. Select **Start Testrun**. + - If you need to stop Testrun during testing, select **Stop** next to the test name. +15. Once the Waiting for Device notification appears, power on the device under test. + ![Waiting for device](/docs/ui/getstarted-waiting-for-device.png) +16. While testing is in progress, you could complete a Risk Assessment. To do so, go to the **Risk assessment** tab. + ![Risk assessment](/docs/ui/getstarted-risk-assessment.png) +17. A report appears under the Reports icon once the test sequence is complete. + ![Reports button](/docs/ui/getstarted-reports.png) # Troubleshooting diff --git a/docs/roadmap.pdf b/docs/roadmap.pdf new file mode 100644 index 0000000000000000000000000000000000000000..7c377f59007b28403e888bbbf394e3ebf9f1340e GIT binary patch literal 456579 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

6xvVLS4=s2xf~5|-nU6V`F5y4A`Q#iGdu)l z1uyeH^QL>VyyxS+GMWPJ_8tb%x#-#sBB=S^+ z-@o0pws7+_Z7w>#q3q6h1XpD`O<0%+UHj=&Zo5v>kSMyM$ID7R!`AD3gHxx?*8Q}h zc`YdmLlPsrewfhOe#f`B88*w>%ai;ET1y9r#&r3*OAwj*+hp}+}Wg>9?$u9nIx63!L z9|&dx*tpkdE629d*3;X^#75lR7vk`US2bj_b8uv#zIu)HdHFx1-#UC*{ns$zKW@=~ z-OID{aJ~5l2J;6z@N4IvFql6Ogn!=j{~3ef2D1M>KK8dfAAg`OZ-DH7iU>k_fIg(-ykODzr!pXf5&0|7h#rvAYs1<{f}T4j=!ZF z`3+{#ldaSrbfXk;cjgUy#0@kPle&K8AlwSUaU_R>;3D9T+0_bmvXW2$#FHS7kwPdj zpD9g_6HEes;+j?HcjZ&lzisU)#J8pjJeSN*~^nb#N_Vx39NnGmky_a?I zJ+C4gzFRx(yi=$P@OpZX8U$tbsG8YINo=w+(aE%}DMoy$E)3fn%V?2{;DU$+cF<0z zXNB19Wr%!sv0^$IRyum1&>`$Pvv9QdHeSjnI#L`>+M4&EmK9$~<+~MO1{C@Q*(~=P z^7j#p`Fr@j%o#r4tSu|EEY%qr#_msQM+MEqS&V3m`MddEY-Liv-tV6uaSs{__y`>) zv3u!8wRJ6P9Uw+|ed=kyq?)OlWSc3ruiDdRvOxSr3$7|11A1f}97jpoMIM&t3;B98 z_vrLgAd|dB(N{;4A)j~=%$(QbXiy@Pv6s8ys-j6QW>zDayoH}(7Oq@HA1%9vkeQ^) zj_Lq2{$Ubr5Ca_kT@`ElT&qB;&6(3o{EkI2WBY)wI!L-UoC(mw9Wg6$wu!~l@X-UN z82-$xA&$VeGnwoJ$PlzV6zQj233{eHvP|o5u>#;vz9HE_?*^LPo1N2sUt=g9LK6A> zOc=F;J}^JO6Tw5x#NL0>CDaYFx`Ml~cwI>Z5{uCHkW-RTX7V2q70jZZtE*dDGQWmF z&mO7Ds=klpN0_4d%pvt4(0o1BN7RK%{k(8~X_&UDNdf=Nr$yb)+Q8zSgCs6u$@lW& z@Uj;6O3n2YJ}1R*)}o^bk89JT!48U*P94+3aK|@zSp9Z33*S}vG|!IU8kSq6Jr9%g zDi7Z!;;+RO&Ymw`vXJ6gL>J_mOR2WSn`6)h@wnoCb|c$$OmqfYmV8uo$x(eizg@qW9Ga0>F z2Hc-exWS>GW7_r7ta+ti0shp9{z~hDjddN=HHHpa{9uf8)$ldr$T+sE#`{exARZ@`06sr!mFkG9(uVtW4_4(%JD0sja61oyex%9)>bK}Z7u02(-Z{>mN? zp{H-Kj666VZ+>h8$Fw?!7o?hfsDg+fD7~D*<Y<)dj1WlsnrwA%__W<|HLIcPaQw^k|!V z{y4GPc-v)OE^&P!5!dcin%+fjpUtO(vL??0$6E%3)sGxmBam}}u2L|3k)kIsr^inHs>nPXn7_NQ3SiqgyuFUax!7gzU- zjd#M2S8I5oye}52JwB+zd2-IMB>R<~r(btH_z5p5cZcFwNFwuREBG(9NaA}&yGyY2 z@{ba1A?ifhGVo&ImlwcWwvOU)D`#3Lv&ZUe&}^Z6V%|Qqji@VuOX!U3TK(iV7e6 zB#Xlghyeyi1fmfWrb%H1t1*=>_{zX;&iCuMizY1?#ea!{X>?!TZVz9op(QUR9n15S zzB9#=NJl$PC*w{^KtohDG1}pwwQ!=a7-;3hjFa`{rMh91Ndhy*UJyS6i!$6ak*p5G z^K<)V%G38)^|qt1?vH`w;1_k$Fe{bz9N*u9Vs zGBmUo%keRY)l`=77Rn_@vHe3}JebK|#WUS=s>)^m{MZyxYjo@MVI?D&xG=^FUFiex@c87^5;(TckZEQIaCaU*znO_{pI3D#U zHn>;mW>NDJcba8CJH(+g#H#HFi+?Y{O7Z2;ZxN$p+}H~xV>JOw;0&FrQ9F?aPZ~T! z90kAg9cDOHzB+f0@deKe*;hSf_K4t~^md}A{+zm%1!~Oj;`LI7ou+4I*9-zDLODh+ zR8v}`bLxBn-)*$`1JF+gf(tKhB^WmW}hiduLyTa>N9;-?GigMD@<9 ze({U|G6z<$_iB$Rt)&`g(jns5IQzIxCE|lXV{$4XEmkLnxgJZ8v`l<@4p8lr$yaYH zICelMxAZ_(%6UMXXi6C)7b!BIa&@n${P~v+0-)K13C?RC#2Ljz4F)WF>eSHG8Enlk{Xxle};Y4_o^i6#1&H5~Mm}OG1VcG9D)lwSsI#%>7RT zCKDH%g-Cve{n?ThsJ%x%Pooq@Z^1i@cWJ7JNS5St z1l-&;?|i)Ks;*5!c&hhN;K7wOh}+6FLd#BovFKu`Y8r zXsd*GFPr#W{NrU{%xSEfyS;HHBA_rJe>6lHD_Ly0qe7PNs4=dc=dnqC%0B41;K~cu zyN5Nf5h&M4ZcyT+W=QYA3u7v&2Y{8CJ3)o=NEXNisOHl0v!2_QGtgUle5@NLV7{-V?`Uf+un34DLDk1n(9AbWik}C5N3D@mHa|oO zuZ75-Z~jw5sY9$(G`D{-(?r)ads4};ha^q2slfOhP_uj?2H;l03w{M@wF-A(*dNc!j8ZV&@h=(>D0g3J3vBpWc zx=F!$lT|HAqFEqfxUqc)$+YnSSX$Kc&};!3M*M4xez@)#1QAcv6LP{dvFudvUMhxP zGwCWU&9zL-P{%d0@gc;;w+*3O0Rp??MJLRY2?5GMT1Gr(8lu6{t2~} zx+%DAAwNzaJ3jDX^Wn&dPoOZ-#1qSP%IPFPnJ#NEQ%V0ESHTM3Gh<$&J;Kx5CxLcM zM^lz_g;K=KWB=xwT_ooXRt-MvgYMCQO+#e2@-D|(N4p6ZtrTIoy6t=UCI_~CcWXjN z$m};kK=4lC2+uwAm~-Dw%Bk7nCh+Qb-Yj4_eVW)1UOR=I57c)f%f*J1$)%cQ*674AzpE4SRzaaT97p6brO_(deF~`?C#r z5&jsa8zK#jp*9hB*o8etW}!|2)QHQ;6~(ba9Q4B#z@N_WeH5V?K{vcjgGDj17yLp z1{>wj*qoeY`8!#z6Mhw)Poj~frpwC-NT4MOhG~;OraUr*S}Ph38L}GdNDsuRyOq#< z2Eq`=L(4XPn}KTxZPunc={dvW##JCxaY{Jl`SJ7^Y}Aiq(!MzDyZAuTQkegmY>En& z{w>&|t3Y2PLdQlCadG4lCV6Oh8`K}>hIB<3(NDdo>scHiT)NndVs-{~()5I4v=+8D27ZTow=fN? z4^&yTh5#g`;d7~KPol)t;3iwGR#y^03OCmvGBwVSCeo*b+tw}bXMsnA-6Pmlh4_gp zmnTzUnRFzUd+;56NApPUk$8yhVhHPOzY(BmsWz}=^;&u_CDONHy2R3|%gk+DZ(YsO zRp~N8wh;h-ptDY-Z$J!6=i8Kdq{>bHpt5LpKC((Q)woyHvi?CTU4om4R3>n?@+hm^ zS9Nhe{XXNF*OgUUzTB$HOp7VMe}>lM#P+hi5OTXfUq6!@dBD2r6l*>ol$rxelE%`( zTB92mVP15KWSD3Gy2iEH1&c@I;mblw?p^&XSfjL*Y#`P3JXiMZ>{)}uA{KaBf}qY0 z0UKCM`&fgUqM4_yh?o7;)Rsn3*27u8ed$8+s_;6vrg_{{de-w>muV}wz>OELCOo4~ zf@UooOJN1;Jg0;p5}eb@nYR)$?H_H$)FleWzw2|J=C`C6wY*PpZ;4-BbmpuG-A|G= zTntEk*HEsAzgJTIrEp`A;}FXl3&K?|!)$`owniqrO+uIuFNmt7Qi3Hmh0M@$iOTV5 zBc|;O6ybI!1SOX1mX2}TanJenJNHL-!sj)8jLib9D{R8va8vkTnU!qAcpJ&Ilo}#< z4tpAMvX-ttAHFQ>Uty10xkCRy)YsFvwBDd6O^K)RO6Du= zuv$~~*7E1&C?fPe(C~U;>BS-qYiLwrV`zi=BofTX>(+YY^aWjZu&t+Cz(D0tB#jV( zp!ki|EMdH=7z#%1(oWbWEJBIfh5ye+q@S=C_I;KfGL7tYEtMhL?y36u;5WM6Twlze2nyvG?+gKCy5Lf zL_`#+bb=cimffiPFlX(ICxUenC#jA7=9&RZQ@;!vWC2f4V%AiOzenV-*#%ZF|TByZ;!jW*`Z zf5rxCbmbz83{TJ?9XFlQKpcglDktGpgvoRuRz)8gk1l|B^?#L zLp0DwJ579v-Wa9OM)bp?kHH2CJ$uEYAVgKL!QD@l16Ifv^t%yFs~kFDc$mAWokhWq z%p*xvJ__^FX`}{%t^tg}ma4iP9l8ltoNLJICtgt@+bth9E4MobvU*j>*1TgxjIzY} zII5pDw5}>5O5AjJKQct<5IsYdF|B(jJY@Fb6sH+YUDhj9!9<>%uf=j%Z|GQ9YBIP) zwg{1|s=2vE>sispMN{aR5#L)q%=cnn7A>Ir)d^&-pq#2WGy}^iSlKiW6>wt8iyqu4HS>cfYRhCHXM_w{F9LxP;pt{Ui(Q9j=i)tq@- z{3smkg#EJfruAQ+7)vmS1;vQi7=FC{-Vf?a-GZcP1%?UoP>{I#O1HyfyI( zlVSmvg-uIGa0$0@OIn}q*hOtzmLxE87q|@$=aG^*PW7%#7vrwIxxkWW2Ts38ZY$Xe zNf;1y_sQHPpxAju@8YK$@Y2m~u$sI*#xe0GHRQ^VjeImlF+{{$dX{gy9j?i3s`ag7 ziu9%=ZzN;bAHuR|r)f0r$Ne}DAG-62-V@CA%a063Bd@861=3QyDC9;H^48i#V`rwL z^~4o5uFrIMq-mCfk!F*cK~eiDsqH!k9r2YMz3^Z8>4$wrNeLv)xzI7jmlc;Zm>|+T znNmHfs#$Y;<=a%XiJ z(Lvo1D0O)rmeD1od5)?woj{zPwP@}~0aK(E(E>-s9G)~i38Dz-4MoMowjbwWYc0xD z=jDx&k}LNo`~<%UH|&L{wZED&&0;GNf3FTmEF3XE72tejOl^H(#D2N$GOPuw`B%vM z54iLflFiBf=AV%FA5iUIAa9OeL396ryj9&yT)iC4|DHJ-^v@oY!P?Qm_E%nNS7$fS z0Zl72S4$up7aNfIt+|zjr7Mt)lMBc!=3wjKtmQwKB9FXYVaEkM0;{56v9b^i4y07XVpS`q*T4gi3G z{s6B_05JeGBos6hBs4S>Gz<(hED!|+?2TLwm(}SrpaYn&z;v5VI#KOkG#iOL6rlF12&rIoddtDC!rrZYvdgPrl?EBJ6#Q-D}0k|r1^P=UlOHCcojb}mJ>ff>%9WjY^45NzfoYm7h=9du2On2o!qP9`180~{`TeN z`@k!Jdi%xr|N1D3oLdj_@29>2iO5F3s&e(VKjTw>IOPd3v9Ke|7L>W>+V-8+dWj^xM_1{^eA#G&;6GEYghE%DQx!X+rKc&FP5Gcg(_R6krc2LL`}3EF22+M~;3AL&sN+!& zKH@DlBUrurqXssfThz-XLbIK+g1uj}Hg$l@OApyQ+6Jo8#Y~}#H2Iyr*P@`QgPhvn za1UI41a_^?6P=|oSx>vxSWSQo^(w*}8#Y!lKvIa?h6b;}%Q>qG|C|?0& zm9eh?10_&*;GLHLy2~RXARJkBW~h_L8?Rnj!Z+^(3PV|#nAv*m0<+^4P&xJr$ipku zxc-gEs>!^w&zswAAgO#2k-$&_`_B|Z**n=634Q#R_B;^BP`;oH$G`75MR^4v5fzsw z`#N87TINUHi!J{Wzo5ZP*A~6cD?mTz6(Gu~QgQGbiH3z@>r#Wm?ik7vTETzlq+fdJ z-z=S}XQ@w7t=!LEVj%SZywCsZj#bhZ0s%!$y1VT?>j??Wi|}U9c>um0i$k&mrQtXe0<6g=sK(UHKzY8n9KWX2-lgY!LDa02YI^Lrw)KDoN-SfGYJw z_sQkOzkcoU?iKJP!FD-Hk*EQyz&~HaEd-TM|^@3aSBHm-aF7AOyp;Isj&`x9H{RKm>gU7bGx<^U z+n_XopqYE`qwxwzs5pIg4|s;1k!kv~5d4#oLy3tQ=anTeP5z-c{)xZ7D~Zet94bDlDuTxH(D3o8YtxWXmZg1u`-2vqdCBCla? zDnGpfgl=+f?*qOjJ{p2-R&Hp{^NZnmRT@p{D?rm2bd-2m^QOKl^;|IfWqwL%j~q0g z1LmHrgc|F+9^kYC)~;Uxc=u6-jlsI*5kH$^3o|5v5uYg|k7#@a2KewKo8Dgnl!%x!n2VL5f>W= z$?UrcSgT~^X3eB&4ln*mfpq3o{jOhpmif-r?){6?Gwq1eRibFBO8J%ktW*9~LHyU~ z3{1v)v=u!msCGMsNftE;Tn0sB={D;=$A1|u`V~RqJ0@mi-RSb}+JleGKUP4gumQlw zhnx$=@E7>#zS)g~GltfJ=u2*86rtYEm*S`FOzjy)4m&~JrF(J5;+?_TpyiBv{xS9x zDnre_Zww71AFG}yfIC@LyZ{YvlZWK!LYwb$4|`gcg5Qm?oXG)JUz#?D8yEXp=_w) znfGT^`&Z5Re_WN)=~_~@HpjtxE@~GTl|->=W%nk!&xEbpK{jck_Bi0na>QVwO3I?o zYqt6do#`16HDPN8na@xdrng1}y z7{@E|=F0!k6k9E~%HN+{JwW@LpntV}1q{4UwVgBNTQpPgxqb@+c#1qrpIDv;{2|ly z6XaC;U*bcJKTveh;yf_-(&??TQq}J2UjMqOZ!t_9ehE_Y9f^Vep$s!|UrtpgUL>0X zHU?+>EyiaqZYo{@q`GSlN3Vbf)T$Dj?PJ{+(Wb8Jg;&58xuH;B7p7wBfp<&e#C^ix>^1MK&6 zmL)sMB~506KiWAY=Tk~dNE-HG$-xx%DiBsq= zdu`*XsTMCecXSPBYJ~<(k)MU0O#Uz{kBJ@(qQdryz-(hWRct=spLaShj9!daEQx|CSN@e{f{~%eC#l;k^FU zPyIjYxc=FR{h!tU>cRcHE?vA~XvOu4BsC1m+xYL)4dw6bv6{|KQV{~f z<7O$0d{6>4!+rVAHV+@R-iZqtH<3v%)m#}4-oNV%u1ekjo+o{luy3$vNLI1pjV}SY zn3v&|zh)?#<>zebC^KzxNA(Y3Vi41)22NEV$halBXri)nNH>Q{Nm~eF8hNyXE^5DT zIYY9ihKhj+8iCI%EKiRt8;W#-for2ri6v>=@C%lFap?oa=e=)Ft=qC6i!_l zD3TLE1}7}wb>V*ib^n5m|A4x=INAOg-Tf;_`5&da&@Xx z$@@zfh0G{IY*W=Ci~@lWqT0fyvfqd{!Gugg_z%`t$f2K+SwuD<7`!Sxj|rXF+E&zO zx&xWqZYz>^znXS=WNF>^4Cuwfzxvkrok?}S8g{n&I`UKw8q54xKf8{6KJ|=v^>K4z zE%2PKTKjHzg!*weylwaf^vtC#6+89R^dmCU*lO3~x<{9nUgssV8{_60vEOck9A#G&e!J9eQ7eWo--IDH9bo@p1!&!e)rHaERzBhIM>WyH!a{?gS4fwTBn1 zhM;oHTr^JfF$OU&sl288Jy395|0Df(%RPHhNA!8y(688- zSNkK}z3k34FKBU|1A$VWjEC3AT2+xaQt6X(OsA_)cQ+lyS{(i_C&!6PRb4)ht0N8$ zg5KV&oua5Y2gnYBTOo!=XF>x0=XXLxUCsA(_dgStJa%@8m!B?n$ggiBNQK({uC6X( zhcB7C!cv#JayBY=su!{vuetd9#Vty05?#(lkg}Nb{QXQkfb=hCc_x?T;EX9}(yFaW zXpJdXN$9Go4@n<%zE|>B6c6D& z;p}vDb7Jqr<|bZ!VwT(O7-a58Fy@W77Yy#;BCZ^%H+j^gej^?pRiX_X+l z3|HJi>FULPJqCp+`=9Sw#Ecx{`o1{Wg*ccRa%Gewe~`9cTGMUlH9-hY*Z<;ZG(_@n5(jZ8^sbX`fFYNyeM1`1(GniAb!NCQ!0!R{7FQA*`tcxWs>)-CCf z?j05t(L!H0SdMNJg}FX3S+2Yk8MkILWvjsmH={k9`uC!>ZY@&cNO15R?q@2(=;%jh zcn2y}8sJgN!>5uqK%sQkEwzJ-2O_Eql}6qgKFS0*g_`e>z=K0i9)3~UveNTGrOj04 zor?|~lBbnzo3%GgYY!CYkKtYSpABA(t{&ov4P!r#-s)L=4_e|@N|caNyS(o|fn=Y* zgd9xeyxTXm5uU6b$V;pyH6-`A%a9)5!ur19*MphCWy!3teGj@SiKUHIhqEkTYiXvt zM)NZv2 zxkSg;JB{fop5;c0>kc?UMSm`yCT3LeCYCPNNRa%fj5)?v5n+aJ>XQ@PBb*sVhD6<)*@DL2UEgfqKf=CxMPD;-5Vp_WrTNbvBt}AVMSp@}t&q{aQ3A-I#C&dJ$8e?&lh{URkj+Q6Ze)r?1vE z&sE~Y*{o;ity4RU_dxrLO3Aj2kZ8n{kww+}jvO7GfSqzg%o}PoK*%r|Dld{=ys-`R z$M$SArw+z(_#{;leKAeQ3i&qa_(J}WwA*Im3|j0C2$h}g#F7I~z&pL}>;7Wh9OJ9U z9u&+xJ!2BCd88rc2f>V)R^x?ySpuJK6f|IHM>s|prp-WZo7CD^6B5*}#z#260x_R3 zL0|_|DZ;R1INtVdY@xxd$tDt3g`XucmLgKe@UKQ8nXv}X*ebtGO+w<>O%DaV%>YY^ z;3ECwW{%v&@2eIOcA@N$`pJSOh7}(uxi=5Kk9=Y9=HvZa%Two(S?B?n)c5K6V%XLu zVz>9{t6$E)Q`$3oX?1hHSDM3|6wz!gKtUd6iXf+e@ewwVODO$9pH5)ulXm=VZkj4Y z1P9^kz{StP_uI+^Yux~}6k}uh1$Xi^_;0g~Bg-=l`3ganb?}kJNOoXIH%@MMf-+R| z1y*bY{JGz*C&+68FNRB`N-Y9eU=nMBCZmFa6Tq4bQ@w?&X&N_LEAGFfbQ8E?&T4g& zX`-XrlvI_1cQAVD>^-z9>dHrZ@>XleMReRmea)2Nx2@J>7{=DBj;(PB+CkuG*2)1b zB(mlA6^USIAdD2DyY#tn?wn$y>~?M|m6d&?k5#U2EZa^2MP|v;(EJ9~u<@OGPM_Qq z4N5C(yDo8TLy1Ay428$CcON&E8!mZ@uqM)cPy`+b>_irA`h4TfKk;T&uzQQJC*;$G_wuZ3a zx+3rmtx`7oX@zmORdtivjDUT$J~x%E()9>?dyo#yz;GTR5f;$f{Fd2|0);;1D~4)d z`w7wC713)3;-dtkr$%xQyu_Aj2TymtvmRpJ%bS)FGt2NNmAcBw)?EVKlrCKaR7;|i zpT2$$O~L#hY205sC4|3w>4<@rHq|5o57#f}wBKXOZF$GfF~0&P+e& z@*k-okxlbSa_@>b(j9B327A0B4Q9`KgalZP|noSivPsp({v(=PGWh|!KM zJ2I#czK{5#@tAmId)`4CI-pqGkCl@>DIw%2Cqx)E{=uZoHj-AgL-r}XC|7Kz9*=Np zp3$={yvr(DwqQBLx}q4vwmNk-IC2GCj=exhkyDD&Kn7=nji0cbh7Ef7gbExo3QMaZ z^P2j7K=m?p#zCZ^#QN?WokRFXCmir?ZFY`+%%yTZgP_2pL%zcI^~>c$5a6ZZp~4{z zeHW@`IZA_w-wZYvP;4?ABTx-xIgTVuVWo>~f}TzG*DN|6sHQ#|K#)Eqd8l5MlxCdb zz4blP$W|X^kqkW;a#Yb4zELY#n=M<>YYa_uMz};n-Evjy(iuoz7`VA3&mZ9rB^XoS z+yWP%Zn8!&54yV?x8unw2(yBLLhN}HfRRZHy$KrF8+C-NkrUSXX=k=ds{pWjyzfVA z@#B_m&*qisjwx;K0(!jlZ507)dlAt8<0nZGIjMpNluQqo3Rm4c8IKquqpzmH)-LnhZr|y zpeR)`oYjRi;aO< zH%)Ba^vb~(bq3mGsJ5A9Y)^y|CDnV0hpUY5 zjwF$N9<9;V-)d1-l;2EH5GZBPMl6dlz{({AN#m0=R}rDZknm$kgzRHogvMj1rN|)M zL>iFxJdk^l@h*apA;?-Q2FYrwF=a3Ho^Gz>?ZW5_PQTC7M_3>Ry@PydjIq+Vu-ve1 zx92$aoLf5i&NBI@Bd`*WitG<786g%&6P3ZNR}+n_A4rAYx{p2VocV^^X|4c5nL5$!F`BtIq(*>ybsCE`_ zmusny(Ddq>d=CjMWXDuZ@>hRSZp<}F)`}J9G&iJsq19EZnamZq){g=fqO^W4 zM?G_V=TNfir?o!Tbo%pXlbBC6NE`6ia4m)7g)!6#--|udy%sBZ%Wl&~H*ep0J*`8YN@FWw&8sie(YXhr(IxylJ0Zm`;3rkOE z&fdS4NMRV%I`Je7#L(hL(v;+)e<^7~kKEyqy4Ff5AHz$M?+|CE`A{BA-h#9VSzc}b z&KMKn;DQU5R@JhOKmp#;cO0~cU z^=3hl0O=+l+1S@%nHmho1^_+u&OeuBO(kc0$(S1&wsCQ|_DyTes7>Q5&)58Jj04#q^||Zzawr*dEZU`c^uSsYo(|nFc3HKIVOIJ9 zYqj%d-&dui-buQP!M+nNCB)4)j<ipi4<3D#lepuKxD ziy}uSA0gGjt5oyZ@QF93p{^_FC<8ZbPwIYmLcK|o0fxS@QYXGyi5y$$jdqocfZ++3 zA34r7_-wHjV=xWm)R>Jx>*~Q^_2<1XJI}E42O94}W$ed@=fMN!l6x%}E{sMyJRt5M zqIi9#i-eX*;>>eNqjzl73`Fd8J!W~sq|4O_IKJ2&ZXw+ok7kRR6%NXt4Uyr|8@!0m zhu3KXwd+{i@VUiTA46aUJ2e|{k>=$a7slNybr!?%j1h}0IYUB5kg%C4^=^sS%{RTB zx@)aH$sQtB$-HTN#UcFdk5SpQ2k+1t3EQFOb(zV8#G# zcZ@|_Z?a*cPPBu=W&CQC#4ee(VQG?mJ;b-+Sl_1vVk<`Ugx^%6_eN|^(}sE9<}T(T z_8pV0Y&f=!D5x!PA0-T$-;1cl0;U^o^v8;nwQ-f4e>Um^%zdv8<4DIhq|VYNSwRa1 zn_Od%v#cQ%CwGJ8N7UKR#ERg!wBdnyuf+q!H-+O_2&BG*INUESVcSWEI!y43G%^sQV9PL?ai5JDA^xT8MNQrYQt(T$Di9Hmg#FSA1%wnG@@n6AQkW)T2LI z$@7>y9kDrYBy1};3kf41UFHe_>ZD-k;FFz)Gsf@3Xrh&!l~mm3l)@r#@^($Y?Q2== z`7pAR*w@h77}=j%xm9y8uu8tGu0xlRI=Pq3JQi<=R6QQCK9Qm{#IY{c^!%uU!0szW ziHJ+JL3BcC3f9o@LBFF%Dc{Kj+Mwwqt}?6VZ0CdaR5@$pZo9kufMXaN9ozRs;f48o z%C67;+@0{sbYM65Qe$FumcsBsyj24>+dNvDR#k@eq)PF}EYjv#iWKCRY(U+3c;;I1 zS;A@|D*56XzEBAqJ=^)GN~0<#Q~nEoyhcew+NA4dmX}h8dBJafrMH);1#)oiUFi=TfoLUXO9Dcwg-2k-i@%t_^yaBH9l3ES=r{cxm+HX3azw*o;|$Kxur$Xy26PwFhU}0y!kH8Y-4DI<@>{@Ot3HYmjojtKivjnGr?#amJy)fxJ`qj zGV`jXKLsw>F2}UM42-1dsxQaT#z!Kie?JTMefK_7i04E3&GE#s=q^z|S!(X;P14g8F!F_Tc+{fI?xBP$KI zYTOQTklkgXWK7Hb{>USIi}<;NT7FM_7nu-A9{y__ablS0!_enzO(cZ#0F5V%&iNa` zJglyOXm9$V76h--3CIXd|FXK!!{u_I59tdUEK91i=ib@M96$&XrLdrH;?8#0f(;{o7O(BdH zWC>XkPnPVvvV_7^WJ$8`Wy!u}U&qY+&OMeSm3eyJ_xrq`pFfP5bDwkWbFOopYd_c7 z!!)!#G~Rl08Hvq<k~b_e)wWm~txmDMYUV`tGFId<2>v~*HIcfIfCF=H+Y zbO>Ezd`s^0ingXOy~wrX^-+&&qU7n@cCM}I+OTd{+tn{5Ei@nb0#;ESb==HHrEjJ2 z(3%pn{ldjY$X8Fdm9sVciCc<-wS$PE{7VH@2gkHY9Xci1D;6CJPeRc&jD?f_;{mN@ zAGXtCBwN*lecyiFCfZ3SBQLRz%s1egYByuaE%|2uC(av2DS9}i1r?Q13h7z8a)zGO z7~Xz8(TM$`l4a|ClsOawH!*kQa35o+ey{`kDU7j`GfYZi<(2K&=3F{&o5XG6DsKk1 z8Q{B=n&Nq) zHdixApsVHABfT51yISu3_;FZTYwY;&Zpj0@mScWbnI|r%Zz&Ahr1qpP`l!EBJ;W`{ zSQk^JFG}yThmpjlIq2D&_}j*hW$M@ObM8LOW1)j~cs~fW-!yk|>v(eFoUu(EStjE7 zt*hrTwCMO-)&oURrz#jKYZD6Du2SzkiY{X47HCaqd-e82-0hFAhM~eT25XRY-}r{} zmMHy$dEEx;Zz&Qe8oaB>`aX2!-R2Icadp>p<+#R?bgR|f78?4{Ng_2;Aae=YG5AFBBSt@dpo}QXq#fZS5$$F>{a7K z9m?T*W%}RxC~n-5y!*5)8@tPkmw7d3w4AZ~9-VGH)=VNLAyLz|QjdYUWQ7^S`IE2M zShI{@U1wWQ{fyU+f@$UMOSI2#>Mf@h^<60p9l`cocD$3$Y8JXKbSH{JpS5OBa?VP> zb|EOQZe`T&NVc%z&h76qhKIgT#w)e(#j~{_<8QUt?_g_5*rC|ibHAOH^1j3Ml*jMO zZnHi)AXFo!67Hb)-Z}o@Htfg&4WsRcD3FpGVOobNr1p!G4SKl7WjSW&Zx*P1-p{MS zR*AHKcEu!KK=@hINq_Y&5;tb0I7A-*WHefNglOCQBeUy}H$}d3$UdKEBLLJ6rrg)+|Z*K-|~Pb#0tT{$#Bc zYEd3hndV2X79Q?7b%}*zcV5eRPKDI=Xd6WDQ4Y?$9s|X&PYJelc}WIdaXFqBE^Z$j zc@fW=+7?rAoGUIsTA1fzK!uELU+pMoFXoZSj+;AcAmPCa@xz{p&*L4eTZ32+Je0H? zeDVB(lwfSzBS%+G?m!ikdgrr;Dm2OL9|!0iT%w#g)!Qn&8X*q4>^R$Vn*(JuB@vZFGx(^(lR5XWXMXM@k863Yo{SEUJ#rkxJdGWjRW~WfxZOXq&Fs>7BkrSv z>?`c;r4p60gGslb4jyZ3$hEnA;p>Me64TIfChkzBLdPMMp3$~@CUVcpKiz#CE$ZfL zJP=>_n8W>?qmwX?&bh0J7U<*6g*5cmA?aIBs55?CMQ_bU zy1MY7{N!e9y@^#~0}`JiT|7QEgI`})p`@L*MaXoM=ttPaId%k3Fr4AtM=oQk60j+j zLFIxLr&#LUXw`$x-7=|7mZ zW^G~`tLTJjYkt6{mKNt$9e1OEj|t6cQKH8;EGJ{v$&{fTAqc$4`O0Ln8SVP6eGPsetp3Y!mg^sL`6Vhy- zckQj`?y~_*$=vL$lTa9vy{K12X1K>+Sbv^W0I}()q%d>8>sQgUca3RA^54AkX=e zgL`B(o>J>w`XCbe`Bn%o@A7v%_B=hY11S&QdwFMgxy74bKP$w&GodlfHc`vAh38C! zrH$5K1-3&IN{^^sKzR;4`KLZPG1}2rlqG$>@WGpgtS|S-=D~fJTU$DOY23{kt=h8s zny-dsK|;J|AvcWMFu9&ho~l00RXsr`QqL-#*HZWT zqf#|dU*7cPhfwe;rkLo_A8U>KODN%VFttC+ePq$sw0mW7CtVdpD8wS(PBaXkI#SvYNZ#UpAStd%nFJPS3R5?PQ6dC+T@bo+m50p+XC~3x3z>UcF~A zk3Th$j^Cc%Zuu352-zx9&1I!3d7UkuYHzP(G8el$X+IA*^M(^C3TzVS~-KK#9;Z-nk=7vLAc03e=Y{$0>X8bZd~a%eN?bk1Bn!eXEtvV+s~K-@Lm z<+1BoNZfWDhUoWvp~;rpv;Bm|!wpPgonv|_YqUCVrd)l458xvmA|q`U(z~Ij*PC8; z_ot!}O zb)GBH=e&Hc@T}tUl6vF8qO&7)nTO!@ZUtjyhuFaUl_E7#RQ;}c@3=V1J0}m}B#h7A zIgVW`yU%aOxW@1kOD5zd+3Gj@^WU>N=h?4Cr@Y;)j->WNTaA?0V_mP*lzU;ahb~-w z@vP>u>P3^`utw_4@w}UKr?(1yl*d`}OkPQu=;@1!!aVg}_0CoHX;5Mzw`~-9MNG1O zhg=1f=n1aS``$7Qd8RZL??bcJY!fOw`4`oPqjx@144rP<&*<=aIH$AkjrmC@n_iWW z>chnix#vLUH!rl=#JeVhU87f_PsycHCpPwXDI_?RXRcM!PgyFDoV&Z0_ws3*RtlkZ ztflw5ovC97T+iPn!ReiedzgaS)%J0*`W6t@?cdk_E-DIk;JbJ&W zq0wWH?#+);%=b3qFdtKC98 zfJ~E{K=G1!@0GI6q(eQUPa4e!1C!I(k|rNaEL&x2ZaMI|BWdh5$M9b-%&UWH^9!Qt zs{G;~pR8i(z0d3T*>R6W;LY>ZydIsrjF+uYBf1eK(VMyGiTw8nj6twXkiIB{ALmRRfEpBNNEKXok6r=@@y_A-}c`|H<#7_64gd zt7Tm%tBidemd`!brCnYoNfDjyuVLJ4oh7`3;r_j|ckIqn2VA**e~*(vh>d;ss#kk2 zudr7@PBthS3NtjUsoj2d$C10PmkwPy8%!$6m3m}b$w?>M=J6+PdEquZwbrx&kAoxc z1xvQB?<3j8Be~)HIr~HGRM(DQD5BYuyC>(;8zu#5?Z@=DT&tIFh`+o(nxBM~KIiGC z+T%{kdj##b9Jmpw!QeMksi>d(A@|Z}jT<|U>|e`9DsV#FPEaiV@F}Uys@jyi6q0@X z4Dkipi7SN|NOIO36Zh~vBkR9O#CGt!)b2NCMeJkiR|wv<-IBN^Fi0%>=|%kpQ>u2d z9gp7LdDoy|zg~s}qPoq@6fVgk@2$Ld+1Y?~52-`mk)aqS52^Afv6s9$yrzpUeW-|~ zCYFp!lWCuZQ|)d2d+yJVGIhU^%{^mH8M2+ymu7X1iHv?j)$XXqZfx9#U0+)0K` zUvY1JqhY{uv!|T7tkMU}!q=LAyf1D2_Ouk!#dx|^ry%t`c}iL&Rx4v9$f+J}&LKN? zU+5yI13SQ}zxC&c65S z1dEtNg`1xgEgyToeg`d&t%W^~#(U>(GvwawZ=4%8G@c80mRr*SSzh_LV#PVf{rM|D zThqGcp8HgMWlbmFjzAj!$DQ}=EE-B==$s^|O7*l2(2&zmulCIKD~oh&c8z*x zli*-Jx47K!y&%7Q(H&ARXH zPg7VQB;mRKqkH+@gJ<~m8E&^$2)*zjc7@qH(g|0ne_!+AsMl$Ac}~oywEXQEb)F}N zYVT>&9HtXp7ccGZPAU(a|h_1R2OnL1K^U&ubQ2JdoYjChvgt7|U;8B>?%)iEbXKWi{@k8ECl zHTQUdmdrW9#YLb!+b#(V9pf#*nP0I#Z?h zPJu(g#$ekaj|T2T=g4(+MBMhWct;+ntYUF6KgA@t^I7nTtd6{>-9;aiKi~i2oW-Gd zrdT9kdqlV%&#J7a8tN2e^_^fPso}^D z>#-L;dN%}4Urvs!YkmCAJf+rSFe|w4>+o@|tIZC_IJCw8x|3I(;QnRL!6+$;4wbFt zvJOnkUfhuoX7~cwc^Ui!*n5!#T%lmxC0(8TcN=eeHL*(wds@W`h?x;9W3iY)%+{%*=+7Z z0jx*TOUsRVUg>zaA6H^iZL2P(+#=-U;Dvjdl+Dq;#!ID-{iwB8TVRMLb!4wlyHOkm zB1b|suXnxQt)|Yxp*xMkt%yxZal_e$8LvCP##pf>pQx3@S8`_ce2})YZe~5wwQgr^ zgF~=qkr$oL?meC_qXXCQ-*3&l#g!x3Yv?NMe%TP~IhHD2QTu>3gAX zg+GEk=(4h~VsBaa=ZJUAHw_UlZz=|3t_5dSKb0E2k+6REjviV>fFZN$`>pmnsj$f} zplyOT!ezr_OHVw1QWVrXfXg$_Yt>?@4^&DK~`!;eCbc2LmkTw#*>rjHrxwhx(nJW2b#t|JO6+Twf zTMusSeKJbn^f~{QP3}?40Abo3j%X1ayXnpgH^LNrqeVa6wRE0}982s~tU;hU(V36-xaepZT1|hS@ z|CJ0Fi#V*a*wg4s^%j3}e+gDguv)@_B^+47fh8PR!ht0mSi*rN99Y7EB^+47fqwxG zu%~};b|!tY%3Oimf!)QQZ4`Y-lJF@;7al2U;a`U(XCQn++JWKY1E^a=G-iZP5_5;3 zwmfWaks{$!fxL66`4Z?3EuqcQmVqI#L@t&{B@BTjHZhk)PN&KJE=3tJWf5k$L`(M1 zDrtB~Q`Ccm{AVei7A#kGy~ziW4~Ib%hML5sshyjCa_Rfuhh3;AMUAgGbW}pHSZUou z#fNvlWt?4b#Xvr{dFAvf(;4SxGS3nFpH^uQt3RRol7LCONQuEjlDr5*e$3n8e}UBc z|6@-7H^~0%MDn_Ic9YaDYO@ zPX$`Qa8B#R{;2>O z8D82;=|)J24^!A(9rXkJhRER-BRmwu#c}C5U+*}568FRa$p2UDMVqXtI+h_jaP){V z8qSfLuxGJvTB^7BRhbW~HjCY}nfFocNimx5`@*L3%OAo+ffY$cGWA(rQ9Jx!r;|GD ziq5B$#DqS2i0cSg(M|*^z%MQNQ;8|k%fCyqB&M8zkz#9!!Rm-n8)?O`I$BmjmReX= zP%K3^8nu$^U7GBNwZR=63pt|k}V&1k?_ixc0l8xzk^aU>yg!~v4O z^IfGEQQ;Dz6?qfgC5cHSS*~BeN&}?^@p&kawmOw4ic$ISN{d5Wi!7!-GDztb2qXi| zS<)I5hRQ9EKvjCi;ha)pUS`chRs;1f-s zD|7K?q3?!&Ds4^UV5&+ls={SdpER9_-LMAc+V()7*PnqSsWZ5KhgmHITx6w&p4j*0 zMc0Poc<5o25*~`vvIcXAtbZkbm9NmurcesI@g7&>7!hk@!RCm$FmbtK6bMrfH z9UVuB%8d#_ReCT^a2ly4#!O0^$M2q`Dk4ZzFj4FzFmEDpizI>wI<}zXIfaCVEz|ru zfO3e4?Qjqvq9F_5)w3X?6ude4K+_9rrU@=p%Xg@amJ`BB#K_{(Vo*cp-(9TmwBmM~ ze7Lr@cM>2WQ0|Da!7tUFm4=9c+rj$pP%l1hp88#q+|SyX8`j4yMxYHaQ7nxB~RG-c!oSivh{uq%MP zU~_PwH65R>n**d(>?5;_b)AG-1?MvqJzh%i)%1+!=8iwevPaX!*a52w3XthX5TIW?Or05Y4K^z{y69-Ix@m zp-lrUA?gyq60!?1q*)PTaB|FTSVwt`E&$w5=Y2-b{VTo6e)$xAQhFkB3%+`L)_3xE zBy;vDBs>+VKIPHTCV~r5NRGafQ^)}yvp}G^{=HxOgwOYCcntd_l68E?_T>6;fQP`9 zMTC;SRQG#Z@?zm}En^<`oj3j7+ZbZ}MYjB*e1rCF0(}68Xix@dX21$!kqcb^{bv&O z6c#4kh|tDEF^pe}$jyn^58GhAy~MnK!#w|!jCnDA|83^Iok-*&QzL?ukuFqs8Vk#7 zrWm#uZ95Uf_BzT!rF<3mD4T%_iR65Hxc&Fe?vl8714y?WTP*R=V*+0w((bjOU3ICC zSxIRj_)rMV3$;-Hj&p0O;aJi&XmI51fZFPwfTc)Zm|H`(3n9!e%9flEW`#_qzc_s8o^n(jF2apH4^XXb|Tx2l?EJG?}TU}YwYW~f8;_u_< z-$jhMXjDNY|DW+o&;E}TsPD4FB$G_N3`0yr;ue|aFIfG8vgG{wQ%G3e8Dxn|LA@eUv_x8{3@-y8Lo+~K5-tCQVlI)Q zCBQ>pIWHhu-kFapzxln|kO(DtL5KYm5~@c&7@cS&+8}A!uUxhcd=$-y*GmS;@5Q=* z14}2Cbpt#!*^dMU$p!J{7x=~>PK1uUkin9D3JJBRxOE~4Q3grz(RcC+@W+xt@?UIN zCE6f~gbk8bz#zF0!u+aiNoM@lIqzmfmVe9t|3#S*H7zp|uW8R!s4QSHQkxm`Ms3z; zzZAne8$h}!DgHN-*+10>ap4&GqpJnIxhE3D))q_q@QP81@6<1VIBC3t_@G23U8f>a zqKcOj6BWLosH1?!Lt2Q>c!(aY!i|S2sYg-S)*QsRDaOzhl+RVa;Gq-j@0RrM|2z73 z9;)2OavyS7m$*Y&0Ch;7|Ea<^i;8jORCs7O6IL(~?kt2%>4`5$izKU z^@*XLc%!y-G<@;-@%|}O5i$J`XI278TH4``isB<7*7?2K=NAs6MQyI9;iy>TC~6)> z)hEK$WC0V-$Ra!xO{+UgG?ucm{n_DxU%6iX?lWSK!QBRadsmvHemh77U`*zKpcu1ve0tlBr*6hW^d0a`7{&e~p z6D||O1SPyE8Y_u4$3w5Ma*%4^;&pqHK5&3iGfDLfNPd}QxTNq9sTur==l;{r{ss^C z@jvz#R#}0Z*h~cb!~YNt90)~_-j86=)TH$f2j|>vrA-kqG~8vgki?vuD@fnOfoE2CGhfD zA%7_q^=w?=>=`N|SkQl>4UuWGU~IJ9XFMz?MypRG<*WJAnSNv3`f64|xOCKKTqtp( z#2Bg~Mlj;b2W#lvUz_)^^PtWWmeq>dA-hDSsU<)O>@iPAJ+6d*JFQ$un>5;uWu^9O0v zcung*5HUH0m$@#e@a&#ZvHF?HzH<=q(YCf4ul-NjJF7u~>G#|^*Q|^uj zdec-?pOhGsUw;H9n)3`LA>XGlTe`Dr@pk$d1?r#vPxhoQ4%}tCEPHSR$On`G02IfJ^@uh8s!2m^x1U;+8pV z9XU>$XuJ?xrA2{vetX59P+d_ah;|{Yb~-Cpo|! zu`eEc(^co)QWNw9*8xZ;!B1S-ddkX2z4g?rPO%t6qE#djXtW?nQJcZYL?Z!k+18YM z4p_WF4*ZP{Ao=}bzp&ngLm46{fO+rw%!^25mvWX8r9mu)S^jAq3jW6L2`178-K+*@ z8)pRB#>2klEZt1FNGZr4tE}QlP>!tGThaTvhTfxjXJhjhBYBVw6Qf@*XaUVQf{n9K z{IYt(7q!1G1{uAK^}keeREbZ`>*`S(r&7|a-DC$Ky(;xe&wgZ23?5>Ym%&5N_8=!j z&7lF~u@j=acZiu73dKWTq8)2T6 zY@uY8ttS08YYp=7z&dY7BrM%) znyrQ3ab}G;i0?TQqO`Zy9uKXyI!srqn)> zQAgan<}s^EvYvg zu-bI;jZvT1oBrA(TX}p1-yeP+uAouA`7g56v(h4YZ0T1=Q3Hxe{uqi{Jk)V&*aW=> z)$D01gnMI-!Z|vpa-hd|)f8gy_TZtR*a>SsI5K0nbFtgwXZ6P7Mk?@-TUZFHY9lw6 zQN%I=NsFtgA5a;J!;pfFAJi5=?cY_0ht9h|rg}MuK6gCyRyh>=svCKJ0D;ni}CWDb&J5=h|gT73wt z)gj;=(+i;X3y;{iQuFiIjk{N^=yYLHPSUu|zOsEKW!1J3e;MbWt58#vMfL~EVl{^| zubZMQ-*kY~KyZmx1dy?EOCwN~Q#S^_&n!hW8TU<}?<=4-Y+W^?aMg@`i&Fq~0^%Iw z_)hvTtSu{W)T~uk#>k`csQcNluTUMwRP(K}5d+8gIJ>zz_V!sYlEKQF1=NvoTt+bVKBDzH z9!loM#vwT?%lw~HOn&qqX~9ED?PQ&>k)B8R*Sk0_@;4O%$VQHr?BVZ{r#CQv7k%>v zo#IKBXSoN?V48-PtzE$k{+JvFU4Rlq&1JRCG}{tEx37cOYEb@_x7c9N>-TO4KHcjP zS=soEjB*%Rm5)ORtKxd%a1}AswYPdr1#tDK5gW)NRs05u2FK8eht{MWt+^4NVG^Bs zOQ=js^6K3bT-T}x`A_ahDyX__UKmjT0I2X#bI6!1?(K;G#6gb7vHp#yNe}c&RF!rq zAYQDH)%`=jU+`IFO48p|K%+p~3QQ8DvEK7!6Y68&f~>G_SA}~nT^0V{KRdn#2{g3< zj&_2Vz)`a_x)7{yZrrRf>>Ewl%?n>!rdWWBh?H>#LW>x!(V!?=wrlY4&)LsV{T;;0 zVQPE9H)g9ISurGC=H?b_aY3^0>C?J%uzAhjK4XMkn37ZGQfSep-984=7efG`mT&JOO`h`edH{ z87^UCs0OtC^%(jRAraBaBWcc?&TZW_pfxNEs<^||^I zCmWWC*?)qVQPn^9nzS;F!qy_OF`A4oM(g^Fu|Qhrp)ABYp_>NZA-~puUxr248ZG@AEZWzI`$KG+^eStMc!~PAnQT>e4lP?x{l*^%kW7XPo^P}vvcU%-wK?+uArTcbrF{e7+Wc+;QVd^zksZPr0O+PZ{*uNU> z=2E_E{G}EEtK7UAxiM5VsP^Un>f#lMikOoH(^e0yVrg+m(BUGNKIsdudBl(k@X3ck z--uz-5;6RLKnzd5ZNu%hob^(fcsHWPH}cJRsSGOS1fO`QowqeS2qGs%y$J$V()d$k zNcnis!hF-*_zmN|3A-%c(`mM{h&44SbY1~hbK*QVnsOc5IzY4aNsFq>TvEAhAyR2s z9ne03vKzW3$03pETqvI;&jnMsuCf6@+m3@jX+B6_P}IK>%TOv)9K)PY+M}FryD5iK za1IGX0@bS}g9DYv-EW7z9fue?QrSjsMRnwj@$&1I6CQyht{KNlW`vJw355e;UShx6+lO`&=Wf-J5dI`SLWB{GjJI-H71~S_=GfwO@p9uQZQz#v?8I{xKfc^|jQ#IvJF~*j?%kfZcFRlm{ zXn!K9SsqQ_)q8j-C>^@OxaGPfLSsz*i%vNwbLz$y8|uWguF(`{eQ*xGx`MfX*dVO4BnosG+>VyvipJIDb4}9cx^1-ecivsI~&So5;NCSc{HFA3g z!}7nn zaxll#O3f7OSS~y-z$ChpiELtd{z&0C)f%Zc!b#sb!D#1E!&u#y#28 z4U9+i6Uww#>z}(!y02EoWdbux`+bfGY#6%PZps+-Xm4~5(;ub4atD6eWmFNPf0qb9fnn`42}6=Z}Mp-u+s^Q%s%7$!-T z;Y1#Anjbj9@daIb5+~#5iHA;~0Ca75B*a1;V}#06ERF63{;Re+3Rq}MG-2IFbm=}Ps^jE&eLmSY|mBQ3`2q#F?+k%IT(U`*tc<3DW*kr#t<^>v~nTWX9 zo8C~5u>_QqhGcj>ppR4~&O;>mld3ZetPW;iUZS9RUIPGZ)j9B_h>N2W;eZ~Hoje2P zdaDq-c6gvI6E!YOg~qHhL{E^vgDh7g?j^cxp}sH3g*I-}%#W6M-<9Q>%hx40SN6a6eNXo1z+Ic(XcDjNne5WAI&bnt zN@sH|%f-n(`z-IXtUbhJZDm+|D(s!Soew(Gf8BoH$T(jkF71!wbn8B43%PuKmis9+ zm&JtqME|Ut9#WU<%f!hb@1eW74S=!JE!C$ZV-qS*X2#g3K+%4k5k#QqPDed&4w;G$ z;1yeIfE?g6L=LwYfp%ORH=bRO8>i>zo)`f9so0A)T~l=|Lw4Zk5o7cqAg&2}=Ht!M z?|=S8MT-TeHQ;G|TzIIPClYW; zIzY|2zbBg}{Q1CzB*6{zh$>wKR-n34S!|Q&0oXoZyylbs%Ww99yFxk-&^EU6&zId< z!NPdQirqrE9Jh7MKCwsDqD$lI$j#go=T@FiC=*+WmXC*+!`X9&l%82!Y2;+iuG^MU zY=S`vRv^7knYwLWej{%SifWbr*_F$HeSpy?QQe?l8JNWT+Op)r45I*F@Jh%K&$`Lu z+=PkVC%|=e<+ej=!c*a2(-N=ejQ&rMco73iX>8vl-px}I?`6iin3DnI1why(3lA+R zT1GEF=``VmHPZy3$akoVPSId~7_6emu}kgpK_(q*`GCC10{Id5PQx}XJ(PUz$>J)QLqR<>B$pf_+I*10 zMdXp}aen%P1(0T7zy~0SrT$=H3;|8rDqMu8&|*I3g!$>u!#jBx?--TE=ypY- z^hHBwz9RwdyCnL9a{b7;-w{*gnozp9B1U>Rs7JEGnp;stw9HbzO|r%%VzQl#I+Jl= zk3P5%gb*k%^KkuvAgM-iXBLG>hrKjwtk=ACh{*Rjek#wQMS8h@pj+@$!-Iuief;65 z7jT-}%22&S)yi--CWA!#uRh5}(Wm`vC%Ct}6=uGOC2wip`n;<}+>gptQrL!q?V#$p z2A8*%N4^|?rhSCf-|nIoResUIbCvHMh3S}_%_!F#zZzM8q` z9>S;$G}`jKbxxOub)IJM=dRu!_?@k~C z_bhLO$@S2(J0#;#;y3&kp<}%(A~_|H$vD1SbOS)7Bsj>h)hKifC|s;;Z&4!~6M-s| zC&u_x0k$ovG{DC{FIjK&;`;IYEhI+cR^vV{2HtFiz;E*4t@ zhd^lZ67JIS(JH+r3$D~+r2%(oDNf(5CB&!u7r1C^O#zMfbjY=VL87kpHXmNO0xo@$ zYlI89fUTbf>fD`}I(;`f9iJ|IMLHc{YG|ti2!ixb0QYhcOkUG1H^W0InYyoQ$`l4F zOx>>zI)z7F0C9nXWi(J~5FbHwfQR}Q>{XEY<6FPrJHp_|@gJ^o-$mS2V!yu#{p!S|PmLtF;C00aoJ?W>9bfZ?&SLY$j}!7XQs@Yt_`bo&Jmjgl^C zTB7S91BaMO1H0nx-2ganDtm>YU9sb3fRq58JVJ#ubZyr`*Bzv#a&Zvzy<&-!RXSj{+n+%S8+w8y=SY#nT~@TO<4MkV_#g>J>1kBt;z=sl(I(?IyV%`&0uD{mcyt zP1u_&*4cDOnnNwWQ;ukd8Q&qNz1AYI*LwU~BYM)0ydG=IkHnIGu@(*KyE*Bk4eW-` zKA!S;LLjZOpKBv#w zxq^nB-{uO#WB%*sR-HdKnrUu17p{um4Ie;milNS}0@&%bIrG;4VO@Uy(BW9gPvQ5o zegGx_%1_|Fpf$McvURE#=_&RNxcD)!@X|goi=0|`pm=)+gQc4qCuj`nz>t}Fc_2VA z0)L<49cv198t{TKUjsdX*#frYBLi2L*##8<_5|%BEMfRp>aUu5Z35)lM5;`7rW80fcr2K|%MnfVs;-M?7IA3Py6BqMsi_W7n7|I#f2 zUHYA0qw6<5V@SUk%s?I4hlg-T5Io|KE6}nr0kJ`N2n6Ecs(~@7jHD8-^LG!}&5Ppi z>XDhREI<~(t)XO<#W*O4s`bKE#y#o+PMRu*R`QqqHVJZ@QXg`%NI9S zZfk0`RZ~<`f2~V)N}edasc!EZb3L1+lLo`vIONZp9xaU*ZCUlyQ8HPW_tv!%YhlFC zS&(*Z0~#7bzA?Uz+gvJ|Re9)WPS5(hSC?*JM-F5+l~#oGqi?72H@O2B%*x|F+!;CO zT4x>)g&=;vp@zwf>iUC?li%LXXKsuEFDFI|eU&ZM~QpgFNwGxMf@A|az71~;@Ewwm}+Z|sb9Ann{s4+I9lvh{!2!#+;Pus04POJJI zp!9~`@l9#h0K0e${A2Zcr{t94d-Rh5Vac#LG)VwbE^nmF&^P$_fv(4Sr z1%N0mp+9@fc9rqQF{F6Em2(a9em8T*bFO%^OBymSht8kh?lHW1*=4DAskrSWcu0?% zs$KA_f25`%eY0CN@Z+p?(|33zv5Jgj?Sb5G@muN4(Ca(jzj1d&XT2}ud3m+TcG6J&f|D3_^W-+kYWH%qAQa`pUJ1U1WA>RR zfec$Qmfj+oUfp?}Ak^O>c|>Q}Q1pXe%y<#G40}DH0sK>pc2^WiS6(e>s-9ChI)N=p z5v~kH_-K{K&Hjq=;Z@534d8c$lg8>7XkrA-lNj~n)dDC1FVy(tCU^*j z`9go*QzP)S%+gmx4x|Fu9EH%6SC*_UEh{3wQ0$X1a($R@`Vp654k#{$OGr-grA9Fv zHun*}2YzTfmAh;gkz+V~i*TpW^Jcd6sqZDNVFPq!#m(Fi0V{YV3{l%H<+xolLajMk z9iBJGr8CH2ipb5i%5XB6V(=3>2ZCfh6N0eV@ZUm(}Qoz4w^Uv6-1emR2@ zrfAtg&<%JFnoM=0geeA1pd&t7Wf{}o#eH~{#;3d7s9zupo)emC1o3k}0!@v2#n$OM zm1SsxreKWXpsC;VcS+C`JeLI3NEq}Bq``9rP4HH;Abz$-BJBf}b=J%UngkpS-fqfemWpy2FBu#K2*NO0RdQ*#^ zgvkRV*zy6C9;!3D-T0$5yP^+)NXUaNuRsnYkK_@RVdIZ3@o-Nh@_f3n1Tqvvpg@pP zU@Z0#0&H*09xIrp>?u>H80FMN=(qq%BoS|KyXFh9o!>|El5`s7@u90--sIn0HA?5x z+$YiKvzd#TY8Qnk>$z1V?UxS?F%4>WK3dDXoFraEPIK$3H6A2Krekn_E`AtdX?}9# zZ*%ZY>w~BtOpjqcNe^OULh1}^?rC52ZRR!>zU9#Spe$MEkT2t@ z7Z^3)Ysm$6A9Ow^-KF-HVs2G16$vM!{>{OJuoDIqJ~!OYhn~nj6QAIG;ItFZMf*)8 z633-qD;#W1NmtMGdNvk0s_Jr~B6Kd^+2RC)tmy!A99B5*q4Ub|x15Pve z1J$s%I;dL!HQ0r~VgM0+iHGcf4G$0Dp$m9u#IRK~^Clh=1%}c3gj2}dOph3o&5ETN zZk#iJ=$|gO?bM&7t(q6-kx}-8JFV( zVZ6P0*>cqb2i{!;!cB+z?a7m{8u((O;`SBbBO8bp0BT@%do!}}%)@RFv%7-L9L*~% z@MRk^lRnto#cp_uuW0{X;atW`4V(5?Y}+iY=z0234pU``AVrT_ROpT|yG&a-u?^|n zy44$2^&QTTd2(4o{@CbiXhLw}*r~lkIRoJqMS?zqNIN7QAL_oB`oo$`R-2&{XN-jI ze0)r1{;-lFyeB8dWOPt!xk}OUsuaafN{$}Gv^iTU3-=g^ccDy+D0H|sVBY$1(P7@E zyfLq@0c&!#xJPXcXt9mjTnQXUryr+_!8VC)b4@|^QeO786bFTPD9zyQF!fBvtKH<3ACQV#$+e7_Y?l{^(FdUF*@dbq|8rHba*P+?$#z=28>TUH z?BsZaIR_kyRq)J6%Elc#cMZ~&sdJ9xA!Y|U_w7K4$iejotO)}HmwXInZ~Oug5!B<- zsZSOG8Y+T;#-46;r2I#_L zDSoy7g8Kpxej(pWFIvm?J=lLB7YXysn>Tr| z-bBoEQ4op$DeS5j15gmJv{-B)zz&$_zDEZnyq*iq6C6$W9`6WTZ8L%mF$8>SJ{@SUv*cq2+#67JSo5N^roPdnIfU6RQggH4)BS6i6ch7!+Yr<1wgr@@-aT#F5vy*lQ zpy-4;)5>stK?U-?lr2^;*n?r2!Q5I9&5uaiHK>evo62hrFP!Z#8b~wIuyk^FpSeTx zgDwDsQn@BVNTGja!?H-|bzEOCFAPnIesr2aXg>i-Vgy!V8>KF7 zfa{v#LVb)Gfm8%4HzLw@!yg>(0uQiFQpQ_gg=7;Bd=%K=)L?bkTI9%=uxzZ+J568oB(oR&qq|`>;|_k}UBSs)9-j%W z65Gn&XjUU@ks}O%)_wlVYQ0l<=w2q~ntGWDH(R%rdM%f>G51Y>-&gk8mw83 zR%>ckKPXBqz1{k;jeFB@?dMxV2ZooYzsmIAY{C77R?0+Sg1_quX7J^D%iwK@1~CN+ zD=y29w{3L^_cczRble@3l6)nV#XggRT0wGol=u55TOFWJbfQCz*LqJJZmWC3W!X-4 zT?S(}f@JxowvTMauF?7ihl&6eFZ@<=i9JC~PHR58fc%}Em+3DsN z;CeafXF+qtYrSGy{0&A!=op6YbBT0_=cGfIs9emEiI@te6i^JBr-1QU6`#1%R_CFG zDOz4%LlMJZIvjn5@X~fNy~5F;!qMp4S9)c^d!UNJ^!CB@E{-0CAv4?%NkrSNlJUA> zuQp6^+pVJI{=U7kZMQ0rzP*wlSdHHAW;&a1uLk%BN1qv*)eV`!b#D>sh7l@hSS|SQ z$Br0K>F3AGKo{^kK?mR^Gw%SMpC5<&o*EB$hxhb57U<@?x>Nnk4)*(we)SNZ?%ZiE zzU8fwzd1etC~g2u@DIEU028-pU@ib@+~SsDftKGE@Rz_1n?Vdk7%zU_kHYdW5Cb`P zUeieOt9CQbq`@NSP2<{p@O;BH@QPod+M*s3!Y6-3;JE|vBP`5(9Q_Gy%|(Lw0{{#A zv*j@WXrA%UVDR)F`pNkIK6?MW#meUgo+@&^Up z-*uBSL)u&IGgZ%Q{8H-V^SQJ6?5Cc441&4lSQUO#FXxQire4`3JzryO{Cw&MS`0i6 zBWgvE{RiN*W0%J~`*V);T3c#0}A21!9)r{JON3{6lYjDfQV-7XM{YS2P8$q1i>E6C1N zPzMzv8Mr`&gEJLQ1%RVo*Jf*|fEqxkzlR0~)&g2V1k&v*c<8|a)9-&7*28MNFkRy%jv=_d$*wPvyU2RZTu0Y2`U-(O#Es*XSwjjGwL3acX$Zl~FOj|6 zZOHYCVvLulLU04P>wz7p479I>IG9NK2;4!3RT;Vt`7whDjJp8?reTH>6fGeXF%pUn zPZbrxZ?Hgsc9|oda8H&Ys5H=%2q<|oVgq_=$AGF}jD1)dl2MXWWH0qcmqhN-oHitN zu^8iJETKN5K4C=EU__TKpTa$G*QjIi$17pi_b2%2_pLyk41}QQuin0>@le#zVVVYm zdZ=IsWdaC&LKwg!kp`0iP;8voMfQ~%jMi%~1fB)}!k9ULza41MGTAZb{R_+$?mK_H zm;i*So){D3Ku_@7T0q~OpzoOyjyWap+k)o;ZK|V^DMi`IG4m;HH)c?r5qG zJjfXU*nwDgxIE6TJ%Bo83KQUEw3}YWsYRPvMtEW2lBp#EXf%s6roe?ocra!K;CPn+ z_=Q094IoqCpSwge=l}zAdOQR~n2R#smT~U9zF&@?_Y5z=+@2Sh7uXBah(ERT9adAj zg8}T0#%WxUp93H)0jp;^g7JK+gbm>NELaKCn3~=t=cP_N^{HJ&DoIOq0Nl3z;p2A(WxhS;YTw(M8uS5vW zLysR9ac;@~$KG3j#kFMZ!i~EnxF--GxLeS0@Ss702Y0u|Es&rI9wfMHknY9_Zow@; zaCdjf-8pCG%$)gV=KE*9``_oecj!E%d)KaAdspqZ)>>7|+Wt5@|AMNEKOk8CR|@5N za0(EjpJTTCk1qRj+c>|{ppm}|*uT{IIm>^o1CYI+gzz7bg8w?F{z3}=!7Te7)jt99 z^PTwuB?bWb{0;ekev3a#{x}o=?!0UMtM~C+m;MX=+WhSXO+P`r@Fx)etn~W~1{i_2 zuS@!GE#U$vw!ba>$vNnMCAB28>WURQ*~_7x#fn zME}$dHBiUkFLk7U)&UN^e!E2aj^~H~*fm+dp%bA580%f^Z;bU&E&br*#h)ty*JyUf zDWgmp{8l&(+)Vn8`%hJ-HGdxQ9Pt3{bOG)Bfk8hydi*P>9?(qEp>MzskoYVUu%|@x14L?k(IuubP@m^Zq>{dEKtC)-r8MU) zdC*aCyHu`H@+0oDtA;x$B{K=_H!`Lpt~L}2rsHBCBk^)h`I>b&M<3@Dbis{c(F zf~T=W9VXv?&9PMlxSL-xLBZbaC5Vxv&ooRnp?P_bFg!Aqq9la+t@q3bTBjq zD*a;WW@&7yD*YUt?YX6kv$Cm^guShUy`8C@3pzhKo0O#u&@50*LhTn*V|x?eiWjDK z=0LkV{J_JgqKk;2|LGxreEYEg!j+Sfl>))RgFtY=KhTdEkOT-B5eXRy5g8c?83hFy z6&)8H9Ssc~9~%b~mk6JjmV7@jix zYy=Jk1qB@y{UJK~Lk3bpQigx|^WzH$4;2xFlz{;E7zB?8hkyt7qa8#AJSP&|uOA?T z^RFK`cm&{iQBcv)F@OpXTo61Q0s=fD0umA;B2e20I1fU^LwfLtLmc^`iXqBlM|{qJ z=uA|a=M^mks>27gTt-fTXy}AQ#3ZCo=;#@qGII0q^6?7@O8g=zB`qT>_fkz=L-UoE zwy}w+nYo3fm9vYhn>*OUGw4ll$lG_JVKK3B@d=3^lajNtb8_?Y3kr)WtEwSTSWRtR zYg>CqXIFPm@5t!b_{8M5sp-X~<(1X7^^MJ~!=vMq)3fu7%d4OLf&(G^)-B-wzuPZ7 zpkMHahzN)%Kl=p-?+*MS;2|PC;y`{Nu7YCd`0z1j04o0T=*)^1G#W0|0|FzbVRS-T z?!_mEKfCs8&;Gx4Eb!mz+3y|uOTVT;K*TZN#m z>8G==HLs~kL0VZ^Q7+C=Z|8D8Ab$7KqiwM2ge;w z;H-h&GQ%jc6c5l-O2>*2wd47qOErUsdnUGo^Rf^dy{)R;O5@5ELQzHkTdUMFzk>+y zyi!^(%^5~LAuJBtzcoqK50rgeCI?BS@Aq@H2g1V8OtdXp4q{#=HZ#>+N&7V$)NJ}e z{g(XBu!>iW?e4A*Q~l)n4}XBd4BB|1A=3E>aNz5rfXx zZp(#%YFldsGgI#$Tnwtz)W5q8neMS&LaB{+lDWaa>23JlO!Tg>AAL|uTjeOC&b#y? z&CDEIMpAjUN$0Xu=Q?U)Wa(M`E4xRw!*YSYh-5^BCD-DFxO4g$E9HJqKPlyOhdJI2 z$i6(;5=GzMo_SN7l@v^xHiGW+wO>Mi_+;iijmo&owd?^|U)m~-4dn$rf9~k%E{$q> z_b5aMqc$+%XrV@yjIsuWzsz{4`E9k%JkmvqYQqTkcBB*w<4Tt+OQleO1|6eBbaYVn zdRWJB&cS&=6B*dg05-%NRqASNX^&eCixSvdizh;BCb@i1oBrIRh+9}O>^Sf6eW(jl zzd(a!=Bw#V_v6ya@84#VB1_)4qNx>h(?|9?h%&nFM|evVTqJ6ZG(5_&oRp9s|Xh0{yNl(X-VOv9G^wOj zX^5%IvB2Qs_yTgLs};S_62ZGRm}-XZ-h5qtMrD3`9@$)~dhy|{+JraT;*^QW=drRU zF&j_2K7J_hV@{W%D^7o~x!y5<5JH75V=MHjNj!ulX}g}RqRL)idsmDy2tSnBnpUx# z%)htS301hIIH5B3h~rB0(~W>cp0z&1!3pbN{w54hxWG7_#q{|ayARx*>ul!qmn%}x z$j=-?1$%U1(`fWH(T_u(upT`_Qd8!XrS}Kz>r%D9JF!^z>VMgB)fCN57~ByYl9#Z{ zS!f&|S!+(uh~o`6UHPC-3VJX;(;VrFzzfUfhd~dKi&9j~l;V3`5h*#jZX}XHr_VgH zY6As!S7Z(2C34=i#E(S;qAmnJ8;f7}!#L?@Q@j>(^W{jl3&VAJp2&fI%?nmeF=^WXc)kJtR@a)!2B;Ty!@Wcg40Ba(bby#)?%n#Hl-7WP z{*Bk-`)Q%(3k6qESnPXI6f+-p5AY9AR{_E%I#ZX+GkdZNjUOPTyS`=v!V9M(v4F2h z7IEo~HL=y$9zFVk2w`W8zuu1#2O@DcUkRiek~zAW{V3|rKj7(F#OLn($#bA{DqgdX z>#Ix;Qz}SUpn%X{xQS$TL%qk$_o`c<_Pa%>b%s^GR}FbaOy@!*xd z7Fpw?rP?|^`=^{}80%$jhbQMe-{&loBVZ1SmDAmupwNaP2SpL%2=|`yhor?yIQ$HJ zU&nVFQzj3F&JU?HBR2UJMAYqTEzY)Pj1zS$L~uwuN6%<%oBIZZIjXkP7!GoZ&n!y= zFo&QLI7+dykjP*;2x3QLP#(#hmgkP}mX+Xo5UQ|x= zvbe&ZIbC@i8b_ZJcU_NfjIZ*}Asuq_x+Mhe%f8y(^Bt=!mNA*%{?u@G6YL4`BCwQ- zmzBakoTW5-_{sTcv9({bU)M1Zmi(Q@vEBtj`@~s=EKmEi=~u@N(gbtkhoB5>S^5Y~ zw554;18YyZYc??k@j=Tmxp`Ok>c9LaS|!pC(meXu1xX9-XY7d5o)-#~nn6d*%Q zRND`}Ck>bBs9Z}{Ygh;{e_aV|tMqq{f@Cho53L`nUxU+IJy&yYz=WioVU_i>bRa6B zS8YL^lLN>CoBDl{ATYGlaH)7OFj;<^FcDVQ@o9%IMoiOnw1s?1?~7}!85)I;N5AdX z@po`+Cl$79xG=qX$Ou#TD0^MC#WdVX{7dRPT$yEIaJgcZztHj07!mZ`mvFL_nH7S{ z?IYIjij-xa(rdz~rFl`2mVWE}DzOf-AMX1EOr<>PBCO5wpi?LI$?AA^MIqW3}MqeuS2?>4L({+RRSK07Kf9{rAxcPBi#>SbB<>jv%0D4%XjlxSM| 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

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

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

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

-

- {{ device.model }} -

-

- {{ device.mac_addr }} -

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

+ {{ device.manufacturer }} +

+ + error + +
+

{{ device.manufacturer }}

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

+ {{ device.model }} +

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

{{ title() }}

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

{{ title() }}

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

{{ data.content }}

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

Welcome to Testrun!

-
- - Pilot Assessment -

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

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

Welcome to Testrun!

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

Certificates

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

{{ data.title }}

-
-
- - - -

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

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

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

-
-

- {{ step.title }} -

-

- {{ step.description }} -

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

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

-

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

-
-
-

Summary

-

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

-
-
- -
-

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

-

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

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

- - error - - Unable to create the device -

-
-
-

- Validation error! -

-

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

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

- All existing fields must be filled in. -

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

Devices

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

- {{ description }} -

+
+ +

+ {{ description }} +

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

{{ label }}

+

+ {{ description }} +

+
+
+ + + + + + +
+
+

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

+
+
+
+ +

+ Single port +

+

+ Two ports +

+
+ +

+ + Opt out from Google Analytics + +

+
+ +

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

+
+ + +

This port is required

+ +
+
+ +

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

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

{{ data.title }}

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

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

Reports

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

Reports

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

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

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

-

Profile name *

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

+ {{ profile.name }} +

+
{{ profile.risk }} risk -

-

- {{ profile.name }} -

+

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

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

Risk assessment

-
-
- -
-
-
-
- -
-

Saved profiles

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

System settings

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

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

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

Settings

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

Description

+

{{ data.testResult.description }}

+
+
+

Test result

+

{{ data.testResult.result }}

+

{{ data.testResult.required_result }}

+
+
+
+

Steps to resolve

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

`AFDsmb!=iJ_qlUEIrCH?DiqUaN?EEai(;RKpT> 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�CR(a2EgZ?JO*Y9o}=c# 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%0AFv-+4QUOE^rol`c>G#2VIc~@La`{B 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 z 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%0Dq4Gws=aD&F^bx%HW7OhD~Xll_q;#f*Ux{+6X!Ym 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%0AO$`jZoN{Pl*anf=Y{Jxhz0O6`UFv+Bo=QT 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

`J+0V;UeO>vIPS^p)T&}}Ut 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 diff --git a/docs/ui/getstarted--7cfvdpdnc5o.png b/docs/ui/getstarted--7cfvdpdnc5o.png deleted file mode 100644 index 288e5f876724a11c0e6bd5d49ab2d40061f17dc1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 17881 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 diff --git a/docs/ui/getstarted--j21skepmx1.png b/docs/ui/getstarted--j21skepmx1.png deleted file mode 100644 index 7a4de661a4a14156711344e2e8fd17a0180eee39..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 28185 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 NW@>c>f9X!-{{np0YA^r* diff --git a/docs/ui/getstarted--q5uw26tfod.png b/docs/ui/getstarted--q5uw26tfod.png deleted file mode 100644 index 4b8b2e847a7994704b089d2ff465d8b2c2fd788c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 13929 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 diff --git a/docs/ui/getstarted--w09wecsry3.png b/docs/ui/getstarted--w09wecsry3.png deleted file mode 100644 index e379040b59473f5035bbe2e76c6b1727d78f43ea..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 14001 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 diff --git a/docs/ui/getstarted-actions-device.png b/docs/ui/getstarted-actions-device.png new file mode 100644 index 0000000000000000000000000000000000000000..4363a194ff9c578d19336250b07ae34c5e2a8648 GIT binary patch literal 108601 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 F{vU&O9k>7h literal 0 HcmV?d00001 diff --git a/docs/ui/getstarted-actions-testing.png b/docs/ui/getstarted-actions-testing.png new file mode 100644 index 0000000000000000000000000000000000000000..dbffebb1486b116ce74f78d6b748fe0a514834cf GIT binary patch literal 115595 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

jz^pQsI3rnNI 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

XLnancw0Y&?^B z53d+w^{HFN88`IAdkf=NWwI3X1W3JsD~!!5e#6|Fe|~ixn{;WrQ2Y~frR~wmHPRN7 zz?Fq8yDD1kx_V4{&+$XY%|q%vT$TGCRaL!S)78-cLa)+Y-5d-qxfvMSa4G8U$!nsF zk7i%E51KdT@$>YSN6@?5nOM(GKGVhMe_ErgKL7-d1IygsoZLXb$=zU+=K2u^w7zNj zP7{Qk$Wz9Rfz>ahDsYXA>>tL$3;?$t=~rnWW9~IB;s)7sI&AwMQTAH*Pj{906;+;Z~d!03w&Tx0PnRM zEH@bd&bz(q2pUwk3~(!)Vy#bpaDz zKjXdbg@@-18_;59qe8>G{R{97rty$QDWP@ct(5;5aC1}jr-8a4a#IebjS{%3)1<(d zfvOz=hZ7BmFHKdX zTl}6sX}~$Si$2^H-m^VGcY9FCAvL(FXbJ1Nulu?!h=caHR-C}@ZpqPK|G(NDNG9F= zRI* z+pAum2-W?4e;A4K@VjX{fX6T03iW>-f&!Q!h!41ZOw%D(X5ivAJ2$yP?H~&+I_Lh= z9;(4H`)RvMd;c*;ctpLB`~UfxHiR{0~Q!>jwaSaNHck{4Jz z{fj62+p+>s)mnJ}S^@r61;}#4gOvfshJT{>qr3ayI|cpD*vB9Y6i`{6VYGB!m$psd zR|_k=&bln>JlPXVPid%G-kPBN?Tx@;&;qSyHQVtwZT?%EGDMP z_!C)u>a9~<*QgatM1RCo`fscFdY<-I2-yjfR2B&*h|NF8&O&I?>IPY7z z+DQwy@&8-l2EYu6q}_Om-ygsWsFku=zW*yO{aCIM;hBm_5Hz|2=t2LtbuO*Dvk3f) zIG}aYe-$hLob!E#={KsT9m;b5vp)Z<&%c)D|IcD$r##n}ZQHi1p4T{I{67}|C<#=L zu8mg*O|qdex{f?KU<6?F(PiCl_0qR$=sUOH13vyF4g~ww2etmqmP3qa?sC|#>f86? jb~AZEjDq-^*Ei_wzM$#mYb|rO0e|Pu>1vdog#`aEoRni- literal 0 HcmV?d00001 diff --git a/docs/ui/getstarted-certificates-menu.png b/docs/ui/getstarted-certificates-menu.png new file mode 100644 index 0000000000000000000000000000000000000000..60eceae7a8bcd8e6c806baf1645213c4259a595a GIT binary patch literal 105854 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 R%me}+uITBXDLzO2{{V!Ft9k$c literal 0 HcmV?d00001 diff --git a/docs/rooadmap.pdf b/docs/rooadmap.pdf deleted file mode 100644 index 6107370e6f7227227503d5aac6b4dc1def384ae2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 828501 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