]> git.apps.os.sepia.ceph.com Git - ceph.git/commitdiff
mgr/dashboard: fixed unit tests 59334/head
authorIvo Almeida <ialmeida@redhat.com>
Wed, 13 Nov 2024 12:16:23 +0000 (12:16 +0000)
committerIvo Almeida <ialmeida@redhat.com>
Thu, 6 Feb 2025 16:09:38 +0000 (16:09 +0000)
* fixed unit tests due to upgrade to angular v18
* run npm fix in order to fix code style violations
* upgraded eslint/* packages' versions
* fixed eslint errors and warnings

Fixes: https://tracker.ceph.com/issues/68896
Signed-off-by: Ivo Almeida <ialmeida@redhat.com>
82 files changed:
package-lock.json [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/cypress/tsconfig.json
src/pybind/mgr/dashboard/frontend/jest.config.cjs
src/pybind/mgr/dashboard/frontend/package-lock.json
src/pybind/mgr/dashboard/frontend/package.json
src/pybind/mgr/dashboard/frontend/src/app/app.module.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/block/iscsi-target-form/iscsi-target-form.component.spec.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-gateway/nvmeof-gateway.component.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-configuration-list/rbd-configuration-list.component.spec.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-snapshot-list/rbd-snapshot-list.component.spec.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs-auth-modal/cephfs-auth-modal.component.html
src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs-auth-modal/cephfs-auth-modal.component.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs-chart/cephfs-chart.component.spec.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs-chart/cephfs-chart.component.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs-directories/cephfs-directories.component.spec.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs-snapshotschedule-list/cephfs-snapshotschedule-list.component.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs.module.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/create-cluster/create-cluster.component.html
src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/create-cluster/create-cluster.component.spec.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/create-cluster/create-cluster.component.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/hosts/host-form/host-form.component.spec.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/hosts/hosts.component.html
src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/mgr-modules/mgr-module-form/mgr-module-form.component.spec.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/prometheus/silence-form/silence-form.component.spec.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/telemetry/telemetry.component.spec.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/dashboard-v3/dashboard-area-chart/dashboard-area-chart.component.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/dashboard-v3/dashboard-v3.module.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/dashboard/dashboard.module.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/dashboard/health/health.component.html
src/pybind/mgr/dashboard/frontend/src/app/ceph/dashboard/health/health.component.spec.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/dashboard/mds-dashboard-summary.pipe.spec.ts [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/src/app/ceph/dashboard/mds-dashboard-summary.pipe.ts [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/src/app/ceph/dashboard/mds-summary.pipe.spec.ts [deleted file]
src/pybind/mgr/dashboard/frontend/src/app/ceph/dashboard/mds-summary.pipe.ts [deleted file]
src/pybind/mgr/dashboard/frontend/src/app/ceph/dashboard/mgr-dashboard-summary.pipe.spec.ts [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/src/app/ceph/dashboard/mgr-dashboard-summary.pipe.ts [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/src/app/ceph/dashboard/mgr-summary.pipe.spec.ts [deleted file]
src/pybind/mgr/dashboard/frontend/src/app/ceph/dashboard/mgr-summary.pipe.ts [deleted file]
src/pybind/mgr/dashboard/frontend/src/app/ceph/dashboard/osd-dashboard-summary.pipe.spec.ts [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/src/app/ceph/dashboard/osd-dashboard-summary.pipe.ts [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/src/app/ceph/dashboard/osd-summary.pipe.spec.ts [deleted file]
src/pybind/mgr/dashboard/frontend/src/app/ceph/dashboard/osd-summary.pipe.ts [deleted file]
src/pybind/mgr/dashboard/frontend/src/app/ceph/nfs/nfs-form/nfs-form.component.spec.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/pool/crush-rule-form-modal/crush-rule-form-modal.component.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/pool/erasure-code-profile-form/erasure-code-profile-form-modal.component.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/pool/pool-form/pool-form.component.spec.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/bucket-tag-modal/bucket-tag-modal.component.spec.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-multisite-realm-form/rgw-multisite-realm-form.component.html
src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-multisite-realm-form/rgw-multisite-realm-form.component.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-multisite-sync-flow-modal/rgw-multisite-sync-flow-modal.component.spec.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-multisite-sync-pipe-modal/rgw-multisite-sync-pipe-modal.component.spec.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-multisite-sync-policy-form/rgw-multisite-sync-policy-form.component.spec.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-multisite-wizard/rgw-multisite-wizard.component.spec.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-sync-data-info/rgw-sync-data-info.component.spec.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-sync-metadata-info/rgw-sync-metadata-info.component.spec.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/shared/health-checks/health-checks.component.spec.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/smb/smb-cluster-form/smb-cluster-form.component.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/smb/smb-domain-setting-modal/smb-domain-setting-modal.component.spec.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/smb/smb.module.ts
src/pybind/mgr/dashboard/frontend/src/app/core/auth/role-form/role-form.component.spec.ts
src/pybind/mgr/dashboard/frontend/src/app/core/auth/user-form/user-form.component.spec.ts
src/pybind/mgr/dashboard/frontend/src/app/shared/components/components.module.ts
src/pybind/mgr/dashboard/frontend/src/app/shared/components/critical-confirmation-modal/critical-confirmation-modal.component.spec.ts
src/pybind/mgr/dashboard/frontend/src/app/shared/components/form-advanced-fieldset/form-advanced-fieldset.component.spec.ts
src/pybind/mgr/dashboard/frontend/src/app/shared/components/form-modal/form-modal.component.html
src/pybind/mgr/dashboard/frontend/src/app/shared/components/progress/progress.component.spec.ts
src/pybind/mgr/dashboard/frontend/src/app/shared/components/upgradable/upgradable.component.ts
src/pybind/mgr/dashboard/frontend/src/app/shared/datatable/table-key-value/table-key-value.component.spec.ts
src/pybind/mgr/dashboard/frontend/src/app/shared/directives/form-loading.directive.spec.ts
src/pybind/mgr/dashboard/frontend/src/app/shared/directives/required-field.directive.ts
src/pybind/mgr/dashboard/frontend/src/app/shared/directives/stateful-tab.directive.spec.ts
src/pybind/mgr/dashboard/frontend/src/app/shared/models/breadcrumbs.ts
src/pybind/mgr/dashboard/frontend/src/app/shared/pipes/pipes.module.ts
src/pybind/mgr/dashboard/frontend/src/app/shared/services/api-interceptor.service.ts
src/pybind/mgr/dashboard/frontend/src/app/shared/services/auth-guard.service.ts
src/pybind/mgr/dashboard/frontend/src/app/shared/services/change-password-guard.service.ts
src/pybind/mgr/dashboard/frontend/src/app/shared/services/feature-toggles-guard.service.ts
src/pybind/mgr/dashboard/frontend/src/app/shared/services/modal.service.spec.ts
src/pybind/mgr/dashboard/frontend/src/app/shared/services/module-status-guard.service.ts
src/pybind/mgr/dashboard/frontend/src/app/shared/services/no-sso-guard.service.ts
src/pybind/mgr/dashboard/frontend/src/testing/unit-test-helper.ts
src/pybind/mgr/dashboard/frontend/tsconfig.json

diff --git a/package-lock.json b/package-lock.json
new file mode 100644 (file)
index 0000000..e711a88
--- /dev/null
@@ -0,0 +1,6 @@
+{
+  "name": "ceph",
+  "lockfileVersion": 3,
+  "requires": true,
+  "packages": {}
+}
index 0d1f6b468206ef6ba4ce0215c4a75590c84b9429..c418e11247688d04f278d1b45fb2db64a1cfb13b 100644 (file)
@@ -6,6 +6,10 @@
     "plugins/index.js"
   ],
   "compilerOptions": {
+    "typeRoots": [
+      "../node_modules",
+      "../node_modules/@types"
+    ],
     "sourceMap": false,
     "types": [
       "cypress",
@@ -14,4 +18,4 @@
     ],
     "target": "es6"
   }
-}
+}
\ No newline at end of file
index 6777546d96312ac455faa4aed6a36949fa3f1017..218f9f4c239123a72c14b2042f0adbbf491933fe 100644 (file)
@@ -9,32 +9,38 @@ const esModules = [
   '@ng-bootstrap'
 ];
 const jestConfig = {
-  globals: {
-    'ts-jest': {
-      useESM: true,
-      stringifyContentPathRegex: '\\.(html|svg)$',
-      tsconfig: '<rootDir>/tsconfig.spec.json',
-      isolatedModules: true
-    }
-  },
-  globalSetup: 'jest-preset-angular/global-setup',
   moduleNameMapper: {
     '\\.scss$': 'identity-obj-proxy',
     '~/(.*)$': '<rootDir>/src/$1',
-    '^@carbon/icons/es/(.*)$': '@carbon/icons/lib/$1.js',
+    '^@carbon/icons/es/(.*)$': '@carbon/icons/lib/$1.js'
   },
   moduleFileExtensions: ['ts', 'html', 'js', 'json', 'mjs', 'cjs'],
   preset: 'jest-preset-angular',
   setupFilesAfterEnv: ['<rootDir>/src/setupJest.ts'],
   transformIgnorePatterns: ['node_modules/(?!.*\\.mjs$|'.concat(esModules.join('|'), ')')],
   transform: {
-    '^.+\\.(ts|html|mjs)$': 'jest-preset-angular',
+    '^.+\\.(ts|html|mjs)$': [
+      'jest-preset-angular',
+      {
+        useESM: true,
+        stringifyContentPathRegex: '\\.(html|svg)$',
+        tsconfig: '<rootDir>/tsconfig.spec.json',
+        isolatedModules: true
+      }
+    ],
     '^.+\\.(js)$': 'babel-jest'
   },
   setupFiles: ['jest-canvas-mock'],
   coverageReporters: ['cobertura', 'html'],
-  modulePathIgnorePatterns: ['<rootDir>/coverage/', '<rootDir>/node_modules/simplebar-angular', '<rootDir>/cypress'],
+  modulePathIgnorePatterns: [
+    '<rootDir>/coverage/',
+    '<rootDir>/node_modules/simplebar-angular',
+    '<rootDir>/cypress'
+  ],
   testMatch: ['**/*.spec.ts'],
-  testRunner: 'jest-jasmine2'
+  testRunner: 'jest-jasmine2',
+  testEnvironmentOptions: {
+    detectOpenHandles: true
+  }
 };
 module.exports = jestConfig;
index 536db275af8d8f4d150aad6e4966f36bd48f544e..975c3f74c69ecb567946a1ea7b8a1a4579702117 100644 (file)
@@ -27,9 +27,9 @@
         "@popperjs/core": "2.10.2",
         "@types/file-saver": "2.0.1",
         "async-mutex": "0.2.4",
-        "bootstrap": "5.2.3",
+        "bootstrap": "5.3.2",
         "carbon-components-angular": "5.56.2",
-        "chart.js": "4.4.0",
+        "chart.js": "4.4.7",
         "chartjs-adapter-moment": "1.0.1",
         "detect-browser": "5.2.0",
         "file-saver": "2.0.2",
@@ -38,7 +38,7 @@
         "moment": "2.29.4",
         "ng-block-ui": "4.0.1",
         "ng-click-outside": "9.0.1",
-        "ng2-charts": "4.1.1",
+        "ng2-charts": "7.0.0",
         "ngx-cookie-service": "18.0.0",
         "ngx-toastr": "17.0.2",
         "rxjs": "6.6.3",
       },
       "devDependencies": {
         "@angular-devkit/build-angular": "18.2.11",
-        "@angular-eslint/builder": "13.5.0",
-        "@angular-eslint/eslint-plugin": "13.5.0",
-        "@angular-eslint/eslint-plugin-template": "13.5.0",
-        "@angular-eslint/schematics": "18.3.1",
-        "@angular-eslint/template-parser": "13.5.0",
+        "@angular-eslint/builder": "18.4.0",
+        "@angular-eslint/eslint-plugin": "18.4.0",
+        "@angular-eslint/eslint-plugin-template": "18.4.0",
+        "@angular-eslint/schematics": "18.4.0",
+        "@angular-eslint/template-parser": "18.4.0",
         "@angular/cli": "18.2.11",
         "@angular/compiler-cli": "18.2.11",
         "@angular/language-service": "18.2.11",
         "@applitools/eyes-cypress": "3.22.5",
         "@compodoc/compodoc": "1.1.18",
         "@cypress/browserify-preprocessor": "3.0.2",
+        "@juggle/resize-observer": "3.4.0",
         "@types/brace-expansion": "1.1.0",
+        "@types/cypress": "0.1.6",
+        "@types/cypress-axe": "0.8.0",
         "@types/cypress-cucumber-preprocessor": "4.0.1",
         "@types/jest": "29.5.4",
         "@types/lodash": "4.14.161",
         "@types/node": "18.17.12",
         "@types/swagger-ui": "3.52.0",
         "@types/xml2js": "0.4.14",
-        "@typescript-eslint/eslint-plugin": "5.27.1",
-        "@typescript-eslint/parser": "5.27.1",
+        "@typescript-eslint/eslint-plugin": "8.14.0",
+        "@typescript-eslint/parser": "8.14.0",
         "axe-core": "4.4.3",
         "cypress": "12.17.4",
         "cypress-axe": "1.5.0",
         "cypress-cucumber-preprocessor": "4.3.1",
         "cypress-iframe": "1.0.1",
         "cypress-multi-reporters": "1.5.0",
-        "eslint": "8.17.0",
+        "eslint": "9.14.0",
         "gherkin-lint": "4.2.2",
         "html-linter": "1.1.1",
         "htmllint-cli": "0.0.7",
         "identity-obj-proxy": "3.0.0",
         "isomorphic-form-data": "2.0.0",
-        "jest": "29.6.4",
+        "jest": "29.7.0",
         "jest-canvas-mock": "2.4.0",
         "jest-jasmine2": "28.1.3",
         "jest-preset-angular": "14.2.4",
-        "jest-silent-reporter": "0.5.0",
+        "jest-silent-reporter": "0.6.0",
         "mocha-junit-reporter": "2.1.0",
         "ng-mocks": "14.13.1",
         "npm-run-all": "4.1.5",
       }
     },
     "node_modules/@angular-eslint/builder": {
-      "version": "13.5.0",
-      "resolved": "https://registry.npmjs.org/@angular-eslint/builder/-/builder-13.5.0.tgz",
-      "integrity": "sha512-IYY/HYS4fSddJLs2pAkMkKhHL07driUILPxGnGLblfWuoJBhRspyrVL3uZc3Q4iJXc1RJfaOno9oRw11FGyL6Q==",
+      "version": "18.4.0",
+      "resolved": "https://registry.npmjs.org/@angular-eslint/builder/-/builder-18.4.0.tgz",
+      "integrity": "sha512-FOzGHX/nHSV1wSduSsabsx3aqC1nfde0opEpEDSOJhxExDxKCwoS1XPy1aERGyKip4ZVA6phC3dLtoBH3QMkVQ==",
       "dev": true,
-      "dependencies": {
-        "@nrwl/devkit": "13.1.3"
-      },
       "peerDependencies": {
-        "eslint": "^7.0.0 || ^8.0.0",
+        "eslint": "^8.57.0 || ^9.0.0",
         "typescript": "*"
       }
     },
     "node_modules/@angular-eslint/bundled-angular-compiler": {
-      "version": "13.5.0",
-      "resolved": "https://registry.npmjs.org/@angular-eslint/bundled-angular-compiler/-/bundled-angular-compiler-13.5.0.tgz",
-      "integrity": "sha512-7M/5ilxqPD3ydgqqdLsYs3kBwZgNg2Y6C01B5SEHZNLqLT9kAJa7I4y6GlxCZqejCIh554kdXGeV3abIxFccSg==",
+      "version": "18.4.0",
+      "resolved": "https://registry.npmjs.org/@angular-eslint/bundled-angular-compiler/-/bundled-angular-compiler-18.4.0.tgz",
+      "integrity": "sha512-HlFHt2qgdd+jqyVIkCXmrjHauXo/XY3Rp0UNabk83ejGi/raM/6lEFI7iFWzHxLyiAKk4OgGI5W26giSQw991A==",
       "dev": true
     },
     "node_modules/@angular-eslint/eslint-plugin": {
-      "version": "13.5.0",
-      "resolved": "https://registry.npmjs.org/@angular-eslint/eslint-plugin/-/eslint-plugin-13.5.0.tgz",
-      "integrity": "sha512-k9o9WIqUkdO8tdYFCJ54PUWsNd9HHflih/GmA13EWciBYx8QxciwBh0u4NSAnbtOwp4Y7juGZ/Dta5ZrT/2VBA==",
+      "version": "18.4.0",
+      "resolved": "https://registry.npmjs.org/@angular-eslint/eslint-plugin/-/eslint-plugin-18.4.0.tgz",
+      "integrity": "sha512-Saz9lkWPN3da7ZKW17UsOSN7DeY+TPh+wz/6GCNZCh67Uw2wvMC9agb+4hgpZNXYCP5+u7erqzxQmBoWnS/A+A==",
       "dev": true,
       "dependencies": {
-        "@angular-eslint/utils": "13.5.0",
-        "@typescript-eslint/experimental-utils": "5.27.1"
+        "@angular-eslint/bundled-angular-compiler": "18.4.0",
+        "@angular-eslint/utils": "18.4.0"
       },
       "peerDependencies": {
-        "eslint": "^7.0.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": "13.5.0",
-      "resolved": "https://registry.npmjs.org/@angular-eslint/eslint-plugin-template/-/eslint-plugin-template-13.5.0.tgz",
-      "integrity": "sha512-ZVSXayn8MqYOhYomH2Cjc0azhuUQbY9fp9dKjJZOD64KhP8BYHw8+Ogc9E/FU5oZQ9fKw6A+23NAYKmLNqSAgA==",
+      "version": "18.4.0",
+      "resolved": "https://registry.npmjs.org/@angular-eslint/eslint-plugin-template/-/eslint-plugin-template-18.4.0.tgz",
+      "integrity": "sha512-n3uZFCy76DnggPqjSVFV3gYD1ik7jCG28o2/HO4kobcMNKnwW8XAlFUagQ4TipNQh7fQiAefsEqvv2quMsYDVw==",
       "dev": true,
       "dependencies": {
-        "@angular-eslint/bundled-angular-compiler": "13.5.0",
-        "@typescript-eslint/experimental-utils": "5.27.1",
-        "aria-query": "^4.2.2",
-        "axobject-query": "^2.2.0"
+        "@angular-eslint/bundled-angular-compiler": "18.4.0",
+        "@angular-eslint/utils": "18.4.0",
+        "aria-query": "5.3.2",
+        "axobject-query": "4.1.0"
       },
       "peerDependencies": {
-        "eslint": "^7.0.0 || ^8.0.0",
+        "@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.0",
+      "resolved": "https://registry.npmjs.org/@angular-eslint/schematics/-/schematics-18.4.0.tgz",
+      "integrity": "sha512-ssqe+0YCfekbWIXNdCrHfoPK/bPZAWybs0Bn/b99dfd8h8uyXkERo9AzIOx4Uyj/08SkP9aPL/0uOOEHDsRGwQ==",
       "dev": true,
       "dependencies": {
-        "@angular-eslint/eslint-plugin": "18.3.1",
-        "@angular-eslint/eslint-plugin-template": "18.3.1",
+        "@angular-eslint/eslint-plugin": "18.4.0",
+        "@angular-eslint/eslint-plugin-template": "18.4.0",
         "ignore": "5.3.2",
         "semver": "7.6.3",
         "strip-json-comments": "3.1.1"
         "@angular-devkit/schematics": ">= 18.0.0 < 19.0.0"
       }
     },
-    "node_modules/@angular-eslint/schematics/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==",
-      "dev": true
-    },
-    "node_modules/@angular-eslint/schematics/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==",
-      "dev": true,
-      "dependencies": {
-        "@angular-eslint/bundled-angular-compiler": "18.3.1",
-        "@angular-eslint/utils": "18.3.1"
-      },
-      "peerDependencies": {
-        "@typescript-eslint/utils": "^7.11.0 || ^8.0.0",
-        "eslint": "^8.57.0 || ^9.0.0",
-        "typescript": "*"
-      }
-    },
-    "node_modules/@angular-eslint/schematics/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==",
-      "dev": true,
-      "dependencies": {
-        "@angular-eslint/bundled-angular-compiler": "18.3.1",
-        "@angular-eslint/utils": "18.3.1",
-        "aria-query": "5.3.0",
-        "axobject-query": "4.1.0"
-      },
-      "peerDependencies": {
-        "@typescript-eslint/utils": "^7.11.0 || ^8.0.0",
-        "eslint": "^8.57.0 || ^9.0.0",
-        "typescript": "*"
-      }
-    },
-    "node_modules/@angular-eslint/schematics/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==",
-      "dev": true,
-      "dependencies": {
-        "@angular-eslint/bundled-angular-compiler": "18.3.1"
-      },
-      "peerDependencies": {
-        "@typescript-eslint/utils": "^7.11.0 || ^8.0.0",
-        "eslint": "^8.57.0 || ^9.0.0",
-        "typescript": "*"
-      }
-    },
-    "node_modules/@angular-eslint/schematics/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==",
-      "dev": true,
-      "dependencies": {
-        "dequal": "^2.0.3"
-      }
-    },
-    "node_modules/@angular-eslint/schematics/node_modules/axobject-query": {
-      "version": "4.1.0",
-      "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz",
-      "integrity": "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==",
-      "dev": true,
-      "engines": {
-        "node": ">= 0.4"
-      }
-    },
     "node_modules/@angular-eslint/schematics/node_modules/semver": {
       "version": "7.6.3",
       "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz",
       }
     },
     "node_modules/@angular-eslint/template-parser": {
-      "version": "13.5.0",
-      "resolved": "https://registry.npmjs.org/@angular-eslint/template-parser/-/template-parser-13.5.0.tgz",
-      "integrity": "sha512-k+24+kBjaOuthfp9RBQB0zH6UqeizZuFQFEuZEQbvirPbdQ2SqNBw7IcmW2Qw1v7fjFe6/6gqK7wm2g7o9ZZvA==",
+      "version": "18.4.0",
+      "resolved": "https://registry.npmjs.org/@angular-eslint/template-parser/-/template-parser-18.4.0.tgz",
+      "integrity": "sha512-VTep3Xd3IOaRIPL+JN/TV4/2DqUPbjtF3TNY15diD/llnrEhqFnmsvMihexbQyTqzOG+zU554oK44YfvAtHOrw==",
       "dev": true,
       "dependencies": {
-        "@angular-eslint/bundled-angular-compiler": "13.5.0",
-        "eslint-scope": "^5.1.0"
+        "@angular-eslint/bundled-angular-compiler": "18.4.0",
+        "eslint-scope": "^8.0.2"
       },
       "peerDependencies": {
-        "eslint": "^7.0.0 || ^8.0.0",
+        "eslint": "^8.57.0 || ^9.0.0",
         "typescript": "*"
       }
     },
+    "node_modules/@angular-eslint/template-parser/node_modules/eslint-scope": {
+      "version": "8.2.0",
+      "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.2.0.tgz",
+      "integrity": "sha512-PHlWUfG6lvPc3yvP5A4PNyBL1W8fkDUccmI21JUu/+GKZBoH/W5u6usENXUrWFRsyoW5ACUjFGgAFQp5gUlb/A==",
+      "dev": true,
+      "dependencies": {
+        "esrecurse": "^4.3.0",
+        "estraverse": "^5.2.0"
+      },
+      "engines": {
+        "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+      },
+      "funding": {
+        "url": "https://opencollective.com/eslint"
+      }
+    },
+    "node_modules/@angular-eslint/template-parser/node_modules/estraverse": {
+      "version": "5.3.0",
+      "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz",
+      "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==",
+      "dev": true,
+      "engines": {
+        "node": ">=4.0"
+      }
+    },
     "node_modules/@angular-eslint/utils": {
-      "version": "13.5.0",
-      "resolved": "https://registry.npmjs.org/@angular-eslint/utils/-/utils-13.5.0.tgz",
-      "integrity": "sha512-wX3W6STSDJDJ7ZyEsUdBp4HUPwmillMmKcdnFsy+qxbpJFzFOxOFpK1zet4ELsq1XpB89i9vRvC3vYbpHn3CSw==",
+      "version": "18.4.0",
+      "resolved": "https://registry.npmjs.org/@angular-eslint/utils/-/utils-18.4.0.tgz",
+      "integrity": "sha512-At1yS8GRviGBoaupiQwEOL4/IcZJCE/+2vpXdItMWPGB1HWetxlKAUZTMmIBX/r5Z7CoXxl+LbqpGhrhyzIQAg==",
       "dev": true,
       "dependencies": {
-        "@angular-eslint/bundled-angular-compiler": "13.5.0",
-        "@typescript-eslint/experimental-utils": "5.27.1"
+        "@angular-eslint/bundled-angular-compiler": "18.4.0"
       },
       "peerDependencies": {
-        "eslint": "^7.0.0 || ^8.0.0",
+        "@typescript-eslint/utils": "^7.11.0 || ^8.0.0",
+        "eslint": "^8.57.0 || ^9.0.0",
         "typescript": "*"
       }
     },
         "node": ">=18"
       }
     },
+    "node_modules/@eslint-community/eslint-utils": {
+      "version": "4.4.1",
+      "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.1.tgz",
+      "integrity": "sha512-s3O3waFUrMV8P/XaF/+ZTp1X9XBZW1a4B97ZnjQF2KYWaFD2A8KyFBsrsfSjEmjn3RGWAIuvlneuZm3CUK3jbA==",
+      "dev": true,
+      "dependencies": {
+        "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.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"
+      }
+    },
+    "node_modules/@eslint/config-array": {
+      "version": "0.18.0",
+      "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.18.0.tgz",
+      "integrity": "sha512-fTxvnS1sRMu3+JjXwJG0j/i4RT9u4qJ+lqS/yCGap4lH4zZGzQ7tu+xZqQmcMZq5OBZDL4QRxQzRjkWcGt8IVw==",
+      "dev": true,
+      "dependencies": {
+        "@eslint/object-schema": "^2.1.4",
+        "debug": "^4.3.1",
+        "minimatch": "^3.1.2"
+      },
+      "engines": {
+        "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+      }
+    },
+    "node_modules/@eslint/config-array/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/@eslint/config-array/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/core": {
+      "version": "0.7.0",
+      "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.7.0.tgz",
+      "integrity": "sha512-xp5Jirz5DyPYlPiKat8jaq0EmYvDXKKpzTbxXMpT9eqlRJkRKIz9AGMdlvYjih+im+QlhWrpvVjl8IPC/lHlUw==",
+      "dev": true,
+      "engines": {
+        "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+      }
+    },
     "node_modules/@eslint/eslintrc": {
-      "version": "1.4.1",
-      "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.4.1.tgz",
-      "integrity": "sha512-XXrH9Uarn0stsyldqDYq8r++mROmWRI1xKMXa640Bb//SY1+ECYX6VzT6Lcx5frD0V30XieqJ0oX9I2Xj5aoMA==",
+      "version": "3.1.0",
+      "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.1.0.tgz",
+      "integrity": "sha512-4Bfj15dVJdoy3RfZmmo86RK1Fwzn6SstsvK9JS+BaVKqC6QQQQyXekNaC+g+LKNgkQ+2VhGAzm6hO40AhMR3zQ==",
       "dev": true,
       "dependencies": {
         "ajv": "^6.12.4",
         "debug": "^4.3.2",
-        "espree": "^9.4.0",
-        "globals": "^13.19.0",
+        "espree": "^10.0.1",
+        "globals": "^14.0.0",
         "ignore": "^5.2.0",
         "import-fresh": "^3.2.1",
         "js-yaml": "^4.1.0",
         "strip-json-comments": "^3.1.1"
       },
       "engines": {
-        "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+        "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
       },
       "funding": {
         "url": "https://opencollective.com/eslint"
       }
     },
     "node_modules/@eslint/eslintrc/node_modules/globals": {
-      "version": "13.23.0",
-      "resolved": "https://registry.npmjs.org/globals/-/globals-13.23.0.tgz",
-      "integrity": "sha512-XAmF0RjlrjY23MA51q3HltdlGxUpXPvg0GioKiD9X6HD28iMjo2dKC8Vqwm7lne4GNr78+RHTfliktR6ZH09wA==",
+      "version": "14.0.0",
+      "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz",
+      "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==",
       "dev": true,
-      "dependencies": {
-        "type-fest": "^0.20.2"
-      },
       "engines": {
-        "node": ">=8"
+        "node": ">=18"
       },
       "funding": {
         "url": "https://github.com/sponsors/sindresorhus"
         "url": "https://github.com/sponsors/sindresorhus"
       }
     },
+    "node_modules/@eslint/js": {
+      "version": "9.14.0",
+      "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.14.0.tgz",
+      "integrity": "sha512-pFoEtFWCPyDOl+C6Ift+wC7Ro89otjigCf5vcuWqWgqNSQbRrpjSvdeE6ofLz4dHmyxD5f7gIdGT4+p36L6Twg==",
+      "dev": true,
+      "engines": {
+        "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+      }
+    },
+    "node_modules/@eslint/object-schema": {
+      "version": "2.1.4",
+      "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.4.tgz",
+      "integrity": "sha512-BsWiH1yFGjXXS2yvrf5LyuoSIIbPrGUWob917o+BTKuZ7qJdxX8aJLRxs1fS9n6r7vESrq1OUqb68dANcFXuQQ==",
+      "dev": true,
+      "engines": {
+        "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+      }
+    },
+    "node_modules/@eslint/plugin-kit": {
+      "version": "0.2.2",
+      "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.2.tgz",
+      "integrity": "sha512-CXtq5nR4Su+2I47WPOlWud98Y5Lv8Kyxp2ukhgFx/eW6Blm18VXJO5WuQylPugRo8nbluoi6GvvxBLqHcvqUUw==",
+      "dev": true,
+      "dependencies": {
+        "levn": "^0.4.1"
+      },
+      "engines": {
+        "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+      }
+    },
+    "node_modules/@eslint/plugin-kit/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/@eslint/plugin-kit/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,
+      "engines": {
+        "node": ">= 0.8.0"
+      }
+    },
+    "node_modules/@eslint/plugin-kit/node_modules/type-check": {
+      "version": "0.4.0",
+      "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
+      "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==",
+      "dev": true,
+      "dependencies": {
+        "prelude-ls": "^1.2.1"
+      },
+      "engines": {
+        "node": ">= 0.8.0"
+      }
+    },
     "node_modules/@fastify/busboy": {
       "version": "2.1.0",
       "resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-2.1.0.tgz",
         "@hapi/hoek": "^9.0.0"
       }
     },
-    "node_modules/@humanwhocodes/config-array": {
-      "version": "0.9.5",
-      "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.9.5.tgz",
-      "integrity": "sha512-ObyMyWxZiCu/yTisA7uzx81s40xR2fD5Cg/2Kq7G02ajkNubJf6BopgDTmDyc3U7sXpNKM8cYOw7s7Tyr+DnCw==",
+    "node_modules/@humanfs/core": {
+      "version": "0.19.1",
+      "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz",
+      "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==",
       "dev": true,
-      "dependencies": {
-        "@humanwhocodes/object-schema": "^1.2.1",
-        "debug": "^4.1.1",
-        "minimatch": "^3.0.4"
-      },
       "engines": {
-        "node": ">=10.10.0"
+        "node": ">=18.18.0"
       }
     },
-    "node_modules/@humanwhocodes/config-array/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/@humanfs/node": {
+      "version": "0.16.6",
+      "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.6.tgz",
+      "integrity": "sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==",
       "dev": true,
       "dependencies": {
-        "balanced-match": "^1.0.0",
-        "concat-map": "0.0.1"
+        "@humanfs/core": "^0.19.1",
+        "@humanwhocodes/retry": "^0.3.0"
+      },
+      "engines": {
+        "node": ">=18.18.0"
       }
     },
-    "node_modules/@humanwhocodes/config-array/node_modules/minimatch": {
-      "version": "3.1.2",
-      "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
-      "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
+    "node_modules/@humanfs/node/node_modules/@humanwhocodes/retry": {
+      "version": "0.3.1",
+      "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.3.1.tgz",
+      "integrity": "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==",
       "dev": true,
-      "dependencies": {
-        "brace-expansion": "^1.1.7"
+      "engines": {
+        "node": ">=18.18"
       },
+      "funding": {
+        "type": "github",
+        "url": "https://github.com/sponsors/nzakas"
+      }
+    },
+    "node_modules/@humanwhocodes/module-importer": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz",
+      "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==",
+      "dev": true,
       "engines": {
-        "node": "*"
+        "node": ">=12.22"
+      },
+      "funding": {
+        "type": "github",
+        "url": "https://github.com/sponsors/nzakas"
       }
     },
-    "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==",
-      "dev": true
+    "node_modules/@humanwhocodes/retry": {
+      "version": "0.4.1",
+      "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.1.tgz",
+      "integrity": "sha512-c7hNEllBlenFTHBky65mhq8WD2kbN9Q6gk0bTk8lSBvc554jpXSkST1iePudpt7+A/AQvuHs9EMqjHDXMY1lrA==",
+      "dev": true,
+      "engines": {
+        "node": ">=18.18"
+      },
+      "funding": {
+        "type": "github",
+        "url": "https://github.com/sponsors/nzakas"
+      }
     },
     "node_modules/@ibm/plex": {
       "version": "6.4.0",
       "resolved": "https://registry.npmjs.org/@types/node/-/node-22.9.0.tgz",
       "integrity": "sha512-vuyHg81vvWA1Z1ELfvLko2c8f34gyA0zaic0+Rllc5lbCnbSyuvb2Oxpm6TAUAC/2xZN3QGqxBNggD1nNR2AfQ==",
       "dev": true,
-      "license": "MIT",
       "dependencies": {
         "undici-types": "~6.19.8"
       }
         "tslib": "2"
       }
     },
+    "node_modules/@juggle/resize-observer": {
+      "version": "3.4.0",
+      "resolved": "https://registry.npmjs.org/@juggle/resize-observer/-/resize-observer-3.4.0.tgz",
+      "integrity": "sha512-dfLbk+PwWvFzSxwk3n5ySL0hfBog779o8h68wK/7/APo/7cgyWp5jcXockbxdk5kFRkbeXWm4Fbi9FrdN381sA==",
+      "dev": true,
+      "license": "Apache-2.0"
+    },
     "node_modules/@kurkle/color": {
-      "version": "0.3.2",
-      "resolved": "https://registry.npmjs.org/@kurkle/color/-/color-0.3.2.tgz",
-      "integrity": "sha512-fuscdXJ9G1qb7W8VdHi+IwRqij3lBkosAm4ydQtEmbY58OzHXqQhvlxqEkoz0yssNVn38bcpRWgA9PP+OGoisw=="
+      "version": "0.3.4",
+      "resolved": "https://registry.npmjs.org/@kurkle/color/-/color-0.3.4.tgz",
+      "integrity": "sha512-M5UknZPHRu3DEDWoipU6sE8PdkZ6Z/S+v4dD+Ke8IaNlpdSQah50lz1KtcFBa2vsdOnwbbnxJwVM4wty6udA5w==",
+      "license": "MIT"
     },
     "node_modules/@leichtgewicht/ip-codec": {
       "version": "2.0.5",
         "node": "^16.13.0 || >=18.0.0"
       }
     },
-    "node_modules/@nrwl/cli": {
-      "version": "15.9.3",
-      "resolved": "https://registry.npmjs.org/@nrwl/cli/-/cli-15.9.3.tgz",
-      "integrity": "sha512-qiAKHkov3iBx6hroPTitUrkRSUZFQqVgNJiF9gXRFC6pNJe9RS4rlmcIaoUFOboi9CnH5jwblNJVcz8YSVYOvA==",
-      "dev": true,
-      "dependencies": {
-        "nx": "15.9.3"
-      }
-    },
-    "node_modules/@nrwl/cli/node_modules/@nrwl/tao": {
-      "version": "15.9.3",
-      "resolved": "https://registry.npmjs.org/@nrwl/tao/-/tao-15.9.3.tgz",
-      "integrity": "sha512-NcjFCbuMa53C3fBrK7qLUImUBySyr9EVwmiZuAv9sZZtm4eILK8w3qihjrB4FFUuLjPU/SViriYXi+hF2tbP4w==",
-      "dev": true,
-      "dependencies": {
-        "nx": "15.9.3"
-      },
-      "bin": {
-        "tao": "index.js"
-      }
-    },
-    "node_modules/@nrwl/cli/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/@nrwl/cli/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/@nrwl/cli/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/@nrwl/cli/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/@nrwl/cli/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==",
-      "dev": true,
-      "engines": {
-        "node": ">=6"
-      },
-      "funding": {
-        "url": "https://github.com/sponsors/sindresorhus"
-      }
-    },
-    "node_modules/@nrwl/cli/node_modules/cliui": {
-      "version": "7.0.4",
-      "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz",
-      "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==",
-      "dev": true,
-      "dependencies": {
-        "string-width": "^4.2.0",
-        "strip-ansi": "^6.0.0",
-        "wrap-ansi": "^7.0.0"
-      }
-    },
-    "node_modules/@nrwl/cli/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/@nrwl/cli/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/@nrwl/cli/node_modules/fast-glob": {
-      "version": "3.2.7",
-      "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.7.tgz",
-      "integrity": "sha512-rYGMRwip6lUMvYD3BTScMwT1HtAs2d71SMv66Vrxs0IekGZEjhM0pcMfjQPnknBt2zeCwQMEupiN02ZP4DiT1Q==",
-      "dev": true,
-      "dependencies": {
-        "@nodelib/fs.stat": "^2.0.2",
-        "@nodelib/fs.walk": "^1.2.3",
-        "glob-parent": "^5.1.2",
-        "merge2": "^1.3.0",
-        "micromatch": "^4.0.4"
-      },
-      "engines": {
-        "node": ">=8"
-      }
-    },
-    "node_modules/@nrwl/cli/node_modules/fs-extra": {
-      "version": "11.1.1",
-      "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.1.1.tgz",
-      "integrity": "sha512-MGIE4HOvQCeUCzmlHs0vXpih4ysz4wg9qiSAu6cd42lVwPbTM1TjV7RusoyQqMmk/95gdQZX72u+YW+c3eEpFQ==",
-      "dev": true,
-      "dependencies": {
-        "graceful-fs": "^4.2.0",
-        "jsonfile": "^6.0.1",
-        "universalify": "^2.0.0"
-      },
-      "engines": {
-        "node": ">=14.14"
-      }
-    },
-    "node_modules/@nrwl/cli/node_modules/glob": {
-      "version": "7.1.4",
-      "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.4.tgz",
-      "integrity": "sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==",
-      "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"
-      },
-      "engines": {
-        "node": "*"
-      }
-    },
-    "node_modules/@nrwl/cli/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/@nrwl/cli/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==",
-      "dev": true,
-      "engines": {
-        "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
-      }
-    },
-    "node_modules/@nrwl/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,
-      "dependencies": {
-        "yallist": "^4.0.0"
-      },
-      "engines": {
-        "node": ">=10"
-      }
-    },
-    "node_modules/@nrwl/cli/node_modules/minimatch": {
-      "version": "3.0.5",
-      "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.5.tgz",
-      "integrity": "sha512-tUpxzX0VAzJHjLu0xUfFv1gwVp9ba3IOuRAVH2EGuRW8a5emA2FlACLqiT/lDVtS1W+TGNwqz3sWaNyLgDJWuw==",
-      "dev": true,
-      "dependencies": {
-        "brace-expansion": "^1.1.7"
-      },
-      "engines": {
-        "node": "*"
-      }
-    },
-    "node_modules/@nrwl/cli/node_modules/nx": {
-      "version": "15.9.3",
-      "resolved": "https://registry.npmjs.org/nx/-/nx-15.9.3.tgz",
-      "integrity": "sha512-GLwbykfTABc7/UZjQEEnV1bQbTVC53W+Zj4xWY640/45I4iZf/TUqKMBCgtLZ9v89gEsKOM4zsx55CqHT3bekA==",
-      "dev": true,
-      "hasInstallScript": true,
-      "dependencies": {
-        "@nrwl/cli": "15.9.3",
-        "@nrwl/tao": "15.9.3",
-        "@parcel/watcher": "2.0.4",
-        "@yarnpkg/lockfile": "^1.1.0",
-        "@yarnpkg/parsers": "^3.0.0-rc.18",
-        "@zkochan/js-yaml": "0.0.6",
-        "axios": "^1.0.0",
-        "chalk": "^4.1.0",
-        "cli-cursor": "3.1.0",
-        "cli-spinners": "2.6.1",
-        "cliui": "^7.0.2",
-        "dotenv": "~10.0.0",
-        "enquirer": "~2.3.6",
-        "fast-glob": "3.2.7",
-        "figures": "3.2.0",
-        "flat": "^5.0.2",
-        "fs-extra": "^11.1.0",
-        "glob": "7.1.4",
-        "ignore": "^5.0.4",
-        "js-yaml": "4.1.0",
-        "jsonc-parser": "3.2.0",
-        "lines-and-columns": "~2.0.3",
-        "minimatch": "3.0.5",
-        "npm-run-path": "^4.0.1",
-        "open": "^8.4.0",
-        "semver": "7.3.4",
-        "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",
-        "v8-compile-cache": "2.3.0",
-        "yargs": "^17.6.2",
-        "yargs-parser": "21.1.1"
-      },
-      "bin": {
-        "nx": "bin/nx.js"
-      },
-      "optionalDependencies": {
-        "@nrwl/nx-darwin-arm64": "15.9.3",
-        "@nrwl/nx-darwin-x64": "15.9.3",
-        "@nrwl/nx-linux-arm-gnueabihf": "15.9.3",
-        "@nrwl/nx-linux-arm64-gnu": "15.9.3",
-        "@nrwl/nx-linux-arm64-musl": "15.9.3",
-        "@nrwl/nx-linux-x64-gnu": "15.9.3",
-        "@nrwl/nx-linux-x64-musl": "15.9.3",
-        "@nrwl/nx-win32-arm64-msvc": "15.9.3",
-        "@nrwl/nx-win32-x64-msvc": "15.9.3"
-      },
-      "peerDependencies": {
-        "@swc-node/register": "^1.4.2",
-        "@swc/core": "^1.2.173"
-      },
-      "peerDependenciesMeta": {
-        "@swc-node/register": {
-          "optional": true
-        },
-        "@swc/core": {
-          "optional": true
-        }
-      }
-    },
-    "node_modules/@nrwl/cli/node_modules/semver": {
-      "version": "7.3.4",
-      "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.4.tgz",
-      "integrity": "sha512-tCfb2WLjqFAtXn4KEdxIhalnRtoKFN7nAwj0B3ZXCbQloV2tq5eDbcTmT68JJD3nRJq24/XgxtQKFIpQdtvmVw==",
-      "dev": true,
-      "dependencies": {
-        "lru-cache": "^6.0.0"
-      },
-      "bin": {
-        "semver": "bin/semver.js"
-      },
-      "engines": {
-        "node": ">=10"
-      }
-    },
-    "node_modules/@nrwl/cli/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/@nrwl/cli/node_modules/tmp": {
-      "version": "0.2.1",
-      "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz",
-      "integrity": "sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==",
-      "dev": true,
-      "dependencies": {
-        "rimraf": "^3.0.0"
-      },
-      "engines": {
-        "node": ">=8.17.0"
-      }
-    },
-    "node_modules/@nrwl/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/@nrwl/devkit": {
-      "version": "13.1.3",
-      "resolved": "https://registry.npmjs.org/@nrwl/devkit/-/devkit-13.1.3.tgz",
-      "integrity": "sha512-TAAsZJvVc/obeH0rZKY6miVhyM2GHGb8qIWp9MAIdLlXf4VDcNC7rxwb5OrGVSwuTTjqGYBGPUx0yEogOOJthA==",
-      "dev": true,
-      "dependencies": {
-        "@nrwl/tao": "13.1.3",
-        "ejs": "^3.1.5",
-        "ignore": "^5.0.4",
-        "rxjs": "^6.5.4",
-        "semver": "7.3.4",
-        "tslib": "^2.0.0"
-      }
-    },
-    "node_modules/@nrwl/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/@nrwl/devkit/node_modules/semver": {
-      "version": "7.3.4",
-      "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.4.tgz",
-      "integrity": "sha512-tCfb2WLjqFAtXn4KEdxIhalnRtoKFN7nAwj0B3ZXCbQloV2tq5eDbcTmT68JJD3nRJq24/XgxtQKFIpQdtvmVw==",
-      "dev": true,
-      "dependencies": {
-        "lru-cache": "^6.0.0"
-      },
-      "bin": {
-        "semver": "bin/semver.js"
-      },
-      "engines": {
-        "node": ">=10"
-      }
-    },
-    "node_modules/@nrwl/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/@nrwl/nx-darwin-arm64": {
-      "version": "15.9.3",
-      "resolved": "https://registry.npmjs.org/@nrwl/nx-darwin-arm64/-/nx-darwin-arm64-15.9.3.tgz",
-      "integrity": "sha512-2htJzVa+S/uLg5tj4nbO/tRz2SRMQIpT6EeWMgDGuEKQdpuRLVj2ez9hMpkRn9tl1tBUwR05hbV28DnOLRESVA==",
-      "cpu": [
-        "arm64"
-      ],
-      "dev": true,
-      "optional": true,
-      "os": [
-        "darwin"
-      ],
-      "engines": {
-        "node": ">= 10"
-      }
-    },
-    "node_modules/@nrwl/nx-darwin-x64": {
-      "version": "15.9.3",
-      "resolved": "https://registry.npmjs.org/@nrwl/nx-darwin-x64/-/nx-darwin-x64-15.9.3.tgz",
-      "integrity": "sha512-p+8UkfC6KTLOX4XRt7NSP8DoTzEgs73+SN0csoXT9VsNO35+F0Z5zMZxpEc7RVo5Wen/4PGh2OWA+8gtgntsJQ==",
-      "cpu": [
-        "x64"
-      ],
-      "dev": true,
-      "optional": true,
-      "os": [
-        "darwin"
-      ],
-      "engines": {
-        "node": ">= 10"
-      }
-    },
-    "node_modules/@nrwl/nx-linux-arm-gnueabihf": {
-      "version": "15.9.3",
-      "resolved": "https://registry.npmjs.org/@nrwl/nx-linux-arm-gnueabihf/-/nx-linux-arm-gnueabihf-15.9.3.tgz",
-      "integrity": "sha512-xwW7bZtggrxhFbYvvWWArtcSWwoxWzi/4wNgP3wPbcZFNZiraahVQSpIyJXrS9aajGbdvuDBM8cbDsMj9v7mwg==",
-      "cpu": [
-        "arm"
-      ],
-      "dev": true,
-      "optional": true,
-      "os": [
-        "linux"
-      ],
-      "engines": {
-        "node": ">= 10"
-      }
-    },
-    "node_modules/@nrwl/nx-linux-arm64-gnu": {
-      "version": "15.9.3",
-      "resolved": "https://registry.npmjs.org/@nrwl/nx-linux-arm64-gnu/-/nx-linux-arm64-gnu-15.9.3.tgz",
-      "integrity": "sha512-KNxDL2OAHxhFqztEjv2mNwXD6xrzoUury7NsYZYqlxJUNc3YYBfRSLEatnw491crvMBndbxfGVTWEO9S4YmRuw==",
-      "cpu": [
-        "arm64"
-      ],
-      "dev": true,
-      "optional": true,
-      "os": [
-        "linux"
-      ],
-      "engines": {
-        "node": ">= 10"
-      }
-    },
-    "node_modules/@nrwl/nx-linux-arm64-musl": {
-      "version": "15.9.3",
-      "resolved": "https://registry.npmjs.org/@nrwl/nx-linux-arm64-musl/-/nx-linux-arm64-musl-15.9.3.tgz",
-      "integrity": "sha512-AxoZzfsXH7ZqDE+WrQtRumufIcSIBw4U/LikiDLaWWoGtNpAfKLkD/PHirZiNxHIeGy1Toi4ccMUolXbafLVFw==",
-      "cpu": [
-        "arm64"
-      ],
-      "dev": true,
-      "optional": true,
-      "os": [
-        "linux"
-      ],
-      "engines": {
-        "node": ">= 10"
-      }
-    },
-    "node_modules/@nrwl/nx-linux-x64-gnu": {
-      "version": "15.9.3",
-      "resolved": "https://registry.npmjs.org/@nrwl/nx-linux-x64-gnu/-/nx-linux-x64-gnu-15.9.3.tgz",
-      "integrity": "sha512-P8AOPRufvV4a5cSczNsw84zFAI7NgAiEBTybYcyymdNJmo0iArJXEmvj/G4mB20O8VCsCkwqMYAu6nQEnES1Kw==",
-      "cpu": [
-        "x64"
-      ],
-      "dev": true,
-      "optional": true,
-      "os": [
-        "linux"
-      ],
-      "engines": {
-        "node": ">= 10"
-      }
-    },
-    "node_modules/@nrwl/nx-linux-x64-musl": {
-      "version": "15.9.3",
-      "resolved": "https://registry.npmjs.org/@nrwl/nx-linux-x64-musl/-/nx-linux-x64-musl-15.9.3.tgz",
-      "integrity": "sha512-4ZYDp7T319+xbw7Z7KVtRefzaXJipZfgrM49r+Y1FAfYDc8y18zvKz3slK26wfWz+EUZwKsa/DfA2KmyRG3DvQ==",
-      "cpu": [
-        "x64"
-      ],
-      "dev": true,
-      "optional": true,
-      "os": [
-        "linux"
-      ],
-      "engines": {
-        "node": ">= 10"
-      }
-    },
-    "node_modules/@nrwl/nx-win32-arm64-msvc": {
-      "version": "15.9.3",
-      "resolved": "https://registry.npmjs.org/@nrwl/nx-win32-arm64-msvc/-/nx-win32-arm64-msvc-15.9.3.tgz",
-      "integrity": "sha512-UhgxIPgTZBKN1oxlLPSklkSzVL3hA4lAiVc9A0Utumpbp0ob/Xx+2vHzg3cnmNH3jWkZ+9OsC2dKyeMB6gAbSw==",
-      "cpu": [
-        "arm64"
-      ],
-      "dev": true,
-      "optional": true,
-      "os": [
-        "win32"
-      ],
-      "engines": {
-        "node": ">= 10"
-      }
-    },
-    "node_modules/@nrwl/nx-win32-x64-msvc": {
-      "version": "15.9.3",
-      "resolved": "https://registry.npmjs.org/@nrwl/nx-win32-x64-msvc/-/nx-win32-x64-msvc-15.9.3.tgz",
-      "integrity": "sha512-gdnvqURKnu0EQGOFJ6NUKq6wSB+viNb7Z8qtKhzSmFwVjT8akOnLWn7ZhL9v28TAjLM7/s1Mwvmz/IMj1PGlcQ==",
-      "cpu": [
-        "x64"
-      ],
-      "dev": true,
-      "optional": true,
-      "os": [
-        "win32"
-      ],
-      "engines": {
-        "node": ">= 10"
-      }
-    },
-    "node_modules/@nrwl/tao": {
-      "version": "13.1.3",
-      "resolved": "https://registry.npmjs.org/@nrwl/tao/-/tao-13.1.3.tgz",
-      "integrity": "sha512-/IwJgSgCBD1SaF+n8RuXX2OxDAh8ut/+P8pMswjm8063ac30UlAHjQ4XTYyskLH8uoUmNi2hNaGgHUrkwt7tQA==",
-      "dev": true,
-      "dependencies": {
-        "chalk": "4.1.0",
-        "enquirer": "~2.3.6",
-        "fs-extra": "^9.1.0",
-        "jsonc-parser": "3.0.0",
-        "nx": "13.1.3",
-        "rxjs": "^6.5.4",
-        "rxjs-for-await": "0.0.2",
-        "semver": "7.3.4",
-        "tmp": "~0.2.1",
-        "tslib": "^2.0.0",
-        "yargs-parser": "20.0.0"
-      },
-      "bin": {
-        "tao": "index.js"
-      }
-    },
-    "node_modules/@nrwl/tao/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/@nrwl/tao/node_modules/chalk": {
-      "version": "4.1.0",
-      "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz",
-      "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==",
-      "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/@nrwl/tao/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/@nrwl/tao/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/@nrwl/tao/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/@nrwl/tao/node_modules/jsonc-parser": {
-      "version": "3.0.0",
-      "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.0.0.tgz",
-      "integrity": "sha512-fQzRfAbIBnR0IQvftw9FJveWiHp72Fg20giDrHz6TdfB12UH/uue0D3hm57UB5KgAVuniLMCaS8P1IMj9NR7cA==",
-      "dev": true
-    },
-    "node_modules/@nrwl/tao/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/@nrwl/tao/node_modules/semver": {
-      "version": "7.3.4",
-      "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.4.tgz",
-      "integrity": "sha512-tCfb2WLjqFAtXn4KEdxIhalnRtoKFN7nAwj0B3ZXCbQloV2tq5eDbcTmT68JJD3nRJq24/XgxtQKFIpQdtvmVw==",
-      "dev": true,
-      "dependencies": {
-        "lru-cache": "^6.0.0"
-      },
-      "bin": {
-        "semver": "bin/semver.js"
-      },
-      "engines": {
-        "node": ">=10"
-      }
-    },
-    "node_modules/@nrwl/tao/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/@nrwl/tao/node_modules/tmp": {
-      "version": "0.2.1",
-      "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz",
-      "integrity": "sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==",
-      "dev": true,
-      "dependencies": {
-        "rimraf": "^3.0.0"
-      },
-      "engines": {
-        "node": ">=8.17.0"
-      }
-    },
-    "node_modules/@nrwl/tao/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/@nrwl/tao/node_modules/yargs-parser": {
-      "version": "20.0.0",
-      "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.0.0.tgz",
-      "integrity": "sha512-8eblPHTL7ZWRkyjIZJjnGf+TijiKJSwA24svzLRVvtgoi/RZiKa9fFQTrlx0OKLnyHSdt/enrdadji6WFfESVA==",
-      "dev": true,
-      "engines": {
-        "node": ">=10"
-      }
-    },
-    "node_modules/@parcel/watcher": {
-      "version": "2.0.4",
-      "resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.0.4.tgz",
-      "integrity": "sha512-cTDi+FUDBIUOBKEtj+nhiJ71AZVlkAsQFuGQTun5tV9mwQBQgZvhCzG+URPQc8myeN32yRVZEfVAPCs1RW+Jvg==",
-      "dev": true,
-      "hasInstallScript": true,
-      "dependencies": {
-        "node-addon-api": "^3.2.1",
-        "node-gyp-build": "^4.3.0"
-      },
-      "engines": {
-        "node": ">= 10.0.0"
-      },
-      "funding": {
-        "type": "opencollective",
-        "url": "https://opencollective.com/parcel"
-      }
-    },
     "node_modules/@pkgjs/parseargs": {
       "version": "0.11.0",
       "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz",
         "node": "^16.14.0 || >=18.0.0"
       }
     },
-    "node_modules/@tufjs/models/node_modules/minimatch": {
-      "version": "9.0.5",
-      "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
-      "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==",
-      "dev": true,
-      "license": "ISC",
-      "dependencies": {
-        "brace-expansion": "^2.0.1"
-      },
-      "engines": {
-        "node": ">=16 || 14 >=14.17"
-      },
-      "funding": {
-        "url": "https://github.com/sponsors/isaacs"
-      }
-    },
     "node_modules/@types/babel__core": {
       "version": "7.20.5",
       "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz",
         "@types/node": "*"
       }
     },
+    "node_modules/@types/cypress": {
+      "version": "0.1.6",
+      "resolved": "https://registry.npmjs.org/@types/cypress/-/cypress-0.1.6.tgz",
+      "integrity": "sha512-FYKQLvCsRYxZ3fp+XsoCiJZ1aK3x17RmaZjHI4Ou43khFkXPycrQaXo9b1J07PNlEfWnRtUc9loxHXzKjSsbYg==",
+      "dev": true,
+      "license": "MIT"
+    },
+    "node_modules/@types/cypress-axe": {
+      "version": "0.8.0",
+      "resolved": "https://registry.npmjs.org/@types/cypress-axe/-/cypress-axe-0.8.0.tgz",
+      "integrity": "sha512-4sTqNsXUXnl/CqmSI+xCCmYwml+/zZ66uBExMLqnF1dn3wd+BIyrLL8fJ81O/AJePuh08igR2VjZqWuZaoqWaw==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "axe-core": "^3.4.1"
+      }
+    },
+    "node_modules/@types/cypress-axe/node_modules/axe-core": {
+      "version": "3.5.6",
+      "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-3.5.6.tgz",
+      "integrity": "sha512-LEUDjgmdJoA3LqklSTwKYqkjcZ4HKc4ddIYGSAiSkr46NTjzg2L9RNB+lekO9P7Dlpa87+hBtzc2Fzn/+GUWMQ==",
+      "dev": true,
+      "license": "MPL-2.0",
+      "engines": {
+        "node": ">=4"
+      }
+    },
     "node_modules/@types/cypress-cucumber-preprocessor": {
       "version": "4.0.1",
       "resolved": "https://registry.npmjs.org/@types/cypress-cucumber-preprocessor/-/cypress-cucumber-preprocessor-4.0.1.tgz",
       }
     },
     "node_modules/@typescript-eslint/eslint-plugin": {
-      "version": "5.27.1",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.27.1.tgz",
-      "integrity": "sha512-6dM5NKT57ZduNnJfpY81Phe9nc9wolnMCnknb1im6brWi1RYv84nbMS3olJa27B6+irUVV1X/Wb+Am0FjJdGFw==",
-      "dev": true,
-      "dependencies": {
-        "@typescript-eslint/scope-manager": "5.27.1",
-        "@typescript-eslint/type-utils": "5.27.1",
-        "@typescript-eslint/utils": "5.27.1",
-        "debug": "^4.3.4",
-        "functional-red-black-tree": "^1.0.1",
-        "ignore": "^5.2.0",
-        "regexpp": "^3.2.0",
-        "semver": "^7.3.7",
-        "tsutils": "^3.21.0"
+      "version": "8.14.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.14.0.tgz",
+      "integrity": "sha512-tqp8H7UWFaZj0yNO6bycd5YjMwxa6wIHOLZvWPkidwbgLCsBMetQoGj7DPuAlWa2yGO3H48xmPwjhsSPPCGU5w==",
+      "dev": true,
+      "dependencies": {
+        "@eslint-community/regexpp": "^4.10.0",
+        "@typescript-eslint/scope-manager": "8.14.0",
+        "@typescript-eslint/type-utils": "8.14.0",
+        "@typescript-eslint/utils": "8.14.0",
+        "@typescript-eslint/visitor-keys": "8.14.0",
+        "graphemer": "^1.4.0",
+        "ignore": "^5.3.1",
+        "natural-compare": "^1.4.0",
+        "ts-api-utils": "^1.3.0"
       },
       "engines": {
-        "node": "^12.22.0 || ^14.17.0 || >=16.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": "^5.0.0",
-        "eslint": "^6.0.0 || ^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": {
         }
       }
     },
-    "node_modules/@typescript-eslint/eslint-plugin/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/@typescript-eslint/eslint-plugin/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==",
-      "dev": true,
-      "dependencies": {
-        "lru-cache": "^6.0.0"
-      },
-      "bin": {
-        "semver": "bin/semver.js"
-      },
-      "engines": {
-        "node": ">=10"
-      }
-    },
-    "node_modules/@typescript-eslint/eslint-plugin/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/@typescript-eslint/experimental-utils": {
-      "version": "5.27.1",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-5.27.1.tgz",
-      "integrity": "sha512-Vd8uewIixGP93sEnmTRIH6jHZYRQRkGPDPpapACMvitJKX8335VHNyqKTE+mZ+m3E2c5VznTZfSsSsS5IF7vUA==",
-      "dev": true,
-      "dependencies": {
-        "@typescript-eslint/utils": "5.27.1"
-      },
-      "engines": {
-        "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
-      },
-      "funding": {
-        "type": "opencollective",
-        "url": "https://opencollective.com/typescript-eslint"
-      },
-      "peerDependencies": {
-        "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0"
-      }
-    },
     "node_modules/@typescript-eslint/parser": {
-      "version": "5.27.1",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.27.1.tgz",
-      "integrity": "sha512-7Va2ZOkHi5NP+AZwb5ReLgNF6nWLGTeUJfxdkVUAPPSaAdbWNnFZzLZ4EGGmmiCTg+AwlbE1KyUYTBglosSLHQ==",
+      "version": "8.14.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.14.0.tgz",
+      "integrity": "sha512-2p82Yn9juUJq0XynBXtFCyrBDb6/dJombnz6vbo6mgQEtWHfvHbQuEa9kAOVIt1c9YFwi7H6WxtPj1kg+80+RA==",
       "dev": true,
       "dependencies": {
-        "@typescript-eslint/scope-manager": "5.27.1",
-        "@typescript-eslint/types": "5.27.1",
-        "@typescript-eslint/typescript-estree": "5.27.1",
+        "@typescript-eslint/scope-manager": "8.14.0",
+        "@typescript-eslint/types": "8.14.0",
+        "@typescript-eslint/typescript-estree": "8.14.0",
+        "@typescript-eslint/visitor-keys": "8.14.0",
         "debug": "^4.3.4"
       },
       "engines": {
-        "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+        "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
       },
       "funding": {
         "type": "opencollective",
         "url": "https://opencollective.com/typescript-eslint"
       },
       "peerDependencies": {
-        "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0"
+        "eslint": "^8.57.0 || ^9.0.0"
       },
       "peerDependenciesMeta": {
         "typescript": {
       }
     },
     "node_modules/@typescript-eslint/scope-manager": {
-      "version": "5.27.1",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.27.1.tgz",
-      "integrity": "sha512-fQEOSa/QroWE6fAEg+bJxtRZJTH8NTskggybogHt4H9Da8zd4cJji76gA5SBlR0MgtwF7rebxTbDKB49YUCpAg==",
+      "version": "8.14.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.14.0.tgz",
+      "integrity": "sha512-aBbBrnW9ARIDn92Zbo7rguLnqQ/pOrUguVpbUwzOhkFg2npFDwTgPGqFqE0H5feXcOoJOfX3SxlJaKEVtq54dw==",
       "dev": true,
       "dependencies": {
-        "@typescript-eslint/types": "5.27.1",
-        "@typescript-eslint/visitor-keys": "5.27.1"
+        "@typescript-eslint/types": "8.14.0",
+        "@typescript-eslint/visitor-keys": "8.14.0"
       },
       "engines": {
-        "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+        "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
       },
       "funding": {
         "type": "opencollective",
       }
     },
     "node_modules/@typescript-eslint/type-utils": {
-      "version": "5.27.1",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.27.1.tgz",
-      "integrity": "sha512-+UC1vVUWaDHRnC2cQrCJ4QtVjpjjCgjNFpg8b03nERmkHv9JV9X5M19D7UFMd+/G7T/sgFwX2pGmWK38rqyvXw==",
+      "version": "8.14.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.14.0.tgz",
+      "integrity": "sha512-Xcz9qOtZuGusVOH5Uk07NGs39wrKkf3AxlkK79RBK6aJC1l03CobXjJbwBPSidetAOV+5rEVuiT1VSBUOAsanQ==",
       "dev": true,
       "dependencies": {
-        "@typescript-eslint/utils": "5.27.1",
+        "@typescript-eslint/typescript-estree": "8.14.0",
+        "@typescript-eslint/utils": "8.14.0",
         "debug": "^4.3.4",
-        "tsutils": "^3.21.0"
+        "ts-api-utils": "^1.3.0"
       },
       "engines": {
-        "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+        "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
       },
       "funding": {
         "type": "opencollective",
         "url": "https://opencollective.com/typescript-eslint"
       },
-      "peerDependencies": {
-        "eslint": "*"
-      },
       "peerDependenciesMeta": {
         "typescript": {
           "optional": true
       }
     },
     "node_modules/@typescript-eslint/types": {
-      "version": "5.27.1",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.27.1.tgz",
-      "integrity": "sha512-LgogNVkBhCTZU/m8XgEYIWICD6m4dmEDbKXESCbqOXfKZxRKeqpiJXQIErv66sdopRKZPo5l32ymNqibYEH/xg==",
+      "version": "8.14.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.14.0.tgz",
+      "integrity": "sha512-yjeB9fnO/opvLJFAsPNYlKPnEM8+z4og09Pk504dkqonT02AyL5Z9SSqlE0XqezS93v6CXn49VHvB2G7XSsl0g==",
       "dev": true,
       "engines": {
-        "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+        "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
       },
       "funding": {
         "type": "opencollective",
       }
     },
     "node_modules/@typescript-eslint/typescript-estree": {
-      "version": "5.27.1",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.27.1.tgz",
-      "integrity": "sha512-DnZvvq3TAJ5ke+hk0LklvxwYsnXpRdqUY5gaVS0D4raKtbznPz71UJGnPTHEFo0GDxqLOLdMkkmVZjSpET1hFw==",
+      "version": "8.14.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.14.0.tgz",
+      "integrity": "sha512-OPXPLYKGZi9XS/49rdaCbR5j/S14HazviBlUQFvSKz3npr3NikF+mrgK7CFVur6XEt95DZp/cmke9d5i3vtVnQ==",
       "dev": true,
       "dependencies": {
-        "@typescript-eslint/types": "5.27.1",
-        "@typescript-eslint/visitor-keys": "5.27.1",
+        "@typescript-eslint/types": "8.14.0",
+        "@typescript-eslint/visitor-keys": "8.14.0",
         "debug": "^4.3.4",
-        "globby": "^11.1.0",
+        "fast-glob": "^3.3.2",
         "is-glob": "^4.0.3",
-        "semver": "^7.3.7",
-        "tsutils": "^3.21.0"
+        "minimatch": "^9.0.4",
+        "semver": "^7.6.0",
+        "ts-api-utils": "^1.3.0"
       },
       "engines": {
-        "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+        "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
       },
       "funding": {
         "type": "opencollective",
         }
       }
     },
-    "node_modules/@typescript-eslint/typescript-estree/node_modules/globby": {
-      "version": "11.1.0",
-      "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz",
-      "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==",
-      "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"
-      },
-      "engines": {
-        "node": ">=10"
-      },
-      "funding": {
-        "url": "https://github.com/sponsors/sindresorhus"
-      }
-    },
-    "node_modules/@typescript-eslint/typescript-estree/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/@typescript-eslint/typescript-estree/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==",
+      "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"
       },
         "node": ">=10"
       }
     },
-    "node_modules/@typescript-eslint/typescript-estree/node_modules/slash": {
-      "version": "3.0.0",
-      "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz",
-      "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==",
-      "dev": true,
-      "engines": {
-        "node": ">=8"
-      }
-    },
-    "node_modules/@typescript-eslint/typescript-estree/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/@typescript-eslint/utils": {
-      "version": "5.27.1",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.27.1.tgz",
-      "integrity": "sha512-mZ9WEn1ZLDaVrhRaYgzbkXBkTPghPFsup8zDbbsYTxC5OmqrFE7skkKS/sraVsLP3TcT3Ki5CSyEFBRkLH/H/w==",
+      "version": "8.14.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.14.0.tgz",
+      "integrity": "sha512-OGqj6uB8THhrHj0Fk27DcHPojW7zKwKkPmHXHvQ58pLYp4hy8CSUdTKykKeh+5vFqTTVmjz0zCOOPKRovdsgHA==",
       "dev": true,
       "dependencies": {
-        "@types/json-schema": "^7.0.9",
-        "@typescript-eslint/scope-manager": "5.27.1",
-        "@typescript-eslint/types": "5.27.1",
-        "@typescript-eslint/typescript-estree": "5.27.1",
-        "eslint-scope": "^5.1.1",
-        "eslint-utils": "^3.0.0"
+        "@eslint-community/eslint-utils": "^4.4.0",
+        "@typescript-eslint/scope-manager": "8.14.0",
+        "@typescript-eslint/types": "8.14.0",
+        "@typescript-eslint/typescript-estree": "8.14.0"
       },
       "engines": {
-        "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+        "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
       },
       "funding": {
         "type": "opencollective",
         "url": "https://opencollective.com/typescript-eslint"
       },
       "peerDependencies": {
-        "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0"
+        "eslint": "^8.57.0 || ^9.0.0"
       }
     },
     "node_modules/@typescript-eslint/visitor-keys": {
-      "version": "5.27.1",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.27.1.tgz",
-      "integrity": "sha512-xYs6ffo01nhdJgPieyk7HAOpjhTsx7r/oB9LWEhwAXgwn33tkr+W8DI2ChboqhZlC4q3TC6geDYPoiX8ROqyOQ==",
+      "version": "8.14.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.14.0.tgz",
+      "integrity": "sha512-vG0XZo8AdTH9OE6VFRwAZldNc7qtJ/6NLGWak+BtENuEUXGZgFpihILPiBvKXvJ2nFu27XNGC6rKiwuaoMbYzQ==",
       "dev": true,
       "dependencies": {
-        "@typescript-eslint/types": "5.27.1",
-        "eslint-visitor-keys": "^3.3.0"
+        "@typescript-eslint/types": "8.14.0",
+        "eslint-visitor-keys": "^3.4.3"
       },
       "engines": {
-        "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+        "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
       },
       "funding": {
         "type": "opencollective",
       "integrity": "sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ==",
       "dev": true
     },
-    "node_modules/@yarnpkg/parsers": {
-      "version": "3.0.0",
-      "resolved": "https://registry.npmjs.org/@yarnpkg/parsers/-/parsers-3.0.0.tgz",
-      "integrity": "sha512-jVZa3njBv6tcOUw34nlUdUM/40wwtm/gnVF8rtk0tA6vNcokqYI8CFU1BZjlpFwUSZaXxYkrtuPE/f2MMFlTxQ==",
-      "dev": true,
-      "dependencies": {
-        "js-yaml": "^3.10.0",
-        "tslib": "^2.4.0"
-      },
-      "engines": {
-        "node": ">=18.12.0"
-      }
-    },
-    "node_modules/@yarnpkg/parsers/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"
-      }
-    },
-    "node_modules/@yarnpkg/parsers/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": {
-        "argparse": "^1.0.7",
-        "esprima": "^4.0.0"
-      },
-      "bin": {
-        "js-yaml": "bin/js-yaml.js"
-      }
-    },
-    "node_modules/@yarnpkg/parsers/node_modules/tslib": {
-      "version": "2.6.2",
-      "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz",
-      "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==",
-      "dev": true
-    },
     "node_modules/abab": {
       "version": "2.0.6",
       "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.6.tgz",
       }
     },
     "node_modules/acorn": {
-      "version": "8.11.2",
-      "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.2.tgz",
-      "integrity": "sha512-nc0Axzp/0FILLEVsm4fNwLCwMttvhEI263QtVPQcbpfZZ3ts0hLsZGOpE6czNlid7CJ9MlyH8reXkpsf3YUY4w==",
+      "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"
       "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="
     },
     "node_modules/aria-query": {
-      "version": "4.2.2",
-      "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-4.2.2.tgz",
-      "integrity": "sha512-o/HelwhuKpTj/frsOsbNLNgnNGVIFsVP/SW2BSF14gVl7kAfMOJ6/8wUAUvG1R1NHKrfG+2sHZTu0yauT1qBrA==",
+      "version": "5.3.2",
+      "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.2.tgz",
+      "integrity": "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==",
       "dev": true,
-      "dependencies": {
-        "@babel/runtime": "^7.10.2",
-        "@babel/runtime-corejs3": "^7.10.2"
-      },
       "engines": {
-        "node": ">=6.0"
+        "node": ">= 0.4"
       }
     },
     "node_modules/arr-diff": {
       }
     },
     "node_modules/axobject-query": {
-      "version": "2.2.0",
-      "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-2.2.0.tgz",
-      "integrity": "sha512-Td525n+iPOOyUQIeBfcASuG6uJsDOITl7Mds5gFyerkWiX7qhUTdYUBlSgNMyVqtSJqwpt1kXGLdUt6SykLMRA==",
-      "dev": true
+      "version": "4.1.0",
+      "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz",
+      "integrity": "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==",
+      "dev": true,
+      "engines": {
+        "node": ">= 0.4"
+      }
     },
     "node_modules/babel-jest": {
       "version": "29.7.0",
       "dev": true
     },
     "node_modules/bootstrap": {
-      "version": "5.2.3",
-      "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.2.3.tgz",
-      "integrity": "sha512-cEKPM+fwb3cT8NzQZYEu4HilJ3anCrWqh3CHAok1p9jXqMPsPTBhU25fBckEJHJ/p+tTxTFTsFQGM+gaHpi3QQ==",
+      "version": "5.3.2",
+      "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.3.2.tgz",
+      "integrity": "sha512-D32nmNWiQHo94BKHLmOrdjlL05q1c8oxbtBphQFb9Z5to6eGRDCm0QgeaZ4zFBHzfg2++rqa2JkqCcxDy0sH0g==",
       "funding": [
         {
           "type": "github",
           "url": "https://opencollective.com/bootstrap"
         }
       ],
+      "license": "MIT",
       "peerDependencies": {
-        "@popperjs/core": "^2.11.6"
+        "@popperjs/core": "^2.11.8"
       }
     },
     "node_modules/brace-expansion": {
       }
     },
     "node_modules/chart.js": {
-      "version": "4.4.0",
-      "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.4.0.tgz",
-      "integrity": "sha512-vQEj6d+z0dcsKLlQvbKIMYFHd3t8W/7L2vfJIbYcfyPcRx92CsHqECpueN8qVGNlKyDcr5wBrYAYKnfu/9Q1hQ==",
+      "version": "4.4.7",
+      "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.4.7.tgz",
+      "integrity": "sha512-pwkcKfdzTMAU/+jNosKhNL2bHtJc/sSmYgVbuGTEDhzkrhmyihmP7vUc/5ZK9WopidMDHNe3Wm7jOd/WhuHWuw==",
+      "license": "MIT",
       "dependencies": {
         "@kurkle/color": "^0.3.0"
       },
       "engines": {
-        "pnpm": ">=7"
+        "pnpm": ">=8"
       }
     },
     "node_modules/chartjs-adapter-moment": {
         "deps-sort": "bin/cmd.js"
       }
     },
-    "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/des.js": {
       "version": "1.1.0",
       "resolved": "https://registry.npmjs.org/des.js/-/des.js-1.1.0.tgz",
         "node": ">=6"
       }
     },
-    "node_modules/doctrine": {
-      "version": "3.0.0",
-      "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz",
-      "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==",
-      "dev": true,
-      "dependencies": {
-        "esutils": "^2.0.2"
-      },
-      "engines": {
-        "node": ">=6.0.0"
-      }
-    },
     "node_modules/dom-serializer": {
       "version": "2.0.0",
       "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz",
       "integrity": "sha512-kxM7fSnNQTXOmaeGuBSXM8O3fEsBb7XSDBllkGbRwa0lJSJTxxDE/4eSNGLKZUmlFw0f1vJ5qSV2BljrgQtgIA==",
       "dev": true
     },
-    "node_modules/dotenv": {
-      "version": "10.0.0",
-      "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-10.0.0.tgz",
-      "integrity": "sha512-rlBi9d8jpv9Sf1klPjNfFAuWDjKLwTIJJ/VxtoTwIR6hnZxcEOQCZg2oIL3MWBYw5GpUDKOEnND7LXTbIpQ03Q==",
-      "dev": true,
-      "engines": {
-        "node": ">=10"
-      }
-    },
     "node_modules/drange": {
       "version": "1.1.1",
       "resolved": "https://registry.npmjs.org/drange/-/drange-1.1.1.tgz",
       "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==",
       "dev": true
     },
-    "node_modules/ejs": {
-      "version": "3.1.9",
-      "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.9.tgz",
-      "integrity": "sha512-rC+QVNMJWv+MtPgkt0y+0rVEIdbtxVADApW9JXrUVlzHetgcyczP/E7DJmWJ4fJCZF2cPcBk0laWO9ZHMG3DmQ==",
-      "dev": true,
-      "dependencies": {
-        "jake": "^10.8.5"
-      },
-      "bin": {
-        "ejs": "bin/cli.js"
-      },
-      "engines": {
-        "node": ">=0.10.0"
-      }
-    },
     "node_modules/electron-to-chromium": {
       "version": "1.5.55",
       "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.55.tgz",
       }
     },
     "node_modules/eslint": {
-      "version": "8.17.0",
-      "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.17.0.tgz",
-      "integrity": "sha512-gq0m0BTJfci60Fz4nczYxNAlED+sMcihltndR8t9t1evnU/azx53x3t2UHXC/uRjcbvRw/XctpaNygSTcQD+Iw==",
-      "dev": true,
-      "dependencies": {
-        "@eslint/eslintrc": "^1.3.0",
-        "@humanwhocodes/config-array": "^0.9.2",
-        "ajv": "^6.10.0",
+      "version": "9.14.0",
+      "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.14.0.tgz",
+      "integrity": "sha512-c2FHsVBr87lnUtjP4Yhvk4yEhKrQavGafRA/Se1ouse8PfbfC/Qh9Mxa00yWsZRlqeUB9raXip0aiiUZkgnr9g==",
+      "dev": true,
+      "dependencies": {
+        "@eslint-community/eslint-utils": "^4.2.0",
+        "@eslint-community/regexpp": "^4.12.1",
+        "@eslint/config-array": "^0.18.0",
+        "@eslint/core": "^0.7.0",
+        "@eslint/eslintrc": "^3.1.0",
+        "@eslint/js": "9.14.0",
+        "@eslint/plugin-kit": "^0.2.0",
+        "@humanfs/node": "^0.16.6",
+        "@humanwhocodes/module-importer": "^1.0.1",
+        "@humanwhocodes/retry": "^0.4.0",
+        "@types/estree": "^1.0.6",
+        "@types/json-schema": "^7.0.15",
+        "ajv": "^6.12.4",
         "chalk": "^4.0.0",
         "cross-spawn": "^7.0.2",
         "debug": "^4.3.2",
-        "doctrine": "^3.0.0",
         "escape-string-regexp": "^4.0.0",
-        "eslint-scope": "^7.1.1",
-        "eslint-utils": "^3.0.0",
-        "eslint-visitor-keys": "^3.3.0",
-        "espree": "^9.3.2",
-        "esquery": "^1.4.0",
+        "eslint-scope": "^8.2.0",
+        "eslint-visitor-keys": "^4.2.0",
+        "espree": "^10.3.0",
+        "esquery": "^1.5.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": "^6.0.1",
-        "globals": "^13.15.0",
+        "file-entry-cache": "^8.0.0",
+        "find-up": "^5.0.0",
+        "glob-parent": "^6.0.2",
         "ignore": "^5.2.0",
-        "import-fresh": "^3.0.0",
         "imurmurhash": "^0.1.4",
         "is-glob": "^4.0.0",
-        "js-yaml": "^4.1.0",
         "json-stable-stringify-without-jsonify": "^1.0.1",
-        "levn": "^0.4.1",
         "lodash.merge": "^4.6.2",
         "minimatch": "^3.1.2",
         "natural-compare": "^1.4.0",
-        "optionator": "^0.9.1",
-        "regexpp": "^3.2.0",
-        "strip-ansi": "^6.0.1",
-        "strip-json-comments": "^3.1.0",
-        "text-table": "^0.2.0",
-        "v8-compile-cache": "^2.0.3"
+        "optionator": "^0.9.3",
+        "text-table": "^0.2.0"
       },
       "bin": {
         "eslint": "bin/eslint.js"
       },
       "engines": {
-        "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+        "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
       },
       "funding": {
-        "url": "https://opencollective.com/eslint"
+        "url": "https://eslint.org/donate"
+      },
+      "peerDependencies": {
+        "jiti": "*"
+      },
+      "peerDependenciesMeta": {
+        "jiti": {
+          "optional": true
+        }
       }
     },
     "node_modules/eslint-scope": {
         "node": ">=8.0.0"
       }
     },
-    "node_modules/eslint-utils": {
-      "version": "3.0.0",
-      "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz",
-      "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==",
-      "dev": true,
-      "dependencies": {
-        "eslint-visitor-keys": "^2.0.0"
-      },
-      "engines": {
-        "node": "^10.0.0 || ^12.0.0 || >= 14.0.0"
-      },
-      "funding": {
-        "url": "https://github.com/sponsors/mysticatea"
-      },
-      "peerDependencies": {
-        "eslint": ">=5"
-      }
-    },
-    "node_modules/eslint-utils/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,
-      "engines": {
-        "node": ">=10"
-      }
-    },
     "node_modules/eslint-visitor-keys": {
       "version": "3.4.3",
       "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz",
       }
     },
     "node_modules/eslint/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": "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",
         "estraverse": "^5.2.0"
       },
       "engines": {
-        "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+        "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+      },
+      "funding": {
+        "url": "https://opencollective.com/eslint"
+      }
+    },
+    "node_modules/eslint/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": ">=4.0"
       }
     },
-    "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==",
+    "node_modules/eslint/node_modules/file-entry-cache": {
+      "version": "8.0.0",
+      "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz",
+      "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==",
       "dev": true,
       "dependencies": {
-        "is-glob": "^4.0.3"
+        "flat-cache": "^4.0.0"
       },
       "engines": {
-        "node": ">=10.13.0"
+        "node": ">=16.0.0"
       }
     },
-    "node_modules/eslint/node_modules/globals": {
-      "version": "13.23.0",
-      "resolved": "https://registry.npmjs.org/globals/-/globals-13.23.0.tgz",
-      "integrity": "sha512-XAmF0RjlrjY23MA51q3HltdlGxUpXPvg0GioKiD9X6HD28iMjo2dKC8Vqwm7lne4GNr78+RHTfliktR6ZH09wA==",
+    "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": {
-        "type-fest": "^0.20.2"
+        "locate-path": "^6.0.0",
+        "path-exists": "^4.0.0"
       },
       "engines": {
-        "node": ">=8"
+        "node": ">=10"
       },
       "funding": {
         "url": "https://github.com/sponsors/sindresorhus"
       }
     },
+    "node_modules/eslint/node_modules/flat-cache": {
+      "version": "4.0.1",
+      "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz",
+      "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==",
+      "dev": true,
+      "dependencies": {
+        "flatted": "^3.2.9",
+        "keyv": "^4.5.4"
+      },
+      "engines": {
+        "node": ">=16"
+      }
+    },
+    "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/has-flag": {
       "version": "4.0.0",
       "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
         "node": ">= 0.8.0"
       }
     },
+    "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",
         "node": ">= 0.8.0"
       }
     },
-    "node_modules/eslint/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/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==",
       "dev": true,
+      "dependencies": {
+        "yocto-queue": "^0.1.0"
+      },
       "engines": {
-        "node": ">= 0.8.0"
+        "node": ">=10"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
       }
     },
-    "node_modules/eslint/node_modules/strip-json-comments": {
-      "version": "3.1.1",
-      "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
-      "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==",
+    "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==",
       "dev": true,
+      "dependencies": {
+        "p-limit": "^3.0.2"
+      },
       "engines": {
-        "node": ">=8"
+        "node": ">=10"
       },
       "funding": {
         "url": "https://github.com/sponsors/sindresorhus"
       }
     },
+    "node_modules/eslint/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,
+      "engines": {
+        "node": ">= 0.8.0"
+      }
+    },
     "node_modules/eslint/node_modules/supports-color": {
       "version": "7.2.0",
       "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
       }
     },
     "node_modules/espree": {
-      "version": "9.6.1",
-      "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz",
-      "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==",
+      "version": "10.3.0",
+      "resolved": "https://registry.npmjs.org/espree/-/espree-10.3.0.tgz",
+      "integrity": "sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==",
       "dev": true,
       "dependencies": {
-        "acorn": "^8.9.0",
+        "acorn": "^8.14.0",
         "acorn-jsx": "^5.3.2",
-        "eslint-visitor-keys": "^3.4.1"
+        "eslint-visitor-keys": "^4.2.0"
       },
       "engines": {
-        "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+        "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+      },
+      "funding": {
+        "url": "https://opencollective.com/eslint"
+      }
+    },
+    "node_modules/espree/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"
       "resolved": "https://registry.npmjs.org/file-saver/-/file-saver-2.0.2.tgz",
       "integrity": "sha512-Wz3c3XQ5xroCxd1G8b7yL0Ehkf0TC9oYC6buPFkNnU9EnaPlifeAFCyCh+iewXTyFRcg0a6j3J7FmJsIhlhBdw=="
     },
-    "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/fill-range": {
       "version": "7.1.1",
       "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
       "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==",
-      "devOptional": true
+      "optional": true
     },
     "node_modules/fs-extra": {
       "version": "9.1.0",
         "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/functions-have-names": {
       "version": "1.2.3",
       "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz",
       "dev": true,
       "license": "BSD-2-Clause"
     },
-    "node_modules/glob/node_modules/minimatch": {
-      "version": "9.0.5",
-      "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
-      "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==",
-      "dev": true,
-      "license": "ISC",
-      "dependencies": {
-        "brace-expansion": "^2.0.1"
-      },
-      "engines": {
-        "node": ">=16 || 14 >=14.17"
-      },
-      "funding": {
-        "url": "https://github.com/sponsors/isaacs"
-      }
-    },
     "node_modules/global-dirs": {
       "version": "3.0.1",
       "resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-3.0.1.tgz",
       "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
       "dev": true
     },
+    "node_modules/graphemer": {
+      "version": "1.4.0",
+      "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz",
+      "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==",
+      "dev": true
+    },
     "node_modules/handle-thing": {
       "version": "2.0.1",
       "resolved": "https://registry.npmjs.org/handle-thing/-/handle-thing-2.0.1.tgz",
         "node": "^14.17.0 || ^16.13.0 || >=18.0.0"
       }
     },
-    "node_modules/ignore-walk/node_modules/minimatch": {
-      "version": "9.0.5",
-      "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
-      "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==",
-      "dev": true,
-      "license": "ISC",
-      "dependencies": {
-        "brace-expansion": "^2.0.1"
-      },
-      "engines": {
-        "node": ">=16 || 14 >=14.17"
-      },
-      "funding": {
-        "url": "https://github.com/sponsors/isaacs"
-      }
-    },
     "node_modules/image-size": {
       "version": "0.5.5",
       "resolved": "https://registry.npmjs.org/image-size/-/image-size-0.5.5.tgz",
         "@pkgjs/parseargs": "^0.11.0"
       }
     },
-    "node_modules/jake": {
-      "version": "10.8.7",
-      "resolved": "https://registry.npmjs.org/jake/-/jake-10.8.7.tgz",
-      "integrity": "sha512-ZDi3aP+fG/LchyBzUM804VjddnwfSfsdeYkwt8NcbKRvo4rFkjhs456iLFn3k2ZUWvNe4i48WACDbza8fhq2+w==",
-      "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/jest": {
-      "version": "29.6.4",
-      "resolved": "https://registry.npmjs.org/jest/-/jest-29.6.4.tgz",
-      "integrity": "sha512-tEFhVQFF/bzoYV1YuGyzLPZ6vlPrdfvDmmAxudA1dLEuiztqg2Rkx20vkKY32xiDROcD2KXlgZ7Cu8RPeEHRKw==",
+      "version": "29.7.0",
+      "resolved": "https://registry.npmjs.org/jest/-/jest-29.7.0.tgz",
+      "integrity": "sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==",
       "dev": true,
+      "license": "MIT",
       "dependencies": {
-        "@jest/core": "^29.6.4",
+        "@jest/core": "^29.7.0",
         "@jest/types": "^29.6.3",
         "import-local": "^3.0.2",
-        "jest-cli": "^29.6.4"
+        "jest-cli": "^29.7.0"
       },
       "bin": {
         "jest": "bin/jest.js"
       }
     },
     "node_modules/jest-silent-reporter": {
-      "version": "0.5.0",
-      "resolved": "https://registry.npmjs.org/jest-silent-reporter/-/jest-silent-reporter-0.5.0.tgz",
-      "integrity": "sha512-epdLt8Oj0a1AyRiR6F8zx/1SVT1Mi7VU3y4wB2uOBHs/ohIquC7v2eeja7UN54uRPyHInIKWdL+RdG228n5pJQ==",
+      "version": "0.6.0",
+      "resolved": "https://registry.npmjs.org/jest-silent-reporter/-/jest-silent-reporter-0.6.0.tgz",
+      "integrity": "sha512-4nmS+5o7ycVlvbQOTx7CnGdbBtP2646hnDgQpQLaVhjHcQNHD+gqBAetyjRDlgpZ8+8N82MWI59K+EX2LsVk7g==",
       "dev": true,
+      "license": "MIT",
       "dependencies": {
         "chalk": "^4.0.0",
         "jest-util": "^26.0.0"
         "node": ">=6"
       }
     },
-    "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/jsonfile": {
       "version": "6.1.0",
       "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz",
       "dev": true
     },
     "node_modules/minimatch": {
-      "version": "5.1.6",
-      "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz",
-      "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==",
+      "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"
       },
       "engines": {
-        "node": ">=10"
+        "node": ">=16 || 14 >=14.17"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/isaacs"
       }
     },
     "node_modules/minimist": {
       "resolved": "https://registry.npmjs.org/mocha-junit-reporter/-/mocha-junit-reporter-2.1.0.tgz",
       "integrity": "sha512-Zhz1J+XqJUaAOuSFtHgi2+b+W3rP1SZtaU3HHNNp1iEKMSeoC1/EQUVkGknkLNOBxJhXJ4xLgOr8TbYAZOkUIw==",
       "dev": true,
+      "license": "MIT",
       "dependencies": {
         "debug": "^2.2.0",
         "md5": "^2.1.0",
       "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
       "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
       "dev": true,
+      "license": "MIT",
       "dependencies": {
         "ms": "2.0.0"
       }
       "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz",
       "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==",
       "dev": true,
+      "license": "MIT",
       "dependencies": {
         "minimist": "^1.2.6"
       },
       "version": "2.0.0",
       "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
       "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
-      "dev": true
+      "dev": true,
+      "license": "MIT"
     },
     "node_modules/module-deps": {
       "version": "6.2.3",
       }
     },
     "node_modules/ng2-charts": {
-      "version": "4.1.1",
-      "resolved": "https://registry.npmjs.org/ng2-charts/-/ng2-charts-4.1.1.tgz",
-      "integrity": "sha512-iHwXDbmX86lfeH8VRcsaW2tJATsuAZo4kvvC/Yk2l35zOHjevja1qBvO6BAibiDazi9r9aS6ZRJOqWPsz1pP2w==",
+      "version": "7.0.0",
+      "resolved": "https://registry.npmjs.org/ng2-charts/-/ng2-charts-7.0.0.tgz",
+      "integrity": "sha512-HofyPPkz7yOX6Dr9JfV/SDddzmmqCFYCKbn71jeDiyWPRjrj99yTBCyqYtjzzNrnlTfWwbdvynYZ4GAhu/lbgQ==",
+      "license": "MIT",
       "dependencies": {
         "lodash-es": "^4.17.15",
         "tslib": "^2.3.0"
       },
       "peerDependencies": {
-        "@angular/cdk": ">=14.0.0",
-        "@angular/common": ">=14.0.0",
-        "@angular/core": ">=14.0.0",
+        "@angular/cdk": ">=18.0.0",
+        "@angular/common": ">=18.0.0",
+        "@angular/core": ">=18.0.0",
+        "@angular/platform-browser": ">=18.0.0",
         "chart.js": "^3.4.0 || ^4.0.0",
         "rxjs": "^6.5.3 || ^7.4.0"
       }
       "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
+      "dev": true,
+      "optional": true
     },
     "node_modules/node-domexception": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.7.0.tgz",
       "integrity": "sha512-PbZERfeFdrHQOOXiAKOY0VPbykZy90ndPKk0d+CFDegTKmWp1VgOTz2xACVbr1BjCWxrQp68CXtvNsveFhqDJg==",
       "dev": true,
+      "optional": true,
       "bin": {
         "node-gyp-build": "bin.js",
         "node-gyp-build-optional": "optional.js",
       "integrity": "sha512-ub5E4+FBPKwAZx0UwIQOjYWGHTEq5sPqHQNRN8Z9e4A7u3Tj1weLJsL59yH9vmvqEtBHaOmT6cYQKIZOxp35FQ==",
       "dev": true
     },
-    "node_modules/nx": {
-      "version": "13.1.3",
-      "resolved": "https://registry.npmjs.org/nx/-/nx-13.1.3.tgz",
-      "integrity": "sha512-clM0NQhQKYkqcNz2E3uYRMLwhp2L/9dBhJhQi9XBX4IAyA2gWAomhRIlLm5Xxg3g4h1xwSpP3eJ5t89VikY8Pw==",
-      "dev": true,
-      "dependencies": {
-        "@nrwl/cli": "*"
-      },
-      "bin": {
-        "nx": "bin/nx.js"
-      }
-    },
     "node_modules/oauth-sign": {
       "version": "0.9.0",
       "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz",
         "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==",
-      "dev": true,
-      "license": "MIT",
-      "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"
-      }
-    },
     "node_modules/opencollective-postinstall": {
       "version": "2.0.3",
       "resolved": "https://registry.npmjs.org/opencollective-postinstall/-/opencollective-postinstall-2.0.3.tgz",
         "url": "https://github.com/sponsors/ljharb"
       }
     },
-    "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": "6.1.1",
       "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-6.1.1.tgz",
         "npm": ">=2.0.0"
       }
     },
-    "node_modules/rxjs-for-await": {
-      "version": "0.0.2",
-      "resolved": "https://registry.npmjs.org/rxjs-for-await/-/rxjs-for-await-0.0.2.tgz",
-      "integrity": "sha512-IJ8R/ZCFMHOcDIqoABs82jal00VrZx8Xkgfe7TOKoaRPAW5nH/VFlG23bXpeGdrmtqI9UobFPgUKgCuFc7Lncw==",
-      "dev": true,
-      "peerDependencies": {
-        "rxjs": "^6.0.0"
-      }
-    },
     "node_modules/rxjs/node_modules/tslib": {
       "version": "1.14.1",
       "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
         "node": ">=0.10.0"
       }
     },
-    "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/style-search": {
       "version": "0.1.0",
       "resolved": "https://registry.npmjs.org/style-search/-/style-search-0.1.0.tgz",
       "version": "2.2.0",
       "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz",
       "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==",
-      "devOptional": true,
+      "optional": true,
       "dependencies": {
         "bl": "^4.0.3",
         "end-of-stream": "^1.4.1",
         "url": "https://github.com/sponsors/wooorm"
       }
     },
+    "node_modules/ts-api-utils": {
+      "version": "1.4.0",
+      "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.4.0.tgz",
+      "integrity": "sha512-032cPxaEKwM+GT3vA5JXNzIaizx388rhsSW79vGRNGXfRRAdEAn2mvk36PvK5HnOchyWZ7afLEXqYCvPCrzuzQ==",
+      "dev": true,
+      "engines": {
+        "node": ">=16"
+      },
+      "peerDependencies": {
+        "typescript": ">=4.2.0"
+      }
+    },
     "node_modules/ts-jest": {
       "version": "29.1.1",
       "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.1.1.tgz",
       "resolved": "https://registry.npmjs.org/ts-toolbelt/-/ts-toolbelt-9.6.0.tgz",
       "integrity": "sha512-nsZd8ZeNUzukXPlJmTBwUAuABDe/9qtVDelJeT/qW0ow3ZS3BsQJtNkan1802aM9Uf68/Y8ljw86Hu0h5IUW3w=="
     },
-    "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==",
-      "dev": true,
-      "dependencies": {
-        "json5": "^2.2.2",
-        "minimist": "^1.2.6",
-        "strip-bom": "^3.0.0"
-      },
-      "engines": {
-        "node": ">=6"
-      }
-    },
     "node_modules/tslib": {
       "version": "2.3.1",
       "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz",
       "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw=="
     },
-    "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": {
-        "tslib": "^1.8.1"
-      },
-      "engines": {
-        "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/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/tty-browserify": {
       "version": "0.0.1",
       "resolved": "https://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.1.tgz",
       "version": "6.19.8",
       "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz",
       "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==",
-      "dev": true,
-      "license": "MIT"
+      "dev": true
     },
     "node_modules/unicode-canonical-property-names-ecmascript": {
       "version": "2.0.1",
index f55d409f039886c00863538a99d2415976407db1..aa950f4cf60a6aab49b2db4c9f39a718d90f0c50 100644 (file)
@@ -19,7 +19,7 @@
     "i18n:merge": "npx i18ntool merge -c i18n.config.json",
     "i18n:token": "npx i18ntool config token",
     "test": "jest --watch",
-    "test:ci": "jest --clearCache && JEST_SILENT_REPORTER_DOTS=true jest --coverage --reporters jest-silent-reporter",
+    "test:ci": "jest --clearCache && JEST_SILENT_REPORTER_DOTS=true jest --detectOpenHandles --coverage --reporters jest-silent-reporter",
     "pree2e": "rm -f cypress/reports/results-*.xml || true",
     "e2e": "start-test 4200 'cypress open'",
     "pree2e:ci": "npm run pree2e",
@@ -28,7 +28,6 @@
     "lint:gherkin": "gherkin-lint -c .gherkin-lintrc cypress/e2e",
     "lint:prettier": "prettier --list-different \"{src,cypress}/**/*.{ts,scss}\"",
     "lint:html": "htmllint src/app/**/*.html && html-linter --config html-linter.config.json",
-    "prelint:tsc": "npm run postinstall",
     "lint:tsc": "tsc -p tsconfig.app.json --noEmit && tsc -p tsconfig.spec.json --noEmit && tsc -p cypress/tsconfig.json --noEmit",
     "lint:scss": "stylelint '**/*.scss'",
     "lint": "run-p -csl --aggregate-output lint:*",
@@ -61,9 +60,9 @@
     "@popperjs/core": "2.10.2",
     "@types/file-saver": "2.0.1",
     "async-mutex": "0.2.4",
-    "bootstrap": "5.2.3",
+    "bootstrap": "5.3.2",
     "carbon-components-angular": "5.56.2",
-    "chart.js": "4.4.0",
+    "chart.js": "4.4.7",
     "chartjs-adapter-moment": "1.0.1",
     "detect-browser": "5.2.0",
     "file-saver": "2.0.2",
@@ -72,7 +71,7 @@
     "moment": "2.29.4",
     "ng-block-ui": "4.0.1",
     "ng-click-outside": "9.0.1",
-    "ng2-charts": "4.1.1",
+    "ng2-charts": "7.0.0",
     "ngx-cookie-service": "18.0.0",
     "ngx-toastr": "17.0.2",
     "rxjs": "6.6.3",
   },
   "devDependencies": {
     "@angular-devkit/build-angular": "18.2.11",
-    "@angular-eslint/builder": "13.5.0",
-    "@angular-eslint/eslint-plugin": "13.5.0",
-    "@angular-eslint/eslint-plugin-template": "13.5.0",
-    "@angular-eslint/schematics": "18.3.1",
-    "@angular-eslint/template-parser": "13.5.0",
+    "@angular-eslint/builder": "18.4.0",
+    "@angular-eslint/eslint-plugin": "18.4.0",
+    "@angular-eslint/eslint-plugin-template": "18.4.0",
+    "@angular-eslint/schematics": "18.4.0",
+    "@angular-eslint/template-parser": "18.4.0",
     "@angular/cli": "18.2.11",
     "@angular/compiler-cli": "18.2.11",
     "@angular/language-service": "18.2.11",
     "@applitools/eyes-cypress": "3.22.5",
     "@compodoc/compodoc": "1.1.18",
     "@cypress/browserify-preprocessor": "3.0.2",
+    "@juggle/resize-observer": "3.4.0",
     "@types/brace-expansion": "1.1.0",
+    "@types/cypress": "0.1.6",
+    "@types/cypress-axe": "0.8.0",
     "@types/cypress-cucumber-preprocessor": "4.0.1",
     "@types/jest": "29.5.4",
     "@types/lodash": "4.14.161",
     "@types/node": "18.17.12",
     "@types/swagger-ui": "3.52.0",
     "@types/xml2js": "0.4.14",
-    "@typescript-eslint/eslint-plugin": "5.27.1",
-    "@typescript-eslint/parser": "5.27.1",
+    "@typescript-eslint/eslint-plugin": "8.14.0",
+    "@typescript-eslint/parser": "8.14.0",
     "axe-core": "4.4.3",
     "cypress": "12.17.4",
     "cypress-axe": "1.5.0",
     "cypress-cucumber-preprocessor": "4.3.1",
     "cypress-iframe": "1.0.1",
     "cypress-multi-reporters": "1.5.0",
-    "eslint": "8.17.0",
+    "eslint": "9.14.0",
     "gherkin-lint": "4.2.2",
     "html-linter": "1.1.1",
     "htmllint-cli": "0.0.7",
     "identity-obj-proxy": "3.0.0",
     "isomorphic-form-data": "2.0.0",
-    "jest": "29.6.4",
+    "jest": "29.7.0",
     "jest-canvas-mock": "2.4.0",
     "jest-jasmine2": "28.1.3",
     "jest-preset-angular": "14.2.4",
-    "jest-silent-reporter": "0.5.0",
+    "jest-silent-reporter": "0.6.0",
     "mocha-junit-reporter": "2.1.0",
     "ng-mocks": "14.13.1",
     "npm-run-all": "4.1.5",
index e60ed5cb8b74a565d4396ef53882fc93e0595443..6b7134bd213ef713e6e3d707f8b0ef71f58a5a91 100644 (file)
@@ -14,32 +14,38 @@ import { ApiInterceptorService } from './shared/services/api-interceptor.service
 import { JsErrorHandler } from './shared/services/js-error-handler.service';
 import { SharedModule } from './shared/shared.module';
 
-@NgModule({ declarations: [AppComponent],
-    exports: [SharedModule],
-    bootstrap: [AppComponent], imports: [BrowserModule,
-        BrowserAnimationsModule,
-        ToastrModule.forRoot({
-            positionClass: 'toast-top-right',
-            preventDuplicates: true,
-            enableHtml: true
-        }),
-        AppRoutingModule,
-        CoreModule,
-        SharedModule,
-        CephModule], providers: [
-        {
-            provide: ErrorHandler,
-            useClass: JsErrorHandler
-        },
-        {
-            provide: HTTP_INTERCEPTORS,
-            useClass: ApiInterceptorService,
-            multi: true
-        },
-        {
-            provide: APP_BASE_HREF,
-            useValue: '/' + (window.location.pathname.split('/', 1)[1] || '')
-        },
-        provideHttpClient(withInterceptorsFromDi())
-    ] })
+@NgModule({
+  declarations: [AppComponent],
+  exports: [SharedModule],
+  bootstrap: [AppComponent],
+  imports: [
+    BrowserModule,
+    BrowserAnimationsModule,
+    ToastrModule.forRoot({
+      positionClass: 'toast-top-right',
+      preventDuplicates: true,
+      enableHtml: true
+    }),
+    AppRoutingModule,
+    CoreModule,
+    SharedModule,
+    CephModule
+  ],
+  providers: [
+    {
+      provide: ErrorHandler,
+      useClass: JsErrorHandler
+    },
+    {
+      provide: HTTP_INTERCEPTORS,
+      useClass: ApiInterceptorService,
+      multi: true
+    },
+    {
+      provide: APP_BASE_HREF,
+      useValue: '/' + (window.location.pathname.split('/', 1)[1] || '')
+    },
+    provideHttpClient(withInterceptorsFromDi())
+  ]
+})
 export class AppModule {}
index 59aac44272851998114ac0b6009b701bc166b289..4ddaea985b568b01bb1042b1f48290b5a49fa479 100644 (file)
@@ -6,7 +6,6 @@ import { RouterTestingModule } from '@angular/router/testing';
 
 import { ToastrModule } from 'ngx-toastr';
 
-import { LoadingPanelComponent } from '~/app/shared/components/loading-panel/loading-panel.component';
 import { SelectOption } from '~/app/shared/components/select/select-option.model';
 import { CdFormGroup } from '~/app/shared/forms/cd-form-group';
 import { SharedModule } from '~/app/shared/shared.module';
@@ -137,25 +136,22 @@ describe('IscsiTargetFormComponent', () => {
     }
   ];
 
-  configureTestBed(
-    {
-      declarations: [IscsiTargetFormComponent],
-      imports: [
-        SharedModule,
-        ReactiveFormsModule,
-        HttpClientTestingModule,
-        RouterTestingModule,
-        ToastrModule.forRoot()
-      ],
-      providers: [
-        {
-          provide: ActivatedRoute,
-          useValue: new ActivatedRouteStub({ target_iqn: undefined })
-        }
-      ]
-    },
-    [LoadingPanelComponent]
-  );
+  configureTestBed({
+    declarations: [IscsiTargetFormComponent],
+    imports: [
+      SharedModule,
+      ReactiveFormsModule,
+      HttpClientTestingModule,
+      RouterTestingModule,
+      ToastrModule.forRoot()
+    ],
+    providers: [
+      {
+        provide: ActivatedRoute,
+        useValue: new ActivatedRouteStub({ target_iqn: undefined })
+      }
+    ]
+  });
 
   beforeEach(() => {
     fixture = TestBed.createComponent(IscsiTargetFormComponent);
index 0ddb8e2f611f6b0d037d58914f5560acc3c3e891..468fc2c4026caade2cbea9c2ba56935bbbd05049 100644 (file)
@@ -1,4 +1,4 @@
-import { Component, TemplateRef, ViewChild } from '@angular/core';
+import { Component, OnInit, TemplateRef, ViewChild } from '@angular/core';
 
 import _ from 'lodash';
 
@@ -28,7 +28,7 @@ type Gateway = {
   templateUrl: './nvmeof-gateway.component.html',
   styleUrls: ['./nvmeof-gateway.component.scss']
 })
-export class NvmeofGatewayComponent {
+export class NvmeofGatewayComponent implements OnInit {
   @ViewChild('statusTpl', { static: true })
   statusTpl: TemplateRef<any>;
 
index c09ec7858c598b324a6a92cac26927ff1986b11b..f4064f0cae84e542236ea686dd4b531dd7b574fe 100644 (file)
@@ -4,7 +4,7 @@ import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
 import { RouterTestingModule } from '@angular/router/testing';
 
 import { NgbDropdownModule, NgbTooltipModule } from '@ng-bootstrap/ng-bootstrap';
-import { NgChartsModule } from 'ng2-charts';
+import { BaseChartDirective } from 'ng2-charts';
 
 import { ComponentsModule } from '~/app/shared/components/components.module';
 import { RbdConfigurationEntry } from '~/app/shared/models/configuration';
@@ -25,7 +25,7 @@ describe('RbdConfigurationListComponent', () => {
       RouterTestingModule,
       ComponentsModule,
       NgbDropdownModule,
-      NgChartsModule,
+      BaseChartDirective,
       SharedModule,
       NgbTooltipModule
     ],
index 10b8c09fabd45b13d7164b92125798695aafbf56..924feb6cf4331a1cdc9e6267233fdbdc2f486026 100644 (file)
@@ -10,7 +10,6 @@ import { Subject, throwError as observableThrowError } from 'rxjs';
 
 import { RbdService } from '~/app/shared/api/rbd.service';
 import { ComponentsModule } from '~/app/shared/components/components.module';
-import { CriticalConfirmationModalComponent } from '~/app/shared/components/critical-confirmation-modal/critical-confirmation-modal.component';
 import { ActionLabelsI18n } from '~/app/shared/constants/app.constants';
 import { DataTableModule } from '~/app/shared/datatable/datatable.module';
 import { TableActionsComponent } from '~/app/shared/datatable/table-actions/table-actions.component';
@@ -55,38 +54,35 @@ describe('RbdSnapshotListComponent', () => {
     }
   };
 
-  configureTestBed(
-    {
-      declarations: [
-        RbdSnapshotListComponent,
-        RbdTabsComponent,
-        MockComponent(RbdSnapshotFormModalComponent),
-        BaseModal
-      ],
-      imports: [
-        BrowserAnimationsModule,
-        ComponentsModule,
-        DataTableModule,
-        HttpClientTestingModule,
-        PipesModule,
-        RouterTestingModule,
-        NgbNavModule,
-        ToastrModule.forRoot(),
-        ModalModule,
-        PlaceholderModule,
-        CoreModule
-      ],
-      providers: [
-        { provide: AuthStorageService, useValue: fakeAuthStorageService },
-        TaskListService,
-        ModalService,
-        PlaceholderService,
-        BaseModalService
-      ],
-      schemas: [NO_ERRORS_SCHEMA]
-    },
-    [CriticalConfirmationModalComponent]
-  );
+  configureTestBed({
+    declarations: [
+      RbdSnapshotListComponent,
+      RbdTabsComponent,
+      MockComponent(RbdSnapshotFormModalComponent),
+      BaseModal
+    ],
+    imports: [
+      BrowserAnimationsModule,
+      ComponentsModule,
+      DataTableModule,
+      HttpClientTestingModule,
+      PipesModule,
+      RouterTestingModule,
+      NgbNavModule,
+      ToastrModule.forRoot(),
+      ModalModule,
+      PlaceholderModule,
+      CoreModule
+    ],
+    providers: [
+      { provide: AuthStorageService, useValue: fakeAuthStorageService },
+      TaskListService,
+      ModalService,
+      PlaceholderService,
+      BaseModalService
+    ],
+    schemas: [NO_ERRORS_SCHEMA]
+  });
 
   beforeEach(() => {
     fixture = TestBed.createComponent(RbdSnapshotListComponent);
index c501a15f9b1fb90cb731cac1584010900a041f11..5e59faf6bdafb6cc67951a1986d2bf95c43545e9 100644 (file)
@@ -90,7 +90,6 @@
                  formControlName="directory"
                  [skeleton]="directoryStore.isLoading"
                  [invalid]="!form.controls['directory'].valid && (form.controls['directory'].dirty)"
-                 [disabled]="directoryStore.isLoading"
                  [ngbTypeahead]="search">
         </cds-text-label>
         <ng-template #directoryError>
index 435cdb9644fdfd83fa68e65be9932d3a5ec75ce0..e626ec6f210d718c93df690e1a2b80a9f76e1f06 100644 (file)
@@ -83,6 +83,11 @@ export class CephfsAuthModalComponent extends CdForm implements OnInit, AfterVie
     this.directoryStore.loadDirectories(this.id, '/', 3);
     this.createForm();
     this.loadingReady();
+    if (this.directoryStore?.isLoading) {
+      this.form.get('directory').disable();
+    } else {
+      this.form.get('directory').disable();
+    }
   }
 
   createForm() {
index 070f8ef98e822e0004eaeeff3a4adb9b782713ef..9537d7284df18e95c5392790756e0a63595c689a 100644 (file)
@@ -1,6 +1,6 @@
 import { ComponentFixture, TestBed } from '@angular/core/testing';
 
-import { NgChartsModule } from 'ng2-charts';
+import { BaseChartDirective } from 'ng2-charts';
 
 import { configureTestBed } from '~/testing/unit-test-helper';
 import { CephfsChartComponent } from './cephfs-chart.component';
@@ -18,7 +18,7 @@ describe('CephfsChartComponent', () => {
   ];
 
   configureTestBed({
-    imports: [NgChartsModule],
+    imports: [BaseChartDirective],
     declarations: [CephfsChartComponent]
   });
 
index 7a161f076842b1b04a9ed1354780d58b47be1eec..5c728d8cda259157a8faa85580c60222c1b053b0 100644 (file)
@@ -3,6 +3,7 @@ import { Component, ElementRef, Input, OnChanges, OnInit, ViewChild } from '@ang
 import _ from 'lodash';
 import moment from 'moment';
 import 'chartjs-adapter-moment';
+import { Chart, registerables } from 'chart.js';
 
 import { ChartTooltip } from '~/app/shared/models/chart-tooltip';
 
@@ -102,6 +103,10 @@ export class CephfsChartComponent implements OnChanges, OnInit {
     chartType: 'line'
   };
 
+  constructor() {
+    Chart.register(...registerables);
+  }
+
   ngOnInit() {
     if (_.isUndefined(this.mdsCounter)) {
       return;
index bdc54f783f000050d8e8cc4facb1b105b9cd63ee..08751aee17dbb824a0395c4cd7710e11570addfc 100644 (file)
@@ -12,7 +12,6 @@ import _ from 'lodash';
 
 import { CephfsService } from '~/app/shared/api/cephfs.service';
 import { ConfirmationModalComponent } from '~/app/shared/components/confirmation-modal/confirmation-modal.component';
-import { CriticalConfirmationModalComponent } from '~/app/shared/components/critical-confirmation-modal/critical-confirmation-modal.component';
 import { FormModalComponent } from '~/app/shared/components/form-modal/form-modal.component';
 import { NotificationType } from '~/app/shared/enum/notification-type.enum';
 import { CdValidators } from '~/app/shared/forms/cd-validators';
@@ -388,21 +387,18 @@ describe('CephfsDirectoriesComponent', () => {
     }
   };
 
-  configureTestBed(
-    {
-      imports: [
-        HttpClientTestingModule,
-        SharedModule,
-        RouterTestingModule,
-        TreeviewModule,
-        ToastrModule.forRoot(),
-        NgbModalModule
-      ],
-      declarations: [CephfsDirectoriesComponent],
-      providers: [NgbActiveModal]
-    },
-    [CriticalConfirmationModalComponent, FormModalComponent, ConfirmationModalComponent]
-  );
+  configureTestBed({
+    imports: [
+      HttpClientTestingModule,
+      SharedModule,
+      RouterTestingModule,
+      TreeviewModule,
+      ToastrModule.forRoot(),
+      NgbModalModule
+    ],
+    declarations: [CephfsDirectoriesComponent],
+    providers: [NgbActiveModal]
+  });
 
   beforeEach(() => {
     noAsyncUpdate = false;
@@ -674,7 +670,6 @@ describe('CephfsDirectoriesComponent', () => {
       mockLib.selectNode('/a/c');
       mockLib.selectNode('/a/c/a');
       component.selectOrigin('/a');
-      console.debug('component.selectedDir', component.selectedDir);
       expect(component.selectedDir.path).toBe('/a');
     });
 
index c711babdd6f4e80da747f01822c821e66ff465f5..b61b9574a1b5d036e0b5450c2d9b46a94c4b2d4d 100644 (file)
@@ -68,26 +68,7 @@ export class CephfsSnapshotscheduleListComponent
   errorMessage: string = '';
   selectedName: string = '';
   icons = Icons;
-  tableActions: CdTableAction[] = [
-    {
-      name: this.actionLabels.CREATE,
-      permission: 'create',
-      icon: Icons.add,
-      click: () => this.openModal(false)
-    },
-    {
-      name: this.actionLabels.EDIT,
-      permission: 'update',
-      icon: Icons.edit,
-      click: () => this.openModal(true)
-    },
-    {
-      name: this.actionLabels.DELETE,
-      permission: 'delete',
-      icon: Icons.trash,
-      click: () => this.deleteSnapshotSchedule()
-    }
-  ];
+  tableActions!: CdTableAction[];
 
   MODULE_NAME = 'snap_schedule';
   ENABLE_MODULE_TIMER = 2 * 1000;
@@ -112,6 +93,27 @@ export class CephfsSnapshotscheduleListComponent
   }
 
   ngOnInit(): void {
+    this.tableActions = [
+      {
+        name: this.actionLabels.CREATE,
+        permission: 'create',
+        icon: Icons.add,
+        click: () => this.openModal(false)
+      },
+      {
+        name: this.actionLabels.EDIT,
+        permission: 'update',
+        icon: Icons.edit,
+        click: () => this.openModal(true)
+      },
+      {
+        name: this.actionLabels.DELETE,
+        permission: 'delete',
+        icon: Icons.trash,
+        click: () => this.deleteSnapshotSchedule()
+      }
+    ];
+
     this.moduleServiceListSub = this.mgrModuleService
       .list()
       .pipe(
index 99b239eb2a467acbafcf1c4f4bd2a59d4807301d..55f957c59c6c1c8b025250a0d1b3f9452131e6b7 100644 (file)
@@ -9,7 +9,7 @@ import {
   NgbTooltipModule,
   NgbTypeaheadModule
 } from '@ng-bootstrap/ng-bootstrap';
-import { NgChartsModule } from 'ng2-charts';
+import { provideCharts, withDefaultRegisterables, BaseChartDirective } from 'ng2-charts';
 
 import { AppRoutingModule } from '~/app/app-routing.module';
 import { SharedModule } from '~/app/shared/shared.module';
@@ -59,7 +59,6 @@ import Trash from '@carbon/icons/es/trash-can/32';
     CommonModule,
     SharedModule,
     AppRoutingModule,
-    NgChartsModule,
     TreeviewModule,
     NgbNavModule,
     FormsModule,
@@ -83,7 +82,8 @@ import Trash from '@carbon/icons/es/trash-can/32';
     NumberModule,
     LayoutModule,
     ComboBoxModule,
-    IconModule
+    IconModule,
+    BaseChartDirective
   ],
   declarations: [
     CephfsDetailComponent,
@@ -104,7 +104,8 @@ import Trash from '@carbon/icons/es/trash-can/32';
     CephfsSubvolumeSnapshotsFormComponent,
     CephfsMountDetailsComponent,
     CephfsAuthModalComponent
-  ]
+  ],
+  providers: [provideCharts(withDefaultRegisterables())]
 })
 export class CephfsModule {
   constructor(private iconService: IconService) {
index 4e30931c1b09e515f074546d7515fbb5d85df04e..2051e61568c454adae50c5a9b359bac4f7651b38 100644 (file)
@@ -45,7 +45,7 @@
   <div cdsCol
        [columnNumbers]="{'lg': 14, 'md': 14, 'sm': 14}">
     <ng-container [ngSwitch]="currentStep?.stepIndex">
-      <div *ngSwitchCase="'0'"
+      <div *ngSwitchCase="0"
            class="ms-5">
         <h4 class="title"
             i18n>Add Hosts</h4>
@@ -56,7 +56,7 @@
                   [showGeneralActionsOnly]="true"
                   [showExpandClusterBtn]="false"></cd-hosts>
       </div>
-      <div *ngSwitchCase="'1'"
+      <div *ngSwitchCase="1"
            class="ms-5">
         <h4 class="title"
             i18n>Create OSDs</h4>
@@ -68,7 +68,7 @@
                        (emitMode)="setDeploymentMode($event)"></cd-osd-form>
         </div>
       </div>
-      <div *ngSwitchCase="'2'"
+      <div *ngSwitchCase="2"
            class="ms-5">
         <h4 class="title"
             i18n>Create Services</h4>
@@ -77,7 +77,7 @@
                      [hiddenColumns]="['status.running', 'status.size', 'status.last_refresh']"
                      [routedModal]="false"></cd-services>
       </div>
-      <div *ngSwitchCase="'3'"
+      <div *ngSwitchCase="3"
            class="ms-5">
         <cd-create-cluster-review></cd-create-cluster-review>
       </div>
index b847517790981247c0ca5a9fb347063f7a3fd4c6..4301c23d8e4431357f27cbdbd983ffea9b7db305 100644 (file)
@@ -10,7 +10,6 @@ import { CoreModule } from '~/app/core/core.module';
 import { HostService } from '~/app/shared/api/host.service';
 import { OsdService } from '~/app/shared/api/osd.service';
 import { ConfirmationModalComponent } from '~/app/shared/components/confirmation-modal/confirmation-modal.component';
-import { LoadingPanelComponent } from '~/app/shared/components/loading-panel/loading-panel.component';
 import { AppConstants } from '~/app/shared/constants/app.constants';
 import { ModalService } from '~/app/shared/services/modal.service';
 import { WizardStepsService } from '~/app/shared/services/wizard-steps.service';
@@ -27,19 +26,16 @@ describe('CreateClusterComponent', () => {
   let modalServiceShowSpy: jasmine.Spy;
   const projectConstants: typeof AppConstants = AppConstants;
 
-  configureTestBed(
-    {
-      imports: [
-        HttpClientTestingModule,
-        RouterTestingModule,
-        ToastrModule.forRoot(),
-        SharedModule,
-        CoreModule,
-        CephModule
-      ]
-    },
-    [LoadingPanelComponent]
-  );
+  configureTestBed({
+    imports: [
+      HttpClientTestingModule,
+      RouterTestingModule,
+      ToastrModule.forRoot(),
+      SharedModule,
+      CoreModule,
+      CephModule
+    ]
+  });
 
   beforeEach(() => {
     fixture = TestBed.createComponent(CreateClusterComponent);
index fc0ce5823aab342e082dcbbb8751905fdbcd207d..892bd3fe49f7c25e3a593ec910e48edb595bf7ee 100644 (file)
@@ -1,4 +1,6 @@
 import {
+  AfterViewInit,
+  ChangeDetectorRef,
   Component,
   EventEmitter,
   OnDestroy,
@@ -39,7 +41,7 @@ import { Step } from 'carbon-components-angular';
   templateUrl: './create-cluster.component.html',
   styleUrls: ['./create-cluster.component.scss']
 })
-export class CreateClusterComponent implements OnInit, OnDestroy {
+export class CreateClusterComponent implements OnInit, OnDestroy, AfterViewInit {
   @ViewChild('skipConfirmTpl', { static: true })
   skipConfirmTpl: TemplateRef<any>;
   currentStep: WizardStepModel;
@@ -88,7 +90,8 @@ export class CreateClusterComponent implements OnInit, OnDestroy {
     private taskWrapper: TaskWrapperService,
     private osdService: OsdService,
     private route: ActivatedRoute,
-    private location: Location
+    private location: Location,
+    private changeDetectorRef: ChangeDetectorRef
   ) {
     this.permissions = this.authStorageService.getPermissions();
     this.currentStepSub = this.wizardStepsService
@@ -98,6 +101,9 @@ export class CreateClusterComponent implements OnInit, OnDestroy {
       });
     this.currentStep.stepIndex = 0;
   }
+  ngAfterViewInit(): void {
+    this.changeDetectorRef.detectChanges();
+  }
 
   ngOnInit(): void {
     this.stepTitles.forEach((steps, index) => {
index 8097bb260182bac174be016b5e93ea523936b160..97194e74fd412dd5b9b157c3b07615c3fdcf75af 100644 (file)
@@ -6,7 +6,6 @@ import { RouterTestingModule } from '@angular/router/testing';
 import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap';
 import { ToastrModule } from 'ngx-toastr';
 
-import { LoadingPanelComponent } from '~/app/shared/components/loading-panel/loading-panel.component';
 import { SharedModule } from '~/app/shared/shared.module';
 import { configureTestBed, FormHelper } from '~/testing/unit-test-helper';
 import { HostFormComponent } from './host-form.component';
@@ -17,22 +16,19 @@ describe('HostFormComponent', () => {
   let fixture: ComponentFixture<HostFormComponent>;
   let formHelper: FormHelper;
 
-  configureTestBed(
-    {
-      imports: [
-        SharedModule,
-        HttpClientTestingModule,
-        RouterTestingModule,
-        ReactiveFormsModule,
-        ToastrModule.forRoot(),
-        InputModule,
-        ModalModule
-      ],
-      declarations: [HostFormComponent],
-      providers: [NgbActiveModal]
-    },
-    [LoadingPanelComponent]
-  );
+  configureTestBed({
+    imports: [
+      SharedModule,
+      HttpClientTestingModule,
+      RouterTestingModule,
+      ReactiveFormsModule,
+      ToastrModule.forRoot(),
+      InputModule,
+      ModalModule
+    ],
+    declarations: [HostFormComponent],
+    providers: [NgbActiveModal]
+  });
 
   beforeEach(() => {
     fixture = TestBed.createComponent(HostFormComponent);
index 8a92776b8be134362e627c8763766c343e0b4934..e474fb854cde5671f341beef404e9b538a5a86c4 100644 (file)
@@ -18,8 +18,7 @@
                 [maxLimit]="25"
                 (setExpandedRow)="setExpandedRow($event)"
                 (updateSelection)="updateSelection($event)"
-                [toolHeader]="!hideToolHeader"
-                [showMenu]="showMenu">
+                [toolHeader]="!hideToolHeader">
         <div class="table-actions">
           <cd-table-actions [permission]="permissions.hosts"
                             [selection]="selection"
index f8c139bc9ce617adbaed683d22f419700defd786..9f7762ceb741c1653482ffe922398d9ad0b1470c 100644 (file)
@@ -5,7 +5,6 @@ import { RouterTestingModule } from '@angular/router/testing';
 
 import { ToastrModule } from 'ngx-toastr';
 
-import { LoadingPanelComponent } from '~/app/shared/components/loading-panel/loading-panel.component';
 import { SharedModule } from '~/app/shared/shared.module';
 import { configureTestBed } from '~/testing/unit-test-helper';
 import { MgrModuleFormComponent } from './mgr-module-form.component';
@@ -14,19 +13,16 @@ describe('MgrModuleFormComponent', () => {
   let component: MgrModuleFormComponent;
   let fixture: ComponentFixture<MgrModuleFormComponent>;
 
-  configureTestBed(
-    {
-      declarations: [MgrModuleFormComponent],
-      imports: [
-        HttpClientTestingModule,
-        ReactiveFormsModule,
-        RouterTestingModule,
-        SharedModule,
-        ToastrModule.forRoot()
-      ]
-    },
-    [LoadingPanelComponent]
-  );
+  configureTestBed({
+    declarations: [MgrModuleFormComponent],
+    imports: [
+      HttpClientTestingModule,
+      ReactiveFormsModule,
+      RouterTestingModule,
+      SharedModule,
+      ToastrModule.forRoot()
+    ]
+  });
 
   beforeEach(() => {
     fixture = TestBed.createComponent(MgrModuleFormComponent);
index b4d8a86526dc59946b81f9f5c15100f1ae9a9d9a..acff1a473d28ac114895a3e099b3fafdad46829a 100644 (file)
@@ -86,7 +86,7 @@ describe('SilenceFormComponent', () => {
     });
 
   const changeAction = (action: string) => {
-    const modes = {
+    const modes: Record<string, string> = {
       add: '/monitoring/silences/add',
       alertAdd: '/monitoring/silences/add/alert0',
       recreate: '/monitoring/silences/recreate/someExpiredId',
@@ -180,7 +180,7 @@ describe('SilenceFormComponent', () => {
     let navigateSpy: jasmine.Spy;
 
     const expectError = (action: string, redirected: boolean) => {
-      Object.defineProperty(router, 'url', { value: action });
+      Object.defineProperty(router, 'url', { value: action, configurable: true });
       if (redirected) {
         expect(() => callInit()).toThrowError(DashboardNotFoundError);
       } else {
index e2d93c4b4526d01e38fdd49991fc80cbd391ef5b..576a6f0e64f0c166cf53ce5779db7e48f92464ca 100644 (file)
@@ -9,8 +9,6 @@ import { ToastrModule } from 'ngx-toastr';
 import { of as observableOf } from 'rxjs';
 
 import { MgrModuleService } from '~/app/shared/api/mgr-module.service';
-import { DownloadButtonComponent } from '~/app/shared/components/download-button/download-button.component';
-import { LoadingPanelComponent } from '~/app/shared/components/loading-panel/loading-panel.component';
 import { SharedModule } from '~/app/shared/shared.module';
 import { configureTestBed } from '~/testing/unit-test-helper';
 import { TelemetryComponent } from './telemetry.component';
@@ -46,19 +44,16 @@ describe('TelemetryComponent', () => {
     'url'
   ];
 
-  configureTestBed(
-    {
-      declarations: [TelemetryComponent],
-      imports: [
-        HttpClientTestingModule,
-        ReactiveFormsModule,
-        RouterTestingModule,
-        SharedModule,
-        ToastrModule.forRoot()
-      ]
-    },
-    [LoadingPanelComponent, DownloadButtonComponent]
-  );
+  configureTestBed({
+    declarations: [TelemetryComponent],
+    imports: [
+      HttpClientTestingModule,
+      ReactiveFormsModule,
+      RouterTestingModule,
+      SharedModule,
+      ToastrModule.forRoot()
+    ]
+  });
 
   describe('configForm', () => {
     beforeEach(() => {
index 8f61791f225e48ee49aabafd7fcbdd49c724d80f..7f5cd05f9071c54f62fe755312adffb77bd26e65 100644 (file)
@@ -1,4 +1,11 @@
-import { Component, Input, ViewChild, OnChanges, SimpleChanges } from '@angular/core';
+import {
+  Component,
+  Input,
+  ViewChild,
+  OnChanges,
+  SimpleChanges,
+  AfterViewInit
+} from '@angular/core';
 
 import { CssHelper } from '~/app/shared/classes/css-helper';
 import { DimlessBinaryPipe } from '~/app/shared/pipes/dimless-binary.pipe';
@@ -14,7 +21,7 @@ import 'chartjs-adapter-moment';
   templateUrl: './dashboard-area-chart.component.html',
   styleUrls: ['./dashboard-area-chart.component.scss']
 })
-export class DashboardAreaChartComponent implements OnChanges {
+export class DashboardAreaChartComponent implements OnChanges, AfterViewInit {
   @ViewChild(BaseChartDirective) chart: BaseChartDirective;
 
   @Input()
@@ -44,32 +51,7 @@ export class DashboardAreaChartComponent implements OnChanges {
   options: any = {};
   currentChartData: any = {};
 
-  chartColors: any[] = [
-    [
-      this.cssHelper.propertyValue('chart-color-strong-blue'),
-      this.cssHelper.propertyValue('chart-color-translucent-blue')
-    ],
-    [
-      this.cssHelper.propertyValue('chart-color-orange'),
-      this.cssHelper.propertyValue('chart-color-translucent-orange')
-    ],
-    [
-      this.cssHelper.propertyValue('chart-color-green'),
-      this.cssHelper.propertyValue('chart-color-translucent-green')
-    ],
-    [
-      this.cssHelper.propertyValue('chart-color-cyan'),
-      this.cssHelper.propertyValue('chart-color-translucent-cyan')
-    ],
-    [
-      this.cssHelper.propertyValue('chart-color-purple'),
-      this.cssHelper.propertyValue('chart-color-translucent-purple')
-    ],
-    [
-      this.cssHelper.propertyValue('chart-color-red'),
-      this.cssHelper.propertyValue('chart-color-translucent-red')
-    ]
-  ];
+  chartColors!: any[];
 
   public chartAreaBorderPlugin: any[] = [
     {
@@ -100,6 +82,33 @@ export class DashboardAreaChartComponent implements OnChanges {
     private formatter: FormatterService,
     private numberFormatter: NumberFormatterService
   ) {
+    this.chartColors = [
+      [
+        this.cssHelper.propertyValue('chart-color-strong-blue'),
+        this.cssHelper.propertyValue('chart-color-translucent-blue')
+      ],
+      [
+        this.cssHelper.propertyValue('chart-color-orange'),
+        this.cssHelper.propertyValue('chart-color-translucent-orange')
+      ],
+      [
+        this.cssHelper.propertyValue('chart-color-green'),
+        this.cssHelper.propertyValue('chart-color-translucent-green')
+      ],
+      [
+        this.cssHelper.propertyValue('chart-color-cyan'),
+        this.cssHelper.propertyValue('chart-color-translucent-cyan')
+      ],
+      [
+        this.cssHelper.propertyValue('chart-color-purple'),
+        this.cssHelper.propertyValue('chart-color-translucent-purple')
+      ],
+      [
+        this.cssHelper.propertyValue('chart-color-red'),
+        this.cssHelper.propertyValue('chart-color-translucent-red')
+      ]
+    ];
+
     this.options = {
       plugins: {
         legend: {
index 82843289b3834a0b0be73d8804b17b0ed26c3855..d437855b8fd875331697020b67ae9f5355a2e811 100644 (file)
@@ -4,7 +4,7 @@ import { FormsModule, ReactiveFormsModule } from '@angular/forms';
 import { RouterModule } from '@angular/router';
 
 import { NgbNavModule, NgbPopoverModule, NgbTooltipModule } from '@ng-bootstrap/ng-bootstrap';
-import { NgChartsModule } from 'ng2-charts';
+import { provideCharts, withDefaultRegisterables, BaseChartDirective } from 'ng2-charts';
 import { SimplebarAngularModule } from 'simplebar-angular';
 
 import { SharedModule } from '~/app/shared/shared.module';
@@ -21,15 +21,14 @@ import { PgSummaryPipe } from './pg-summary.pipe';
     CommonModule,
     NgbNavModule,
     SharedModule,
-    NgChartsModule,
     RouterModule,
     NgbPopoverModule,
     NgbTooltipModule,
     FormsModule,
     ReactiveFormsModule,
-    SimplebarAngularModule
+    SimplebarAngularModule,
+    BaseChartDirective
   ],
-
   declarations: [
     DashboardV3Component,
     DashboardPieComponent,
@@ -37,12 +36,12 @@ import { PgSummaryPipe } from './pg-summary.pipe';
     DashboardAreaChartComponent,
     DashboardTimeSelectorComponent
   ],
-
   exports: [
     DashboardV3Component,
     DashboardAreaChartComponent,
     DashboardTimeSelectorComponent,
     DashboardPieComponent
-  ]
+  ],
+  providers: [provideCharts(withDefaultRegisterables())]
 })
 export class DashboardV3Module {}
index c779feb3156ff6dd601965a86a7fc53313466e59..e6101343966748f3066a9f98cd36a68f89ccc66f 100644 (file)
@@ -4,7 +4,7 @@ import { FormsModule, ReactiveFormsModule } from '@angular/forms';
 import { RouterModule } from '@angular/router';
 
 import { NgbNavModule, NgbPopoverModule } from '@ng-bootstrap/ng-bootstrap';
-import { NgChartsModule } from 'ng2-charts';
+import { provideCharts, withDefaultRegisterables, BaseChartDirective } from 'ng2-charts';
 
 import { SharedModule } from '~/app/shared/shared.module';
 import { DashboardV3Module } from '../dashboard-v3/dashboard-v3.module';
@@ -15,10 +15,10 @@ import { HealthPieComponent } from './health-pie/health-pie.component';
 import { HealthComponent } from './health/health.component';
 import { InfoCardComponent } from './info-card/info-card.component';
 import { InfoGroupComponent } from './info-group/info-group.component';
-import { MdsSummaryPipe } from './mds-summary.pipe';
-import { MgrSummaryPipe } from './mgr-summary.pipe';
+import { MdsDashboardSummaryPipe } from './mds-dashboard-summary.pipe';
+import { MgrDashboardSummaryPipe } from './mgr-dashboard-summary.pipe';
 import { MonSummaryPipe } from './mon-summary.pipe';
-import { OsdSummaryPipe } from './osd-summary.pipe';
+import { osdDashboardSummaryPipe } from './osd-dashboard-summary.pipe';
 
 @NgModule({
   imports: [
@@ -26,25 +26,25 @@ import { OsdSummaryPipe } from './osd-summary.pipe';
     CommonModule,
     NgbNavModule,
     SharedModule,
-    NgChartsModule,
     RouterModule,
     NgbPopoverModule,
     FormsModule,
     ReactiveFormsModule,
-    DashboardV3Module
+    DashboardV3Module,
+    BaseChartDirective
   ],
-
   declarations: [
     HealthComponent,
     DashboardComponent,
     MonSummaryPipe,
-    OsdSummaryPipe,
-    MgrSummaryPipe,
-    MdsSummaryPipe,
+    osdDashboardSummaryPipe,
+    MgrDashboardSummaryPipe,
+    MdsDashboardSummaryPipe,
     HealthPieComponent,
     InfoCardComponent,
     InfoGroupComponent,
     FeedbackComponent
-  ]
+  ],
+  providers: [provideCharts(withDefaultRegisterables())]
 })
 export class DashboardModule {}
index 9e68fc22ca0ada5751e97ab2aeccc62301af63e2..d1159514583296941405d0fdbd8ede5113b2230c 100644 (file)
@@ -2,36 +2,36 @@
      class="container-fluid">
   <cd-info-group groupTitle="Status"
                  i18n-groupTitle
-                 *ngIf="healthData.health?.status
-                 || healthData.mon_status
-                 || healthData.osd_map
-                 || healthData.mgr_map
-                 || healthData.hosts != null
-                 || healthData.rgw != null
-                 || healthData.fs_map
-                 || healthData.iscsi_daemons != null">
+                 *ngIf="healthData?.health?.status
+                 || healthData?.mon_status
+                 || healthData?.osd_map
+                 || healthData?.mgr_map
+                 || healthData?.hosts != null
+                 || healthData?.rgw != null
+                 || healthData?.fs_map
+                 || healthData?.iscsi_daemons != null">
 
     <cd-info-card cardTitle="Cluster Status"
                   i18n-cardTitle
                   class="cd-status-card"
                   contentClass="content-highlight"
-                  *ngIf="healthData.health?.status">
-      <ng-container *ngIf="healthData.health?.checks?.length > 0">
+                  *ngIf="healthData?.health?.status">
+      <ng-container *ngIf="healthData?.health?.checks?.length > 0">
         <ng-template #healthChecks>
           <cd-health-checks [healthData]="healthData"></cd-health-checks>
         </ng-template>
         <div class="info-card-content-clickable"
-             [ngStyle]="healthData.health.status | healthColor"
+             [ngStyle]="healthData?.health?.status | healthColor"
              [ngbPopover]="healthChecks"
              popoverClass="info-card-popover-cluster-status">
-          {{ healthData.health.status | healthLabel | uppercase }}
-          <i *ngIf="healthData.health?.status !== 'HEALTH_OK'"
+          {{ healthData?.health?.status | healthLabel | uppercase }}
+          <i *ngIf="healthData?.health?.status !== 'HEALTH_OK'"
              class="fa fa-exclamation-triangle"></i>
         </div>
       </ng-container>
-      <ng-container *ngIf="!healthData.health?.checks?.length">
-        <div [ngStyle]="healthData.health.status | healthColor">
-          {{ healthData.health.status | healthLabel | uppercase }}
+      <ng-container *ngIf="!healthData?.health?.checks?.length">
+        <div [ngStyle]="healthData?.health.status | healthColor">
+          {{ healthData?.health.status | healthLabel | uppercase }}
         </div>
       </ng-container>
     </cd-info-card>
@@ -41,8 +41,8 @@
                   link="/hosts"
                   class="cd-status-card"
                   contentClass="content-highlight"
-                  *ngIf="healthData.hosts != null">
-      {{ healthData.hosts }} total
+                  *ngIf="healthData?.hosts != null">
+      {{ healthData?.hosts }} total
     </cd-info-card>
 
     <cd-info-card cardTitle="Monitors"
                   link="/monitor"
                   class="cd-status-card"
                   contentClass="content-highlight"
-                  *ngIf="healthData.mon_status">
-      {{ healthData.mon_status | monSummary }}
+                  *ngIf="healthData?.mon_status">
+      {{ healthData?.mon_status | monSummary }}
     </cd-info-card>
 
     <cd-info-card cardTitle="OSDs"
                   i18n-cardTitle
                   link="/osd"
                   class="cd-status-card"
-                  *ngIf="(healthData.osd_map | osdSummary) as transformedResult"
+                  *ngIf="(healthData?.osd_map | osdDashboardSummary) as transformedResult"
                   contentClass="content-highlight">
       <span *ngFor="let result of transformedResult"
             [ngClass]="result.class">
@@ -70,8 +70,8 @@
                   i18n-cardTitle
                   class="cd-status-card"
                   contentClass="content-highlight"
-                  *ngIf="healthData.mgr_map">
-      <span *ngFor="let result of (healthData.mgr_map | mgrSummary)"
+                  *ngIf="healthData?.mgr_map">
+      <span *ngFor="let result of (healthData?.mgr_map | mgrDashboardSummary)"
             [ngClass]="result.class"
             [title]="result.titleText != null ? result.titleText : ''">
         {{ result.content }}
                   link="/rgw/daemon"
                   class="cd-status-card"
                   contentClass="content-highlight"
-                  *ngIf="enabledFeature.rgw && healthData?.rgw != null">
-      {{ healthData.rgw }} total
+                  *ngIf="enabledFeature?.rgw && healthData?.rgw != null">
+      {{ healthData?.rgw }} total
     </cd-info-card>
 
     <cd-info-card cardTitle="Metadata Servers"
                   i18n-cardTitle
                   class="cd-status-card"
-                  *ngIf="(enabledFeature.cephfs && healthData.fs_map | mdsSummary) as transformedResult"
+                  *ngIf="(enabledFeature?.cephfs && healthData?.fs_map | mdsDashboardSummary) as transformedResult"
                   [contentClass]="(transformedResult.length > 1 ? 'text-area-size-2' : '') + ' content-highlight'">
       <!-- TODO: check text-area-size-2 -->
       <span *ngFor="let result of transformedResult"
                   link="/block/iscsi"
                   class="cd-status-card"
                   contentClass="content-highlight"
-                  *ngIf="enabledFeature.iscsi && healthData?.iscsi_daemons != null">
-      {{ healthData.iscsi_daemons.up + healthData.iscsi_daemons.down }} total
+                  *ngIf="enabledFeature?.iscsi && healthData?.iscsi_daemons != null">
+      {{ healthData?.iscsi_daemons.up + healthData?.iscsi_daemons.down }} total
       <span class="card-text-line-break"></span>
-      {{ healthData.iscsi_daemons.up }} up,
-      <span [ngClass]="{'card-text-error': healthData.iscsi_daemons.down > 0}">{{ healthData.iscsi_daemons.down }}
+      {{ healthData?.iscsi_daemons.up }} up,
+      <span [ngClass]="{'card-text-error': healthData?.iscsi_daemons.down > 0}">{{ healthData?.iscsi_daemons.down }}
         down</span>
     </cd-info-card>
   </cd-info-group>
 
   <cd-info-group groupTitle="Capacity"
                  i18n-groupTitle
-                 *ngIf="healthData.pools
-                 || healthData.df
-                 || healthData.pg_info">
+                 *ngIf="healthData?.pools
+                 || healthData?.df
+                 || healthData?.pg_info">
     <cd-info-card cardTitle="Raw Capacity"
                   i18n-cardTitle
                   class="cd-capacity-card cd-chart-card"
                   contentClass="content-chart"
-                  *ngIf="healthData.df">
+                  *ngIf="healthData?.df">
       <cd-health-pie [data]="healthData"
                      [config]="rawCapacityChartConfig"
                      [isBytesData]="true"
                   i18n-cardTitle
                   class="cd-capacity-card cd-chart-card"
                   contentClass="content-chart"
-                  *ngIf="healthData.pg_info?.object_stats?.num_objects != null">
+                  *ngIf="healthData?.pg_info?.object_stats?.num_objects != null">
       <cd-health-pie [data]="healthData"
                      (prepareFn)="prepareObjects($event[0], $event[1])">
       </cd-health-pie>
                   i18n-cardTitle
                   class="cd-capacity-card cd-chart-card"
                   contentClass="content-chart"
-                  *ngIf="healthData.pg_info">
+                  *ngIf="healthData?.pg_info">
       <ng-template #pgStatus>
         <ng-container *ngTemplateOutlet="logsLink"></ng-container>
         <ul>
-          <li *ngFor="let pgStatesText of healthData.pg_info.statuses | keyvalue">
+          <li *ngFor="let pgStatesText of healthData?.pg_info.statuses | keyvalue">
             {{ pgStatesText.key }}: {{ pgStatesText.value }}
           </li>
         </ul>
                   link="/pool"
                   class="cd-capacity-card"
                   contentClass="content-highlight"
-                  *ngIf="healthData.pools">
-      {{ healthData.pools.length }}
+                  *ngIf="healthData?.pools">
+      {{ healthData?.pools.length }}
     </cd-info-card>
 
     <cd-info-card cardTitle="PGs per OSD"
                   i18n-cardTitle
                   class="cd-capacity-card"
                   contentClass="content-highlight"
-                  *ngIf="healthData.pg_info">
-      {{ healthData.pg_info.pgs_per_osd | dimless }}
+                  *ngIf="healthData?.pg_info">
+      {{ healthData?.pg_info.pgs_per_osd | dimless }}
     </cd-info-card>
   </cd-info-group>
 
   <cd-info-group groupTitle="Performance"
                  i18n-groupTitle
-                 *ngIf="healthData.client_perf || healthData.scrub_status">
+                 *ngIf="healthData?.client_perf || healthData?.scrub_status">
     <cd-info-card cardTitle="Client Read/Write"
                   i18n-cardTitle
                   class="cd-performance-card cd-chart-card"
                   contentClass="content-chart"
-                  *ngIf="healthData.client_perf">
+                  *ngIf="healthData?.client_perf">
       <cd-health-pie [data]="healthData"
                      [config]="clientStatsConfig"
                      (prepareFn)="prepareReadWriteRatio($event[0], $event[1])">
                   i18n-cardTitle
                   class="cd-performance-card cd-chart-card"
                   contentClass="content-chart"
-                  *ngIf="healthData.client_perf">
+                  *ngIf="healthData?.client_perf">
       <cd-health-pie [data]="healthData"
                      [config]="clientStatsConfig"
                      (prepareFn)="prepareClientThroughput($event[0], $event[1])">
                   i18n-cardTitle
                   class="cd-performance-card"
                   contentClass="content-highlight"
-                  *ngIf="healthData.client_perf">
-      {{ (healthData.client_perf.recovering_bytes_per_sec | dimlessBinary) + '/s' }}
+                  *ngIf="healthData?.client_perf">
+      {{ (healthData?.client_perf.recovering_bytes_per_sec | dimlessBinary) + '/s' }}
     </cd-info-card>
 
     <cd-info-card cardTitle="Scrubbing"
                   i18n-cardTitle
                   class="cd-performance-card"
                   contentClass="content-highlight"
-                  *ngIf="healthData.scrub_status">
-      {{ healthData.scrub_status }}
+                  *ngIf="healthData?.scrub_status">
+      {{ healthData?.scrub_status }}
     </cd-info-card>
   </cd-info-group>
 
   <ng-template #logsLink>
-    <ng-container *ngIf="permissions.log.read">
+    <ng-container *ngIf="permissions?.log?.read">
       <p class="logs-link"
          i18n><i [ngClass]="[icons.infoCircle]"></i> See <a routerLink="/logs">Logs</a> for more details.</p>
     </ng-container>
index 3e25e922878379952d7a20473ebcc379ba7debe9..4b8a4167ad2fbc9a62fd57639d6efa30291a51ea 100644 (file)
@@ -16,10 +16,10 @@ import { RefreshIntervalService } from '~/app/shared/services/refresh-interval.s
 import { SharedModule } from '~/app/shared/shared.module';
 import { configureTestBed } from '~/testing/unit-test-helper';
 import { HealthPieComponent } from '../health-pie/health-pie.component';
-import { MdsSummaryPipe } from '../mds-summary.pipe';
-import { MgrSummaryPipe } from '../mgr-summary.pipe';
+import { MdsDashboardSummaryPipe } from '../mds-dashboard-summary.pipe';
+import { MgrDashboardSummaryPipe } from '../mgr-dashboard-summary.pipe';
 import { MonSummaryPipe } from '../mon-summary.pipe';
-import { OsdSummaryPipe } from '../osd-summary.pipe';
+import { osdDashboardSummaryPipe } from '../osd-dashboard-summary.pipe';
 import { HealthComponent } from './health.component';
 
 describe('HealthComponent', () => {
@@ -54,9 +54,9 @@ describe('HealthComponent', () => {
       HealthComponent,
       HealthPieComponent,
       MonSummaryPipe,
-      OsdSummaryPipe,
-      MdsSummaryPipe,
-      MgrSummaryPipe
+      osdDashboardSummaryPipe,
+      MdsDashboardSummaryPipe,
+      MgrDashboardSummaryPipe
     ],
     schemas: [NO_ERRORS_SCHEMA],
     providers: [
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/dashboard/mds-dashboard-summary.pipe.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/dashboard/mds-dashboard-summary.pipe.spec.ts
new file mode 100644 (file)
index 0000000..c091a35
--- /dev/null
@@ -0,0 +1,72 @@
+import { TestBed } from '@angular/core/testing';
+
+import { configureTestBed } from '~/testing/unit-test-helper';
+import { MdsDashboardSummaryPipe } from './mds-dashboard-summary.pipe';
+
+describe('MdsDashboardSummaryPipe', () => {
+  let pipe: MdsDashboardSummaryPipe;
+
+  configureTestBed({
+    providers: [MdsDashboardSummaryPipe]
+  });
+
+  beforeEach(() => {
+    pipe = TestBed.inject(MdsDashboardSummaryPipe);
+  });
+
+  it('create an instance', () => {
+    expect(pipe).toBeTruthy();
+  });
+
+  it('transforms with 0 active and 2 standy', () => {
+    const payload = {
+      standbys: [{ name: 'a' }],
+      filesystems: [{ mdsmap: { info: [{ state: 'up:standby-replay' }] } }]
+    };
+    const expected = [
+      { class: 'popover-info', content: '0 active', titleText: '1 standbyReplay' },
+      { class: 'card-text-line-break', content: '', titleText: '' },
+      { class: 'popover-info', content: '2 standby', titleText: 'standby daemons: a' }
+    ];
+
+    expect(pipe.transform(payload)).toEqual(expected);
+  });
+
+  it('transforms with 1 active and 1 standy', () => {
+    const payload = {
+      standbys: [{ name: 'b' }],
+      filesystems: [{ mdsmap: { info: [{ state: 'up:active', name: 'a' }] } }]
+    };
+    const expected = [
+      { class: 'popover-info', content: '1 active', titleText: 'active daemon: a' },
+      { class: 'card-text-line-break', content: '', titleText: '' },
+      { class: 'popover-info', content: '1 standby', titleText: 'standby daemons: b' }
+    ];
+    expect(pipe.transform(payload)).toEqual(expected);
+  });
+
+  it('transforms with 0 filesystems', () => {
+    const payload: Record<string, any> = {
+      standbys: [0],
+      filesystems: []
+    };
+    const expected = [{ class: 'popover-info', content: 'no filesystems', titleText: '' }];
+
+    expect(pipe.transform(payload)).toEqual(expected);
+  });
+
+  it('transforms without filesystem', () => {
+    const payload = { standbys: [{ name: 'a' }] };
+    const expected = [
+      { class: 'popover-info', content: '1 up', titleText: '' },
+      { class: 'card-text-line-break', content: '', titleText: '' },
+      { class: 'popover-info', content: 'no filesystems', titleText: 'standby daemons: a' }
+    ];
+
+    expect(pipe.transform(payload)).toEqual(expected);
+  });
+
+  it('transforms without value', () => {
+    expect(pipe.transform(undefined)).toBe('');
+  });
+});
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/dashboard/mds-dashboard-summary.pipe.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/dashboard/mds-dashboard-summary.pipe.ts
new file mode 100644 (file)
index 0000000..2b4a899
--- /dev/null
@@ -0,0 +1,78 @@
+import { Pipe, PipeTransform } from '@angular/core';
+
+import _ from 'lodash';
+
+@Pipe({
+  name: 'mdsDashboardSummary'
+})
+export class MdsDashboardSummaryPipe implements PipeTransform {
+  transform(value: any): any {
+    if (!value) {
+      return '';
+    }
+
+    let contentLine1 = '';
+    let contentLine2 = '';
+    let standbys = 0;
+    let active = 0;
+    let standbyReplay = 0;
+    _.each(value.standbys, () => {
+      standbys += 1;
+    });
+
+    if (value.standbys && !value.filesystems) {
+      contentLine1 = `${standbys} ${$localize`up`}`;
+      contentLine2 = $localize`no filesystems`;
+    } else if (value.filesystems.length === 0) {
+      contentLine1 = $localize`no filesystems`;
+    } else {
+      _.each(value.filesystems, (fs) => {
+        _.each(fs.mdsmap.info, (mds) => {
+          if (mds.state === 'up:standby-replay') {
+            standbyReplay += 1;
+          } else {
+            active += 1;
+          }
+        });
+      });
+
+      contentLine1 = `${active} ${$localize`active`}`;
+      contentLine2 = `${standbys + standbyReplay} ${$localize`standby`}`;
+    }
+    const standbyHoverText = value.standbys.map((s: any): string => s.name).join(', ');
+    const standbyTitleText = !standbyHoverText
+      ? ''
+      : `${$localize`standby daemons`}: ${standbyHoverText}`;
+    const fsLength = value.filesystems ? value.filesystems.length : 0;
+    const infoObject = fsLength > 0 ? value.filesystems[0].mdsmap.info : {};
+    const activeHoverText = Object.values(infoObject)
+      .map((info: any): string => info.name)
+      .join(', ');
+    let activeTitleText = !activeHoverText ? '' : `${$localize`active daemon`}: ${activeHoverText}`;
+    // There is always one standbyreplay to replace active daemon, if active one is down
+    if (!active && fsLength > 0) {
+      activeTitleText = `${standbyReplay} ${$localize`standbyReplay`}`;
+    }
+    const mgrSummary = [
+      {
+        content: contentLine1,
+        class: 'popover-info',
+        titleText: activeTitleText
+      }
+    ];
+    if (contentLine2) {
+      mgrSummary.push({
+        content: '',
+        class: 'card-text-line-break',
+        titleText: ''
+      });
+      mgrSummary.push({
+        content: contentLine2,
+        class: 'popover-info',
+        titleText: standbyTitleText
+      });
+    }
+
+    return mgrSummary;
+  }
+}
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/dashboard/mds-summary.pipe.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/dashboard/mds-summary.pipe.spec.ts
deleted file mode 100644 (file)
index c62b35c..0000000
+++ /dev/null
@@ -1,72 +0,0 @@
-import { TestBed } from '@angular/core/testing';
-
-import { configureTestBed } from '~/testing/unit-test-helper';
-import { MdsSummaryPipe } from './mds-summary.pipe';
-
-describe('MdsSummaryPipe', () => {
-  let pipe: MdsSummaryPipe;
-
-  configureTestBed({
-    providers: [MdsSummaryPipe]
-  });
-
-  beforeEach(() => {
-    pipe = TestBed.inject(MdsSummaryPipe);
-  });
-
-  it('create an instance', () => {
-    expect(pipe).toBeTruthy();
-  });
-
-  it('transforms with 0 active and 2 standy', () => {
-    const payload = {
-      standbys: [{ name: 'a' }],
-      filesystems: [{ mdsmap: { info: [{ state: 'up:standby-replay' }] } }]
-    };
-    const expected = [
-      { class: 'popover-info', content: '0 active', titleText: '1 standbyReplay' },
-      { class: 'card-text-line-break', content: '', titleText: '' },
-      { class: 'popover-info', content: '2 standby', titleText: 'standby daemons: a' }
-    ];
-
-    expect(pipe.transform(payload)).toEqual(expected);
-  });
-
-  it('transforms with 1 active and 1 standy', () => {
-    const payload = {
-      standbys: [{ name: 'b' }],
-      filesystems: [{ mdsmap: { info: [{ state: 'up:active', name: 'a' }] } }]
-    };
-    const expected = [
-      { class: 'popover-info', content: '1 active', titleText: 'active daemon: a' },
-      { class: 'card-text-line-break', content: '', titleText: '' },
-      { class: 'popover-info', content: '1 standby', titleText: 'standby daemons: b' }
-    ];
-    expect(pipe.transform(payload)).toEqual(expected);
-  });
-
-  it('transforms with 0 filesystems', () => {
-    const payload: Record<string, any> = {
-      standbys: [0],
-      filesystems: []
-    };
-    const expected = [{ class: 'popover-info', content: 'no filesystems', titleText: '' }];
-
-    expect(pipe.transform(payload)).toEqual(expected);
-  });
-
-  it('transforms without filesystem', () => {
-    const payload = { standbys: [{ name: 'a' }] };
-    const expected = [
-      { class: 'popover-info', content: '1 up', titleText: '' },
-      { class: 'card-text-line-break', content: '', titleText: '' },
-      { class: 'popover-info', content: 'no filesystems', titleText: 'standby daemons: a' }
-    ];
-
-    expect(pipe.transform(payload)).toEqual(expected);
-  });
-
-  it('transforms without value', () => {
-    expect(pipe.transform(undefined)).toBe('');
-  });
-});
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/dashboard/mds-summary.pipe.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/dashboard/mds-summary.pipe.ts
deleted file mode 100644 (file)
index 9cc72ac..0000000
+++ /dev/null
@@ -1,78 +0,0 @@
-import { Pipe, PipeTransform } from '@angular/core';
-
-import _ from 'lodash';
-
-@Pipe({
-  name: 'mdsSummary'
-})
-export class MdsSummaryPipe implements PipeTransform {
-  transform(value: any): any {
-    if (!value) {
-      return '';
-    }
-
-    let contentLine1 = '';
-    let contentLine2 = '';
-    let standbys = 0;
-    let active = 0;
-    let standbyReplay = 0;
-    _.each(value.standbys, () => {
-      standbys += 1;
-    });
-
-    if (value.standbys && !value.filesystems) {
-      contentLine1 = `${standbys} ${$localize`up`}`;
-      contentLine2 = $localize`no filesystems`;
-    } else if (value.filesystems.length === 0) {
-      contentLine1 = $localize`no filesystems`;
-    } else {
-      _.each(value.filesystems, (fs) => {
-        _.each(fs.mdsmap.info, (mds) => {
-          if (mds.state === 'up:standby-replay') {
-            standbyReplay += 1;
-          } else {
-            active += 1;
-          }
-        });
-      });
-
-      contentLine1 = `${active} ${$localize`active`}`;
-      contentLine2 = `${standbys + standbyReplay} ${$localize`standby`}`;
-    }
-    const standbyHoverText = value.standbys.map((s: any): string => s.name).join(', ');
-    const standbyTitleText = !standbyHoverText
-      ? ''
-      : `${$localize`standby daemons`}: ${standbyHoverText}`;
-    const fsLength = value.filesystems ? value.filesystems.length : 0;
-    const infoObject = fsLength > 0 ? value.filesystems[0].mdsmap.info : {};
-    const activeHoverText = Object.values(infoObject)
-      .map((info: any): string => info.name)
-      .join(', ');
-    let activeTitleText = !activeHoverText ? '' : `${$localize`active daemon`}: ${activeHoverText}`;
-    // There is always one standbyreplay to replace active daemon, if active one is down
-    if (!active && fsLength > 0) {
-      activeTitleText = `${standbyReplay} ${$localize`standbyReplay`}`;
-    }
-    const mgrSummary = [
-      {
-        content: contentLine1,
-        class: 'popover-info',
-        titleText: activeTitleText
-      }
-    ];
-    if (contentLine2) {
-      mgrSummary.push({
-        content: '',
-        class: 'card-text-line-break',
-        titleText: ''
-      });
-      mgrSummary.push({
-        content: contentLine2,
-        class: 'popover-info',
-        titleText: standbyTitleText
-      });
-    }
-
-    return mgrSummary;
-  }
-}
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/dashboard/mgr-dashboard-summary.pipe.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/dashboard/mgr-dashboard-summary.pipe.spec.ts
new file mode 100644 (file)
index 0000000..3bd9242
--- /dev/null
@@ -0,0 +1,52 @@
+import { TestBed } from '@angular/core/testing';
+
+import { configureTestBed } from '~/testing/unit-test-helper';
+import { MgrDashboardSummaryPipe } from './mgr-dashboard-summary.pipe';
+
+describe('MgrDashboardSummaryPipe', () => {
+  let pipe: MgrDashboardSummaryPipe;
+
+  configureTestBed({
+    providers: [MgrDashboardSummaryPipe]
+  });
+
+  beforeEach(() => {
+    pipe = TestBed.inject(MgrDashboardSummaryPipe);
+  });
+
+  it('create an instance', () => {
+    expect(pipe).toBeTruthy();
+  });
+
+  it('transforms without value', () => {
+    expect(pipe.transform(undefined)).toBe('');
+  });
+
+  it('transforms with active_name undefined', () => {
+    const payload: Record<string, any> = {
+      active_name: undefined,
+      standbys: []
+    };
+    const expected = [
+      { class: 'popover-info', content: 'n/a active', titleText: '' },
+      { class: 'card-text-line-break', content: '', titleText: '' },
+      { class: 'popover-info', content: '0 standby', titleText: '' }
+    ];
+
+    expect(pipe.transform(payload)).toEqual(expected);
+  });
+
+  it('transforms with 1 active and 2 standbys', () => {
+    const payload = {
+      active_name: 'x',
+      standbys: [{ name: 'y' }, { name: 'z' }]
+    };
+    const expected = [
+      { class: 'popover-info', content: '1 active', titleText: 'active daemon: x' },
+      { class: 'card-text-line-break', content: '', titleText: '' },
+      { class: 'popover-info', content: '2 standby', titleText: 'standby daemons: y, z' }
+    ];
+
+    expect(pipe.transform(payload)).toEqual(expected);
+  });
+});
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/dashboard/mgr-dashboard-summary.pipe.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/dashboard/mgr-dashboard-summary.pipe.ts
new file mode 100644 (file)
index 0000000..b82e5d8
--- /dev/null
@@ -0,0 +1,48 @@
+import { Pipe, PipeTransform } from '@angular/core';
+
+import _ from 'lodash';
+
+@Pipe({
+  name: 'mgrDashboardSummary'
+})
+export class MgrDashboardSummaryPipe implements PipeTransform {
+  transform(value: any): any {
+    if (!value) {
+      return '';
+    }
+
+    let activeCount = $localize`n/a`;
+    const activeTitleText = _.isUndefined(value.active_name)
+      ? ''
+      : `${$localize`active daemon`}: ${value.active_name}`;
+    // There is always one standbyreplay to replace active daemon, if active one is down
+    if (activeTitleText.length > 0) {
+      activeCount = '1';
+    }
+    const standbyHoverText = value.standbys.map((s: any): string => s.name).join(', ');
+    const standbyTitleText = !standbyHoverText
+      ? ''
+      : `${$localize`standby daemons`}: ${standbyHoverText}`;
+    const standbyCount = value.standbys.length;
+    const mgrSummary = [
+      {
+        content: `${activeCount} ${$localize`active`}`,
+        class: 'popover-info',
+        titleText: activeTitleText
+      }
+    ];
+
+    mgrSummary.push({
+      content: '',
+      class: 'card-text-line-break',
+      titleText: ''
+    });
+    mgrSummary.push({
+      content: `${standbyCount} ${$localize`standby`}`,
+      class: 'popover-info',
+      titleText: standbyTitleText
+    });
+
+    return mgrSummary;
+  }
+}
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/dashboard/mgr-summary.pipe.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/dashboard/mgr-summary.pipe.spec.ts
deleted file mode 100644 (file)
index 8bc2753..0000000
+++ /dev/null
@@ -1,52 +0,0 @@
-import { TestBed } from '@angular/core/testing';
-
-import { configureTestBed } from '~/testing/unit-test-helper';
-import { MgrSummaryPipe } from './mgr-summary.pipe';
-
-describe('MgrSummaryPipe', () => {
-  let pipe: MgrSummaryPipe;
-
-  configureTestBed({
-    providers: [MgrSummaryPipe]
-  });
-
-  beforeEach(() => {
-    pipe = TestBed.inject(MgrSummaryPipe);
-  });
-
-  it('create an instance', () => {
-    expect(pipe).toBeTruthy();
-  });
-
-  it('transforms without value', () => {
-    expect(pipe.transform(undefined)).toBe('');
-  });
-
-  it('transforms with active_name undefined', () => {
-    const payload: Record<string, any> = {
-      active_name: undefined,
-      standbys: []
-    };
-    const expected = [
-      { class: 'popover-info', content: 'n/a active', titleText: '' },
-      { class: 'card-text-line-break', content: '', titleText: '' },
-      { class: 'popover-info', content: '0 standby', titleText: '' }
-    ];
-
-    expect(pipe.transform(payload)).toEqual(expected);
-  });
-
-  it('transforms with 1 active and 2 standbys', () => {
-    const payload = {
-      active_name: 'x',
-      standbys: [{ name: 'y' }, { name: 'z' }]
-    };
-    const expected = [
-      { class: 'popover-info', content: '1 active', titleText: 'active daemon: x' },
-      { class: 'card-text-line-break', content: '', titleText: '' },
-      { class: 'popover-info', content: '2 standby', titleText: 'standby daemons: y, z' }
-    ];
-
-    expect(pipe.transform(payload)).toEqual(expected);
-  });
-});
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/dashboard/mgr-summary.pipe.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/dashboard/mgr-summary.pipe.ts
deleted file mode 100644 (file)
index ffdee73..0000000
+++ /dev/null
@@ -1,48 +0,0 @@
-import { Pipe, PipeTransform } from '@angular/core';
-
-import _ from 'lodash';
-
-@Pipe({
-  name: 'mgrSummary'
-})
-export class MgrSummaryPipe implements PipeTransform {
-  transform(value: any): any {
-    if (!value) {
-      return '';
-    }
-
-    let activeCount = $localize`n/a`;
-    const activeTitleText = _.isUndefined(value.active_name)
-      ? ''
-      : `${$localize`active daemon`}: ${value.active_name}`;
-    // There is always one standbyreplay to replace active daemon, if active one is down
-    if (activeTitleText.length > 0) {
-      activeCount = '1';
-    }
-    const standbyHoverText = value.standbys.map((s: any): string => s.name).join(', ');
-    const standbyTitleText = !standbyHoverText
-      ? ''
-      : `${$localize`standby daemons`}: ${standbyHoverText}`;
-    const standbyCount = value.standbys.length;
-    const mgrSummary = [
-      {
-        content: `${activeCount} ${$localize`active`}`,
-        class: 'popover-info',
-        titleText: activeTitleText
-      }
-    ];
-
-    mgrSummary.push({
-      content: '',
-      class: 'card-text-line-break',
-      titleText: ''
-    });
-    mgrSummary.push({
-      content: `${standbyCount} ${$localize`standby`}`,
-      class: 'popover-info',
-      titleText: standbyTitleText
-    });
-
-    return mgrSummary;
-  }
-}
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/dashboard/osd-dashboard-summary.pipe.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/dashboard/osd-dashboard-summary.pipe.spec.ts
new file mode 100644 (file)
index 0000000..f5af99b
--- /dev/null
@@ -0,0 +1,193 @@
+import { TestBed } from '@angular/core/testing';
+
+import { configureTestBed } from '~/testing/unit-test-helper';
+import { osdDashboardSummaryPipe } from './osd-dashboard-summary.pipe';
+
+describe('osdDashboardSummaryPipe', () => {
+  let pipe: osdDashboardSummaryPipe;
+
+  configureTestBed({
+    providers: [osdDashboardSummaryPipe]
+  });
+
+  beforeEach(() => {
+    pipe = TestBed.inject(osdDashboardSummaryPipe);
+  });
+
+  it('create an instance', () => {
+    expect(pipe).toBeTruthy();
+  });
+
+  it('transforms without value', () => {
+    expect(pipe.transform(undefined)).toBe('');
+  });
+
+  it('transforms having 3 osd with 3 up, 3 in, 0 down, 0 out', () => {
+    const value = {
+      osds: [
+        { up: 1, in: 1, state: ['up', 'exists'] },
+        { up: 1, in: 1, state: ['up', 'exists'] },
+        { up: 1, in: 1, state: ['up', 'exists'] }
+      ]
+    };
+    expect(pipe.transform(value)).toEqual([
+      {
+        content: '3 total',
+        class: ''
+      },
+      {
+        content: '',
+        class: 'card-text-line-break'
+      },
+      {
+        content: '3 up, 3 in',
+        class: ''
+      }
+    ]);
+  });
+
+  it('transforms having 3 osd with 2 up, 1 in, 1 down, 2 out', () => {
+    const value = {
+      osds: [
+        { up: 1, in: 1, state: ['up', 'exists'] },
+        { up: 1, in: 0, state: ['up', 'exists'] },
+        { up: 0, in: 0, state: ['exists'] }
+      ]
+    };
+    expect(pipe.transform(value)).toEqual([
+      {
+        content: '3 total',
+        class: ''
+      },
+      {
+        content: '',
+        class: 'card-text-line-break'
+      },
+      {
+        content: '2 up, 1 in',
+        class: ''
+      },
+      {
+        content: '',
+        class: 'card-text-line-break'
+      },
+      {
+        content: '1 down, 2 out',
+        class: 'card-text-error'
+      }
+    ]);
+  });
+
+  it('transforms having 3 osd with 2 up, 3 in, 1 full, 1 nearfull, 1 down, 0 out', () => {
+    const value = {
+      osds: [
+        { up: 1, in: 1, state: ['up', 'nearfull'] },
+        { up: 1, in: 1, state: ['up', 'exists'] },
+        { up: 0, in: 1, state: ['full'] }
+      ]
+    };
+    expect(pipe.transform(value)).toEqual([
+      {
+        content: '3 total',
+        class: ''
+      },
+      {
+        content: '',
+        class: 'card-text-line-break'
+      },
+      {
+        content: '2 up, 3 in',
+        class: ''
+      },
+      {
+        content: '',
+        class: 'card-text-line-break'
+      },
+      {
+        content: '1 down',
+        class: 'card-text-error'
+      },
+      {
+        content: '',
+        class: 'card-text-line-break'
+      },
+      {
+        content: '1 near full',
+        class: 'card-text-error'
+      },
+      {
+        content: '',
+        class: 'card-text-line-break'
+      },
+      {
+        content: '1 full',
+        class: 'card-text-error'
+      }
+    ]);
+  });
+
+  it('transforms having 3 osd with 3 up, 2 in, 0 down, 1 out', () => {
+    const value = {
+      osds: [
+        { up: 1, in: 1, state: ['up', 'exists'] },
+        { up: 1, in: 1, state: ['up', 'exists'] },
+        { up: 1, in: 0, state: ['up', 'exists'] }
+      ]
+    };
+    expect(pipe.transform(value)).toEqual([
+      {
+        content: '3 total',
+        class: ''
+      },
+      {
+        content: '',
+        class: 'card-text-line-break'
+      },
+      {
+        content: '3 up, 2 in',
+        class: ''
+      },
+      {
+        content: '',
+        class: 'card-text-line-break'
+      },
+      {
+        content: '1 out',
+        class: 'card-text-error'
+      }
+    ]);
+  });
+
+  it('transforms having 4 osd with 3 up, 2 in, 1 down, another 2 out', () => {
+    const value = {
+      osds: [
+        { up: 1, in: 1, state: ['up', 'exists'] },
+        { up: 1, in: 0, state: ['up', 'exists'] },
+        { up: 1, in: 0, state: ['up', 'exists'] },
+        { up: 0, in: 1, state: ['exists'] }
+      ]
+    };
+    expect(pipe.transform(value)).toEqual([
+      {
+        content: '4 total',
+        class: ''
+      },
+      {
+        content: '',
+        class: 'card-text-line-break'
+      },
+      {
+        content: '3 up, 2 in',
+        class: ''
+      },
+      {
+        content: '',
+        class: 'card-text-line-break'
+      },
+      {
+        content: '1 down, 2 out',
+        class: 'card-text-error'
+      }
+    ]);
+  });
+});
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/dashboard/osd-dashboard-summary.pipe.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/dashboard/osd-dashboard-summary.pipe.ts
new file mode 100644 (file)
index 0000000..a3e292f
--- /dev/null
@@ -0,0 +1,91 @@
+import { Pipe, PipeTransform } from '@angular/core';
+
+import _ from 'lodash';
+
+@Pipe({
+  name: 'osdDashboardSummary'
+})
+export class osdDashboardSummaryPipe implements PipeTransform {
+  transform(value: any): any {
+    if (!value) {
+      return '';
+    }
+
+    let inCount = 0;
+    let upCount = 0;
+    let nearFullCount = 0;
+    let fullCount = 0;
+    _.each(value.osds, (osd) => {
+      if (osd.in) {
+        inCount++;
+      }
+      if (osd.up) {
+        upCount++;
+      }
+      if (osd.state.includes('nearfull')) {
+        nearFullCount++;
+      }
+      if (osd.state.includes('full')) {
+        fullCount++;
+      }
+    });
+
+    const osdSummary = [
+      {
+        content: `${value.osds.length} ${$localize`total`}`,
+        class: ''
+      }
+    ];
+    osdSummary.push({
+      content: '',
+      class: 'card-text-line-break'
+    });
+    osdSummary.push({
+      content: `${upCount} ${$localize`up`}, ${inCount} ${$localize`in`}`,
+      class: ''
+    });
+
+    const downCount = value.osds.length - upCount;
+    const outCount = value.osds.length - inCount;
+    if (downCount > 0 || outCount > 0) {
+      osdSummary.push({
+        content: '',
+        class: 'card-text-line-break'
+      });
+
+      const downText = downCount > 0 ? `${downCount} ${$localize`down`}` : '';
+      const separator = downCount > 0 && outCount > 0 ? ', ' : '';
+      const outText = outCount > 0 ? `${outCount} ${$localize`out`}` : '';
+      osdSummary.push({
+        content: `${downText}${separator}${outText}`,
+        class: 'card-text-error'
+      });
+    }
+
+    if (nearFullCount > 0) {
+      osdSummary.push(
+        {
+          content: '',
+          class: 'card-text-line-break'
+        },
+        {
+          content: `${nearFullCount} ${$localize`near full`}`,
+          class: 'card-text-error'
+        },
+        {
+          content: '',
+          class: 'card-text-line-break'
+        }
+      );
+    }
+
+    if (fullCount > 0) {
+      osdSummary.push({
+        content: `${fullCount} ${$localize`full`}`,
+        class: 'card-text-error'
+      });
+    }
+
+    return osdSummary;
+  }
+}
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/dashboard/osd-summary.pipe.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/dashboard/osd-summary.pipe.spec.ts
deleted file mode 100644 (file)
index 22f5eef..0000000
+++ /dev/null
@@ -1,193 +0,0 @@
-import { TestBed } from '@angular/core/testing';
-
-import { configureTestBed } from '~/testing/unit-test-helper';
-import { OsdSummaryPipe } from './osd-summary.pipe';
-
-describe('OsdSummaryPipe', () => {
-  let pipe: OsdSummaryPipe;
-
-  configureTestBed({
-    providers: [OsdSummaryPipe]
-  });
-
-  beforeEach(() => {
-    pipe = TestBed.inject(OsdSummaryPipe);
-  });
-
-  it('create an instance', () => {
-    expect(pipe).toBeTruthy();
-  });
-
-  it('transforms without value', () => {
-    expect(pipe.transform(undefined)).toBe('');
-  });
-
-  it('transforms having 3 osd with 3 up, 3 in, 0 down, 0 out', () => {
-    const value = {
-      osds: [
-        { up: 1, in: 1, state: ['up', 'exists'] },
-        { up: 1, in: 1, state: ['up', 'exists'] },
-        { up: 1, in: 1, state: ['up', 'exists'] }
-      ]
-    };
-    expect(pipe.transform(value)).toEqual([
-      {
-        content: '3 total',
-        class: ''
-      },
-      {
-        content: '',
-        class: 'card-text-line-break'
-      },
-      {
-        content: '3 up, 3 in',
-        class: ''
-      }
-    ]);
-  });
-
-  it('transforms having 3 osd with 2 up, 1 in, 1 down, 2 out', () => {
-    const value = {
-      osds: [
-        { up: 1, in: 1, state: ['up', 'exists'] },
-        { up: 1, in: 0, state: ['up', 'exists'] },
-        { up: 0, in: 0, state: ['exists'] }
-      ]
-    };
-    expect(pipe.transform(value)).toEqual([
-      {
-        content: '3 total',
-        class: ''
-      },
-      {
-        content: '',
-        class: 'card-text-line-break'
-      },
-      {
-        content: '2 up, 1 in',
-        class: ''
-      },
-      {
-        content: '',
-        class: 'card-text-line-break'
-      },
-      {
-        content: '1 down, 2 out',
-        class: 'card-text-error'
-      }
-    ]);
-  });
-
-  it('transforms having 3 osd with 2 up, 3 in, 1 full, 1 nearfull, 1 down, 0 out', () => {
-    const value = {
-      osds: [
-        { up: 1, in: 1, state: ['up', 'nearfull'] },
-        { up: 1, in: 1, state: ['up', 'exists'] },
-        { up: 0, in: 1, state: ['full'] }
-      ]
-    };
-    expect(pipe.transform(value)).toEqual([
-      {
-        content: '3 total',
-        class: ''
-      },
-      {
-        content: '',
-        class: 'card-text-line-break'
-      },
-      {
-        content: '2 up, 3 in',
-        class: ''
-      },
-      {
-        content: '',
-        class: 'card-text-line-break'
-      },
-      {
-        content: '1 down',
-        class: 'card-text-error'
-      },
-      {
-        content: '',
-        class: 'card-text-line-break'
-      },
-      {
-        content: '1 near full',
-        class: 'card-text-error'
-      },
-      {
-        content: '',
-        class: 'card-text-line-break'
-      },
-      {
-        content: '1 full',
-        class: 'card-text-error'
-      }
-    ]);
-  });
-
-  it('transforms having 3 osd with 3 up, 2 in, 0 down, 1 out', () => {
-    const value = {
-      osds: [
-        { up: 1, in: 1, state: ['up', 'exists'] },
-        { up: 1, in: 1, state: ['up', 'exists'] },
-        { up: 1, in: 0, state: ['up', 'exists'] }
-      ]
-    };
-    expect(pipe.transform(value)).toEqual([
-      {
-        content: '3 total',
-        class: ''
-      },
-      {
-        content: '',
-        class: 'card-text-line-break'
-      },
-      {
-        content: '3 up, 2 in',
-        class: ''
-      },
-      {
-        content: '',
-        class: 'card-text-line-break'
-      },
-      {
-        content: '1 out',
-        class: 'card-text-error'
-      }
-    ]);
-  });
-
-  it('transforms having 4 osd with 3 up, 2 in, 1 down, another 2 out', () => {
-    const value = {
-      osds: [
-        { up: 1, in: 1, state: ['up', 'exists'] },
-        { up: 1, in: 0, state: ['up', 'exists'] },
-        { up: 1, in: 0, state: ['up', 'exists'] },
-        { up: 0, in: 1, state: ['exists'] }
-      ]
-    };
-    expect(pipe.transform(value)).toEqual([
-      {
-        content: '4 total',
-        class: ''
-      },
-      {
-        content: '',
-        class: 'card-text-line-break'
-      },
-      {
-        content: '3 up, 2 in',
-        class: ''
-      },
-      {
-        content: '',
-        class: 'card-text-line-break'
-      },
-      {
-        content: '1 down, 2 out',
-        class: 'card-text-error'
-      }
-    ]);
-  });
-});
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/dashboard/osd-summary.pipe.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/dashboard/osd-summary.pipe.ts
deleted file mode 100644 (file)
index 46d2eda..0000000
+++ /dev/null
@@ -1,91 +0,0 @@
-import { Pipe, PipeTransform } from '@angular/core';
-
-import _ from 'lodash';
-
-@Pipe({
-  name: 'osdSummary'
-})
-export class OsdSummaryPipe implements PipeTransform {
-  transform(value: any): any {
-    if (!value) {
-      return '';
-    }
-
-    let inCount = 0;
-    let upCount = 0;
-    let nearFullCount = 0;
-    let fullCount = 0;
-    _.each(value.osds, (osd) => {
-      if (osd.in) {
-        inCount++;
-      }
-      if (osd.up) {
-        upCount++;
-      }
-      if (osd.state.includes('nearfull')) {
-        nearFullCount++;
-      }
-      if (osd.state.includes('full')) {
-        fullCount++;
-      }
-    });
-
-    const osdSummary = [
-      {
-        content: `${value.osds.length} ${$localize`total`}`,
-        class: ''
-      }
-    ];
-    osdSummary.push({
-      content: '',
-      class: 'card-text-line-break'
-    });
-    osdSummary.push({
-      content: `${upCount} ${$localize`up`}, ${inCount} ${$localize`in`}`,
-      class: ''
-    });
-
-    const downCount = value.osds.length - upCount;
-    const outCount = value.osds.length - inCount;
-    if (downCount > 0 || outCount > 0) {
-      osdSummary.push({
-        content: '',
-        class: 'card-text-line-break'
-      });
-
-      const downText = downCount > 0 ? `${downCount} ${$localize`down`}` : '';
-      const separator = downCount > 0 && outCount > 0 ? ', ' : '';
-      const outText = outCount > 0 ? `${outCount} ${$localize`out`}` : '';
-      osdSummary.push({
-        content: `${downText}${separator}${outText}`,
-        class: 'card-text-error'
-      });
-    }
-
-    if (nearFullCount > 0) {
-      osdSummary.push(
-        {
-          content: '',
-          class: 'card-text-line-break'
-        },
-        {
-          content: `${nearFullCount} ${$localize`near full`}`,
-          class: 'card-text-error'
-        },
-        {
-          content: '',
-          class: 'card-text-line-break'
-        }
-      );
-    }
-
-    if (fullCount > 0) {
-      osdSummary.push({
-        content: `${fullCount} ${$localize`full`}`,
-        class: 'card-text-error'
-      });
-    }
-
-    return osdSummary;
-  }
-}
index 1024f00adc5d85157c3887b29cbe74c26102e291..7f5888f15c1c6a700e25257f3770c9ed48715644 100644 (file)
@@ -11,7 +11,6 @@ import { Observable, of } from 'rxjs';
 import { NfsFormClientComponent } from '~/app/ceph/nfs/nfs-form-client/nfs-form-client.component';
 import { NfsFormComponent } from '~/app/ceph/nfs/nfs-form/nfs-form.component';
 import { Directory } from '~/app/shared/api/nfs.service';
-import { LoadingPanelComponent } from '~/app/shared/components/loading-panel/loading-panel.component';
 import { SharedModule } from '~/app/shared/shared.module';
 import { ActivatedRouteStub } from '~/testing/activated-route-stub';
 import { configureTestBed, RgwHelper } from '~/testing/unit-test-helper';
@@ -23,26 +22,23 @@ describe('NfsFormComponent', () => {
   let activatedRoute: ActivatedRouteStub;
   let router: Router;
 
-  configureTestBed(
-    {
-      declarations: [NfsFormComponent, NfsFormClientComponent],
-      imports: [
-        HttpClientTestingModule,
-        ReactiveFormsModule,
-        RouterTestingModule,
-        SharedModule,
-        ToastrModule.forRoot(),
-        NgbTypeaheadModule
-      ],
-      providers: [
-        {
-          provide: ActivatedRoute,
-          useValue: new ActivatedRouteStub({ cluster_id: 'mynfs', export_id: '1' })
-        }
-      ]
-    },
-    [LoadingPanelComponent]
-  );
+  configureTestBed({
+    declarations: [NfsFormComponent, NfsFormClientComponent],
+    imports: [
+      HttpClientTestingModule,
+      ReactiveFormsModule,
+      RouterTestingModule,
+      SharedModule,
+      ToastrModule.forRoot(),
+      NgbTypeaheadModule
+    ],
+    providers: [
+      {
+        provide: ActivatedRoute,
+        useValue: new ActivatedRouteStub({ cluster_id: 'mynfs', export_id: '1' })
+      }
+    ]
+  });
 
   const matchSquash = (backendSquashValue: string, uiSquashValue: string) => {
     component.ngOnInit();
index 308b09d721641a74d96b5a7331ff59e6be42aa5f..0afb2442735cf8fe2ab5a89a91c10e8adeb277a9 100644 (file)
@@ -23,7 +23,7 @@ export class CrushRuleFormModalComponent extends CrushNodeSelectionClass impleme
   @Output()
   submitAction = new EventEmitter();
 
-  tooltips = this.crushRuleService.formTooltips;
+  tooltips!: Record<string, string>;
 
   form: CdFormGroup;
   names: string[];
@@ -67,6 +67,8 @@ export class CrushRuleFormModalComponent extends CrushNodeSelectionClass impleme
   }
 
   ngOnInit() {
+    this.tooltips = this.crushRuleService.formTooltips;
+
     this.crushRuleService
       .getInfo()
       .subscribe(({ names, nodes }: { names: string[]; nodes: CrushNode[] }) => {
index 1521ae83f1b20ca56deaa6ce4cf8604183e9d592..b78b5d483f5207da128a1cb60ecdeec2b3761ab0 100644 (file)
@@ -26,7 +26,7 @@ export class ErasureCodeProfileFormModalComponent
   @Output()
   submitAction = new EventEmitter();
 
-  tooltips = this.ecpService.formTooltips;
+  tooltips!: Record<string, any>;
   PLUGIN = {
     LRC: 'lrc', // Locally Repairable Erasure Code
     SHEC: 'shec', // Shingled Erasure Code
@@ -365,6 +365,8 @@ export class ErasureCodeProfileFormModalComponent
   }
 
   ngOnInit() {
+    this.tooltips = this.ecpService.formTooltips;
+
     this.ecpService
       .getInfo()
       .subscribe(
index caf8c0b6a71a66a1c737bad5f625bcf4f369c20b..d831c59ca2f2035be80d99711679eec3906e0f85 100644 (file)
@@ -135,28 +135,25 @@ describe('PoolFormComponent', () => {
 
   const routes: Routes = [{ path: '404', component: ErrorComponent }];
 
-  configureTestBed(
-    {
-      declarations: [ErrorComponent],
-      imports: [
-        BrowserAnimationsModule,
-        HttpClientTestingModule,
-        RouterTestingModule.withRoutes(routes),
-        ToastrModule.forRoot(),
-        NgbNavModule,
-        PoolModule,
-        SharedModule,
-        NgbModalModule
-      ],
-      providers: [
-        ErasureCodeProfileService,
-        NgbActiveModal,
-        SelectBadgesComponent,
-        { provide: ActivatedRoute, useValue: { params: of({ name: 'somePoolName' }) } }
-      ]
-    },
-    [CriticalConfirmationModalComponent]
-  );
+  configureTestBed({
+    declarations: [ErrorComponent],
+    imports: [
+      BrowserAnimationsModule,
+      HttpClientTestingModule,
+      RouterTestingModule.withRoutes(routes),
+      ToastrModule.forRoot(),
+      NgbNavModule,
+      PoolModule,
+      SharedModule,
+      NgbModalModule
+    ],
+    providers: [
+      ErasureCodeProfileService,
+      NgbActiveModal,
+      SelectBadgesComponent,
+      { provide: ActivatedRoute, useValue: { params: of({ name: 'somePoolName' }) } }
+    ]
+  });
 
   let navigationSpy: jasmine.Spy;
 
index a54e7eeee08257584ed2b7f37cfc0060ddac4b7c..d6104930e35e090f768d6f857936e5edff5e5c7a 100644 (file)
@@ -4,6 +4,7 @@ import { BucketTagModalComponent } from './bucket-tag-modal.component';
 import { HttpClientTestingModule } from '@angular/common/http/testing';
 import { ReactiveFormsModule } from '@angular/forms';
 import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap';
+import { CUSTOM_ELEMENTS_SCHEMA, NO_ERRORS_SCHEMA } from '@angular/core';
 
 describe('BucketTagModalComponent', () => {
   let component: BucketTagModalComponent;
@@ -13,6 +14,7 @@ describe('BucketTagModalComponent', () => {
     await TestBed.configureTestingModule({
       declarations: [BucketTagModalComponent],
       imports: [HttpClientTestingModule, ReactiveFormsModule],
+      schemas: [NO_ERRORS_SCHEMA, CUSTOM_ELEMENTS_SCHEMA],
       providers: [NgbActiveModal]
     }).compileComponents();
 
index 5ca36f4bd2fc466d6c81e4c0e7d97099d7089678..c2c775d1282412db389c5c64cfde603a25e206a5 100644 (file)
@@ -62,7 +62,6 @@
           for="default_realm"
           formControlName="default_realm"
           name="default_realm"
-          [disabled]="action === actionLabels.EDIT"
           i18n
           >Default
           <cd-help-text *ngIf="action === actionLabels.EDIT && info.data.is_default">
index 1e18598b0dbb18a9e4634034b5a7b6dbfd0d162d..98ad92b98b8ab748b5be15429d80a07afe8e3f1c 100644 (file)
@@ -93,6 +93,12 @@ export class RgwMultisiteRealmFormComponent extends BaseModal implements OnInit
       this.defaultRealmDisabled = true;
     }
     this.docUrl = this.docService.urlGenerator('rgw-multisite');
+
+    if (this.action === this.actionLabels?.EDIT) {
+      this.multisiteRealmForm.get('default_realm').disable();
+    } else {
+      this.multisiteRealmForm.get('default_realm').enable();
+    }
   }
 
   submit() {
index 2efdbfb8c9db41d1fdde4dfe14dfb1e64bba29c2..1aa21f437eaca1abecbcd8d6e38d9d931502920d 100644 (file)
@@ -8,6 +8,7 @@ import { ReactiveFormsModule } from '@angular/forms';
 import { CommonModule } from '@angular/common';
 import { RgwMultisiteService } from '~/app/shared/api/rgw-multisite.service';
 import { of } from 'rxjs';
+import { CUSTOM_ELEMENTS_SCHEMA, NO_ERRORS_SCHEMA } from '@angular/core';
 
 enum FlowType {
   symmetrical = 'symmetrical',
@@ -33,6 +34,7 @@ describe('RgwMultisiteSyncFlowModalComponent', () => {
         ReactiveFormsModule,
         CommonModule
       ],
+      schemas: [NO_ERRORS_SCHEMA, CUSTOM_ELEMENTS_SCHEMA],
       providers: [NgbActiveModal, { provide: RgwMultisiteService, useClass: MultisiteServiceMock }]
     }).compileComponents();
 
index 1127db1c59a59bdb477f8bcc01bfd5fc4d6727a2..faf382b78028e34529b72a8fb2fd2597dd3d9beb 100644 (file)
@@ -9,6 +9,7 @@ import { CommonModule } from '@angular/common';
 import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap';
 import { of } from 'rxjs';
 import { RgwMultisiteService } from '~/app/shared/api/rgw-multisite.service';
+import { CUSTOM_ELEMENTS_SCHEMA, NO_ERRORS_SCHEMA } from '@angular/core';
 
 class MultisiteServiceMock {
   createEditSyncPipe = jest.fn().mockReturnValue(of(null));
@@ -29,6 +30,7 @@ describe('RgwMultisiteSyncPipeModalComponent', () => {
         ReactiveFormsModule,
         CommonModule
       ],
+      schemas: [NO_ERRORS_SCHEMA, CUSTOM_ELEMENTS_SCHEMA],
       providers: [NgbActiveModal, { provide: RgwMultisiteService, useClass: MultisiteServiceMock }]
     }).compileComponents();
 
index b886ad1d5e57be042f12b690d41751b0a8649f59..c09e7392a0b639e95fa4bd75112b3462edf57ec8 100644 (file)
@@ -6,6 +6,7 @@ import { ReactiveFormsModule } from '@angular/forms';
 import { PipesModule } from '~/app/shared/pipes/pipes.module';
 import { ComponentsModule } from '~/app/shared/components/components.module';
 import { RouterTestingModule } from '@angular/router/testing';
+import { CUSTOM_ELEMENTS_SCHEMA, NO_ERRORS_SCHEMA } from '@angular/core';
 
 describe('RgwMultisiteSyncPolicyFormComponent', () => {
   let component: RgwMultisiteSyncPolicyFormComponent;
@@ -22,6 +23,7 @@ describe('RgwMultisiteSyncPolicyFormComponent', () => {
         ComponentsModule,
         RouterTestingModule
       ],
+      schemas: [NO_ERRORS_SCHEMA, CUSTOM_ELEMENTS_SCHEMA],
       providers: []
     }).compileComponents();
 
index 9047eef2d1771f7ad46ff263dc5fcf8547d2b081..21f36f9d0b31823b0b62dfa4fce86bb5f590719a 100644 (file)
@@ -7,6 +7,7 @@ import { SharedModule } from '~/app/shared/shared.module';
 import { ReactiveFormsModule } from '@angular/forms';
 import { ToastrModule } from 'ngx-toastr';
 import { RouterTestingModule } from '@angular/router/testing';
+import { CUSTOM_ELEMENTS_SCHEMA, NO_ERRORS_SCHEMA } from '@angular/core';
 
 describe('RgwMultisiteWizardComponent', () => {
   let component: RgwMultisiteWizardComponent;
@@ -22,6 +23,7 @@ describe('RgwMultisiteWizardComponent', () => {
         ToastrModule.forRoot(),
         RouterTestingModule
       ],
+      schemas: [NO_ERRORS_SCHEMA, CUSTOM_ELEMENTS_SCHEMA],
       providers: [NgbActiveModal]
     }).compileComponents();
 
index 944435cba16aca9020748bdfe126ff30aa7764cd..ebcb017ece4be3eb2c37e010dcc98999d9d344b0 100644 (file)
@@ -62,8 +62,8 @@ describe('RgwSyncDataInfoComponent', () => {
     const syncStatus = fixture.debugElement.query(By.css('.text-primary'));
     expect(syncStatus).toBeTruthy();
     expect(syncStatus.nativeElement.textContent).toEqual('Syncing');
-    const syncPopover = fixture.debugElement.query(By.css('a'));
-    syncPopover.triggerEventHandler('click', null);
+    const syncPopover = fixture.nativeElement.querySelector('a');
+    syncPopover.dispatchEvent(new Event('click'));
     fixture.detectChanges();
     expect(syncPopover).toBeTruthy();
     const syncPopoverText = fixture.debugElement.query(By.css('.text-center'));
index 0b7e4ede377dce02ac45f4a66f3d172ed4204247..0457fe2a98348fb8824989b79cb05a615b11fd95 100644 (file)
@@ -65,8 +65,8 @@ describe('RgwSyncMetadataInfoComponent', () => {
     const syncStatus = fixture.debugElement.query(By.css('.text-primary'));
     expect(syncStatus).toBeTruthy();
     expect(syncStatus.nativeElement.textContent).toEqual('Syncing');
-    const syncPopover = fixture.debugElement.query(By.css('a'));
-    syncPopover.triggerEventHandler('click', null);
+    const syncPopover = fixture.nativeElement.querySelector('a');
+    syncPopover.dispatchEvent(new Event('click'));
     fixture.detectChanges();
     expect(syncPopover).toBeTruthy();
     const syncPopoverText = fixture.debugElement.query(By.css('.text-center'));
index e9a4da80fd6c8fdc830b843b30af26702bb35335..43633dc37895bec98065a390ec8825159ecff254 100644 (file)
@@ -4,6 +4,7 @@ import { HealthChecksComponent } from './health-checks.component';
 import { HealthColorPipe } from '~/app/shared/pipes/health-color.pipe';
 import { By } from '@angular/platform-browser';
 import { CssHelper } from '~/app/shared/classes/css-helper';
+import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
 
 describe('HealthChecksComponent', () => {
   let component: HealthChecksComponent;
@@ -12,7 +13,8 @@ describe('HealthChecksComponent', () => {
   beforeEach(async () => {
     await TestBed.configureTestingModule({
       declarations: [HealthChecksComponent, HealthColorPipe],
-      providers: [CssHelper]
+      providers: [CssHelper],
+      schemas: [CUSTOM_ELEMENTS_SCHEMA]
     }).compileComponents();
 
     fixture = TestBed.createComponent(HealthChecksComponent);
index 1acbb8efec337835efae8a5c8f8b9ab3a5d21cf0..da7b05713faa8c2caf0bad147c6fdc7d226f1a93 100644 (file)
@@ -49,7 +49,7 @@ export class SmbClusterFormComponent extends CdForm implements OnInit {
   resource: string;
   icons = Icons;
   domainSettingsObject: DomainSettings;
-  modalData$ = this.smbService.modalData$;
+  modalData$!: Observable<DomainSettings>;
 
   constructor(
     private hostService: HostService,
@@ -63,6 +63,7 @@ export class SmbClusterFormComponent extends CdForm implements OnInit {
   ) {
     super();
     this.resource = $localize`Cluster`;
+    this.modalData$ = this.smbService.modalData$;
   }
   ngOnInit() {
     this.action = this.actionLabels.CREATE;
index 44a9ca555d5430fdc3102acbac935acc5c4568a9..7bb5e131d5ae804edf52430d028cca4e7239e5f9 100644 (file)
@@ -8,6 +8,7 @@ import { HttpClientTestingModule } from '@angular/common/http/testing';
 import { RouterTestingModule } from '@angular/router/testing';
 import { NgbActiveModal, NgbTypeaheadModule } from '@ng-bootstrap/ng-bootstrap';
 import { InputModule, ModalModule, SelectModule } from 'carbon-components-angular';
+import { CUSTOM_ELEMENTS_SCHEMA, NO_ERRORS_SCHEMA } from '@angular/core';
 
 describe('SmbDomainSettingModalComponent', () => {
   let component: SmbDomainSettingModalComponent;
@@ -27,6 +28,7 @@ describe('SmbDomainSettingModalComponent', () => {
         InputModule,
         SelectModule
       ],
+      schemas: [NO_ERRORS_SCHEMA, CUSTOM_ELEMENTS_SCHEMA],
       providers: [NgbActiveModal, { provide: 'domainSettingsObject', useValue: [[]] }]
     }).compileComponents();
 
index 9caadd5a97cdf64bb37830d2fc066967eb3eb489..a3a9816e93e6012d0030f45368ef3395d48096e3 100644 (file)
@@ -2,7 +2,7 @@ import Close from '@carbon/icons/es/close/32';
 import { SmbClusterListComponent } from './smb-cluster-list/smb-cluster-list.component';
 import { SmbClusterFormComponent } from './smb-cluster-form/smb-cluster-form.component';
 import { AppRoutingModule } from '~/app/app-routing.module';
-import { NgChartsModule } from 'ng2-charts';
+import { provideCharts, withDefaultRegisterables, BaseChartDirective } from 'ng2-charts';
 import { DataTableModule } from '~/app/shared/datatable/datatable.module';
 import { SmbDomainSettingModalComponent } from './smb-domain-setting-modal/smb-domain-setting-modal.component';
 import {
@@ -33,7 +33,7 @@ import { NgModule } from '@angular/core';
     CommonModule,
     SharedModule,
     AppRoutingModule,
-    NgChartsModule,
+    BaseChartDirective,
     CommonModule,
     FormsModule,
     ReactiveFormsModule,
@@ -53,7 +53,8 @@ import { NgModule } from '@angular/core';
     IconModule
   ],
   exports: [SmbClusterListComponent, SmbClusterFormComponent],
-  declarations: [SmbClusterListComponent, SmbClusterFormComponent, SmbDomainSettingModalComponent]
+  declarations: [SmbClusterListComponent, SmbClusterFormComponent, SmbDomainSettingModalComponent],
+  providers: [provideCharts(withDefaultRegisterables())]
 })
 export class SmbModule {
   constructor(private iconService: IconService) {
index f4e8e1d0bce0fa37a26e219da8d938f056a2e21b..7eb708d6e7953608774922f7f4a3daaf5e2b29c9 100644 (file)
@@ -10,7 +10,6 @@ import { of } from 'rxjs';
 
 import { RoleService } from '~/app/shared/api/role.service';
 import { ScopeService } from '~/app/shared/api/scope.service';
-import { LoadingPanelComponent } from '~/app/shared/components/loading-panel/loading-panel.component';
 import { CdFormGroup } from '~/app/shared/forms/cd-form-group';
 import { NotificationService } from '~/app/shared/services/notification.service';
 import { SharedModule } from '~/app/shared/shared.module';
@@ -32,19 +31,16 @@ describe('RoleFormComponent', () => {
 
   const routes: Routes = [{ path: 'roles', component: FakeComponent }];
 
-  configureTestBed(
-    {
-      imports: [
-        RouterTestingModule.withRoutes(routes),
-        HttpClientTestingModule,
-        ReactiveFormsModule,
-        ToastrModule.forRoot(),
-        SharedModule
-      ],
-      declarations: [RoleFormComponent, FakeComponent]
-    },
-    [LoadingPanelComponent]
-  );
+  configureTestBed({
+    imports: [
+      RouterTestingModule.withRoutes(routes),
+      HttpClientTestingModule,
+      ReactiveFormsModule,
+      ToastrModule.forRoot(),
+      SharedModule
+    ],
+    declarations: [RoleFormComponent, FakeComponent]
+  });
 
   beforeEach(() => {
     fixture = TestBed.createComponent(RoleFormComponent);
index 943fc033ede059e4f1bdbdae8ac1f38f80749173..efe5a819e8091c6f388d45c343a2ae943f008f99 100644 (file)
@@ -13,7 +13,6 @@ import { RoleService } from '~/app/shared/api/role.service';
 import { SettingsService } from '~/app/shared/api/settings.service';
 import { UserService } from '~/app/shared/api/user.service';
 import { ComponentsModule } from '~/app/shared/components/components.module';
-import { LoadingPanelComponent } from '~/app/shared/components/loading-panel/loading-panel.component';
 import { CdFormGroup } from '~/app/shared/forms/cd-form-group';
 import { AuthStorageService } from '~/app/shared/services/auth-storage.service';
 import { NotificationService } from '~/app/shared/services/notification.service';
@@ -44,21 +43,18 @@ describe('UserFormComponent', () => {
     { path: 'users', component: FakeComponent }
   ];
 
-  configureTestBed(
-    {
-      imports: [
-        RouterTestingModule.withRoutes(routes),
-        HttpClientTestingModule,
-        ReactiveFormsModule,
-        ComponentsModule,
-        ToastrModule.forRoot(),
-        SharedModule,
-        NgbPopoverModule
-      ],
-      declarations: [UserFormComponent, FakeComponent]
-    },
-    [LoadingPanelComponent]
-  );
+  configureTestBed({
+    imports: [
+      RouterTestingModule.withRoutes(routes),
+      HttpClientTestingModule,
+      ReactiveFormsModule,
+      ComponentsModule,
+      ToastrModule.forRoot(),
+      SharedModule,
+      NgbPopoverModule
+    ],
+    declarations: [UserFormComponent, FakeComponent]
+  });
 
   beforeEach(() => {
     spyOn(TestBed.inject(PasswordPolicyService), 'getHelpText').and.callFake(() => of(''));
index 4c966f42bfbf45d77651fb5a136a7b639898adf6..47657aedf6177730e54beb10b7778cfc2a8e7a96 100644 (file)
@@ -13,7 +13,7 @@ import {
   NgbTooltipModule
 } from '@ng-bootstrap/ng-bootstrap';
 import { ClickOutsideModule } from 'ng-click-outside';
-import { NgChartsModule } from 'ng2-charts';
+import { provideCharts, withDefaultRegisterables, BaseChartDirective } from 'ng2-charts';
 import { SimplebarAngularModule } from 'simplebar-angular';
 import {
   UIShellModule,
@@ -93,7 +93,6 @@ import InfoIcon from '@carbon/icons/es/information/16';
     NgbPopoverModule,
     NgbProgressbarModule,
     NgbTooltipModule,
-    NgChartsModule,
     ReactiveFormsModule,
     PipesModule,
     DirectivesModule,
@@ -121,7 +120,8 @@ import InfoIcon from '@carbon/icons/es/information/16';
     DropdownModule,
     SelectModule,
     ComboBoxModule,
-    ProgressIndicatorModule
+    ProgressIndicatorModule,
+    BaseChartDirective
   ],
   declarations: [
     SparklineComponent,
@@ -165,7 +165,7 @@ import InfoIcon from '@carbon/icons/es/information/16';
     UpgradableComponent,
     ProgressComponent
   ],
-  providers: [],
+  providers: [provideCharts(withDefaultRegisterables())],
   exports: [
     SparklineComponent,
     HelperComponent,
index 97080a62ae4848d3bb652976db7988d3347957b8..2e0706786e38f446c7ce377c80401155ac933f32 100644 (file)
@@ -1,4 +1,4 @@
-import { Component, NgModule, NO_ERRORS_SCHEMA, TemplateRef, ViewChild } from '@angular/core';
+import { Component, NgModule, TemplateRef, ViewChild } from '@angular/core';
 import { ComponentFixture, fakeAsync, TestBed, tick } from '@angular/core/testing';
 import { NgForm, ReactiveFormsModule } from '@angular/forms';
 
@@ -9,7 +9,7 @@ import { configureTestBed, modalServiceShow } from '~/testing/unit-test-helper';
 import { AlertPanelComponent } from '../alert-panel/alert-panel.component';
 import { LoadingPanelComponent } from '../loading-panel/loading-panel.component';
 import { CriticalConfirmationModalComponent } from './critical-confirmation-modal.component';
-import { ModalService, PlaceholderService } from 'carbon-components-angular';
+import { CheckboxModule, ModalService, PlaceholderService } from 'carbon-components-angular';
 import { ModalCdsService } from '../../services/modal-cds.service';
 
 @NgModule({})
@@ -88,26 +88,22 @@ describe('CriticalConfirmationModalComponent', () => {
   let component: CriticalConfirmationModalComponent;
   let mockFixture: ComponentFixture<MockComponent>;
 
-  configureTestBed(
-    {
-      declarations: [
-        MockComponent,
-        CriticalConfirmationModalComponent,
-        LoadingPanelComponent,
-        AlertPanelComponent
-      ],
-      schemas: [NO_ERRORS_SCHEMA],
-      imports: [ReactiveFormsModule, MockModule, DirectivesModule],
-      providers: [
-        ModalService,
-        PlaceholderService,
-        { provide: 'itemNames', useValue: [] },
-        { provide: 'itemDescription', useValue: 'entry' },
-        { provide: 'actionDescription', useValue: 'delete' }
-      ]
-    },
-    [CriticalConfirmationModalComponent]
-  );
+  configureTestBed({
+    declarations: [
+      MockComponent,
+      CriticalConfirmationModalComponent,
+      LoadingPanelComponent,
+      AlertPanelComponent
+    ],
+    imports: [ReactiveFormsModule, MockModule, DirectivesModule, CheckboxModule],
+    providers: [
+      ModalService,
+      PlaceholderService,
+      { provide: 'itemNames', useValue: [] },
+      { provide: 'itemDescription', useValue: 'entry' },
+      { provide: 'actionDescription', useValue: 'delete' }
+    ]
+  });
 
   beforeEach(() => {
     mockFixture = TestBed.createComponent(MockComponent);
index de2c48c48d74c913309ef556ec563e72e423c101..901105aff3fe0513dacf5f7dc309fa3c1d83d611 100644 (file)
@@ -1,6 +1,7 @@
 import { ComponentFixture, TestBed } from '@angular/core/testing';
 
 import { FormAdvancedFieldsetComponent } from './form-advanced-fieldset.component';
+import { CUSTOM_ELEMENTS_SCHEMA, NO_ERRORS_SCHEMA } from '@angular/core';
 
 describe('FormAdvancedFieldsetComponent', () => {
   let component: FormAdvancedFieldsetComponent;
@@ -8,7 +9,8 @@ describe('FormAdvancedFieldsetComponent', () => {
 
   beforeEach(async () => {
     await TestBed.configureTestingModule({
-      declarations: [FormAdvancedFieldsetComponent]
+      declarations: [FormAdvancedFieldsetComponent],
+      schemas: [NO_ERRORS_SCHEMA, CUSTOM_ELEMENTS_SCHEMA]
     }).compileComponents();
 
     fixture = TestBed.createComponent(FormAdvancedFieldsetComponent);
index 163890abf6bb0fb86972757f8f0ff6d88c7b406f..a32b687470752165dc0673d14fe60dd2f17630e6 100755 (executable)
       <ng-container *ngFor="let field of fields">
         <div class="form-item">
           <cds-text-label *ngIf="field.type === 'text'"
-                          [for]="field.name"
+                          [labelInputID]="field.name"
                           [invalid]="getError(field)"
                           [invalidText]="getError(field)"
-                          [label]="field.label"
                           [cdRequiredField]="field?.required === true ? field.label : ''"
                           i18n>
             {{ field.label }}
                    autofocus>
           </cds-text-label>
           <cds-number *ngIf="field.type === 'number'"
-                      [for]="field.name"
                       [invalid]="getError(field)"
                       [invalidText]="getError(field)"
                       [label]="field.label"
                       [cdRequiredField]="field?.required === true ? field.label : ''"
                       [formControlName]="field.name"
                       [id]="field.name"
-                      [name]="field.name"
                       i18n></cds-number>
           <cds-text-label *ngIf="field.type === 'binary'"
-                          [for]="field.name"
-                          [label]="field.label"
+                          [labelInputID]="field.name"
                           [invalid]="getError(field)"
                           [invalidText]="getError(field)"
                           [cdRequiredField]="field?.required === true ? field.label : ''"
index 93f4598690102e67c827120d783e1d0757acd761..5977aa2a4189c85c9336036416061a19183db71a 100644 (file)
@@ -1,6 +1,7 @@
 import { ComponentFixture, TestBed } from '@angular/core/testing';
 
 import { ProgressComponent } from './progress.component';
+import { CUSTOM_ELEMENTS_SCHEMA, NO_ERRORS_SCHEMA } from '@angular/core';
 
 describe('ProgressComponent', () => {
   let component: ProgressComponent;
@@ -8,7 +9,8 @@ describe('ProgressComponent', () => {
 
   beforeEach(async () => {
     await TestBed.configureTestingModule({
-      declarations: [ProgressComponent]
+      declarations: [ProgressComponent],
+      schemas: [NO_ERRORS_SCHEMA, CUSTOM_ELEMENTS_SCHEMA]
     }).compileComponents();
 
     fixture = TestBed.createComponent(ProgressComponent);
index d478df25d8a91c561da898a29b341b9c60957770..39e006f3b29bdeb818499a979122d684ee2e2b53 100644 (file)
@@ -1,4 +1,4 @@
-import { Component } from '@angular/core';
+import { Component, OnDestroy, OnInit } from '@angular/core';
 import { NgbModalRef } from '@ng-bootstrap/ng-bootstrap';
 import { Observable, Subscription } from 'rxjs';
 import { UpgradeService } from '../../api/upgrade.service';
@@ -13,7 +13,7 @@ import { ExecutingTask } from '../../models/executing-task';
   templateUrl: './upgradable.component.html',
   styleUrls: ['./upgradable.component.scss']
 })
-export class UpgradableComponent {
+export class UpgradableComponent implements OnInit, OnDestroy {
   orchAvailable: boolean = false;
   upgradeInfo$: Observable<UpgradeInfoInterface>;
   upgradeStatus$: Observable<UpgradeStatusInterface>;
index e6b176710b4214a376901c7d8c54e1cea7dc6375..62caa51315ce14364ba325cf241c3d51c33c47e8 100644 (file)
@@ -26,7 +26,7 @@ describe('TableKeyValueComponent', () => {
       RouterTestingModule,
       NgbDropdownModule,
       PipesModule,
-      NgbTooltipModule,
+      NgbTooltipModule
     ]
   });
 
index dc0e174c1e8870c6d6f65aa51bab8d9caab13643..2713bbfec69f64d7d3951a09c1fd848a37bc32a4 100644 (file)
@@ -5,8 +5,6 @@ import { By } from '@angular/platform-browser';
 import { NgbAlertModule } from '@ng-bootstrap/ng-bootstrap';
 
 import { configureTestBed } from '~/testing/unit-test-helper';
-import { AlertPanelComponent } from '../components/alert-panel/alert-panel.component';
-import { LoadingPanelComponent } from '../components/loading-panel/loading-panel.component';
 import { CdForm } from '../forms/cd-form';
 import { SharedModule } from '../shared.module';
 import { FormLoadingDirective } from './form-loading.directive';
@@ -28,13 +26,10 @@ describe('FormLoadingDirective', () => {
     expect(fixture.debugElement.queryAll(By.css('cd-loading-panel')).length).toEqual(loading);
   };
 
-  configureTestBed(
-    {
-      declarations: [TestComponent],
-      imports: [SharedModule, NgbAlertModule]
-    },
-    [LoadingPanelComponent, AlertPanelComponent]
-  );
+  configureTestBed({
+    declarations: [TestComponent],
+    imports: [SharedModule, NgbAlertModule]
+  });
 
   afterEach(() => {
     fixture = null;
index a14a983f54bd2ee635f09b39e35e0745795a2be9..7257bf9240a5e160d957012097b0e793f3084fcb 100644 (file)
@@ -5,7 +5,7 @@ import { AfterViewInit, Directive, ElementRef, Input, Renderer2 } from '@angular
 })
 export class RequiredFieldDirective implements AfterViewInit {
   @Input('cdRequiredField') label: string;
-  @Input('skeleton') skeleton: boolean;
+  @Input() skeleton: boolean;
   constructor(private elementRef: ElementRef, private renderer: Renderer2) {}
 
   ngAfterViewInit() {
index 295bb008c36174df82e3a6ae3723c6c9a35ec98d..0a703d7f0e4defaaddff73efa7c94a23417b2a96 100644 (file)
@@ -1,15 +1,29 @@
-import { NgbConfig, NgbNav, NgbNavChangeEvent, NgbNavConfig } from '@ng-bootstrap/ng-bootstrap';
+import { NgbNav, NgbNavChangeEvent } from '@ng-bootstrap/ng-bootstrap';
+import { CUSTOM_ELEMENTS_SCHEMA, NO_ERRORS_SCHEMA } from '@angular/core';
 
 import { StatefulTabDirective } from './stateful-tab.directive';
+import { TestBed } from '@angular/core/testing';
+
+class NgbNavMock {
+  select() {}
+}
 
 describe('StatefulTabDirective', () => {
+  beforeEach(async () => {
+    await TestBed.configureTestingModule({
+      declarations: [StatefulTabDirective],
+      providers: [{ provide: NgbNav, useClass: NgbNavMock }],
+      schemas: [NO_ERRORS_SCHEMA, CUSTOM_ELEMENTS_SCHEMA]
+    }).compileComponents();
+  });
+
   it('should create an instance', () => {
     const directive = new StatefulTabDirective(null);
     expect(directive).toBeTruthy();
   });
 
   it('should get and select active tab', () => {
-    const nav = new NgbNav('tablist', new NgbNavConfig(new NgbConfig()), <any>null, null);
+    const nav = TestBed.inject(NgbNav);
     spyOn(nav, 'select');
     const directive = new StatefulTabDirective(nav);
     directive.cdStatefulTab = 'bar';
@@ -27,7 +41,7 @@ describe('StatefulTabDirective', () => {
   });
 
   it('should select the default tab if provided', () => {
-    const nav = new NgbNav('tablist', new NgbNavConfig(new NgbConfig()), <any>null, null);
+    const nav = TestBed.inject(NgbNav);
     spyOn(nav, 'select');
     const directive = new StatefulTabDirective(nav);
     directive.cdStatefulTab = 'bar';
index 14361cceeb74574555c02987478bc40d029cc5d7..586b477f6465a0b7c477e60b8e7a6f06ebd82969 100644 (file)
@@ -26,7 +26,7 @@ import { ActivatedRouteSnapshot, UrlSegment } from '@angular/router';
 
 import { Observable, of } from 'rxjs';
 
-export class BreadcrumbsResolver  {
+export class BreadcrumbsResolver {
   public resolve(
     route: ActivatedRouteSnapshot
   ): Observable<IBreadcrumb[]> | Promise<IBreadcrumb[]> | IBreadcrumb[] {
index 22e17acf63dcedbc9585e26d0b4c5cb71f9eec63..d08132f4120c53cde330efd149514d279e68c4d4 100755 (executable)
@@ -125,7 +125,8 @@ import { PipeFunctionPipe } from './pipe-function.pipe';
     PathPipe,
     PluralizePipe,
     XmlPipe,
-    MbpersecondPipe
+    MbpersecondPipe,
+    PipeFunctionPipe
   ],
   providers: [
     ArrayPipe,
index 2a663c49a46b0a89d0ac737654fb5a9289a19dae..84a630346d47672c73111b9ae2999b187ed24a2f 100644 (file)
@@ -1,4 +1,10 @@
-import { HttpErrorResponse, HttpEvent, HttpHandler, HttpInterceptor, HttpRequest } from '@angular/common/http';
+import {
+  HttpErrorResponse,
+  HttpEvent,
+  HttpHandler,
+  HttpInterceptor,
+  HttpRequest
+} from '@angular/common/http';
 import { Injectable } from '@angular/core';
 import { Router } from '@angular/router';
 
index b75b730a1b842f51e05faf15c12e48c725dc5bf2..1f1c3adf2bb2178aedf231041158bddea076faae 100644 (file)
@@ -6,7 +6,7 @@ import { AuthStorageService } from './auth-storage.service';
 @Injectable({
   providedIn: 'root'
 })
-export class AuthGuardService  {
+export class AuthGuardService {
   constructor(private router: Router, private authStorageService: AuthStorageService) {}
 
   canActivate(_route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
index 92de27690b6f0d761a9e573a3d7ecd29c41c2949..8e577687c14b9d6d3c05208f3bba33e65c9ea14f 100644 (file)
@@ -10,7 +10,7 @@ import { AuthStorageService } from './auth-storage.service';
 @Injectable({
   providedIn: 'root'
 })
-export class ChangePasswordGuardService  {
+export class ChangePasswordGuardService {
   constructor(private router: Router, private authStorageService: AuthStorageService) {}
 
   canActivate(_route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
index 791826aeff649fb49d3a2b4e4d307583ccebca77..ab4416bb3fd4a19836c0275f20d51bc11d5d815d 100644 (file)
@@ -9,7 +9,7 @@ import { FeatureTogglesMap, FeatureTogglesService } from './feature-toggles.serv
 @Injectable({
   providedIn: 'root'
 })
-export class FeatureTogglesGuardService  {
+export class FeatureTogglesGuardService {
   constructor(private featureToggles: FeatureTogglesService) {}
 
   canActivate(route: ActivatedRouteSnapshot) {
index 4e5ed061d830c04f0db3ca8797733d8b3d2ae224..d1f14160da13e75f9528a77394ee5ba3b4869583 100644 (file)
@@ -19,7 +19,7 @@ describe('ModalService', () => {
   let service: ModalService;
   let ngbModal: NgbModal;
 
-  configureTestBed({ declarations: [MockComponent], imports: [NgbModalModule] }, [MockComponent]);
+  configureTestBed({ declarations: [MockComponent], imports: [NgbModalModule] });
 
   beforeEach(() => {
     service = TestBed.inject(ModalService);
index d511b12a1cb875b938decb2f728342e9343fdf68..e828a5ba523cfc3fa36bba1b05bfcb9299ed9a92 100644 (file)
@@ -36,7 +36,7 @@ import { Icons } from '~/app/shared/enum/icons.enum';
 @Injectable({
   providedIn: 'root'
 })
-export class ModuleStatusGuardService  {
+export class ModuleStatusGuardService {
   // TODO: Hotfix - remove ALLOWLIST'ing when a generic ErrorComponent is implemented
   static readonly ALLOWLIST: string[] = ['501'];
 
index 4907b971fb3a8e8e85ebd3fbe8b9ca53dfa45480..d5f2f642fbae486eac7ffd8ca6fd221a1879082e 100644 (file)
@@ -1,6 +1,5 @@
 import { Injectable } from '@angular/core';
 
-
 import { DashboardUserDeniedError } from '~/app/core/error/error';
 import { AuthStorageService } from './auth-storage.service';
 
@@ -11,7 +10,7 @@ import { AuthStorageService } from './auth-storage.service';
 @Injectable({
   providedIn: 'root'
 })
-export class NoSsoGuardService  {
+export class NoSsoGuardService {
   constructor(private authStorageService: AuthStorageService) {}
 
   canActivate() {
index 21e94e9996cc6d2fe82464f3f70437b7f0e07508..fabbe22c7b9457a0a322ec4fa6356e23642dc3c1 100644 (file)
@@ -1,8 +1,7 @@
-import { DebugElement, Type } from '@angular/core';
+import { CUSTOM_ELEMENTS_SCHEMA, DebugElement, NO_ERRORS_SCHEMA, Type } from '@angular/core';
 import { ComponentFixture, TestBed } from '@angular/core/testing';
 import { AbstractControl } from '@angular/forms';
 import { By } from '@angular/platform-browser';
-import { BrowserDynamicTestingModule } from '@angular/platform-browser-dynamic/testing';
 
 import { NgbModal, NgbNav, NgbNavItem, NgbNavLink } from '@ng-bootstrap/ng-bootstrap';
 import _ from 'lodash';
@@ -29,20 +28,12 @@ import {
   PrometheusRule
 } from '~/app/shared/models/prometheus-alerts';
 
-export function configureTestBed(configuration: any, entryComponents?: any) {
-  beforeEach(async () => {
-    if (entryComponents) {
-      // Declare entryComponents without having to add them to a module
-      // This is needed since Jest doesn't yet support not declaring entryComponents
-      await TestBed.configureTestingModule(configuration).overrideModule(
-        BrowserDynamicTestingModule,
-        {
-          set: { entryComponents: entryComponents }
-        }
-      );
-    } else {
-      await TestBed.configureTestingModule(configuration);
-    }
+export function configureTestBed(configuration: any) {
+  beforeEach(() => {
+    TestBed.configureTestingModule({
+      ...configuration,
+      schemas: [NO_ERRORS_SCHEMA, CUSTOM_ELEMENTS_SCHEMA]
+    });
   });
 }
 
index 9433b2e67f6af31788b33c5d7c32d441c45f97f5..eda40c3953ac4dead71831cd4a16347ebc70e3f1 100644 (file)
@@ -18,8 +18,8 @@
     "noImplicitAny": true,
     "ignoreDeprecations": "5.0",
     "suppressImplicitAnyIndexErrors": true,
-    "target": "ES2020",
-    "module": "es2020",
+    "target": "ES2022",
+    "module": "ES2022",
     "baseUrl": "./",
     "resolveJsonModule": true,
     "paths": {