]> git.apps.os.sepia.ceph.com Git - ceph.git/commitdiff
mgr/dashboard: update to cypress 10
authorNizamudeen A <nia@redhat.com>
Wed, 8 Feb 2023 15:49:31 +0000 (21:19 +0530)
committerNizamudeen A <nia@redhat.com>
Wed, 24 May 2023 14:52:22 +0000 (20:22 +0530)
Fixes: https://tracker.ceph.com/issues/61354
Signed-off-by: Nizamudeen A <nia@redhat.com>
165 files changed:
src/pybind/mgr/dashboard/ci/cephadm/run-cephadm-e2e-tests.sh
src/pybind/mgr/dashboard/frontend/cypress.config.ts [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/cypress.json [deleted file]
src/pybind/mgr/dashboard/frontend/cypress/e2e/a11y/dashboard.e2e-spec.ts [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/cypress/e2e/a11y/navigation.e2e-spec.ts [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/cypress/e2e/block/images.e2e-spec.ts [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/cypress/e2e/block/images.po.ts [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/cypress/e2e/block/iscsi.e2e-spec.ts [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/cypress/e2e/block/iscsi.po.ts [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/cypress/e2e/block/mirroring.e2e-spec.ts [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/cypress/e2e/block/mirroring.po.ts [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/cypress/e2e/cluster/configuration.e2e-spec.ts [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/cypress/e2e/cluster/configuration.po.ts [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/cypress/e2e/cluster/create-cluster.po.ts [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/cypress/e2e/cluster/crush-map.e2e-spec.ts [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/cypress/e2e/cluster/crush-map.po.ts [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/cypress/e2e/cluster/hosts.e2e-spec.ts [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/cypress/e2e/cluster/hosts.po.ts [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/cypress/e2e/cluster/inventory.po.ts [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/cypress/e2e/cluster/logs.e2e-spec.ts [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/cypress/e2e/cluster/logs.po.ts [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/cypress/e2e/cluster/mgr-modules.e2e-spec.ts [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/cypress/e2e/cluster/mgr-modules.po.ts [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/cypress/e2e/cluster/monitors.e2e-spec.ts [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/cypress/e2e/cluster/monitors.po.ts [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/cypress/e2e/cluster/osds.e2e-spec.ts [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/cypress/e2e/cluster/osds.po.ts [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/cypress/e2e/cluster/services.po.ts [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/cypress/e2e/cluster/users.e2e-spec.ts [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/cypress/e2e/cluster/users.po.ts [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/cypress/e2e/common/01-global.feature.po.ts [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/cypress/e2e/common/create-cluster/create-cluster.feature.po.ts [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/cypress/e2e/common/grafana.feature.po.ts [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/cypress/e2e/common/urls.po.ts [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/cypress/e2e/filesystems/filesystems.e2e-spec.ts [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/cypress/e2e/filesystems/filesystems.po.ts [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/cypress/e2e/orchestrator/01-hosts.e2e-spec.ts [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/cypress/e2e/orchestrator/03-inventory.e2e-spec.ts [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/cypress/e2e/orchestrator/04-osds.e2e-spec.ts [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/cypress/e2e/orchestrator/05-services.e2e-spec.ts [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/cypress/e2e/orchestrator/grafana/grafana.feature [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/cypress/e2e/orchestrator/workflow/01-create-cluster-welcome.feature [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/cypress/e2e/orchestrator/workflow/02-create-cluster-add-host.feature [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/cypress/e2e/orchestrator/workflow/03-create-cluster-create-services.e2e-spec.ts [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/cypress/e2e/orchestrator/workflow/04-create-cluster-create-osds.e2e-spec.ts [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/cypress/e2e/orchestrator/workflow/05-create-cluster-review.e2e-spec.ts [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/cypress/e2e/orchestrator/workflow/06-cluster-check.e2e-spec.ts [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/cypress/e2e/orchestrator/workflow/07-osds.e2e-spec.ts [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/cypress/e2e/orchestrator/workflow/08-hosts.e2e-spec.ts [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/cypress/e2e/orchestrator/workflow/09-services.e2e-spec.ts [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/cypress/e2e/orchestrator/workflow/10-nfs-exports.e2e-spec.ts [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/cypress/e2e/orchestrator/workflow/nfs/nfs-export.po.ts [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/cypress/e2e/page-helper.po.ts [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/cypress/e2e/pools/pools.e2e-spec.ts [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/cypress/e2e/pools/pools.po.ts [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/cypress/e2e/rgw/buckets.e2e-spec.ts [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/cypress/e2e/rgw/buckets.po.ts [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/cypress/e2e/rgw/daemons.e2e-spec.ts [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/cypress/e2e/rgw/daemons.po.ts [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/cypress/e2e/rgw/users.e2e-spec.ts [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/cypress/e2e/rgw/users.po.ts [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/cypress/e2e/ui/api-docs.e2e-spec.ts [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/cypress/e2e/ui/api-docs.po.ts [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/cypress/e2e/ui/dashboard-v3.e2e-spec.ts [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/cypress/e2e/ui/dashboard-v3.po.ts [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/cypress/e2e/ui/dashboard.e2e-spec.ts [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/cypress/e2e/ui/dashboard.po.ts [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/cypress/e2e/ui/language.e2e-spec.ts [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/cypress/e2e/ui/language.po.ts [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/cypress/e2e/ui/login.e2e-spec.ts [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/cypress/e2e/ui/login.po.ts [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/cypress/e2e/ui/navigation.e2e-spec.ts [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/cypress/e2e/ui/navigation.po.ts [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/cypress/e2e/ui/notification.e2e-spec.ts [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/cypress/e2e/ui/notification.po.ts [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/cypress/e2e/ui/role-mgmt.e2e-spec.ts [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/cypress/e2e/ui/role-mgmt.po.ts [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/cypress/e2e/ui/user-mgmt.e2e-spec.ts [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/cypress/e2e/ui/user-mgmt.po.ts [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/cypress/e2e/visualTests/dashboard.vrt-spec.ts [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/cypress/e2e/visualTests/login.vrt-spec.ts [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/cypress/integration/a11y/dashboard.e2e-spec.ts [deleted file]
src/pybind/mgr/dashboard/frontend/cypress/integration/a11y/navigation.e2e-spec.ts [deleted file]
src/pybind/mgr/dashboard/frontend/cypress/integration/block/images.e2e-spec.ts [deleted file]
src/pybind/mgr/dashboard/frontend/cypress/integration/block/images.po.ts [deleted file]
src/pybind/mgr/dashboard/frontend/cypress/integration/block/iscsi.e2e-spec.ts [deleted file]
src/pybind/mgr/dashboard/frontend/cypress/integration/block/iscsi.po.ts [deleted file]
src/pybind/mgr/dashboard/frontend/cypress/integration/block/mirroring.e2e-spec.ts [deleted file]
src/pybind/mgr/dashboard/frontend/cypress/integration/block/mirroring.po.ts [deleted file]
src/pybind/mgr/dashboard/frontend/cypress/integration/cluster/configuration.e2e-spec.ts [deleted file]
src/pybind/mgr/dashboard/frontend/cypress/integration/cluster/configuration.po.ts [deleted file]
src/pybind/mgr/dashboard/frontend/cypress/integration/cluster/create-cluster.po.ts [deleted file]
src/pybind/mgr/dashboard/frontend/cypress/integration/cluster/crush-map.e2e-spec.ts [deleted file]
src/pybind/mgr/dashboard/frontend/cypress/integration/cluster/crush-map.po.ts [deleted file]
src/pybind/mgr/dashboard/frontend/cypress/integration/cluster/hosts.e2e-spec.ts [deleted file]
src/pybind/mgr/dashboard/frontend/cypress/integration/cluster/hosts.po.ts [deleted file]
src/pybind/mgr/dashboard/frontend/cypress/integration/cluster/inventory.po.ts [deleted file]
src/pybind/mgr/dashboard/frontend/cypress/integration/cluster/logs.e2e-spec.ts [deleted file]
src/pybind/mgr/dashboard/frontend/cypress/integration/cluster/logs.po.ts [deleted file]
src/pybind/mgr/dashboard/frontend/cypress/integration/cluster/mgr-modules.e2e-spec.ts [deleted file]
src/pybind/mgr/dashboard/frontend/cypress/integration/cluster/mgr-modules.po.ts [deleted file]
src/pybind/mgr/dashboard/frontend/cypress/integration/cluster/monitors.e2e-spec.ts [deleted file]
src/pybind/mgr/dashboard/frontend/cypress/integration/cluster/monitors.po.ts [deleted file]
src/pybind/mgr/dashboard/frontend/cypress/integration/cluster/osds.e2e-spec.ts [deleted file]
src/pybind/mgr/dashboard/frontend/cypress/integration/cluster/osds.po.ts [deleted file]
src/pybind/mgr/dashboard/frontend/cypress/integration/cluster/services.po.ts [deleted file]
src/pybind/mgr/dashboard/frontend/cypress/integration/cluster/users.e2e-spec.ts [deleted file]
src/pybind/mgr/dashboard/frontend/cypress/integration/cluster/users.po.ts [deleted file]
src/pybind/mgr/dashboard/frontend/cypress/integration/common/01-global.feature.po.ts [deleted file]
src/pybind/mgr/dashboard/frontend/cypress/integration/common/create-cluster/create-cluster.feature.po.ts [deleted file]
src/pybind/mgr/dashboard/frontend/cypress/integration/common/grafana.feature.po.ts [deleted file]
src/pybind/mgr/dashboard/frontend/cypress/integration/common/urls.po.ts [deleted file]
src/pybind/mgr/dashboard/frontend/cypress/integration/filesystems/filesystems.e2e-spec.ts [deleted file]
src/pybind/mgr/dashboard/frontend/cypress/integration/filesystems/filesystems.po.ts [deleted file]
src/pybind/mgr/dashboard/frontend/cypress/integration/orchestrator/01-hosts.e2e-spec.ts [deleted file]
src/pybind/mgr/dashboard/frontend/cypress/integration/orchestrator/03-inventory.e2e-spec.ts [deleted file]
src/pybind/mgr/dashboard/frontend/cypress/integration/orchestrator/04-osds.e2e-spec.ts [deleted file]
src/pybind/mgr/dashboard/frontend/cypress/integration/orchestrator/05-services.e2e-spec.ts [deleted file]
src/pybind/mgr/dashboard/frontend/cypress/integration/orchestrator/grafana/grafana.feature [deleted file]
src/pybind/mgr/dashboard/frontend/cypress/integration/orchestrator/workflow/01-create-cluster-welcome.feature [deleted file]
src/pybind/mgr/dashboard/frontend/cypress/integration/orchestrator/workflow/02-create-cluster-add-host.feature [deleted file]
src/pybind/mgr/dashboard/frontend/cypress/integration/orchestrator/workflow/03-create-cluster-create-services.e2e-spec.ts [deleted file]
src/pybind/mgr/dashboard/frontend/cypress/integration/orchestrator/workflow/04-create-cluster-create-osds.e2e-spec.ts [deleted file]
src/pybind/mgr/dashboard/frontend/cypress/integration/orchestrator/workflow/05-create-cluster-review.e2e-spec.ts [deleted file]
src/pybind/mgr/dashboard/frontend/cypress/integration/orchestrator/workflow/06-cluster-check.e2e-spec.ts [deleted file]
src/pybind/mgr/dashboard/frontend/cypress/integration/orchestrator/workflow/07-osds.e2e-spec.ts [deleted file]
src/pybind/mgr/dashboard/frontend/cypress/integration/orchestrator/workflow/08-hosts.e2e-spec.ts [deleted file]
src/pybind/mgr/dashboard/frontend/cypress/integration/orchestrator/workflow/09-services.e2e-spec.ts [deleted file]
src/pybind/mgr/dashboard/frontend/cypress/integration/orchestrator/workflow/10-nfs-exports.e2e-spec.ts [deleted file]
src/pybind/mgr/dashboard/frontend/cypress/integration/orchestrator/workflow/nfs/nfs-export.po.ts [deleted file]
src/pybind/mgr/dashboard/frontend/cypress/integration/page-helper.po.ts [deleted file]
src/pybind/mgr/dashboard/frontend/cypress/integration/pools/pools.e2e-spec.ts [deleted file]
src/pybind/mgr/dashboard/frontend/cypress/integration/pools/pools.po.ts [deleted file]
src/pybind/mgr/dashboard/frontend/cypress/integration/rgw/buckets.e2e-spec.ts [deleted file]
src/pybind/mgr/dashboard/frontend/cypress/integration/rgw/buckets.po.ts [deleted file]
src/pybind/mgr/dashboard/frontend/cypress/integration/rgw/daemons.e2e-spec.ts [deleted file]
src/pybind/mgr/dashboard/frontend/cypress/integration/rgw/daemons.po.ts [deleted file]
src/pybind/mgr/dashboard/frontend/cypress/integration/rgw/users.e2e-spec.ts [deleted file]
src/pybind/mgr/dashboard/frontend/cypress/integration/rgw/users.po.ts [deleted file]
src/pybind/mgr/dashboard/frontend/cypress/integration/ui/api-docs.e2e-spec.ts [deleted file]
src/pybind/mgr/dashboard/frontend/cypress/integration/ui/api-docs.po.ts [deleted file]
src/pybind/mgr/dashboard/frontend/cypress/integration/ui/dashboard-v3.e2e-spec.ts [deleted file]
src/pybind/mgr/dashboard/frontend/cypress/integration/ui/dashboard-v3.po.ts [deleted file]
src/pybind/mgr/dashboard/frontend/cypress/integration/ui/dashboard.e2e-spec.ts [deleted file]
src/pybind/mgr/dashboard/frontend/cypress/integration/ui/dashboard.po.ts [deleted file]
src/pybind/mgr/dashboard/frontend/cypress/integration/ui/language.e2e-spec.ts [deleted file]
src/pybind/mgr/dashboard/frontend/cypress/integration/ui/language.po.ts [deleted file]
src/pybind/mgr/dashboard/frontend/cypress/integration/ui/login.e2e-spec.ts [deleted file]
src/pybind/mgr/dashboard/frontend/cypress/integration/ui/login.po.ts [deleted file]
src/pybind/mgr/dashboard/frontend/cypress/integration/ui/navigation.e2e-spec.ts [deleted file]
src/pybind/mgr/dashboard/frontend/cypress/integration/ui/navigation.po.ts [deleted file]
src/pybind/mgr/dashboard/frontend/cypress/integration/ui/notification.e2e-spec.ts [deleted file]
src/pybind/mgr/dashboard/frontend/cypress/integration/ui/notification.po.ts [deleted file]
src/pybind/mgr/dashboard/frontend/cypress/integration/ui/role-mgmt.e2e-spec.ts [deleted file]
src/pybind/mgr/dashboard/frontend/cypress/integration/ui/role-mgmt.po.ts [deleted file]
src/pybind/mgr/dashboard/frontend/cypress/integration/ui/user-mgmt.e2e-spec.ts [deleted file]
src/pybind/mgr/dashboard/frontend/cypress/integration/ui/user-mgmt.po.ts [deleted file]
src/pybind/mgr/dashboard/frontend/cypress/integration/visualTests/dashboard.vrt-spec.ts [deleted file]
src/pybind/mgr/dashboard/frontend/cypress/integration/visualTests/login.vrt-spec.ts [deleted file]
src/pybind/mgr/dashboard/frontend/cypress/support/commands.ts
src/pybind/mgr/dashboard/frontend/cypress/support/e2e.ts [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/cypress/support/index.ts [deleted file]
src/pybind/mgr/dashboard/frontend/package-lock.json
src/pybind/mgr/dashboard/frontend/package.json
src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/crushmap/crushmap.component.html

index 45bb3f0257ebc5f87f2ca3f47666fbd20548dc1f..e416de1b1337d84810fcc0e8d3a07da74bcc8df7 100755 (executable)
@@ -24,7 +24,7 @@ export CYPRESS_BASE_URL CYPRESS_LOGIN_USER CYPRESS_LOGIN_PWD
 cypress_run () {
     local specs="$1"
     local timeout="$2"
-    local override_config="ignoreTestFiles=*.po.ts,retries=0,testFiles=${specs},chromeWebSecurity=false"
+    local override_config="excludeSpecPattern=*.po.ts,retries=0,specPattern=${specs},chromeWebSecurity=false"
     if [[ -n "$timeout" ]]; then
         override_config="${override_config},defaultCommandTimeout=${timeout}"
     fi
@@ -55,5 +55,5 @@ kcli ssh -u root ceph-node-00 'cephadm shell "ceph orch apply node-exporter --pl
 
 kcli ssh -u root ceph-node-00 'cephadm shell "ceph config set mgr mgr/prometheus/exclude_perf_counters false"'
 
-cypress_run ["orchestrator/workflow/*.feature, orchestrator/workflow/*-spec.ts"]
-cypress_run "orchestrator/grafana/*.feature"
+cypress_run ["cypress/e2e/orchestrator/workflow/*.feature, cypress/e2e/orchestrator/workflow/*-spec.ts"]
+cypress_run "cypress/e2e/orchestrator/grafana/*.feature"
diff --git a/src/pybind/mgr/dashboard/frontend/cypress.config.ts b/src/pybind/mgr/dashboard/frontend/cypress.config.ts
new file mode 100644 (file)
index 0000000..3948ea8
--- /dev/null
@@ -0,0 +1,42 @@
+import { defineConfig } from 'cypress'
+
+export default defineConfig({
+  video: true,
+  videoUploadOnPasses: false,
+  defaultCommandTimeout: 120000,
+  responseTimeout: 45000,
+  viewportHeight: 1080,
+  viewportWidth: 1920,
+  projectId: 'k7ab29',
+  reporter: 'cypress-multi-reporters',
+  reporterOptions: {
+    reporterEnabled: 'spec, mocha-junit-reporter',
+    mochaJunitReporterReporterOptions: {
+      mochaFile: 'cypress/reports/results-[hash].xml',
+    },
+  },
+  retries: 1,
+  env: {
+    LOGIN_USER: 'admin',
+    LOGIN_PWD: 'admin',
+    CEPH2_URL: 'https://localhost:4202/',
+  },
+  chromeWebSecurity: false,
+  eyesIsDisabled: false,
+  eyesFailCypressOnDiff: true,
+  eyesDisableBrowserFetching: false,
+  eyesLegacyHooks: true,
+  eyesTestConcurrency: 5,
+  eyesPort: 35321,
+  e2e: {
+    // We've imported your old cypress plugins here.
+    // You may want to clean this up later by importing these.
+    setupNodeEvents(on, config) {
+      return require('./cypress/plugins/index.js')(on, config)
+    },
+    baseUrl: 'https://localhost:4200/',
+    excludeSpecPattern: ['*.po.ts', '**/orchestrator/**'],
+    experimentalSessionAndOrigin: true,
+    specPattern: 'cypress/e2e/**/*-spec.{js,jsx,ts,tsx}',
+  },
+})
diff --git a/src/pybind/mgr/dashboard/frontend/cypress.json b/src/pybind/mgr/dashboard/frontend/cypress.json
deleted file mode 100644 (file)
index 35cf872..0000000
+++ /dev/null
@@ -1,30 +0,0 @@
-{
-  "baseUrl": "https://localhost:4200/",
-  "ignoreTestFiles": [
-    "*.po.ts",
-    "**/orchestrator/**"
-  ],
-  "supportFile": "cypress/support/index.ts",
-  "video": true,
-  "videoUploadOnPasses": false,
-  "defaultCommandTimeout": 120000,
-  "responseTimeout": 45000,
-  "viewportHeight": 1080,
-  "viewportWidth": 1920,
-  "projectId": "k7ab29",
-  "reporter": "cypress-multi-reporters",
-  "reporterOptions": {
-    "reporterEnabled": "spec, mocha-junit-reporter",
-    "mochaJunitReporterReporterOptions": {
-      "mochaFile": "cypress/reports/results-[hash].xml"
-    }
-  },
-  "retries": 1,
-  "env": {
-    "LOGIN_USER": "admin",
-    "LOGIN_PWD": "admin",
-    "CEPH2_URL": "http://localhost:4202/"
-  },
-  "experimentalSessionAndOrigin": true,
-  "chromeWebSecurity": false
-}
diff --git a/src/pybind/mgr/dashboard/frontend/cypress/e2e/a11y/dashboard.e2e-spec.ts b/src/pybind/mgr/dashboard/frontend/cypress/e2e/a11y/dashboard.e2e-spec.ts
new file mode 100644 (file)
index 0000000..4feea0d
--- /dev/null
@@ -0,0 +1,27 @@
+import { DashboardPageHelper } from '../ui/dashboard.po';
+
+describe('Dashboard Main Page', { retries: 0 }, () => {
+  const dashboard = new DashboardPageHelper();
+
+  beforeEach(() => {
+    cy.login();
+    Cypress.Cookies.preserveOnce('token');
+    dashboard.navigateTo();
+  });
+
+  describe('Dashboard accessibility', () => {
+    it('should have no accessibility violations', () => {
+      cy.injectAxe();
+      cy.checkAccessibility(
+        {
+          exclude: [['.cd-navbar-main']]
+        },
+        {
+          rules: {
+            'page-has-heading-one': { enabled: false }
+          }
+        }
+      );
+    });
+  });
+});
diff --git a/src/pybind/mgr/dashboard/frontend/cypress/e2e/a11y/navigation.e2e-spec.ts b/src/pybind/mgr/dashboard/frontend/cypress/e2e/a11y/navigation.e2e-spec.ts
new file mode 100644 (file)
index 0000000..2a0c5c5
--- /dev/null
@@ -0,0 +1,21 @@
+import { NavigationPageHelper } from '../ui/navigation.po';
+
+describe('Navigation accessibility', { retries: 0 }, () => {
+  const shared = new NavigationPageHelper();
+
+  beforeEach(() => {
+    cy.login();
+    Cypress.Cookies.preserveOnce('token');
+    shared.navigateTo();
+  });
+
+  it('top-nav should have no accessibility violations', () => {
+    cy.injectAxe();
+    cy.checkAccessibility('.cd-navbar-top');
+  });
+
+  it('sidebar should have no accessibility violations', () => {
+    cy.injectAxe();
+    cy.checkAccessibility('nav[id=sidebar]');
+  });
+});
diff --git a/src/pybind/mgr/dashboard/frontend/cypress/e2e/block/images.e2e-spec.ts b/src/pybind/mgr/dashboard/frontend/cypress/e2e/block/images.e2e-spec.ts
new file mode 100644 (file)
index 0000000..5c89359
--- /dev/null
@@ -0,0 +1,95 @@
+import { PoolPageHelper } from '../pools/pools.po';
+import { ImagesPageHelper } from './images.po';
+
+describe('Images page', () => {
+  const pools = new PoolPageHelper();
+  const images = new ImagesPageHelper();
+
+  const poolName = 'e2e_images_pool';
+
+  before(() => {
+    cy.login();
+    Cypress.Cookies.preserveOnce('token');
+    // Need pool for image testing
+    pools.navigateTo('create');
+    pools.create(poolName, 8, 'rbd');
+    pools.existTableCell(poolName);
+  });
+
+  after(() => {
+    // Deletes images test pool
+    pools.navigateTo();
+    pools.delete(poolName);
+    pools.navigateTo();
+    pools.existTableCell(poolName, false);
+  });
+
+  beforeEach(() => {
+    cy.login();
+    Cypress.Cookies.preserveOnce('token');
+    images.navigateTo();
+  });
+
+  it('should open and show breadcrumb', () => {
+    images.expectBreadcrumbText('Images');
+  });
+
+  it('should show four tabs', () => {
+    images.getTabsCount().should('eq', 4);
+  });
+
+  it('should show text for all tabs', () => {
+    images.getTabText(0).should('eq', 'Images');
+    images.getTabText(1).should('eq', 'Namespaces');
+    images.getTabText(2).should('eq', 'Trash');
+    images.getTabText(3).should('eq', 'Overall Performance');
+  });
+
+  describe('create, edit & delete image test', () => {
+    const imageName = 'e2e_images#image';
+    const newImageName = 'e2e_images#image_new';
+
+    it('should create image', () => {
+      images.createImage(imageName, poolName, '1');
+      images.getFirstTableCell(imageName).should('exist');
+    });
+
+    it('should edit image', () => {
+      images.editImage(imageName, poolName, newImageName, '2');
+      images.getFirstTableCell(newImageName).should('exist');
+    });
+
+    it('should delete image', () => {
+      images.delete(newImageName);
+    });
+  });
+
+  describe('move to trash, restore and purge image tests', () => {
+    const imageName = 'e2e_trash#image';
+    const newImageName = 'e2e_newtrash#image';
+
+    before(() => {
+      cy.login();
+      Cypress.Cookies.preserveOnce('token');
+      // Need image for trash testing
+      images.createImage(imageName, poolName, '1');
+      images.getFirstTableCell(imageName).should('exist');
+    });
+
+    it('should move the image to the trash', () => {
+      images.moveToTrash(imageName);
+      images.getFirstTableCell(imageName).should('exist');
+    });
+
+    it('should restore image to images table', () => {
+      images.restoreImage(imageName, newImageName);
+      images.getFirstTableCell(newImageName).should('exist');
+    });
+
+    it('should purge trash in images trash tab', () => {
+      images.getFirstTableCell(newImageName).should('exist');
+      images.moveToTrash(newImageName);
+      images.purgeTrash(newImageName, poolName);
+    });
+  });
+});
diff --git a/src/pybind/mgr/dashboard/frontend/cypress/e2e/block/images.po.ts b/src/pybind/mgr/dashboard/frontend/cypress/e2e/block/images.po.ts
new file mode 100644 (file)
index 0000000..bf6cbc0
--- /dev/null
@@ -0,0 +1,110 @@
+import { PageHelper } from '../page-helper.po';
+
+export class ImagesPageHelper extends PageHelper {
+  pages = {
+    index: { url: '#/block/rbd', id: 'cd-rbd-list' },
+    create: { url: '#/block/rbd/create', id: 'cd-rbd-form' }
+  };
+
+  // Creates a block image and fills in the name, pool, and size fields.
+  // Then checks if the image is present in the Images table.
+  createImage(name: string, pool: string, size: string) {
+    this.navigateTo('create');
+
+    cy.get('#name').type(name); // Enter in image name
+
+    // Select image pool
+    cy.contains('Loading...').should('not.exist');
+    this.selectOption('pool', pool);
+    cy.get('#pool').should('have.class', 'ng-valid'); // check if selected
+
+    // Enter in the size of the image
+    cy.get('#size').type(size);
+
+    // Click the create button and wait for image to be made
+    cy.get('[data-cy=submitBtn]').click();
+    this.getFirstTableCell(name).should('exist');
+  }
+
+  editImage(name: string, pool: string, newName: string, newSize: string) {
+    this.navigateEdit(name);
+
+    // Wait until data is loaded
+    cy.get('#pool').should('contain.value', pool);
+
+    cy.get('#name').clear().type(newName);
+    cy.get('#size').clear().type(newSize); // click the size box and send new size
+
+    cy.get('[data-cy=submitBtn]').click();
+
+    this.getExpandCollapseElement(newName).click();
+    cy.get('.table.table-striped.table-bordered').contains('td', newSize);
+  }
+
+  // Selects RBD image and moves it to the trash,
+  // checks that it is present in the trash table
+  moveToTrash(name: string) {
+    // wait for image to be created
+    cy.get('.datatable-body').first().should('not.contain.text', '(Creating...)');
+
+    this.getFirstTableCell(name).click();
+
+    // click on the drop down and selects the move to trash option
+    cy.get('.table-actions button.dropdown-toggle').first().click();
+    cy.get('button.move-to-trash').click();
+
+    cy.get('[data-cy=submitBtn]').should('be.visible').click();
+
+    // Clicks trash tab
+    cy.contains('.nav-link', 'Trash').click();
+    this.getFirstTableCell(name).should('exist');
+  }
+
+  // Checks trash tab table for image and then restores it to the RBD Images table
+  // (could change name if new name is given)
+  restoreImage(name: string, newName?: string) {
+    // clicks on trash tab
+    cy.contains('.nav-link', 'Trash').click();
+
+    // wait for table to load
+    this.getFirstTableCell(name).click();
+    cy.contains('button', 'Restore').click();
+
+    // wait for pop-up to be visible (checks for title of pop-up)
+    cy.get('cd-modal #name').should('be.visible');
+
+    // If a new name for the image is passed, it changes the name of the image
+    if (newName !== undefined) {
+      // click name box and send new name
+      cy.get('cd-modal #name').clear().type(newName);
+    }
+
+    cy.get('[data-cy=submitBtn]').click();
+
+    // clicks images tab
+    cy.contains('.nav-link', 'Images').click();
+
+    this.getFirstTableCell(newName).should('exist');
+  }
+
+  // Enters trash tab and purges trash, thus emptying the trash table.
+  // Checks if Image is still in the table.
+  purgeTrash(name: string, pool?: string) {
+    // clicks trash tab
+    cy.contains('.nav-link', 'Trash').click();
+    cy.contains('button', 'Purge Trash').click();
+
+    // Check for visibility of modal container
+    cy.get('.modal-header').should('be.visible');
+
+    // If purgeing a specific pool, selects that pool if given
+    if (pool !== undefined) {
+      this.selectOption('poolName', pool);
+      cy.get('#poolName').should('have.class', 'ng-valid'); // check if pool is selected
+    }
+    cy.get('[data-cy=submitBtn]').click();
+    // Wait for image to delete and check it is not present
+
+    this.getFirstTableCell(name).should('not.exist');
+  }
+}
diff --git a/src/pybind/mgr/dashboard/frontend/cypress/e2e/block/iscsi.e2e-spec.ts b/src/pybind/mgr/dashboard/frontend/cypress/e2e/block/iscsi.e2e-spec.ts
new file mode 100644 (file)
index 0000000..cef4874
--- /dev/null
@@ -0,0 +1,25 @@
+import { IscsiPageHelper } from './iscsi.po';
+
+describe('Iscsi Page', () => {
+  const iscsi = new IscsiPageHelper();
+
+  beforeEach(() => {
+    cy.login();
+    Cypress.Cookies.preserveOnce('token');
+    iscsi.navigateTo();
+  });
+
+  it('should open and show breadcrumb', () => {
+    iscsi.expectBreadcrumbText('Overview');
+  });
+
+  it('should check that tables are displayed and legends are correct', () => {
+    // Check tables are displayed
+    iscsi.getDataTables().its(0).should('be.visible');
+    iscsi.getDataTables().its(1).should('be.visible');
+
+    // Check that legends are correct
+    iscsi.getLegends().its(0).should('contain.text', 'Gateways');
+    iscsi.getLegends().its(1).should('contain.text', 'Images');
+  });
+});
diff --git a/src/pybind/mgr/dashboard/frontend/cypress/e2e/block/iscsi.po.ts b/src/pybind/mgr/dashboard/frontend/cypress/e2e/block/iscsi.po.ts
new file mode 100644 (file)
index 0000000..08efa64
--- /dev/null
@@ -0,0 +1,7 @@
+import { PageHelper } from '../page-helper.po';
+
+export class IscsiPageHelper extends PageHelper {
+  pages = {
+    index: { url: '#/block/iscsi/overview', id: 'cd-iscsi' }
+  };
+}
diff --git a/src/pybind/mgr/dashboard/frontend/cypress/e2e/block/mirroring.e2e-spec.ts b/src/pybind/mgr/dashboard/frontend/cypress/e2e/block/mirroring.e2e-spec.ts
new file mode 100644 (file)
index 0000000..2661769
--- /dev/null
@@ -0,0 +1,120 @@
+import { PoolPageHelper } from '../pools/pools.po';
+import { MirroringPageHelper } from './mirroring.po';
+
+describe('Mirroring page', () => {
+  const pools = new PoolPageHelper();
+  const mirroring = new MirroringPageHelper();
+
+  beforeEach(() => {
+    cy.login();
+    Cypress.Cookies.preserveOnce('token');
+    mirroring.navigateTo();
+  });
+
+  it('should open and show breadcrumb', () => {
+    mirroring.expectBreadcrumbText('Mirroring');
+  });
+
+  it('should show three tabs', () => {
+    mirroring.getTabsCount().should('eq', 3);
+  });
+
+  it('should show text for all tabs', () => {
+    mirroring.getTabText(0).should('eq', 'Issues (0)');
+    mirroring.getTabText(1).should('eq', 'Syncing (0)');
+    mirroring.getTabText(2).should('eq', 'Ready (0)');
+  });
+
+  describe('rbd mirroring bootstrap', () => {
+    const poolName = 'rbd-mirror';
+
+    beforeEach(() => {
+      // login to the second ceph cluster
+      cy.ceph2Login();
+      cy.login();
+      Cypress.Cookies.preserveOnce('token');
+      pools.navigateTo('create');
+      pools.create(poolName, 8, 'rbd');
+      pools.navigateTo();
+      pools.existTableCell(poolName, true);
+      mirroring.navigateTo();
+    });
+
+    it('should generate and import the bootstrap token between clusters', () => {
+      const url: string = Cypress.env('CEPH2_URL');
+      mirroring.navigateTo();
+      mirroring.generateToken(poolName);
+      cy.get('@token').then((bootstrapToken) => {
+        // pass the token to the origin as an arg
+        const args = { name: poolName, bootstrapToken: String(bootstrapToken) };
+        // can't use any imports or functions inside the origin
+        // so writing the code to copy the token inside the origin manually
+        // rather than using a function call
+        // @ts-ignore
+        cy.origin(url, { args }, ({ name, bootstrapToken }) => {
+          // Create an rbd pool in the second cluster
+
+          // Login to the second cluster
+          // Somehow its not working with the cypress login function
+          cy.visit('#/pool/create').wait(100);
+
+          cy.get('[name=username]').type('admin');
+          cy.get('#password').type('admin');
+          cy.get('[type=submit]').click();
+          cy.get('input[name=name]').clear().type(name);
+          cy.get(`select[name=poolType]`).select('replicated');
+          cy.get(`select[name=poolType] option:checked`).contains('replicated');
+          cy.get('.float-start.me-2.select-menu-edit').click();
+          cy.get('.popover-body').should('be.visible');
+          // Choose rbd as the application label
+          cy.get('.select-menu-item-content').contains('rbd').click();
+          cy.get('cd-submit-button').click();
+          cy.get('cd-pool-list').should('exist');
+
+          cy.visit('#/block/mirroring').wait(1000);
+          cy.get('.table-actions button.dropdown-toggle').first().click();
+          cy.get('[aria-label="Import Bootstrap Token"]').click();
+          cy.get('cd-bootstrap-import-modal').within(() => {
+            cy.get(`label[for=${name}]`).click();
+            cy.get('textarea[id=token]').wait(100).type(bootstrapToken);
+            cy.get('button[type=submit]').click();
+          });
+        });
+      });
+
+      // login again since origin removes all the cookies
+      // sessions, localStorage items etc..
+      cy.login();
+      Cypress.Cookies.preserveOnce('token');
+      mirroring.navigateTo();
+      mirroring.checkPoolHealthStatus(poolName, 'OK');
+    });
+  });
+
+  describe('checks that edit mode functionality shows in the pools table', () => {
+    const poolName = 'mirroring_test';
+
+    beforeEach(() => {
+      pools.navigateTo('create'); // Need pool for mirroring testing
+      pools.create(poolName, 8, 'rbd');
+      pools.navigateTo();
+      pools.existTableCell(poolName, true);
+    });
+
+    it('tests editing mode for pools', () => {
+      mirroring.navigateTo();
+
+      mirroring.editMirror(poolName, 'Pool');
+      mirroring.getFirstTableCell('pool').should('be.visible');
+      mirroring.editMirror(poolName, 'Image');
+      mirroring.getFirstTableCell('image').should('be.visible');
+      mirroring.editMirror(poolName, 'Disabled');
+      mirroring.getFirstTableCell('disabled').should('be.visible');
+    });
+
+    afterEach(() => {
+      pools.navigateTo();
+      pools.delete(poolName);
+    });
+  });
+});
diff --git a/src/pybind/mgr/dashboard/frontend/cypress/e2e/block/mirroring.po.ts b/src/pybind/mgr/dashboard/frontend/cypress/e2e/block/mirroring.po.ts
new file mode 100644 (file)
index 0000000..c4adca8
--- /dev/null
@@ -0,0 +1,61 @@
+import { PageHelper } from '../page-helper.po';
+
+const pages = {
+  index: { url: '#/block/mirroring', id: 'cd-mirroring' }
+};
+
+export class MirroringPageHelper extends PageHelper {
+  pages = pages;
+
+  poolsColumnIndex = {
+    name: 1,
+    health: 6
+  };
+
+  /**
+   * Goes to the mirroring page and edits a pool in the Pool table. Clicks on the
+   * pool and chooses an option (either pool, image, or disabled)
+   */
+  @PageHelper.restrictTo(pages.index.url)
+  editMirror(name: string, option: string) {
+    // Clicks the pool in the table
+    this.getFirstTableCell(name).click();
+
+    // Clicks the Edit Mode button
+    cy.contains('button', 'Edit Mode').click();
+
+    // Clicks the drop down in the edit pop-up, then clicks the Update button
+    cy.get('.modal-content').should('be.visible');
+    this.selectOption('mirrorMode', option);
+
+    // Clicks update button and checks if the mode has been changed
+    cy.contains('button', 'Update').click();
+    cy.contains('.modal-dialog', 'Edit pool mirror mode').should('not.exist');
+    const val = option.toLowerCase(); // used since entries in table are lower case
+    this.getFirstTableCell(val).should('be.visible');
+  }
+
+  @PageHelper.restrictTo(pages.index.url)
+  generateToken(poolName: string) {
+    cy.get('[aria-label="Create Bootstrap Token"]').first().click();
+    cy.get('cd-bootstrap-create-modal').within(() => {
+      cy.get(`label[for=${poolName}]`).click();
+      cy.get('button[type=submit]').click();
+      cy.get('textarea[id=token]').wait(200).invoke('val').as('token');
+      cy.get('[aria-label="Back"]').click();
+    });
+  }
+
+  @PageHelper.restrictTo(pages.index.url)
+  checkPoolHealthStatus(poolName: string, status: string) {
+    cy.get('cd-mirroring-pools').within(() => {
+      this.getTableCell(this.poolsColumnIndex.name, poolName)
+        .parent()
+        .find(`datatable-body-cell:nth-child(${this.poolsColumnIndex.health}) .badge`)
+        .should(($ele) => {
+          const newLabels = $ele.toArray().map((v) => v.innerText);
+          expect(newLabels).to.include(status);
+        });
+    });
+  }
+}
diff --git a/src/pybind/mgr/dashboard/frontend/cypress/e2e/cluster/configuration.e2e-spec.ts b/src/pybind/mgr/dashboard/frontend/cypress/e2e/cluster/configuration.e2e-spec.ts
new file mode 100644 (file)
index 0000000..d022d59
--- /dev/null
@@ -0,0 +1,78 @@
+import { ConfigurationPageHelper } from './configuration.po';
+
+describe('Configuration page', () => {
+  const configuration = new ConfigurationPageHelper();
+
+  beforeEach(() => {
+    cy.login();
+    Cypress.Cookies.preserveOnce('token');
+    configuration.navigateTo();
+  });
+
+  describe('breadcrumb test', () => {
+    it('should open and show breadcrumb', () => {
+      configuration.expectBreadcrumbText('Configuration');
+    });
+  });
+
+  describe('fields check', () => {
+    beforeEach(() => {
+      configuration.getExpandCollapseElement().click();
+    });
+
+    it('should check that details table opens (w/o tab header)', () => {
+      configuration.getStatusTables().should('be.visible');
+      configuration.getTabs().should('not.exist');
+    });
+  });
+
+  describe('edit configuration test', () => {
+    const configName = 'client_cache_size';
+
+    beforeEach(() => {
+      configuration.clearTableSearchInput();
+      configuration.getTableCount('found').as('configFound');
+    });
+
+    after(() => {
+      configuration.configClear(configName);
+    });
+
+    it('should click and edit a configuration and results should appear in the table', () => {
+      configuration.edit(
+        configName,
+        ['global', '1'],
+        ['mon', '2'],
+        ['mgr', '3'],
+        ['osd', '4'],
+        ['mds', '5'],
+        ['client', '6']
+      );
+    });
+
+    it('should verify modified filter is applied properly', () => {
+      configuration.filterTable('Modified', 'no');
+      configuration.getTableCount('found').as('unmodifiedConfigs');
+
+      // Modified filter value to yes
+      configuration.filterTable('Modified', 'yes');
+      configuration.getTableCount('found').as('modifiedConfigs');
+
+      cy.get('@configFound').then((configFound) => {
+        cy.get('@unmodifiedConfigs').then((unmodifiedConfigs) => {
+          const modifiedConfigs = Number(configFound) - Number(unmodifiedConfigs);
+          configuration.getTableCount('found').should('eq', modifiedConfigs);
+        });
+      });
+
+      // Modified filter value to no
+      configuration.filterTable('Modified', 'no');
+      cy.get('@configFound').then((configFound) => {
+        cy.get('@modifiedConfigs').then((modifiedConfigs) => {
+          const unmodifiedConfigs = Number(configFound) - Number(modifiedConfigs);
+          configuration.getTableCount('found').should('eq', unmodifiedConfigs);
+        });
+      });
+    });
+  });
+});
diff --git a/src/pybind/mgr/dashboard/frontend/cypress/e2e/cluster/configuration.po.ts b/src/pybind/mgr/dashboard/frontend/cypress/e2e/cluster/configuration.po.ts
new file mode 100644 (file)
index 0000000..0133dc3
--- /dev/null
@@ -0,0 +1,75 @@
+import { PageHelper } from '../page-helper.po';
+
+export class ConfigurationPageHelper extends PageHelper {
+  pages = {
+    index: { url: '#/configuration', id: 'cd-configuration' }
+  };
+
+  /**
+   * Clears out all the values in a config to reset before and after testing
+   * Does not work for configs with checkbox only, possible future PR
+   */
+  configClear(name: string) {
+    const valList = ['global', 'mon', 'mgr', 'osd', 'mds', 'client']; // Editable values
+
+    this.navigateEdit(name);
+    // Waits for the data to load
+    cy.contains('.card-header', `Edit ${name}`);
+
+    for (const i of valList) {
+      cy.get(`#${i}`).clear();
+    }
+    // Clicks save button and checks that values are not present for the selected config
+    cy.get('[data-cy=submitBtn]').click();
+
+    // Enter config setting name into filter box
+    this.searchTable(name);
+
+    // Expand row
+    this.getExpandCollapseElement(name).click();
+
+    // Checks for visibility of details tab
+    this.getStatusTables().should('be.visible');
+
+    for (const i of valList) {
+      // Waits until values are not present in the details table
+      this.getStatusTables().should('not.contain.text', i + ':');
+    }
+  }
+
+  /**
+   * Clicks the designated config, then inputs the values passed into the edit function.
+   * Then checks if the edit is reflected in the config table.
+   * Takes in name of config and a list of tuples of values the user wants edited,
+   * each tuple having the desired value along with the number tehey want for that value.
+   * Ex: [global, '2'] is the global value with an input of 2
+   */
+  edit(name: string, ...values: [string, string][]) {
+    this.navigateEdit(name);
+
+    // Waits for data to load
+    cy.contains('.card-header', `Edit ${name}`);
+
+    values.forEach((valtuple) => {
+      // Finds desired value based off given list
+      cy.get(`#${valtuple[0]}`).type(valtuple[1]); // of values and inserts the given number for the value
+    });
+
+    // Clicks save button then waits until the desired config is visible, clicks it,
+    // then checks that each desired value appears with the desired number
+    cy.get('[data-cy=submitBtn]').click();
+
+    // Enter config setting name into filter box
+    this.searchTable(name);
+
+    // Checks for visibility of config in table
+    this.getExpandCollapseElement(name).should('be.visible').click();
+
+    // Clicks config
+    values.forEach((value) => {
+      // iterates through list of values and
+      // checks if the value appears in details with the correct number attatched
+      cy.contains('.table.table-striped.table-bordered', `${value[0]}\: ${value[1]}`);
+    });
+  }
+}
diff --git a/src/pybind/mgr/dashboard/frontend/cypress/e2e/cluster/create-cluster.po.ts b/src/pybind/mgr/dashboard/frontend/cypress/e2e/cluster/create-cluster.po.ts
new file mode 100644 (file)
index 0000000..300eddb
--- /dev/null
@@ -0,0 +1,56 @@
+import { PageHelper } from '../page-helper.po';
+import { NotificationSidebarPageHelper } from '../ui/notification.po';
+import { HostsPageHelper } from './hosts.po';
+import { ServicesPageHelper } from './services.po';
+
+const pages = {
+  index: { url: '#/expand-cluster', id: 'cd-create-cluster' }
+};
+export class CreateClusterWizardHelper extends PageHelper {
+  pages = pages;
+
+  createCluster() {
+    cy.get('cd-create-cluster').should('contain.text', 'Please expand your cluster first');
+    cy.get('[name=expand-cluster]').click();
+    cy.get('cd-wizard').should('exist');
+  }
+
+  doSkip() {
+    cy.get('[name=skip-cluster-creation]').click();
+    cy.contains('cd-modal button', 'Continue').click();
+
+    cy.get('cd-dashboard').should('exist');
+    const notification = new NotificationSidebarPageHelper();
+    notification.open();
+    notification.getNotifications().should('contain', 'Cluster expansion skipped by user');
+  }
+}
+
+export class CreateClusterHostPageHelper extends HostsPageHelper {
+  pages = {
+    index: { url: '#/expand-cluster', id: 'cd-wizard' },
+    add: { url: '', id: 'cd-host-form' }
+  };
+
+  columnIndex = {
+    hostname: 1,
+    labels: 2,
+    status: 3,
+    services: 0
+  };
+}
+
+export class CreateClusterServicePageHelper extends ServicesPageHelper {
+  pages = {
+    index: { url: '#/expand-cluster', id: 'cd-wizard' },
+    create: { url: '', id: 'cd-service-form' }
+  };
+
+  columnIndex = {
+    service_name: 1,
+    placement: 2,
+    running: 0,
+    size: 0,
+    last_refresh: 0
+  };
+}
diff --git a/src/pybind/mgr/dashboard/frontend/cypress/e2e/cluster/crush-map.e2e-spec.ts b/src/pybind/mgr/dashboard/frontend/cypress/e2e/cluster/crush-map.e2e-spec.ts
new file mode 100644 (file)
index 0000000..0a45473
--- /dev/null
@@ -0,0 +1,37 @@
+import { CrushMapPageHelper } from './crush-map.po';
+
+describe('CRUSH map page', () => {
+  const crushmap = new CrushMapPageHelper();
+
+  beforeEach(() => {
+    cy.login();
+    Cypress.Cookies.preserveOnce('token');
+    crushmap.navigateTo();
+  });
+
+  describe('breadcrumb test', () => {
+    it('should open and show breadcrumb', () => {
+      crushmap.expectBreadcrumbText('CRUSH map');
+    });
+  });
+
+  describe('fields check', () => {
+    it('should check that title & table appears', () => {
+      // Check that title (CRUSH map viewer) appears
+      crushmap.getPageTitle().should('equal', 'CRUSH map viewer');
+
+      // Check that title appears once OSD is clicked
+      crushmap.getCrushNode(0).click();
+
+      crushmap
+        .getLegends()
+        .invoke('text')
+        .then((legend) => {
+          crushmap.getCrushNode(0).should('have.text', legend);
+        });
+
+      // Check that table appears once OSD is clicked
+      crushmap.getDataTables().should('be.visible');
+    });
+  });
+});
diff --git a/src/pybind/mgr/dashboard/frontend/cypress/e2e/cluster/crush-map.po.ts b/src/pybind/mgr/dashboard/frontend/cypress/e2e/cluster/crush-map.po.ts
new file mode 100644 (file)
index 0000000..a5d2d59
--- /dev/null
@@ -0,0 +1,13 @@
+import { PageHelper } from '../page-helper.po';
+
+export class CrushMapPageHelper extends PageHelper {
+  pages = { index: { url: '#/crush-map', id: 'cd-crushmap' } };
+
+  getPageTitle() {
+    return cy.get('cd-crushmap .card-header').text();
+  }
+
+  getCrushNode(idx: number) {
+    return cy.get('.node-name.type-osd').eq(idx);
+  }
+}
diff --git a/src/pybind/mgr/dashboard/frontend/cypress/e2e/cluster/hosts.e2e-spec.ts b/src/pybind/mgr/dashboard/frontend/cypress/e2e/cluster/hosts.e2e-spec.ts
new file mode 100644 (file)
index 0000000..e4f9936
--- /dev/null
@@ -0,0 +1,35 @@
+import { HostsPageHelper } from './hosts.po';
+
+describe('Hosts page', () => {
+  const hosts = new HostsPageHelper();
+
+  beforeEach(() => {
+    cy.login();
+    Cypress.Cookies.preserveOnce('token');
+    hosts.navigateTo();
+  });
+
+  describe('breadcrumb and tab tests', () => {
+    it('should open and show breadcrumb', () => {
+      hosts.expectBreadcrumbText('Hosts');
+    });
+
+    it('should show two tabs', () => {
+      hosts.getTabsCount().should('eq', 2);
+    });
+
+    it('should show hosts list tab at first', () => {
+      hosts.getTabText(0).should('eq', 'Hosts List');
+    });
+
+    it('should show overall performance as a second tab', () => {
+      hosts.getTabText(1).should('eq', 'Overall Performance');
+    });
+  });
+
+  describe('services link test', () => {
+    it('should check at least one host is present', () => {
+      hosts.check_for_host();
+    });
+  });
+});
diff --git a/src/pybind/mgr/dashboard/frontend/cypress/e2e/cluster/hosts.po.ts b/src/pybind/mgr/dashboard/frontend/cypress/e2e/cluster/hosts.po.ts
new file mode 100644 (file)
index 0000000..9511142
--- /dev/null
@@ -0,0 +1,182 @@
+import { PageHelper } from '../page-helper.po';
+
+const pages = {
+  index: { url: '#/hosts', id: 'cd-hosts' },
+  add: { url: '#/hosts/(modal:add)', id: 'cd-host-form' }
+};
+
+export class HostsPageHelper extends PageHelper {
+  pages = pages;
+
+  columnIndex = {
+    hostname: 2,
+    services: 3,
+    labels: 4,
+    status: 5
+  };
+
+  check_for_host() {
+    this.getTableCount('total').should('not.be.eq', 0);
+  }
+
+  add(hostname: string, exist?: boolean, maintenance?: boolean, labels: string[] = []) {
+    cy.get(`${this.pages.add.id}`).within(() => {
+      cy.get('#hostname').type(hostname);
+      if (maintenance) {
+        cy.get('label[for=maintenance]').click();
+      }
+      if (exist) {
+        cy.get('#hostname').should('have.class', 'ng-invalid');
+      }
+    });
+
+    if (labels.length) {
+      this.selectPredefinedLabels(labels);
+    }
+
+    cy.get('cd-submit-button').click();
+    // back to host list
+    cy.get(`${this.pages.index.id}`);
+  }
+
+  selectPredefinedLabels(labels: string[]) {
+    cy.get('a[data-testid=select-menu-edit]').click();
+    for (const label of labels) {
+      cy.get('.popover-body div.select-menu-item-content').contains(label).click();
+    }
+  }
+
+  checkExist(hostname: string, exist: boolean) {
+    this.getTableCell(this.columnIndex.hostname, hostname).should(($elements) => {
+      const hosts = $elements.map((_, el) => el.textContent).get();
+      if (exist) {
+        expect(hosts).to.include(hostname);
+      } else {
+        expect(hosts).to.not.include(hostname);
+      }
+    });
+  }
+
+  remove(hostname: string) {
+    super.delete(hostname, this.columnIndex.hostname, 'hosts');
+  }
+
+  // Add or remove labels on a host, then verify labels in the table
+  editLabels(hostname: string, labels: string[], add: boolean) {
+    this.getTableCell(this.columnIndex.hostname, hostname).click();
+    this.clickActionButton('edit');
+
+    // add or remove label badges
+    if (add) {
+      cy.get('cd-modal').find('.select-menu-edit').click();
+      for (const label of labels) {
+        cy.contains('cd-modal .badge', new RegExp(`^${label}$`)).should('not.exist');
+        cy.get('.popover-body input').type(`${label}{enter}`);
+      }
+    } else {
+      for (const label of labels) {
+        cy.contains('cd-modal .badge', new RegExp(`^${label}$`))
+          .find('.badge-remove')
+          .click();
+      }
+    }
+    cy.get('cd-modal cd-submit-button').click();
+    this.checkLabelExists(hostname, labels, add);
+  }
+
+  checkLabelExists(hostname: string, labels: string[], add: boolean) {
+    // Verify labels are added or removed from Labels column
+    // First find row with hostname, then find labels in the row
+    this.getTableCell(this.columnIndex.hostname, hostname)
+      .click()
+      .parent()
+      .find(`datatable-body-cell:nth-child(${this.columnIndex.labels}) .badge`)
+      .should(($ele) => {
+        const newLabels = $ele.toArray().map((v) => v.innerText);
+        for (const label of labels) {
+          if (add) {
+            expect(newLabels).to.include(label);
+          } else {
+            expect(newLabels).to.not.include(label);
+          }
+        }
+      });
+  }
+
+  @PageHelper.restrictTo(pages.index.url)
+  maintenance(hostname: string, exit = false, force = false) {
+    this.clearTableSearchInput();
+    if (force) {
+      this.getTableCell(this.columnIndex.hostname, hostname).click();
+      this.clickActionButton('enter-maintenance');
+
+      cy.get('cd-modal').within(() => {
+        cy.contains('button', 'Continue').click();
+      });
+
+      this.getTableCell(this.columnIndex.hostname, hostname)
+        .parent()
+        .find(`datatable-body-cell:nth-child(${this.columnIndex.status}) .badge`)
+        .should(($ele) => {
+          const status = $ele.toArray().map((v) => v.innerText);
+          expect(status).to.include('maintenance');
+        });
+    }
+    if (exit) {
+      this.getTableCell(this.columnIndex.hostname, hostname)
+        .click()
+        .parent()
+        .find(`datatable-body-cell:nth-child(${this.columnIndex.status})`)
+        .then(($ele) => {
+          const status = $ele.toArray().map((v) => v.innerText);
+          if (status[0].includes('maintenance')) {
+            this.clickActionButton('exit-maintenance');
+          }
+        });
+
+      this.getTableCell(this.columnIndex.hostname, hostname)
+        .parent()
+        .find(`datatable-body-cell:nth-child(${this.columnIndex.status})`)
+        .should(($ele) => {
+          const status = $ele.toArray().map((v) => v.innerText);
+          expect(status).to.not.include('maintenance');
+        });
+    } else {
+      this.getTableCell(this.columnIndex.hostname, hostname).click();
+      this.clickActionButton('enter-maintenance');
+
+      this.getTableCell(this.columnIndex.hostname, hostname)
+        .parent()
+        .find(`datatable-body-cell:nth-child(${this.columnIndex.status}) .badge`)
+        .should(($ele) => {
+          const status = $ele.toArray().map((v) => v.innerText);
+          expect(status).to.include('maintenance');
+        });
+    }
+  }
+
+  @PageHelper.restrictTo(pages.index.url)
+  drain(hostname: string) {
+    this.getTableCell(this.columnIndex.hostname, hostname).click();
+    this.clickActionButton('start-drain');
+    this.checkLabelExists(hostname, ['_no_schedule'], true);
+
+    this.clickTab('cd-host-details', hostname, 'Daemons');
+    cy.get('cd-host-details').within(() => {
+      cy.wait(20000);
+      this.expectTableCount('total', 0);
+    });
+  }
+
+  checkServiceInstancesExist(hostname: string, instances: string[]) {
+    this.getTableCell(this.columnIndex.hostname, hostname)
+      .parent()
+      .find(`datatable-body-cell:nth-child(${this.columnIndex.services}) .badge`)
+      .should(($ele) => {
+        const serviceInstances = $ele.toArray().map((v) => v.innerText);
+        for (const instance of instances) {
+          expect(serviceInstances).to.include(instance);
+        }
+      });
+  }
+}
diff --git a/src/pybind/mgr/dashboard/frontend/cypress/e2e/cluster/inventory.po.ts b/src/pybind/mgr/dashboard/frontend/cypress/e2e/cluster/inventory.po.ts
new file mode 100644 (file)
index 0000000..5a9abdc
--- /dev/null
@@ -0,0 +1,22 @@
+import { PageHelper } from '../page-helper.po';
+
+const pages = {
+  index: { url: '#/inventory', id: 'cd-inventory' }
+};
+
+export class InventoryPageHelper extends PageHelper {
+  pages = pages;
+
+  identify() {
+    // Nothing we can do, just verify the form is there
+    this.getFirstTableCell().click();
+    cy.contains('cd-table-actions button', 'Identify').click();
+    cy.get('cd-modal').within(() => {
+      cy.get('#duration').select('15 minutes');
+      cy.get('#duration').select('10 minutes');
+      cy.get('cd-back-button').click();
+    });
+    cy.get('cd-modal').should('not.exist');
+    cy.get(`${this.pages.index.id}`);
+  }
+}
diff --git a/src/pybind/mgr/dashboard/frontend/cypress/e2e/cluster/logs.e2e-spec.ts b/src/pybind/mgr/dashboard/frontend/cypress/e2e/cluster/logs.e2e-spec.ts
new file mode 100644 (file)
index 0000000..ecc3cc1
--- /dev/null
@@ -0,0 +1,62 @@
+import { PoolPageHelper } from '../pools/pools.po';
+import { LogsPageHelper } from './logs.po';
+
+describe('Logs page', () => {
+  const logs = new LogsPageHelper();
+  const pools = new PoolPageHelper();
+
+  const poolname = 'e2e_logs_test_pool';
+  const today = new Date();
+  let hour = today.getHours();
+  if (hour > 12) {
+    hour = hour - 12;
+  }
+  const minute = today.getMinutes();
+
+  beforeEach(() => {
+    cy.login();
+    Cypress.Cookies.preserveOnce('token');
+  });
+
+  describe('breadcrumb and tab tests', () => {
+    beforeEach(() => {
+      logs.navigateTo();
+    });
+
+    it('should open and show breadcrumb', () => {
+      logs.expectBreadcrumbText('Logs');
+    });
+
+    it('should show three tabs', () => {
+      logs.getTabsCount().should('eq', 3);
+    });
+
+    it('should show cluster logs tab at first', () => {
+      logs.getTabText(0).should('eq', 'Cluster Logs');
+    });
+
+    it('should show audit logs as a second tab', () => {
+      logs.getTabText(1).should('eq', 'Audit Logs');
+    });
+
+    it('should show daemon logs as a third tab', () => {
+      logs.getTabText(2).should('eq', 'Daemon Logs');
+    });
+  });
+
+  describe('audit logs respond to pool creation and deletion test', () => {
+    it('should create pool and check audit logs reacted', () => {
+      pools.navigateTo('create');
+      pools.create(poolname, 8);
+      pools.navigateTo();
+      pools.existTableCell(poolname, true);
+      logs.checkAuditForPoolFunction(poolname, 'create', hour, minute);
+    });
+
+    it('should delete pool and check audit logs reacted', () => {
+      pools.navigateTo();
+      pools.delete(poolname);
+      logs.checkAuditForPoolFunction(poolname, 'delete', hour, minute);
+    });
+  });
+});
diff --git a/src/pybind/mgr/dashboard/frontend/cypress/e2e/cluster/logs.po.ts b/src/pybind/mgr/dashboard/frontend/cypress/e2e/cluster/logs.po.ts
new file mode 100644 (file)
index 0000000..7efd8a6
--- /dev/null
@@ -0,0 +1,70 @@
+import { PageHelper } from '../page-helper.po';
+
+export class LogsPageHelper extends PageHelper {
+  pages = {
+    index: { url: '#/logs', id: 'cd-logs' }
+  };
+
+  checkAuditForPoolFunction(poolname: string, poolfunction: string, hour: number, minute: number) {
+    this.navigateTo();
+
+    // sometimes the modal from deleting pool is still present at this point.
+    // This wait makes sure it isn't
+    cy.contains('.modal-dialog', 'Delete Pool').should('not.exist');
+
+    // go to audit logs tab
+    cy.contains('.nav-link', 'Audit Logs').click();
+
+    // Enter an earliest time so that no old messages with the same pool name show up
+    cy.get('.ngb-tp-input').its(0).clear();
+
+    if (hour < 10) {
+      cy.get('.ngb-tp-input').its(0).type('0');
+    }
+    cy.get('.ngb-tp-input').its(0).type(`${hour}`);
+
+    cy.get('.ngb-tp-input').its(1).clear();
+    if (minute < 10) {
+      cy.get('.ngb-tp-input').its(1).type('0');
+    }
+    cy.get('.ngb-tp-input').its(1).type(`${minute}`);
+
+    // Enter the pool name into the filter box
+    cy.get('input.form-control.ng-valid').first().clear().type(poolname);
+
+    cy.get('.tab-pane.active')
+      .get('.card-body')
+      .get('.message')
+      .should('contain.text', poolname)
+      .and('contain.text', `pool ${poolfunction}`);
+  }
+
+  checkAuditForConfigChange(configname: string, setting: string, hour: number, minute: number) {
+    this.navigateTo();
+
+    // go to audit logs tab
+    cy.contains('.nav-link', 'Audit Logs').click();
+
+    // Enter an earliest time so that no old messages with the same config name show up
+    cy.get('.ngb-tp-input').its(0).clear();
+    if (hour < 10) {
+      cy.get('.ngb-tp-input').its(0).type('0');
+    }
+    cy.get('.ngb-tp-input').its(0).type(`${hour}`);
+
+    cy.get('.ngb-tp-input').its(1).clear();
+    if (minute < 10) {
+      cy.get('.ngb-tp-input').its(1).type('0');
+    }
+    cy.get('.ngb-tp-input').its(1).type(`${minute}`);
+
+    // Enter the config name into the filter box
+    cy.get('input.form-control.ng-valid').first().clear().type(configname);
+
+    cy.get('.tab-pane.active')
+      .get('.card-body')
+      .get('.message')
+      .should('contain.text', configname)
+      .and('contain.text', setting);
+  }
+}
diff --git a/src/pybind/mgr/dashboard/frontend/cypress/e2e/cluster/mgr-modules.e2e-spec.ts b/src/pybind/mgr/dashboard/frontend/cypress/e2e/cluster/mgr-modules.e2e-spec.ts
new file mode 100644 (file)
index 0000000..0a2aa81
--- /dev/null
@@ -0,0 +1,78 @@
+import { Input, ManagerModulesPageHelper } from './mgr-modules.po';
+
+describe('Manager modules page', () => {
+  const mgrmodules = new ManagerModulesPageHelper();
+
+  beforeEach(() => {
+    cy.login();
+    Cypress.Cookies.preserveOnce('token');
+    mgrmodules.navigateTo();
+  });
+
+  describe('breadcrumb test', () => {
+    it('should open and show breadcrumb', () => {
+      mgrmodules.expectBreadcrumbText('Manager Modules');
+    });
+  });
+
+  describe('verifies editing functionality for manager modules', () => {
+    it('should test editing on balancer module', () => {
+      const balancerArr: Input[] = [
+        {
+          id: 'crush_compat_max_iterations',
+          newValue: '123',
+          oldValue: '25'
+        }
+      ];
+      mgrmodules.editMgrModule('balancer', balancerArr);
+    });
+
+    it('should test editing on dashboard module', () => {
+      const dashboardArr: Input[] = [
+        {
+          id: 'GRAFANA_API_PASSWORD',
+          newValue: 'rafa',
+          oldValue: ''
+        }
+      ];
+      mgrmodules.editMgrModule('dashboard', dashboardArr);
+    });
+
+    it('should test editing on devicehealth module', () => {
+      const devHealthArray: Input[] = [
+        {
+          id: 'mark_out_threshold',
+          newValue: '1987',
+          oldValue: '2419200'
+        },
+        {
+          id: 'pool_name',
+          newValue: 'sox',
+          oldValue: '.mgr'
+        },
+        {
+          id: 'retention_period',
+          newValue: '1999',
+          oldValue: '15552000'
+        },
+        {
+          id: 'scrape_frequency',
+          newValue: '2020',
+          oldValue: '86400'
+        },
+        {
+          id: 'sleep_interval',
+          newValue: '456',
+          oldValue: '600'
+        },
+        {
+          id: 'warn_threshold',
+          newValue: '567',
+          oldValue: '7257600'
+        }
+      ];
+
+      mgrmodules.editMgrModule('devicehealth', devHealthArray);
+    });
+  });
+});
diff --git a/src/pybind/mgr/dashboard/frontend/cypress/e2e/cluster/mgr-modules.po.ts b/src/pybind/mgr/dashboard/frontend/cypress/e2e/cluster/mgr-modules.po.ts
new file mode 100644 (file)
index 0000000..04d2eee
--- /dev/null
@@ -0,0 +1,57 @@
+import { PageHelper } from '../page-helper.po';
+
+export class Input {
+  id: string;
+  oldValue: string;
+  newValue: string;
+}
+
+export class ManagerModulesPageHelper extends PageHelper {
+  pages = { index: { url: '#/mgr-modules', id: 'cd-mgr-module-list' } };
+
+  /**
+   * Selects the Manager Module and then fills in the desired fields.
+   */
+  editMgrModule(name: string, inputs: Input[]) {
+    this.navigateEdit(name);
+
+    for (const input of inputs) {
+      // Clears fields and adds edits
+      cy.get(`#${input.id}`).clear().type(input.newValue);
+    }
+
+    cy.contains('button', 'Update').click();
+    // Checks if edits appear
+    this.getExpandCollapseElement(name).should('be.visible').click();
+
+    for (const input of inputs) {
+      cy.get('.datatable-body').last().contains(input.newValue);
+    }
+
+    // Clear mgr module of all edits made to it
+    this.navigateEdit(name);
+
+    // Clears the editable fields
+    for (const input of inputs) {
+      if (input.oldValue) {
+        const id = `#${input.id}`;
+        cy.get(id).clear();
+        if (input.oldValue) {
+          cy.get(id).type(input.oldValue);
+        }
+      }
+    }
+
+    // Checks that clearing represents in details tab of module
+    cy.contains('button', 'Update').click();
+    this.getExpandCollapseElement(name).should('be.visible').click();
+    for (const input of inputs) {
+      if (input.oldValue) {
+        cy.get('.datatable-body')
+          .eq(1)
+          .should('contain', input.id)
+          .and('not.contain', input.newValue);
+      }
+    }
+  }
+}
diff --git a/src/pybind/mgr/dashboard/frontend/cypress/e2e/cluster/monitors.e2e-spec.ts b/src/pybind/mgr/dashboard/frontend/cypress/e2e/cluster/monitors.e2e-spec.ts
new file mode 100644 (file)
index 0000000..a23d071
--- /dev/null
@@ -0,0 +1,62 @@
+import { MonitorsPageHelper } from './monitors.po';
+
+describe('Monitors page', () => {
+  const monitors = new MonitorsPageHelper();
+
+  beforeEach(() => {
+    cy.login();
+    Cypress.Cookies.preserveOnce('token');
+    monitors.navigateTo();
+  });
+
+  describe('breadcrumb test', () => {
+    it('should open and show breadcrumb', () => {
+      monitors.expectBreadcrumbText('Monitors');
+    });
+  });
+
+  describe('fields check', () => {
+    it('should check status table is present', () => {
+      // check for table header 'Status'
+      monitors.getLegends().its(0).should('have.text', 'Status');
+
+      // check for fields in table
+      monitors
+        .getStatusTables()
+        .should('contain.text', 'Cluster ID')
+        .and('contain.text', 'monmap modified')
+        .and('contain.text', 'monmap epoch')
+        .and('contain.text', 'quorum con')
+        .and('contain.text', 'quorum mon')
+        .and('contain.text', 'required con')
+        .and('contain.text', 'required mon');
+    });
+
+    it('should check In Quorum and Not In Quorum tables are present', () => {
+      // check for there to be two tables
+      monitors.getDataTables().should('have.length', 2);
+
+      // check for table header 'In Quorum'
+      monitors.getLegends().its(1).should('have.text', 'In Quorum');
+
+      // check for table header 'Not In Quorum'
+      monitors.getLegends().its(2).should('have.text', 'Not In Quorum');
+
+      // verify correct columns on In Quorum table
+      monitors.getDataTableHeaders(0).contains('Name');
+
+      monitors.getDataTableHeaders(0).contains('Rank');
+
+      monitors.getDataTableHeaders(0).contains('Public Address');
+
+      monitors.getDataTableHeaders(0).contains('Open Sessions');
+
+      // verify correct columns on Not In Quorum table
+      monitors.getDataTableHeaders(1).contains('Name');
+
+      monitors.getDataTableHeaders(1).contains('Rank');
+
+      monitors.getDataTableHeaders(1).contains('Public Address');
+    });
+  });
+});
diff --git a/src/pybind/mgr/dashboard/frontend/cypress/e2e/cluster/monitors.po.ts b/src/pybind/mgr/dashboard/frontend/cypress/e2e/cluster/monitors.po.ts
new file mode 100644 (file)
index 0000000..4113b99
--- /dev/null
@@ -0,0 +1,7 @@
+import { PageHelper } from '../page-helper.po';
+
+export class MonitorsPageHelper extends PageHelper {
+  pages = {
+    index: { url: '#/monitor', id: 'cd-monitor' }
+  };
+}
diff --git a/src/pybind/mgr/dashboard/frontend/cypress/e2e/cluster/osds.e2e-spec.ts b/src/pybind/mgr/dashboard/frontend/cypress/e2e/cluster/osds.e2e-spec.ts
new file mode 100644 (file)
index 0000000..2fc148a
--- /dev/null
@@ -0,0 +1,57 @@
+import { OSDsPageHelper } from './osds.po';
+
+describe('OSDs page', () => {
+  const osds = new OSDsPageHelper();
+
+  beforeEach(() => {
+    cy.login();
+    Cypress.Cookies.preserveOnce('token');
+    osds.navigateTo();
+  });
+
+  describe('breadcrumb and tab tests', () => {
+    it('should open and show breadcrumb', () => {
+      osds.expectBreadcrumbText('OSDs');
+    });
+
+    it('should show two tabs', () => {
+      osds.getTabsCount().should('eq', 2);
+      osds.getTabText(0).should('eq', 'OSDs List');
+      osds.getTabText(1).should('eq', 'Overall Performance');
+    });
+  });
+
+  describe('check existence of fields on OSD page', () => {
+    it('should check that number of rows and count in footer match', () => {
+      osds.getTableCount('total').then((text) => {
+        osds.getTableRows().its('length').should('equal', text);
+      });
+    });
+
+    it('should verify that buttons exist', () => {
+      cy.contains('button', 'Create');
+      cy.contains('button', 'Cluster-wide configuration');
+    });
+
+    describe('by selecting one row in OSDs List', () => {
+      beforeEach(() => {
+        osds.getExpandCollapseElement().click();
+      });
+
+      it('should show the correct text for the tab labels', () => {
+        cy.get('#tabset-osd-details > a').then(($tabs) => {
+          const tabHeadings = $tabs.map((_i, e) => e.textContent).get();
+
+          expect(tabHeadings).to.eql([
+            'Devices',
+            'Attributes (OSD map)',
+            'Metadata',
+            'Device health',
+            'Performance counter',
+            'Performance Details'
+          ]);
+        });
+      });
+    });
+  });
+});
diff --git a/src/pybind/mgr/dashboard/frontend/cypress/e2e/cluster/osds.po.ts b/src/pybind/mgr/dashboard/frontend/cypress/e2e/cluster/osds.po.ts
new file mode 100644 (file)
index 0000000..cd812f4
--- /dev/null
@@ -0,0 +1,84 @@
+import { PageHelper } from '../page-helper.po';
+
+const pages = {
+  index: { url: '#/osd', id: 'cd-osd-list' },
+  create: { url: '#/osd/create', id: 'cd-osd-form' }
+};
+
+export class OSDsPageHelper extends PageHelper {
+  pages = pages;
+
+  columnIndex = {
+    id: 3,
+    status: 5
+  };
+
+  create(deviceType: 'hdd' | 'ssd', hostname?: string, expandCluster = false) {
+    cy.get('[aria-label="toggle advanced mode"]').click();
+    // Click Primary devices Add button
+    cy.get('cd-osd-devices-selection-groups[name="Primary"]').as('primaryGroups');
+    cy.get('@primaryGroups').find('button').click();
+
+    // Select all devices with `deviceType`
+    cy.get('cd-osd-devices-selection-modal').within(() => {
+      cy.get('.modal-footer .tc_submitButton').as('addButton').should('be.disabled');
+      this.filterTable('Type', deviceType);
+      if (hostname) {
+        this.filterTable('Hostname', hostname);
+      }
+
+      if (expandCluster) {
+        this.getTableCount('total').should('be.gte', 1);
+      }
+      cy.get('@addButton').click();
+    });
+
+    if (!expandCluster) {
+      cy.get('@primaryGroups').within(() => {
+        this.getTableCount('total').as('newOSDCount');
+      });
+
+      cy.get(`${pages.create.id} .card-footer .tc_submitButton`).click();
+      cy.get(`cd-osd-creation-preview-modal .modal-footer .tc_submitButton`).click();
+    }
+  }
+
+  @PageHelper.restrictTo(pages.index.url)
+  checkStatus(id: number, status: string[]) {
+    this.searchTable(`id:${id}`);
+    this.expectTableCount('found', 1);
+    cy.get(`datatable-body-cell:nth-child(${this.columnIndex.status}) .badge`).should(($ele) => {
+      const allStatus = $ele.toArray().map((v) => v.innerText);
+      for (const s of status) {
+        expect(allStatus).to.include(s);
+      }
+    });
+  }
+
+  @PageHelper.restrictTo(pages.index.url)
+  ensureNoOsd(id: number) {
+    this.searchTable(`id:${id}`);
+    this.expectTableCount('found', 0);
+    this.clearTableSearchInput();
+  }
+
+  @PageHelper.restrictTo(pages.index.url)
+  deleteByIDs(osdIds: number[], replace?: boolean) {
+    this.getTableRows().each(($el) => {
+      const rowOSD = Number(
+        $el.find('datatable-body-cell .datatable-body-cell-label').get(this.columnIndex.id - 1)
+          .textContent
+      );
+      if (osdIds.includes(rowOSD)) {
+        cy.wrap($el).click();
+      }
+    });
+    this.clickActionButton('delete');
+    if (replace) {
+      cy.get('cd-modal label[for="preserve"]').click();
+    }
+    cy.get('cd-modal label[for="confirmation"]').click();
+    cy.contains('cd-modal button', 'Delete').click();
+    cy.get('cd-modal').should('not.exist');
+  }
+}
diff --git a/src/pybind/mgr/dashboard/frontend/cypress/e2e/cluster/services.po.ts b/src/pybind/mgr/dashboard/frontend/cypress/e2e/cluster/services.po.ts
new file mode 100644 (file)
index 0000000..c464a3f
--- /dev/null
@@ -0,0 +1,200 @@
+import { PageHelper } from '../page-helper.po';
+
+const pages = {
+  index: { url: '#/services', id: 'cd-services' },
+  create: { url: '#/services/(modal:create)', id: 'cd-service-form' }
+};
+
+export class ServicesPageHelper extends PageHelper {
+  pages = pages;
+
+  columnIndex = {
+    service_name: 2,
+    placement: 3,
+    running: 4,
+    size: 5,
+    last_refresh: 6
+  };
+
+  serviceDetailColumnIndex = {
+    daemonName: 2,
+    status: 4
+  };
+
+  check_for_service() {
+    this.getTableCount('total').should('not.be.eq', 0);
+  }
+
+  private selectServiceType(serviceType: string) {
+    return this.selectOption('service_type', serviceType);
+  }
+
+  clickServiceTab(serviceName: string, tabName: string) {
+    this.getExpandCollapseElement(serviceName).click();
+    cy.get('cd-service-details').within(() => {
+      this.getTab(tabName).click();
+    });
+  }
+
+  addService(
+    serviceType: string,
+    exist?: boolean,
+    count = 1,
+    snmpVersion?: string,
+    snmpPrivProtocol?: boolean,
+    unmanaged = false
+  ) {
+    cy.get(`${this.pages.create.id}`).within(() => {
+      this.selectServiceType(serviceType);
+      switch (serviceType) {
+        case 'rgw':
+          cy.get('#service_id').type('foo');
+          unmanaged ? cy.get('label[for=unmanaged]').click() : cy.get('#count').type(String(count));
+          break;
+
+        case 'ingress':
+          if (unmanaged) {
+            cy.get('label[for=unmanaged]').click();
+          }
+          this.selectOption('backend_service', 'rgw.foo');
+          cy.get('#service_id').should('have.value', 'rgw.foo');
+          cy.get('#virtual_ip').type('192.168.100.1/24');
+          cy.get('#frontend_port').type('8081');
+          cy.get('#monitor_port').type('8082');
+          break;
+
+        case 'nfs':
+          cy.get('#service_id').type('testnfs');
+          unmanaged ? cy.get('label[for=unmanaged]').click() : cy.get('#count').type(String(count));
+          break;
+
+        case 'snmp-gateway':
+          this.selectOption('snmp_version', snmpVersion);
+          cy.get('#snmp_destination').type('192.168.0.1:8443');
+          if (snmpVersion === 'V2c') {
+            cy.get('#snmp_community').type('public');
+          } else {
+            cy.get('#engine_id').type('800C53F00000');
+            this.selectOption('auth_protocol', 'SHA');
+            if (snmpPrivProtocol) {
+              this.selectOption('privacy_protocol', 'DES');
+              cy.get('#snmp_v3_priv_password').type('testencrypt');
+            }
+
+            // Credentials
+            cy.get('#snmp_v3_auth_username').type('test');
+            cy.get('#snmp_v3_auth_password').type('testpass');
+          }
+          break;
+
+        default:
+          cy.get('#service_id').type('test');
+          unmanaged ? cy.get('label[for=unmanaged]').click() : cy.get('#count').type(String(count));
+          break;
+      }
+      if (serviceType === 'snmp-gateway') {
+        cy.get('cd-submit-button').dblclick();
+      } else {
+        cy.get('cd-submit-button').click();
+      }
+    });
+    if (exist) {
+      cy.get('#service_id').should('have.class', 'ng-invalid');
+    } else {
+      // back to service list
+      cy.get(`${this.pages.index.id}`);
+    }
+  }
+
+  editService(name: string, daemonCount: string) {
+    this.navigateEdit(name, true, false);
+    cy.get(`${this.pages.create.id}`).within(() => {
+      cy.get('#service_type').should('be.disabled');
+      cy.get('#service_id').should('be.disabled');
+      cy.get('#count').clear().type(daemonCount);
+      cy.get('cd-submit-button').click();
+    });
+  }
+
+  checkServiceStatus(daemon: string, expectedStatus = 'running') {
+    let daemonNameIndex = this.serviceDetailColumnIndex.daemonName;
+    let statusIndex = this.serviceDetailColumnIndex.status;
+
+    // since hostname row is hidden from the hosts details table,
+    // we'll need to manually override the indexes when this check is being
+    // done for the daemons in host details page. So we'll get the url and
+    // verify if the current page is not the services index page
+    cy.url().then((url) => {
+      if (!url.includes(pages.index.url)) {
+        daemonNameIndex = 1;
+        statusIndex = 3;
+      }
+
+      cy.get('cd-service-daemon-list').within(() => {
+        this.getTableCell(daemonNameIndex, daemon, true)
+          .parent()
+          .find(`datatable-body-cell:nth-child(${statusIndex}) .badge`)
+          .should(($ele) => {
+            const status = $ele.toArray().map((v) => v.innerText);
+            expect(status).to.include(expectedStatus);
+          });
+      });
+    });
+  }
+
+  expectPlacementCount(serviceName: string, expectedCount: string) {
+    this.getTableCell(this.columnIndex.service_name, serviceName)
+      .parent()
+      .find(`datatable-body-cell:nth-child(${this.columnIndex.placement})`)
+      .should(($ele) => {
+        const running = $ele.text().split(';');
+        expect(running).to.include(`count:${expectedCount}`);
+      });
+  }
+
+  checkExist(serviceName: string, exist: boolean) {
+    this.getTableCell(this.columnIndex.service_name, serviceName).should(($elements) => {
+      const services = $elements.map((_, el) => el.textContent).get();
+      if (exist) {
+        expect(services).to.include(serviceName);
+      } else {
+        expect(services).to.not.include(serviceName);
+      }
+    });
+  }
+
+  isUnmanaged(serviceName: string, unmanaged: boolean) {
+    this.getTableCell(this.columnIndex.service_name, serviceName)
+      .parent()
+      .find(`datatable-body-cell:nth-child(${this.columnIndex.placement})`)
+      .should(($ele) => {
+        const placement = $ele.text().split(';');
+        unmanaged
+          ? expect(placement).to.include('unmanaged')
+          : expect(placement).to.not.include('unmanaged');
+      });
+  }
+
+  deleteService(serviceName: string) {
+    const getRow = this.getTableCell.bind(this, this.columnIndex.service_name);
+    getRow(serviceName).click();
+
+    // Clicks on table Delete button
+    this.clickActionButton('delete');
+
+    // Confirms deletion
+    cy.get('cd-modal .custom-control-label').click();
+    cy.contains('cd-modal button', 'Delete').click();
+
+    // Wait for modal to close
+    cy.get('cd-modal').should('not.exist');
+    this.checkExist(serviceName, false);
+  }
+
+  daemonAction(daemon: string, action: string) {
+    cy.get('cd-service-daemon-list').within(() => {
+      this.getTableRow(daemon).click();
+      this.clickActionButton(action);
+    });
+  }
+}
diff --git a/src/pybind/mgr/dashboard/frontend/cypress/e2e/cluster/users.e2e-spec.ts b/src/pybind/mgr/dashboard/frontend/cypress/e2e/cluster/users.e2e-spec.ts
new file mode 100644 (file)
index 0000000..87acda9
--- /dev/null
@@ -0,0 +1,27 @@
+import { UsersPageHelper } from './users.po';
+
+describe('Cluster Ceph Users', () => {
+  const users = new UsersPageHelper();
+
+  beforeEach(() => {
+    cy.login();
+    Cypress.Cookies.preserveOnce('token');
+    users.navigateTo();
+  });
+
+  describe('breadcrumb and tab tests', () => {
+    it('should open and show breadcrumb', () => {
+      users.expectBreadcrumbText('Ceph Users');
+    });
+  });
+
+  describe('Cluster users table', () => {
+    it('should verify the table is not empty', () => {
+      users.checkForUsers();
+    });
+
+    it('should verify the keys are hidden', () => {
+      users.verifyKeysAreHidden();
+    });
+  });
+});
diff --git a/src/pybind/mgr/dashboard/frontend/cypress/e2e/cluster/users.po.ts b/src/pybind/mgr/dashboard/frontend/cypress/e2e/cluster/users.po.ts
new file mode 100644 (file)
index 0000000..bce659f
--- /dev/null
@@ -0,0 +1,29 @@
+import { PageHelper } from '../page-helper.po';
+
+const pages = {
+  index: { url: '#/ceph-users', id: 'cd-crud-table' }
+};
+
+export class UsersPageHelper extends PageHelper {
+  pages = pages;
+
+  columnIndex = {
+    entity: 2,
+    capabilities: 3,
+    key: 4
+  };
+
+  checkForUsers() {
+    this.getTableCount('total').should('not.be.eq', 0);
+  }
+
+  verifyKeysAreHidden() {
+    this.getTableCell(this.columnIndex.entity, 'osd.0')
+      .parent()
+      .find(`datatable-body-cell:nth-child(${this.columnIndex.key}) span`)
+      .should(($ele) => {
+        const serviceInstances = $ele.toArray().map((v) => v.innerText);
+        expect(serviceInstances).not.contains(/^[a-z0-9]+$/i);
+      });
+  }
+}
diff --git a/src/pybind/mgr/dashboard/frontend/cypress/e2e/common/01-global.feature.po.ts b/src/pybind/mgr/dashboard/frontend/cypress/e2e/common/01-global.feature.po.ts
new file mode 100644 (file)
index 0000000..d5b4645
--- /dev/null
@@ -0,0 +1,188 @@
+import { And, Given, Then, When } from 'cypress-cucumber-preprocessor/steps';
+
+import { UrlsCollection } from './urls.po';
+
+const urlsCollection = new UrlsCollection();
+
+Given('I am logged in', () => {
+  cy.login();
+  Cypress.Cookies.preserveOnce('token');
+});
+
+Given('I am on the {string} page', (page: string) => {
+  cy.visit(urlsCollection.pages[page].url);
+  cy.get(urlsCollection.pages[page].id).should('exist');
+});
+
+Then('I should be on the {string} page', (page: string) => {
+  cy.get(urlsCollection.pages[page].id).should('exist');
+});
+
+And('I should see a button to {string}', (button: string) => {
+  cy.get(`[aria-label="${button}"]`).should('be.visible');
+});
+
+When('I click on {string} button', (button: string) => {
+  cy.get(`[aria-label="${button}"]`).first().click();
+});
+
+// When you are clicking on an action in the table actions dropdown button
+When('I click on {string} button from the table actions', (button: string) => {
+  cy.get('.table-actions button.dropdown-toggle').first().click();
+  cy.get(`[aria-label="${button}"]`).first().click();
+});
+
+And('select options {string}', (labels: string) => {
+  if (labels) {
+    cy.get('a[data-testid=select-menu-edit]').click();
+    for (const label of labels.split(', ')) {
+      cy.get('.popover-body div.select-menu-item-content').contains(label).click();
+    }
+  }
+});
+
+And('{string} option {string}', (action: string, labels: string) => {
+  if (labels) {
+    if (action === 'add') {
+      cy.get('cd-modal').find('.select-menu-edit').click();
+      for (const label of labels.split(', ')) {
+        cy.get('.popover-body input').type(`${label}{enter}`);
+      }
+    } else {
+      for (const label of labels.split(', ')) {
+        cy.contains('cd-modal .badge', new RegExp(`^${label}$`))
+          .find('.badge-remove')
+          .click();
+      }
+    }
+  }
+});
+
+/**
+ * Fills in the given field using the value provided
+ * @param field ID of the field that needs to be filled out.
+ * @param value Value that should be filled in the field.
+ */
+And('enter {string} {string}', (field: string, value: string) => {
+  cy.get('cd-modal').within(() => {
+    cy.get(`input[id=${field}]`).type(value);
+  });
+});
+
+And('I click on submit button', () => {
+  cy.get('[data-cy=submitBtn]').click();
+});
+
+/**
+ * Selects any row on the datatable if it matches the given name
+ */
+When('I select a row {string}', (row: string) => {
+  cy.get('cd-table .search input').first().clear().type(row);
+  cy.contains(`datatable-body-row datatable-body-cell .datatable-body-cell-label`, row).click();
+});
+
+Then('I should see the modal', () => {
+  cy.get('cd-modal').should('exist');
+});
+
+Then('I should not see the modal', () => {
+  cy.get('cd-modal').should('not.exist');
+});
+
+/**
+ * Some modals have an additional confirmation to be provided
+ * by ticking the 'Are you sure?' box.
+ */
+Then('I check the tick box in modal', () => {
+  cy.get('cd-modal .custom-control-label').click();
+});
+
+And('I confirm to {string}', (action: string) => {
+  cy.contains('cd-modal button', action).click();
+  cy.get('cd-modal').should('not.exist');
+});
+
+Then('I should see an error in {string} field', (field: string) => {
+  cy.get('cd-modal').within(() => {
+    cy.get(`input[id=${field}]`).should('have.class', 'ng-invalid');
+  });
+});
+
+Then('I should see a row with {string}', (row: string) => {
+  cy.get('cd-table .search input').first().clear().type(row);
+  cy.contains(`datatable-body-row datatable-body-cell .datatable-body-cell-label`, row).should(
+    'exist'
+  );
+});
+
+Then('I should not see a row with {string}', (row: string) => {
+  cy.get('cd-table .search input').first().clear().type(row);
+  cy.contains(`datatable-body-row datatable-body-cell .datatable-body-cell-label`, row).should(
+    'not.exist'
+  );
+});
+
+Then('I should see rows with following entries', (entries) => {
+  entries.hashes().forEach((entry: any) => {
+    cy.get('cd-table .search input').first().clear().type(entry.hostname);
+    cy.contains(
+      `datatable-body-row datatable-body-cell .datatable-body-cell-label`,
+      entry.hostname
+    ).should('exist');
+  });
+});
+
+And('I should see row {string} have {string}', (row: string, options: string) => {
+  if (options) {
+    cy.get('cd-table .search input').first().clear().type(row);
+    for (const option of options.split(',')) {
+      cy.contains(
+        `datatable-body-row datatable-body-cell .datatable-body-cell-label .badge`,
+        option
+      ).should('exist');
+    }
+  }
+});
+
+And('I should see row {string} does not have {string}', (row: string, options: string) => {
+  if (options) {
+    cy.get('cd-table .search input').first().clear().type(row);
+    for (const option of options.split(',')) {
+      cy.contains(
+        `datatable-body-row datatable-body-cell .datatable-body-cell-label .badge`,
+        option
+      ).should('not.exist');
+    }
+  }
+});
+
+And('I go to the {string} tab', (names: string) => {
+  for (const name of names.split(', ')) {
+    cy.contains('.nav.nav-tabs a', name).click();
+  }
+});
+
+And('select {string} {string}', (selectionName: string, option: string) => {
+  cy.get(`select[name=${selectionName}]`).select(option);
+  cy.get(`select[name=${selectionName}] option:checked`).contains(option);
+});
+
+When('I expand the row {string}', (row: string) => {
+  cy.contains('.datatable-body-row', row).first().find('.tc_expand-collapse').click();
+});
+
+And('I should see row {string} have {string} on this tab', (row: string, options: string) => {
+  if (options) {
+    cy.get('cd-table').should('exist');
+    cy.get('datatable-scroller, .empty-row');
+    cy.get('.datatable-row-detail').within(() => {
+      cy.get('cd-table .search input').first().clear().type(row);
+      for (const option of options.split(',')) {
+        cy.contains(
+          `datatable-body-row datatable-body-cell .datatable-body-cell-label span`,
+          option
+        ).should('exist');
+      }
+    });
+  }
+});
diff --git a/src/pybind/mgr/dashboard/frontend/cypress/e2e/common/create-cluster/create-cluster.feature.po.ts b/src/pybind/mgr/dashboard/frontend/cypress/e2e/common/create-cluster/create-cluster.feature.po.ts
new file mode 100644 (file)
index 0000000..d18c348
--- /dev/null
@@ -0,0 +1,12 @@
+import { Given, Then } from 'cypress-cucumber-preprocessor/steps';
+
+Given('I am on the {string} section', (page: string) => {
+  cy.get('cd-wizard').within(() => {
+    cy.get('.nav-link').should('contain.text', page).first().click();
+    cy.get('.nav-link.active').should('contain.text', page);
+  });
+});
+
+Then('I should see a message {string}', () => {
+  cy.get('cd-create-cluster').should('contain.text', 'Please expand your cluster first');
+});
diff --git a/src/pybind/mgr/dashboard/frontend/cypress/e2e/common/grafana.feature.po.ts b/src/pybind/mgr/dashboard/frontend/cypress/e2e/common/grafana.feature.po.ts
new file mode 100644 (file)
index 0000000..4b2ee4d
--- /dev/null
@@ -0,0 +1,87 @@
+import { Then, When } from 'cypress-cucumber-preprocessor/steps';
+import 'cypress-iframe';
+
+function getIframe() {
+  cy.frameLoaded('#iframe');
+  return cy.iframe();
+}
+
+Then('I should see the grafana panel {string}', (panels: string) => {
+  getIframe().within(() => {
+    for (const panel of panels.split(', ')) {
+      cy.get('.grafana-app')
+        .wait(100)
+        .within(() => {
+          cy.get(`[aria-label="${panel} panel"]`).should('be.visible');
+        });
+    }
+  });
+});
+
+When('I view the grafana panel {string}', (panels: string) => {
+  getIframe().within(() => {
+    for (const panel of panels.split(', ')) {
+      cy.get('.grafana-app')
+        .wait(100)
+        .within(() => {
+          cy.get(`[aria-label="${panel} panel"]`).within(() => {
+            cy.get('h2').click();
+          });
+          cy.get('[aria-label="Panel header item View"]').click();
+        });
+    }
+  });
+});
+
+Then('I should not see {string} in the panel {string}', (value: string, panels: string) => {
+  getIframe().within(() => {
+    for (const panel of panels.split(', ')) {
+      cy.get('.grafana-app')
+        .wait(100)
+        .within(() => {
+          cy.get(`[aria-label="${panel} panel"]`)
+            .should('be.visible')
+            .within(() => {
+              cy.get('span').first().should('not.have.text', value);
+            });
+        });
+    }
+  });
+});
+
+Then(
+  'I should see the legends {string} in the graph {string}',
+  (legends: string, panels: string) => {
+    getIframe().within(() => {
+      for (const panel of panels.split(', ')) {
+        cy.get('.grafana-app')
+          .wait(100)
+          .within(() => {
+            cy.get(`[aria-label="${panel} panel"]`)
+              .should('be.visible')
+              .within(() => {
+                for (const legend of legends.split(', ')) {
+                  cy.get('a').contains(legend);
+                }
+              });
+          });
+      }
+    });
+  }
+);
+
+Then('I should not see No Data in the graph {string}', (panels: string) => {
+  getIframe().within(() => {
+    for (const panel of panels.split(', ')) {
+      cy.get('.grafana-app')
+        .wait(100)
+        .within(() => {
+          cy.get(`[aria-label="${panel} panel"]`)
+            .should('be.visible')
+            .within(() => {
+              cy.get('div.datapoints-warning').should('not.exist');
+            });
+        });
+    }
+  });
+});
diff --git a/src/pybind/mgr/dashboard/frontend/cypress/e2e/common/urls.po.ts b/src/pybind/mgr/dashboard/frontend/cypress/e2e/common/urls.po.ts
new file mode 100644 (file)
index 0000000..2863550
--- /dev/null
@@ -0,0 +1,44 @@
+import { PageHelper } from '../page-helper.po';
+
+export class UrlsCollection extends PageHelper {
+  pages = {
+    // Cluster expansion
+    welcome: { url: '#/expand-cluster', id: 'cd-create-cluster' },
+
+    // Landing page
+    dashboard: { url: '#/dashboard', id: 'cd-dashboard' },
+
+    // Hosts
+    hosts: { url: '#/hosts', id: 'cd-hosts' },
+    'add hosts': { url: '#/hosts/(modal:add)', id: 'cd-host-form' },
+
+    // Services
+    services: { url: '#/services', id: 'cd-services' },
+    'create services': { url: '#/services/(modal:create)', id: 'cd-service-form' },
+
+    // Physical Disks
+    'physical disks': { url: '#/inventory', id: 'cd-inventory' },
+
+    // Monitors
+    monitors: { url: '#/monitor', id: 'cd-monitor' },
+
+    // OSDs
+    osds: { url: '#/osd', id: 'cd-osd-list' },
+    'create osds': { url: '#/osd/create', id: 'cd-osd-form' },
+
+    // Configuration
+    configuration: { url: '#/configuration', id: 'cd-configuration' },
+
+    // Crush Map
+    'crush map': { url: '#/crush-map', id: 'cd-crushmap' },
+
+    // Mgr modules
+    'mgr-modules': { url: '#/mgr-modules', id: 'cd-mgr-module-list' },
+
+    // Logs
+    logs: { url: '#/logs', id: 'cd-logs' },
+
+    // RGW Daemons
+    'rgw daemons': { url: '#/rgw/daemon', id: 'cd-rgw-daemon-list' }
+  };
+}
diff --git a/src/pybind/mgr/dashboard/frontend/cypress/e2e/filesystems/filesystems.e2e-spec.ts b/src/pybind/mgr/dashboard/frontend/cypress/e2e/filesystems/filesystems.e2e-spec.ts
new file mode 100644 (file)
index 0000000..e623475
--- /dev/null
@@ -0,0 +1,17 @@
+import { FilesystemsPageHelper } from './filesystems.po';
+
+describe('File Systems page', () => {
+  const filesystems = new FilesystemsPageHelper();
+
+  beforeEach(() => {
+    cy.login();
+    Cypress.Cookies.preserveOnce('token');
+    filesystems.navigateTo();
+  });
+
+  describe('breadcrumb test', () => {
+    it('should open and show breadcrumb', () => {
+      filesystems.expectBreadcrumbText('File Systems');
+    });
+  });
+});
diff --git a/src/pybind/mgr/dashboard/frontend/cypress/e2e/filesystems/filesystems.po.ts b/src/pybind/mgr/dashboard/frontend/cypress/e2e/filesystems/filesystems.po.ts
new file mode 100644 (file)
index 0000000..bd6e5b8
--- /dev/null
@@ -0,0 +1,5 @@
+import { PageHelper } from '../page-helper.po';
+
+export class FilesystemsPageHelper extends PageHelper {
+  pages = { index: { url: '#/cephfs', id: 'cd-cephfs-list' } };
+}
diff --git a/src/pybind/mgr/dashboard/frontend/cypress/e2e/orchestrator/01-hosts.e2e-spec.ts b/src/pybind/mgr/dashboard/frontend/cypress/e2e/orchestrator/01-hosts.e2e-spec.ts
new file mode 100644 (file)
index 0000000..aca36ad
--- /dev/null
@@ -0,0 +1,86 @@
+import { HostsPageHelper } from '../cluster/hosts.po';
+
+describe('Hosts page', () => {
+  const hosts = new HostsPageHelper();
+
+  beforeEach(() => {
+    cy.login();
+    Cypress.Cookies.preserveOnce('token');
+    hosts.navigateTo();
+  });
+
+  describe('when Orchestrator is available', () => {
+    beforeEach(function () {
+      cy.fixture('orchestrator/inventory.json').as('hosts');
+      cy.fixture('orchestrator/services.json').as('services');
+    });
+
+    it('should not add an exsiting host', function () {
+      const hostname = Cypress._.sample(this.hosts).name;
+      hosts.navigateTo('add');
+      hosts.add(hostname, true);
+    });
+
+    it('should drain and remove a host and then add it back', function () {
+      const hostname = Cypress._.last(this.hosts)['name'];
+
+      // should drain the host first before deleting
+      hosts.drain(hostname);
+      hosts.remove(hostname);
+
+      // add it back
+      hosts.navigateTo('add');
+      hosts.add(hostname);
+      hosts.checkExist(hostname, true);
+    });
+
+    it('should display inventory', function () {
+      for (const host of this.hosts) {
+        hosts.clickTab('cd-host-details', host.name, 'Physical Disks');
+        cy.get('cd-host-details').within(() => {
+          hosts.expectTableCount('total', host.devices.length);
+        });
+      }
+    });
+
+    it('should display daemons', function () {
+      for (const host of this.hosts) {
+        hosts.clickTab('cd-host-details', host.name, 'Daemons');
+        cy.get('cd-host-details').within(() => {
+          hosts.getTableCount('total').should('be.gte', 0);
+        });
+      }
+    });
+
+    it('should edit host labels', function () {
+      const hostname = Cypress._.sample(this.hosts).name;
+      const labels = ['foo', 'bar'];
+      hosts.editLabels(hostname, labels, true);
+      hosts.editLabels(hostname, labels, false);
+    });
+
+    it('should enter host into maintenance', function () {
+      const hostname = Cypress._.sample(this.hosts).name;
+      const serviceList = new Array();
+      this.services.forEach((service: any) => {
+        if (hostname === service.hostname) {
+          serviceList.push(service.daemon_type);
+        }
+      });
+      let enterMaintenance = true;
+      serviceList.forEach((service: string) => {
+        if (service === 'mgr' || service === 'alertmanager') {
+          enterMaintenance = false;
+        }
+      });
+      if (enterMaintenance) {
+        hosts.maintenance(hostname);
+      }
+    });
+
+    it('should exit host from maintenance', function () {
+      const hostname = Cypress._.sample(this.hosts).name;
+      hosts.maintenance(hostname, true);
+    });
+  });
+});
diff --git a/src/pybind/mgr/dashboard/frontend/cypress/e2e/orchestrator/03-inventory.e2e-spec.ts b/src/pybind/mgr/dashboard/frontend/cypress/e2e/orchestrator/03-inventory.e2e-spec.ts
new file mode 100644 (file)
index 0000000..a64e3bc
--- /dev/null
@@ -0,0 +1,26 @@
+import { InventoryPageHelper } from '../cluster/inventory.po';
+
+describe('Physical Disks page', () => {
+  const inventory = new InventoryPageHelper();
+
+  beforeEach(() => {
+    cy.login();
+    Cypress.Cookies.preserveOnce('token');
+    inventory.navigateTo();
+  });
+
+  it('should have correct devices', () => {
+    cy.fixture('orchestrator/inventory.json').then((hosts) => {
+      const totalDiskCount = Cypress._.sumBy(hosts, 'devices.length');
+      inventory.expectTableCount('total', totalDiskCount);
+      for (const host of hosts) {
+        inventory.filterTable('Hostname', host['name']);
+        inventory.getTableCount('found').should('be.eq', host.devices.length);
+      }
+    });
+  });
+
+  it('should identify device', () => {
+    inventory.identify();
+  });
+});
diff --git a/src/pybind/mgr/dashboard/frontend/cypress/e2e/orchestrator/04-osds.e2e-spec.ts b/src/pybind/mgr/dashboard/frontend/cypress/e2e/orchestrator/04-osds.e2e-spec.ts
new file mode 100644 (file)
index 0000000..41f0933
--- /dev/null
@@ -0,0 +1,50 @@
+import { OSDsPageHelper } from '../cluster/osds.po';
+import { DashboardPageHelper } from '../ui/dashboard.po';
+
+describe('OSDs page', () => {
+  const osds = new OSDsPageHelper();
+  const dashboard = new DashboardPageHelper();
+
+  beforeEach(() => {
+    cy.login();
+    Cypress.Cookies.preserveOnce('token');
+    osds.navigateTo();
+  });
+
+  describe('when Orchestrator is available', () => {
+    it('should create and delete OSDs', () => {
+      osds.getTableCount('total').as('initOSDCount');
+      osds.navigateTo('create');
+      osds.create('hdd');
+
+      cy.get('@newOSDCount').then((newCount) => {
+        cy.get('@initOSDCount').then((oldCount) => {
+          const expectedCount = Number(oldCount) + Number(newCount);
+
+          // check total rows
+          osds.expectTableCount('total', expectedCount);
+
+          // landing page is easier to check OSD status
+          dashboard.navigateTo();
+          dashboard.infoCardBody('OSDs').should('contain.text', `${expectedCount} total`);
+          dashboard.infoCardBody('OSDs').should('contain.text', `${expectedCount} up`);
+          dashboard.infoCardBody('OSDs').should('contain.text', `${expectedCount} in`);
+
+          cy.wait(30000);
+          expect(Number(newCount)).to.be.gte(2);
+          // Delete the first OSD we created
+          osds.navigateTo();
+          const deleteOsdId = Number(oldCount);
+          osds.deleteByIDs([deleteOsdId], false);
+          osds.ensureNoOsd(deleteOsdId);
+
+          cy.wait(30000);
+          // Replace the second OSD we created
+          const replaceID = Number(oldCount) + 1;
+          osds.deleteByIDs([replaceID], true);
+          osds.checkStatus(replaceID, ['destroyed']);
+        });
+      });
+    });
+  });
+});
diff --git a/src/pybind/mgr/dashboard/frontend/cypress/e2e/orchestrator/05-services.e2e-spec.ts b/src/pybind/mgr/dashboard/frontend/cypress/e2e/orchestrator/05-services.e2e-spec.ts
new file mode 100644 (file)
index 0000000..fb5e6ac
--- /dev/null
@@ -0,0 +1,36 @@
+import { ServicesPageHelper } from '../cluster/services.po';
+
+describe('Services page', () => {
+  const services = new ServicesPageHelper();
+  const serviceName = 'rgw.foo';
+
+  beforeEach(() => {
+    cy.login();
+    Cypress.Cookies.preserveOnce('token');
+    services.navigateTo();
+  });
+
+  describe('when Orchestrator is available', () => {
+    it('should create an rgw service', () => {
+      services.navigateTo('create');
+      services.addService('rgw');
+
+      services.checkExist(serviceName, true);
+    });
+
+    it('should edit a service', () => {
+      const count = '2';
+      services.editService(serviceName, count);
+      services.expectPlacementCount(serviceName, count);
+    });
+
+    it('should create and delete an ingress service', () => {
+      services.navigateTo('create');
+      services.addService('ingress');
+
+      services.checkExist('ingress.rgw.foo', true);
+
+      services.deleteService('ingress.rgw.foo');
+    });
+  });
+});
diff --git a/src/pybind/mgr/dashboard/frontend/cypress/e2e/orchestrator/grafana/grafana.feature b/src/pybind/mgr/dashboard/frontend/cypress/e2e/orchestrator/grafana/grafana.feature
new file mode 100644 (file)
index 0000000..62476ad
--- /dev/null
@@ -0,0 +1,63 @@
+Feature: Grafana panels
+
+    Go to some of the grafana performance section and check if
+    panels are populated without any issues
+
+    Background: Log in
+        Given I am logged in
+
+    Scenario Outline: Hosts Overall Performance
+        Given I am on the "hosts" page
+        When I go to the "Overall Performance" tab
+        Then I should see the grafana panel "<panel>"
+        When I view the grafana panel "<panel>"
+        Then I should not see "No Data" in the panel "<panel>"
+
+        Examples:
+            | panel |
+            | OSD Hosts |
+            | AVG CPU Busy |
+            | AVG RAM Utilization |
+            | Physical IOPS |
+            | AVG Disk Utilization |
+            | Network Load |
+            | CPU Busy - Top 10 Hosts |
+            | Network Load - Top 10 Hosts |
+
+    Scenario Outline: RGW Daemon Overall Performance
+        Given I am on the "rgw daemons" page
+        When I go to the "Overall Performance" tab
+        Then I should see the grafana panel "<panel>"
+        When I view the grafana panel "<panel>"
+        Then I should not see No Data in the graph "<panel>"
+        And I should see the legends "<legends>" in the graph "<panel>"
+
+        Examples:
+            | panel | legends |
+            | Total Requests/sec by RGW Instance | foo.ceph-node-00, foo.ceph-node-01, foo.ceph-node-02 |
+            | GET Latencies by RGW Instance | foo.ceph-node-00, foo.ceph-node-01, foo.ceph-node-02 |
+            | Bandwidth by RGW Instance | foo.ceph-node-00, foo.ceph-node-01, foo.ceph-node-02 |
+            | PUT Latencies by RGW Instance | foo.ceph-node-00, foo.ceph-node-01, foo.ceph-node-02 |
+            | Average GET/PUT Latencies | GET AVG, PUT AVG |
+            | Bandwidth Consumed by Type | GETs, PUTs |
+
+    Scenario Outline: RGW per Daemon Performance
+        Given I am on the "rgw daemons" page
+        When I expand the row "<name>"
+        And I go to the "Performance Details" tab
+        Then I should see the grafana panel "<panel>"
+        When I view the grafana panel "<panel>"
+        Then I should not see No Data in the graph "<panel>"
+        And I should see the legends "<name>" in the graph "<panel>"
+
+        Examples:
+            | name | panel |
+            | foo.ceph-node-00 | Bandwidth by HTTP Operation |
+            | foo.ceph-node-00 | HTTP Request Breakdown |
+            | foo.ceph-node-00 | Workload Breakdown |
+            | foo.ceph-node-01 | Bandwidth by HTTP Operation |
+            | foo.ceph-node-01 | HTTP Request Breakdown |
+            | foo.ceph-node-01 | Workload Breakdown |
+            | foo.ceph-node-02 | Bandwidth by HTTP Operation |
+            | foo.ceph-node-02 | HTTP Request Breakdown |
+            | foo.ceph-node-02 | Workload Breakdown |
diff --git a/src/pybind/mgr/dashboard/frontend/cypress/e2e/orchestrator/workflow/01-create-cluster-welcome.feature b/src/pybind/mgr/dashboard/frontend/cypress/e2e/orchestrator/workflow/01-create-cluster-welcome.feature
new file mode 100644 (file)
index 0000000..6ba2fc4
--- /dev/null
@@ -0,0 +1,26 @@
+Feature: Cluster expansion welcome screen
+
+    Go to the welcome screen and decide whether
+    to proceed to wizard or skips to landing page
+
+    Background: Login
+        Given I am logged in
+
+    Scenario: Cluster expansion welcome screen
+        Given I am on the "welcome" page
+        And I should see a button to "Expand Cluster"
+        And I should see a button to "Skip"
+        And I should see a message "Please expand your cluster first"
+
+    Scenario: Go to the Cluster expansion wizard
+        Given I am on the "welcome" page
+        And I should see a button to "Expand Cluster"
+        When I click on "Expand Cluster" button
+        Then I am on the "Add Hosts" section
+
+    Scenario: Skips the process and go to the landing page
+        Given I am on the "welcome" page
+        And I should see a button to "Skip"
+        When I click on "Skip" button
+        And I confirm to "Continue"
+        Then I should be on the "dashboard" page
diff --git a/src/pybind/mgr/dashboard/frontend/cypress/e2e/orchestrator/workflow/02-create-cluster-add-host.feature b/src/pybind/mgr/dashboard/frontend/cypress/e2e/orchestrator/workflow/02-create-cluster-add-host.feature
new file mode 100644 (file)
index 0000000..be49fcb
--- /dev/null
@@ -0,0 +1,76 @@
+Feature: Cluster expansion host addition
+
+    Add some hosts and perform some host related actions like editing the labels
+    and removing the hosts from the cluster and verify all of the actions are performed
+    as expected
+
+    Background: Cluster expansion wizard
+        Given I am logged in
+        And I am on the "welcome" page
+        And I click on "Expand Cluster" button
+
+    Scenario Outline: Add hosts
+        Given I am on the "Add Hosts" section
+        When I click on "Add" button
+        And enter "hostname" "<hostname>"
+        And select options "<labels>"
+        And I click on "Add Host" button
+        Then I should not see the modal
+        And I should see a row with "<hostname>"
+        And I should see row "<hostname>" have "<labels>"
+
+        Examples:
+            | hostname | labels |
+            | ceph-node-01 | mon, mgr |
+            | ceph-node-02 ||
+
+    Scenario Outline: Remove hosts
+        Given I am on the "Add Hosts" section
+        And I should see a row with "<hostname>"
+        When I select a row "<hostname>"
+        And I click on "Remove" button from the table actions
+        Then I should see the modal
+        And I check the tick box in modal
+        And I click on "Remove Host" button
+        Then I should not see the modal
+        And I should not see a row with "<hostname>"
+
+        Examples:
+            | hostname |
+            | ceph-node-01 |
+            | ceph-node-02 |
+
+    Scenario: Add hosts using pattern 'ceph-node-[01-02]'
+        Given I am on the "Add Hosts" section
+        When I click on "Add" button
+        And enter "hostname" "ceph-node-[01-02]"
+        And I click on "Add Host" button
+        Then I should not see the modal
+        And I should see rows with following entries
+            | hostname |
+            | ceph-node-01 |
+            | ceph-node-02 |
+
+    Scenario: Add exisiting host and verify it failed
+        Given I am on the "Add Hosts" section
+        And I should see a row with "ceph-node-00"
+        When I click on "Add" button
+        And enter "hostname" "ceph-node-00"
+        Then I should see an error in "hostname" field
+
+    Scenario Outline: Add and remove labels on host
+        Given I am on the "Add Hosts" section
+        When I select a row "<hostname>"
+        And I click on "Edit" button from the table actions
+        And "add" option "<labels>"
+        And I click on "Edit Host" button
+        Then I should see row "<hostname>" have "<labels>"
+        When I select a row "<hostname>"
+        And I click on "Edit" button from the table actions
+        And "remove" option "<labels>"
+        And I click on "Edit Host" button
+        Then I should see row "<hostname>" does not have "<labels>"
+
+        Examples:
+            | hostname | labels |
+            | ceph-node-01 | foo |
diff --git a/src/pybind/mgr/dashboard/frontend/cypress/e2e/orchestrator/workflow/03-create-cluster-create-services.e2e-spec.ts b/src/pybind/mgr/dashboard/frontend/cypress/e2e/orchestrator/workflow/03-create-cluster-create-services.e2e-spec.ts
new file mode 100644 (file)
index 0000000..745a2ec
--- /dev/null
@@ -0,0 +1,47 @@
+/* tslint:disable*/
+import {
+  CreateClusterServicePageHelper,
+  CreateClusterWizardHelper
+} from '../../cluster/create-cluster.po';
+/* tslint:enable*/
+
+describe('Create cluster create services page', () => {
+  const createCluster = new CreateClusterWizardHelper();
+  const createClusterServicePage = new CreateClusterServicePageHelper();
+
+  const createService = (serviceType: string, serviceName: string, count = 1) => {
+    cy.get('[aria-label=Create]').first().click();
+    createClusterServicePage.addService(serviceType, false, count);
+    createClusterServicePage.checkExist(serviceName, true);
+  };
+
+  beforeEach(() => {
+    cy.login();
+    Cypress.Cookies.preserveOnce('token');
+    createCluster.navigateTo();
+    createCluster.createCluster();
+    cy.get('.nav-link').contains('Create Services').click();
+  });
+
+  it('should check if title contains Create Services', () => {
+    cy.get('.title').should('contain.text', 'Create Services');
+  });
+
+  describe('when Orchestrator is available', () => {
+    const serviceName = 'mds.test';
+
+    it('should create an mds service', () => {
+      createService('mds', serviceName);
+    });
+
+    it('should edit a service', () => {
+      const daemonCount = '2';
+      createClusterServicePage.editService(serviceName, daemonCount);
+      createClusterServicePage.expectPlacementCount(serviceName, daemonCount);
+    });
+
+    it('should delete mds service', () => {
+      createClusterServicePage.deleteService('mds.test');
+    });
+  });
+});
diff --git a/src/pybind/mgr/dashboard/frontend/cypress/e2e/orchestrator/workflow/04-create-cluster-create-osds.e2e-spec.ts b/src/pybind/mgr/dashboard/frontend/cypress/e2e/orchestrator/workflow/04-create-cluster-create-osds.e2e-spec.ts
new file mode 100644 (file)
index 0000000..2426243
--- /dev/null
@@ -0,0 +1,41 @@
+/* tslint:disable*/
+import { CreateClusterWizardHelper } from '../../cluster/create-cluster.po';
+import { OSDsPageHelper } from '../../cluster/osds.po';
+/* tslint:enable*/
+
+const osds = new OSDsPageHelper();
+
+describe('Create cluster create osds page', () => {
+  const createCluster = new CreateClusterWizardHelper();
+
+  beforeEach(() => {
+    cy.login();
+    Cypress.Cookies.preserveOnce('token');
+    createCluster.navigateTo();
+    createCluster.createCluster();
+    cy.get('.nav-link').contains('Create OSDs').click();
+  });
+
+  it('should check if title contains Create OSDs', () => {
+    cy.get('.title').should('contain.text', 'Create OSDs');
+  });
+
+  describe('when Orchestrator is available', () => {
+    it('should create OSDs', () => {
+      const hostnames = ['ceph-node-00', 'ceph-node-01'];
+      for (const hostname of hostnames) {
+        osds.create('hdd', hostname, true);
+
+        // Go to the Review section and Expand the cluster
+        // because the drive group spec is only stored
+        // in frontend and will be lost when refreshed
+        cy.get('.nav-link').contains('Review').click();
+        cy.get('button[aria-label="Next"]').click();
+        cy.get('cd-dashboard').should('exist');
+        createCluster.navigateTo();
+        createCluster.createCluster();
+        cy.get('.nav-link').contains('Create OSDs').click();
+      }
+    });
+  });
+});
diff --git a/src/pybind/mgr/dashboard/frontend/cypress/e2e/orchestrator/workflow/05-create-cluster-review.e2e-spec.ts b/src/pybind/mgr/dashboard/frontend/cypress/e2e/orchestrator/workflow/05-create-cluster-review.e2e-spec.ts
new file mode 100644 (file)
index 0000000..f93ad7a
--- /dev/null
@@ -0,0 +1,67 @@
+/* tslint:disable*/
+import {
+  CreateClusterHostPageHelper,
+  CreateClusterWizardHelper
+} from '../../cluster/create-cluster.po';
+/* tslint:enable*/
+
+describe('Create Cluster Review page', () => {
+  const createCluster = new CreateClusterWizardHelper();
+  const createClusterHostPage = new CreateClusterHostPageHelper();
+
+  beforeEach(() => {
+    cy.login();
+    Cypress.Cookies.preserveOnce('token');
+    createCluster.navigateTo();
+    createCluster.createCluster();
+
+    cy.get('.nav-link').contains('Review').click();
+  });
+
+  describe('navigation link test', () => {
+    it('should check if active nav-link is of Review section', () => {
+      cy.get('.nav-link.active').should('contain.text', 'Review');
+    });
+  });
+
+  describe('fields check', () => {
+    it('should check cluster resources table is present', () => {
+      // check for table header 'Cluster Resources'
+      createCluster.getLegends().its(0).should('have.text', 'Cluster Resources');
+
+      // check for fields in table
+      createCluster.getStatusTables().should('contain.text', 'Hosts');
+      createCluster.getStatusTables().should('contain.text', 'Storage Capacity');
+      createCluster.getStatusTables().should('contain.text', 'CPUs');
+      createCluster.getStatusTables().should('contain.text', 'Memory');
+    });
+
+    it('should check Host Details table is present', () => {
+      // check for there to be two tables
+      createCluster.getDataTables().should('have.length', 1);
+
+      // verify correct columns on Host Details table
+      createCluster.getDataTableHeaders(0).contains('Hostname');
+
+      createCluster.getDataTableHeaders(0).contains('Labels');
+
+      createCluster.getDataTableHeaders(0).contains('CPUs');
+
+      createCluster.getDataTableHeaders(0).contains('Cores');
+
+      createCluster.getDataTableHeaders(0).contains('Total Memory');
+
+      createCluster.getDataTableHeaders(0).contains('Raw Capacity');
+
+      createCluster.getDataTableHeaders(0).contains('HDDs');
+
+      createCluster.getDataTableHeaders(0).contains('Flash');
+
+      createCluster.getDataTableHeaders(0).contains('NICs');
+    });
+
+    it('should check default host name is present', () => {
+      createClusterHostPage.check_for_host();
+    });
+  });
+});
diff --git a/src/pybind/mgr/dashboard/frontend/cypress/e2e/orchestrator/workflow/06-cluster-check.e2e-spec.ts b/src/pybind/mgr/dashboard/frontend/cypress/e2e/orchestrator/workflow/06-cluster-check.e2e-spec.ts
new file mode 100644 (file)
index 0000000..94cb36c
--- /dev/null
@@ -0,0 +1,83 @@
+/* tslint:disable*/
+import { CreateClusterWizardHelper } from '../../cluster/create-cluster.po';
+import { HostsPageHelper } from '../../cluster/hosts.po';
+import { ServicesPageHelper } from '../../cluster/services.po';
+/* tslint:enable*/
+
+describe('when cluster creation is completed', () => {
+  const createCluster = new CreateClusterWizardHelper();
+  const services = new ServicesPageHelper();
+  const hosts = new HostsPageHelper();
+
+  const hostnames = ['ceph-node-00', 'ceph-node-01', 'ceph-node-02', 'ceph-node-03'];
+
+  beforeEach(() => {
+    cy.login();
+    Cypress.Cookies.preserveOnce('token');
+  });
+
+  it('should redirect to dashboard landing page after cluster creation', () => {
+    createCluster.navigateTo();
+    createCluster.createCluster();
+
+    // Explicitly skip OSD Creation Step so that it prevents from
+    // deploying OSDs to the hosts automatically.
+    cy.get('.nav-link').contains('Create OSDs').click();
+    cy.get('button[aria-label="Skip this step"]').click();
+
+    cy.get('.nav-link').contains('Review').click();
+    cy.get('button[aria-label="Next"]').click();
+    cy.get('cd-dashboard').should('exist');
+  });
+
+  describe('Hosts page', () => {
+    beforeEach(() => {
+      hosts.navigateTo();
+    });
+
+    it('should add one more host', () => {
+      hosts.navigateTo('add');
+      hosts.add(hostnames[3]);
+      hosts.checkExist(hostnames[3], true);
+    });
+
+    it('should check if monitoring stacks are running on the root host', { retries: 2 }, () => {
+      const monitoringStack = ['alertmanager', 'grafana', 'node-exporter', 'prometheus'];
+      hosts.clickTab('cd-host-details', 'ceph-node-00', 'Daemons');
+      for (const daemon of monitoringStack) {
+        cy.get('cd-host-details').within(() => {
+          services.checkServiceStatus(daemon);
+        });
+      }
+    });
+
+    it('should have removed "_no_schedule" label', () => {
+      for (const hostname of hostnames) {
+        hosts.checkLabelExists(hostname, ['_no_schedule'], false);
+      }
+    });
+
+    it('should display inventory', () => {
+      hosts.clickTab('cd-host-details', hostnames[1], 'Physical Disks');
+      cy.get('cd-host-details').within(() => {
+        hosts.getTableCount('total').should('be.gte', 0);
+      });
+    });
+
+    it('should display daemons', () => {
+      hosts.clickTab('cd-host-details', hostnames[1], 'Daemons');
+      cy.get('cd-host-details').within(() => {
+        hosts.getTableCount('total').should('be.gte', 0);
+      });
+    });
+
+    it('should check if mon daemon is running on all hosts', () => {
+      for (const hostname of hostnames) {
+        hosts.clickTab('cd-host-details', hostname, 'Daemons');
+        cy.get('cd-host-details').within(() => {
+          services.checkServiceStatus('mon');
+        });
+      }
+    });
+  });
+});
diff --git a/src/pybind/mgr/dashboard/frontend/cypress/e2e/orchestrator/workflow/07-osds.e2e-spec.ts b/src/pybind/mgr/dashboard/frontend/cypress/e2e/orchestrator/workflow/07-osds.e2e-spec.ts
new file mode 100644 (file)
index 0000000..a0a1dd0
--- /dev/null
@@ -0,0 +1,24 @@
+/* tslint:disable*/
+import { OSDsPageHelper } from '../../cluster/osds.po';
+/* tslint:enable*/
+
+describe('OSDs page', () => {
+  const osds = new OSDsPageHelper();
+
+  beforeEach(() => {
+    cy.login();
+    Cypress.Cookies.preserveOnce('token');
+    osds.navigateTo();
+  });
+
+  it('should check if atleast 3 osds are created', { retries: 3 }, () => {
+    // we have created a total of more than 3 osds throughout
+    // the whole tests so ensuring that atleast
+    // 3 osds are listed in the table. Since the OSD
+    // creation can take more time going with
+    // retry of 3
+    for (let id = 0; id < 3; id++) {
+      osds.checkStatus(id, ['in', 'up']);
+    }
+  });
+});
diff --git a/src/pybind/mgr/dashboard/frontend/cypress/e2e/orchestrator/workflow/08-hosts.e2e-spec.ts b/src/pybind/mgr/dashboard/frontend/cypress/e2e/orchestrator/workflow/08-hosts.e2e-spec.ts
new file mode 100644 (file)
index 0000000..6e8c632
--- /dev/null
@@ -0,0 +1,49 @@
+/* tslint:disable*/
+import { HostsPageHelper } from '../../cluster/hosts.po';
+import { ServicesPageHelper } from '../../cluster/services.po';
+/* tslint:enable*/
+
+describe('Host Page', () => {
+  const hosts = new HostsPageHelper();
+  const services = new ServicesPageHelper();
+
+  const hostnames = ['ceph-node-00', 'ceph-node-01', 'ceph-node-02', 'ceph-node-03'];
+
+  beforeEach(() => {
+    cy.login();
+    Cypress.Cookies.preserveOnce('token');
+    hosts.navigateTo();
+  });
+
+  // rgw is needed for testing the force maintenance
+  it('should create rgw services', () => {
+    services.navigateTo('create');
+    services.addService('rgw', false, 4);
+    services.checkExist('rgw.foo', true);
+  });
+
+  it('should check if rgw daemon is running on all hosts', () => {
+    for (const hostname of hostnames) {
+      hosts.clickTab('cd-host-details', hostname, 'Daemons');
+      cy.get('cd-host-details').within(() => {
+        services.checkServiceStatus('rgw');
+      });
+    }
+  });
+
+  it('should force maintenance and exit', () => {
+    hosts.maintenance(hostnames[3], true, true);
+  });
+
+  it('should drain, remove and add the host back', () => {
+    hosts.drain(hostnames[3]);
+    hosts.remove(hostnames[3]);
+    hosts.navigateTo('add');
+    hosts.add(hostnames[3]);
+    hosts.checkExist(hostnames[3], true);
+  });
+
+  it('should show the exact count of daemons', () => {
+    hosts.checkServiceInstancesExist(hostnames[0], ['mgr: 1', 'prometheus: 1']);
+  });
+});
diff --git a/src/pybind/mgr/dashboard/frontend/cypress/e2e/orchestrator/workflow/09-services.e2e-spec.ts b/src/pybind/mgr/dashboard/frontend/cypress/e2e/orchestrator/workflow/09-services.e2e-spec.ts
new file mode 100644 (file)
index 0000000..91f2de5
--- /dev/null
@@ -0,0 +1,133 @@
+/* tslint:disable*/
+import { ServicesPageHelper } from '../../cluster/services.po';
+/* tslint:enable*/
+
+describe('Services page', () => {
+  const services = new ServicesPageHelper();
+  const mdsDaemonName = 'mds.test';
+  beforeEach(() => {
+    cy.login();
+    Cypress.Cookies.preserveOnce('token');
+    services.navigateTo();
+  });
+
+  it('should check if rgw service is created', () => {
+    services.checkExist('rgw.foo', true);
+  });
+
+  it('should create an mds service', () => {
+    services.navigateTo('create');
+    services.addService('mds', false);
+    services.checkExist(mdsDaemonName, true);
+
+    services.clickServiceTab(mdsDaemonName, 'Daemons');
+    cy.get('cd-service-details').within(() => {
+      services.checkServiceStatus(mdsDaemonName);
+    });
+  });
+
+  it('should stop a daemon', () => {
+    services.clickServiceTab(mdsDaemonName, 'Daemons');
+    services.checkServiceStatus(mdsDaemonName);
+
+    services.daemonAction('mds', 'stop');
+    cy.get('cd-service-details').within(() => {
+      services.checkServiceStatus(mdsDaemonName, 'stopped');
+    });
+  });
+
+  it('should restart a daemon', () => {
+    services.checkExist(mdsDaemonName, true);
+    services.clickServiceTab(mdsDaemonName, 'Daemons');
+    services.daemonAction('mds', 'restart');
+    cy.get('cd-service-details').within(() => {
+      services.checkServiceStatus(mdsDaemonName, 'running');
+    });
+  });
+
+  it('should redeploy a daemon', () => {
+    services.checkExist(mdsDaemonName, true);
+    services.clickServiceTab(mdsDaemonName, 'Daemons');
+
+    services.daemonAction('mds', 'stop');
+    cy.get('cd-service-details').within(() => {
+      services.checkServiceStatus(mdsDaemonName, 'stopped');
+    });
+    services.daemonAction('mds', 'redeploy');
+    cy.get('cd-service-details').within(() => {
+      services.checkServiceStatus(mdsDaemonName, 'running');
+    });
+  });
+
+  it('should start a daemon', () => {
+    services.checkExist(mdsDaemonName, true);
+    services.clickServiceTab(mdsDaemonName, 'Daemons');
+
+    services.daemonAction('mds', 'stop');
+    cy.get('cd-service-details').within(() => {
+      services.checkServiceStatus(mdsDaemonName, 'stopped');
+    });
+    services.daemonAction('mds', 'start');
+    cy.get('cd-service-details').within(() => {
+      services.checkServiceStatus(mdsDaemonName, 'running');
+    });
+  });
+
+  it('should delete an mds service', () => {
+    services.deleteService(mdsDaemonName);
+  });
+
+  it('should create and delete snmp-gateway service with version V2c', () => {
+    services.navigateTo('create');
+    services.addService('snmp-gateway', false, 1, 'V2c');
+    services.checkExist('snmp-gateway', true);
+
+    services.clickServiceTab('snmp-gateway', 'Daemons');
+    cy.get('cd-service-details').within(() => {
+      services.checkServiceStatus('snmp-gateway');
+    });
+
+    services.deleteService('snmp-gateway');
+  });
+
+  it('should create and delete snmp-gateway service with version V3', () => {
+    services.navigateTo('create');
+    services.addService('snmp-gateway', false, 1, 'V3', true);
+    services.checkExist('snmp-gateway', true);
+
+    services.clickServiceTab('snmp-gateway', 'Daemons');
+    cy.get('cd-service-details').within(() => {
+      services.checkServiceStatus('snmp-gateway');
+    });
+
+    services.deleteService('snmp-gateway');
+  });
+
+  it('should create and delete snmp-gateway service with version V3 and w/o privacy protocol', () => {
+    services.navigateTo('create');
+    services.addService('snmp-gateway', false, 1, 'V3', false);
+    services.checkExist('snmp-gateway', true);
+
+    services.clickServiceTab('snmp-gateway', 'Daemons');
+    cy.get('cd-service-details').within(() => {
+      services.checkServiceStatus('snmp-gateway');
+    });
+
+    services.deleteService('snmp-gateway');
+  });
+
+  it('should create ingress as unmanaged', () => {
+    services.navigateTo('create');
+    services.addService('ingress', false, undefined, undefined, undefined, true);
+    services.checkExist('ingress.rgw.foo', true);
+    services.isUnmanaged('ingress.rgw.foo', true);
+    services.deleteService('ingress.rgw.foo');
+  });
+
+  it('should check if exporter daemons are running', () => {
+    services.clickServiceTab('ceph-exporter', 'Daemons');
+    cy.get('cd-service-details').within(() => {
+      services.checkServiceStatus('ceph-exporter', 'running');
+    });
+  });
+});
diff --git a/src/pybind/mgr/dashboard/frontend/cypress/e2e/orchestrator/workflow/10-nfs-exports.e2e-spec.ts b/src/pybind/mgr/dashboard/frontend/cypress/e2e/orchestrator/workflow/10-nfs-exports.e2e-spec.ts
new file mode 100644 (file)
index 0000000..f97509d
--- /dev/null
@@ -0,0 +1,83 @@
+/* tslint:disable*/
+import { ServicesPageHelper } from '../../cluster/services.po';
+import { NFSPageHelper } from '../../orchestrator/workflow/nfs/nfs-export.po';
+import { BucketsPageHelper } from '../../rgw/buckets.po';
+/* tslint:enable*/
+
+describe('nfsExport page', () => {
+  const nfsExport = new NFSPageHelper();
+  const services = new ServicesPageHelper();
+  const buckets = new BucketsPageHelper();
+  const bucketName = 'e2e.nfs.bucket';
+  // @TODO: uncomment this when a CephFS volume can be created through Dashboard.
+  // const fsPseudo = '/fsPseudo';
+  const rgwPseudo = '/rgwPseudo';
+  const editPseudo = '/editPseudo';
+  const backends = ['CephFS', 'Object Gateway'];
+  const squash = 'no_root_squash';
+  const client: object = { addresses: '192.168.0.10' };
+
+  beforeEach(() => {
+    cy.login();
+    Cypress.Cookies.preserveOnce('token');
+    nfsExport.navigateTo();
+  });
+
+  describe('breadcrumb test', () => {
+    it('should open and show breadcrumb', () => {
+      nfsExport.expectBreadcrumbText('NFS');
+    });
+  });
+
+  describe('Create, edit and delete', () => {
+    it('should create an NFS cluster', () => {
+      services.navigateTo('create');
+
+      services.addService('nfs');
+
+      services.checkExist('nfs.testnfs', true);
+      services.clickServiceTab('nfs.testnfs', 'Daemons');
+      services.checkServiceStatus('nfs');
+    });
+
+    it('should create a nfs-export with RGW backend', () => {
+      buckets.navigateTo('create');
+      buckets.create(bucketName, 'dashboard', 'default-placement');
+
+      nfsExport.navigateTo();
+      nfsExport.existTableCell(rgwPseudo, false);
+      nfsExport.navigateTo('create');
+      nfsExport.create(backends[1], squash, client, rgwPseudo, bucketName);
+      nfsExport.existTableCell(rgwPseudo);
+    });
+
+    // @TODO: uncomment this when a CephFS volume can be created through Dashboard.
+    // it('should create a nfs-export with CephFS backend', () => {
+    //   nfsExport.navigateTo();
+    //   nfsExport.existTableCell(fsPseudo, false);
+    //   nfsExport.navigateTo('create');
+    //   nfsExport.create(backends[0], squash, client, fsPseudo);
+    //   nfsExport.existTableCell(fsPseudo);
+    // });
+
+    it('should show Clients', () => {
+      nfsExport.clickTab('cd-nfs-details', rgwPseudo, 'Clients (1)');
+      cy.get('cd-nfs-details').within(() => {
+        nfsExport.getTableCount('total').should('be.gte', 0);
+      });
+    });
+
+    it('should edit an export', () => {
+      nfsExport.editExport(rgwPseudo, editPseudo);
+
+      nfsExport.existTableCell(editPseudo);
+    });
+
+    it('should delete exports and bucket', () => {
+      nfsExport.delete(editPseudo);
+
+      buckets.navigateTo();
+      buckets.delete(bucketName);
+    });
+  });
+});
diff --git a/src/pybind/mgr/dashboard/frontend/cypress/e2e/orchestrator/workflow/nfs/nfs-export.po.ts b/src/pybind/mgr/dashboard/frontend/cypress/e2e/orchestrator/workflow/nfs/nfs-export.po.ts
new file mode 100644 (file)
index 0000000..c700ef0
--- /dev/null
@@ -0,0 +1,52 @@
+/* tslint:disable*/
+import { PageHelper } from '../../../page-helper.po';
+/* tslint:enable*/
+
+const pages = {
+  index: { url: '#/nfs', id: 'cd-nfs-list' },
+  create: { url: '#/nfs/create', id: 'cd-nfs-form' }
+};
+
+export class NFSPageHelper extends PageHelper {
+  pages = pages;
+
+  @PageHelper.restrictTo(pages.create.url)
+  create(backend: string, squash: string, client: object, pseudo: string, rgwPath?: string) {
+    this.selectOption('cluster_id', 'testnfs');
+    // select a storage backend
+    this.selectOption('name', backend);
+    if (backend === 'CephFS') {
+      this.selectOption('fs_name', 'myfs');
+
+      cy.get('#security_label').click({ force: true });
+    } else {
+      cy.get('input[data-testid=rgw_path]').type(rgwPath);
+    }
+
+    cy.get('input[name=pseudo]').type(pseudo);
+    this.selectOption('squash', squash);
+
+    // Add clients
+    cy.get('button[name=add_client]').click({ force: true });
+    cy.get('input[name=addresses]').type(client['addresses']);
+
+    // Check if we can remove clients and add it again
+    cy.get('span[name=remove_client]').click({ force: true });
+    cy.get('button[name=add_client]').click({ force: true });
+    cy.get('input[name=addresses]').type(client['addresses']);
+
+    cy.get('cd-submit-button').click();
+  }
+
+  editExport(pseudo: string, editPseudo: string) {
+    this.navigateEdit(pseudo);
+
+    cy.get('input[name=pseudo]').clear().type(editPseudo);
+
+    cy.get('cd-submit-button').click();
+
+    // Click the export and check its details table for updated content
+    this.getExpandCollapseElement(editPseudo).click();
+    cy.get('.active.tab-pane').should('contain.text', editPseudo);
+  }
+}
diff --git a/src/pybind/mgr/dashboard/frontend/cypress/e2e/page-helper.po.ts b/src/pybind/mgr/dashboard/frontend/cypress/e2e/page-helper.po.ts
new file mode 100644 (file)
index 0000000..e4bbd3f
--- /dev/null
@@ -0,0 +1,309 @@
+interface Page {
+  url: string;
+  id: string;
+}
+
+export abstract class PageHelper {
+  pages: Record<string, Page>;
+
+  /**
+   * Decorator to be used on Helper methods to restrict access to one particular URL.  This shall
+   * help developers to prevent and highlight mistakes.  It also reduces boilerplate code and by
+   * thus, increases readability.
+   */
+  static restrictTo(page: string): Function {
+    return (target: any, propertyKey: string, descriptor: PropertyDescriptor) => {
+      const fn: Function = descriptor.value;
+      descriptor.value = function (...args: any) {
+        cy.location('hash').should((url) => {
+          expect(url).to.eq(
+            page,
+            `Method ${target.constructor.name}::${propertyKey} is supposed to be ` +
+              `run on path "${page}", but was run on URL "${url}"`
+          );
+        });
+        fn.apply(this, args);
+      };
+    };
+  }
+
+  /**
+   * Navigates to the given page or to index.
+   * Waits until the page component is loaded
+   */
+  navigateTo(name: string = null) {
+    name = name || 'index';
+    const page = this.pages[name];
+
+    cy.visit(page.url);
+    cy.get(page.id);
+  }
+
+  /**
+   * Navigates back and waits for the hash to change
+   */
+  navigateBack() {
+    cy.location('hash').then((hash) => {
+      cy.go('back');
+      cy.location('hash').should('not.be', hash);
+    });
+  }
+
+  /**
+   * Navigates to the edit page
+   */
+  navigateEdit(name: string, select = true, breadcrumb = true) {
+    if (select) {
+      this.navigateTo();
+      this.getFirstTableCell(name).click();
+    }
+    cy.contains('Creating...').should('not.exist');
+    cy.contains('button', 'Edit').click();
+    if (breadcrumb) {
+      this.expectBreadcrumbText('Edit');
+    }
+  }
+
+  /**
+   * Checks the active breadcrumb value.
+   */
+  expectBreadcrumbText(text: string) {
+    cy.get('.breadcrumb-item.active').should('have.text', text);
+  }
+
+  getTabs() {
+    return cy.get('.nav.nav-tabs a');
+  }
+
+  getTab(tabName: string) {
+    return cy.contains('.nav.nav-tabs a', tabName);
+  }
+
+  getTabText(index: number) {
+    return this.getTabs().its(index).text();
+  }
+
+  getTabsCount(): any {
+    return this.getTabs().its('length');
+  }
+
+  /**
+   * Helper method to navigate/click a tab inside the expanded table row.
+   * @param selector The selector of the expanded table row.
+   * @param name The name of the row which should expand.
+   * @param tabName Name of the tab to be navigated/clicked.
+   */
+  clickTab(selector: string, name: string, tabName: string) {
+    this.getExpandCollapseElement(name).click();
+    cy.get(selector).within(() => {
+      this.getTab(tabName).click();
+    });
+  }
+
+  /**
+   * Helper method to select an option inside a select element.
+   * This method will also expect that the option was set.
+   * @param option The option text (not value) to be selected.
+   */
+  selectOption(selectionName: string, option: string) {
+    cy.get(`select[name=${selectionName}]`).select(option);
+    return this.expectSelectOption(selectionName, option);
+  }
+
+  /**
+   * Helper method to expect a set option inside a select element.
+   * @param option The selected option text (not value) that is to
+   *   be expected.
+   */
+  expectSelectOption(selectionName: string, option: string) {
+    return cy.get(`select[name=${selectionName}] option:checked`).contains(option);
+  }
+
+  getLegends() {
+    return cy.get('legend');
+  }
+
+  getToast() {
+    return cy.get('.ngx-toastr');
+  }
+
+  /**
+   * Waits for the table to load its data
+   * Should be used in all methods that access the datatable
+   */
+  private waitDataTableToLoad() {
+    cy.get('cd-table').should('exist');
+    cy.get('datatable-scroller, .empty-row');
+  }
+
+  getDataTables() {
+    this.waitDataTableToLoad();
+
+    return cy.get('cd-table .dataTables_wrapper');
+  }
+
+  private getTableCountSpan(spanType: 'selected' | 'found' | 'total') {
+    return cy.contains('.datatable-footer-inner .page-count span', spanType);
+  }
+
+  // Get 'selected', 'found', or 'total' row count of a table.
+  getTableCount(spanType: 'selected' | 'found' | 'total') {
+    this.waitDataTableToLoad();
+    return this.getTableCountSpan(spanType).then(($elem) => {
+      const text = $elem
+        .filter((_i, e) => e.innerText.includes(spanType))
+        .first()
+        .text();
+
+      return Number(text.match(/(\d+)\s+\w*/)[1]);
+    });
+  }
+
+  // Wait until selected', 'found', or 'total' row count of a table equal to a number.
+  expectTableCount(spanType: 'selected' | 'found' | 'total', count: number) {
+    this.waitDataTableToLoad();
+    this.getTableCountSpan(spanType).should(($elem) => {
+      const text = $elem.first().text();
+      expect(Number(text.match(/(\d+)\s+\w*/)[1])).to.equal(count);
+    });
+  }
+
+  getTableRow(content: string) {
+    this.waitDataTableToLoad();
+
+    this.searchTable(content);
+    return cy.contains('.datatable-body-row', content);
+  }
+
+  getTableRows() {
+    this.waitDataTableToLoad();
+
+    return cy.get('datatable-row-wrapper');
+  }
+
+  /**
+   * Returns the first table cell.
+   * Optionally, you can specify the content of the cell.
+   */
+  getFirstTableCell(content?: string) {
+    this.waitDataTableToLoad();
+
+    if (content) {
+      this.searchTable(content);
+      return cy.contains('.datatable-body-cell-label', content);
+    } else {
+      return cy.get('.datatable-body-cell-label').first();
+    }
+  }
+
+  getTableCell(columnIndex: number, exactContent: string, partialMatch = false) {
+    this.waitDataTableToLoad();
+    this.clearTableSearchInput();
+    this.searchTable(exactContent);
+    if (partialMatch) {
+      return cy.contains(
+        `datatable-body-row datatable-body-cell:nth-child(${columnIndex})`,
+        exactContent
+      );
+    }
+    return cy.contains(
+      `datatable-body-row datatable-body-cell:nth-child(${columnIndex})`,
+      new RegExp(`^${exactContent}$`)
+    );
+  }
+
+  existTableCell(name: string, oughtToBePresent = true) {
+    const waitRule = oughtToBePresent ? 'be.visible' : 'not.exist';
+    this.getFirstTableCell(name).should(waitRule);
+  }
+
+  getExpandCollapseElement(content?: string) {
+    this.waitDataTableToLoad();
+
+    if (content) {
+      return cy.contains('.datatable-body-row', content).find('.tc_expand-collapse');
+    } else {
+      return cy.get('.tc_expand-collapse').first();
+    }
+  }
+
+  /**
+   * Gets column headers of table
+   */
+  getDataTableHeaders(index = 0) {
+    this.waitDataTableToLoad();
+
+    return cy.get('.datatable-header').its(index).find('.datatable-header-cell');
+  }
+
+  /**
+   * Grabs striped tables
+   */
+  getStatusTables() {
+    return cy.get('.table.table-striped');
+  }
+
+  filterTable(name: string, option: string) {
+    this.waitDataTableToLoad();
+
+    cy.get('.tc_filter_name > button').click();
+    cy.contains(`.tc_filter_name .dropdown-item`, name).click();
+
+    cy.get('.tc_filter_option > button').click();
+    cy.contains(`.tc_filter_option .dropdown-item`, option).click();
+  }
+
+  setPageSize(size: string) {
+    cy.get('cd-table .dataTables_paginate input').first().clear({ force: true }).type(size);
+  }
+
+  searchTable(text: string) {
+    this.waitDataTableToLoad();
+
+    this.setPageSize('10');
+    cy.get('[aria-label=search]').first().clear({ force: true }).type(text);
+  }
+
+  clearTableSearchInput() {
+    this.waitDataTableToLoad();
+
+    return cy.get('cd-table .search button').first().click();
+  }
+
+  // Click the action button
+  clickActionButton(action: string) {
+    cy.get('.table-actions button.dropdown-toggle').first().click(); // open submenu
+    cy.get(`button.${action}`).click(); // click on "action" menu item
+  }
+
+  /**
+   * This is a generic method to delete table rows.
+   * It will select the first row that contains the provided name and delete it.
+   * After that it will wait until the row is no longer displayed.
+   * @param name The string to search in table cells.
+   * @param columnIndex If provided, search string in columnIndex column.
+   */
+  delete(name: string, columnIndex?: number, section?: string) {
+    // Selects row
+    const getRow = columnIndex
+      ? this.getTableCell.bind(this, columnIndex)
+      : this.getFirstTableCell.bind(this);
+    getRow(name).click();
+    let action: string;
+    section === 'hosts' ? (action = 'remove') : (action = 'delete');
+
+    // Clicks on table Delete/Remove button
+    this.clickActionButton(action);
+
+    // Convert action to SentenceCase and Confirms deletion
+    const actionUpperCase = action.charAt(0).toUpperCase() + action.slice(1);
+    cy.get('cd-modal .custom-control-label').click();
+    cy.contains('cd-modal button', actionUpperCase).click();
+
+    // Wait for modal to close
+    cy.get('cd-modal').should('not.exist');
+
+    // Waits for item to be removed from table
+    getRow(name).should('not.exist');
+  }
+}
diff --git a/src/pybind/mgr/dashboard/frontend/cypress/e2e/pools/pools.e2e-spec.ts b/src/pybind/mgr/dashboard/frontend/cypress/e2e/pools/pools.e2e-spec.ts
new file mode 100644 (file)
index 0000000..b4c3c75
--- /dev/null
@@ -0,0 +1,54 @@
+import { PoolPageHelper } from './pools.po';
+
+describe('Pools page', () => {
+  const pools = new PoolPageHelper();
+  const poolName = 'pool_e2e_pool-test';
+
+  beforeEach(() => {
+    cy.login();
+    Cypress.Cookies.preserveOnce('token');
+    pools.navigateTo();
+  });
+
+  describe('breadcrumb and tab tests', () => {
+    it('should open and show breadcrumb', () => {
+      pools.expectBreadcrumbText('Pools');
+    });
+
+    it('should show two tabs', () => {
+      pools.getTabsCount().should('equal', 2);
+    });
+
+    it('should show pools list tab at first', () => {
+      pools.getTabText(0).should('eq', 'Pools List');
+    });
+
+    it('should show overall performance as a second tab', () => {
+      pools.getTabText(1).should('eq', 'Overall Performance');
+    });
+  });
+
+  describe('Create, update and destroy', () => {
+    it('should create a pool', () => {
+      pools.existTableCell(poolName, false);
+      pools.navigateTo('create');
+      pools.create(poolName, 8, 'rbd');
+      pools.existTableCell(poolName);
+    });
+
+    it('should edit a pools placement group', () => {
+      pools.existTableCell(poolName);
+      pools.edit_pool_pg(poolName, 32);
+    });
+
+    it('should show updated configuration field values', () => {
+      pools.existTableCell(poolName);
+      const bpsLimit = '4 B/s';
+      pools.edit_pool_configuration(poolName, bpsLimit);
+    });
+
+    it('should delete a pool', () => {
+      pools.delete(poolName);
+    });
+  });
+});
diff --git a/src/pybind/mgr/dashboard/frontend/cypress/e2e/pools/pools.po.ts b/src/pybind/mgr/dashboard/frontend/cypress/e2e/pools/pools.po.ts
new file mode 100644 (file)
index 0000000..7cca96a
--- /dev/null
@@ -0,0 +1,70 @@
+import { PageHelper } from '../page-helper.po';
+
+const pages = {
+  index: { url: '#/pool', id: 'cd-pool-list' },
+  create: { url: '#/pool/create', id: 'cd-pool-form' }
+};
+
+export class PoolPageHelper extends PageHelper {
+  pages = pages;
+
+  private isPowerOf2(n: number) {
+    // tslint:disable-next-line: no-bitwise
+    return expect((n & (n - 1)) === 0, `Placement groups ${n} are not a power of 2`).to.be.true;
+  }
+
+  @PageHelper.restrictTo(pages.create.url)
+  create(name: string, placement_groups: number, ...apps: string[]) {
+    cy.get('input[name=name]').clear().type(name);
+
+    this.isPowerOf2(placement_groups);
+
+    this.selectOption('poolType', 'replicated');
+
+    this.expectSelectOption('pgAutoscaleMode', 'on');
+    this.selectOption('pgAutoscaleMode', 'off'); // To show pgNum field
+    cy.get('input[name=pgNum]').clear().type(`${placement_groups}`);
+    this.setApplications(apps);
+    cy.get('cd-submit-button').click();
+  }
+
+  edit_pool_pg(name: string, new_pg: number, wait = true) {
+    this.isPowerOf2(new_pg);
+    this.navigateEdit(name);
+
+    cy.get('input[name=pgNum]').clear().type(`${new_pg}`);
+    cy.get('cd-submit-button').click();
+    const str = `${new_pg} active+clean`;
+    this.getTableRow(name);
+    if (wait) {
+      this.getTableRow(name).contains(str);
+    }
+  }
+
+  edit_pool_configuration(name: string, bpsLimit: string) {
+    this.navigateEdit(name);
+
+    cy.get('.collapsible').click();
+    cy.get('cd-rbd-configuration-form')
+      .get('input[name=rbd_qos_bps_limit]')
+      .clear()
+      .type(`${bpsLimit}`);
+    cy.get('cd-submit-button').click();
+
+    this.navigateEdit(name);
+
+    cy.get('.collapsible').click();
+    cy.get('cd-rbd-configuration-form')
+      .get('input[name=rbd_qos_bps_limit]')
+      .should('have.value', bpsLimit);
+  }
+
+  private setApplications(apps: string[]) {
+    if (!apps || apps.length === 0) {
+      return;
+    }
+    cy.get('.float-start.me-2.select-menu-edit').click();
+    cy.get('.popover-body').should('be.visible');
+    apps.forEach((app) => cy.get('.select-menu-item-content').contains(app).click());
+  }
+}
diff --git a/src/pybind/mgr/dashboard/frontend/cypress/e2e/rgw/buckets.e2e-spec.ts b/src/pybind/mgr/dashboard/frontend/cypress/e2e/rgw/buckets.e2e-spec.ts
new file mode 100644 (file)
index 0000000..6c50b48
--- /dev/null
@@ -0,0 +1,67 @@
+import { BucketsPageHelper } from './buckets.po';
+
+describe('RGW buckets page', () => {
+  const buckets = new BucketsPageHelper();
+  const bucket_name = 'e2ebucket';
+
+  beforeEach(() => {
+    cy.login();
+    Cypress.Cookies.preserveOnce('token');
+    buckets.navigateTo();
+  });
+
+  describe('breadcrumb tests', () => {
+    it('should open and show breadcrumb', () => {
+      buckets.expectBreadcrumbText('Buckets');
+    });
+  });
+
+  describe('create, edit & delete bucket tests', () => {
+    it('should create bucket', () => {
+      buckets.navigateTo('create');
+      buckets.create(bucket_name, BucketsPageHelper.USERS[0], 'default-placement');
+      buckets.getFirstTableCell(bucket_name).should('exist');
+    });
+
+    it('should edit bucket', () => {
+      buckets.edit(bucket_name, BucketsPageHelper.USERS[1]);
+      buckets.getDataTables().should('contain.text', BucketsPageHelper.USERS[1]);
+    });
+
+    it('should delete bucket', () => {
+      buckets.delete(bucket_name);
+    });
+
+    it('should check default encryption is SSE-S3', () => {
+      buckets.navigateTo('create');
+      buckets.checkForDefaultEncryption();
+    });
+
+    it('should create bucket with object locking enabled', () => {
+      buckets.navigateTo('create');
+      buckets.create(bucket_name, BucketsPageHelper.USERS[0], 'default-placement', true);
+      buckets.getFirstTableCell(bucket_name).should('exist');
+    });
+
+    it('should not allow to edit versioning if object locking is enabled', () => {
+      buckets.edit(bucket_name, BucketsPageHelper.USERS[1], true);
+      buckets.getDataTables().should('contain.text', BucketsPageHelper.USERS[1]);
+
+      buckets.delete(bucket_name);
+    });
+  });
+
+  describe('Invalid Input in Create and Edit tests', () => {
+    it('should test invalid inputs in create fields', () => {
+      buckets.testInvalidCreate();
+    });
+
+    it('should test invalid input in edit owner field', () => {
+      buckets.navigateTo('create');
+      buckets.create(bucket_name, BucketsPageHelper.USERS[0], 'default-placement');
+      buckets.testInvalidEdit(bucket_name);
+      buckets.navigateTo();
+      buckets.delete(bucket_name);
+    });
+  });
+});
diff --git a/src/pybind/mgr/dashboard/frontend/cypress/e2e/rgw/buckets.po.ts b/src/pybind/mgr/dashboard/frontend/cypress/e2e/rgw/buckets.po.ts
new file mode 100644 (file)
index 0000000..a27be3c
--- /dev/null
@@ -0,0 +1,202 @@
+import { PageHelper } from '../page-helper.po';
+
+const pages = {
+  index: { url: '#/rgw/bucket', id: 'cd-rgw-bucket-list' },
+  create: { url: '#/rgw/bucket/create', id: 'cd-rgw-bucket-form' }
+};
+
+export class BucketsPageHelper extends PageHelper {
+  static readonly USERS = ['dashboard', 'testid'];
+
+  pages = pages;
+
+  versioningStateEnabled = 'Enabled';
+  versioningStateSuspended = 'Suspended';
+
+  private selectOwner(owner: string) {
+    return this.selectOption('owner', owner);
+  }
+
+  private selectPlacementTarget(placementTarget: string) {
+    return this.selectOption('placement-target', placementTarget);
+  }
+
+  private selectLockMode(lockMode: string) {
+    return this.selectOption('lock_mode', lockMode);
+  }
+
+  @PageHelper.restrictTo(pages.create.url)
+  create(name: string, owner: string, placementTarget: string, isLocking = false) {
+    // Enter in bucket name
+    cy.get('#bid').type(name);
+
+    // Select bucket owner
+    this.selectOwner(owner);
+    cy.get('#owner').should('have.class', 'ng-valid');
+
+    // Select bucket placement target:
+    this.selectPlacementTarget(placementTarget);
+    cy.get('#placement-target').should('have.class', 'ng-valid');
+
+    if (isLocking) {
+      cy.get('#lock_enabled').click({ force: true });
+      // Select lock mode:
+      this.selectLockMode('Compliance');
+      cy.get('#lock_mode').should('have.class', 'ng-valid');
+      cy.get('#lock_retention_period_days').type('3');
+    }
+
+    // Click the create button and wait for bucket to be made
+    cy.contains('button', 'Create Bucket').click();
+
+    this.getFirstTableCell(name).should('exist');
+  }
+
+  @PageHelper.restrictTo(pages.create.url)
+  checkForDefaultEncryption() {
+    cy.get("cd-helper[aria-label='toggle encryption helper']").click();
+    cy.get("a[aria-label='click here']").click();
+    cy.get('cd-modal').within(() => {
+      cy.get('input[id=s3Enabled]').should('be.checked');
+    });
+  }
+
+  @PageHelper.restrictTo(pages.index.url)
+  edit(name: string, new_owner: string, isLocking = false) {
+    this.navigateEdit(name);
+
+    cy.get('input[name=placement-target]').should('have.value', 'default-placement');
+    this.selectOwner(new_owner);
+
+    // If object locking is enabled versioning shouldn't be visible
+    if (isLocking) {
+      cy.get('input[id=versioning]').should('be.disabled');
+      cy.contains('button', 'Edit Bucket').click();
+
+      // wait to be back on buckets page with table visible and click
+      this.getExpandCollapseElement(name).click();
+
+      // check its details table for edited owner field
+      cy.get('.table.table-striped.table-bordered')
+        .first()
+        .should('contains.text', new_owner)
+        .as('bucketDataTable');
+
+      // Check versioning enabled:
+      cy.get('@bucketDataTable').find('tr').its(2).find('td').last().should('have.text', new_owner);
+      cy.get('@bucketDataTable').find('tr').its(11).find('td').last().as('versioningValueCell');
+
+      return cy.get('@versioningValueCell').should('have.text', this.versioningStateEnabled);
+    }
+    // Enable versioning
+    cy.get('input[id=versioning]').should('not.be.checked');
+    cy.get('label[for=versioning]').click();
+    cy.get('input[id=versioning]').should('be.checked');
+
+    cy.contains('button', 'Edit Bucket').click();
+
+    // wait to be back on buckets page with table visible and click
+    this.getExpandCollapseElement(name).click();
+
+    // check its details table for edited owner field
+    cy.get('.table.table-striped.table-bordered')
+      .first()
+      .should('contains.text', new_owner)
+      .as('bucketDataTable');
+
+    // Check versioning enabled:
+    cy.get('@bucketDataTable').find('tr').its(2).find('td').last().should('have.text', new_owner);
+    cy.get('@bucketDataTable').find('tr').its(11).find('td').last().as('versioningValueCell');
+
+    cy.get('@versioningValueCell').should('have.text', this.versioningStateEnabled);
+
+    // Disable versioning:
+    this.navigateEdit(name);
+
+    cy.get('label[for=versioning]').click();
+    cy.get('input[id=versioning]').should('not.be.checked');
+    cy.contains('button', 'Edit Bucket').click();
+
+    // Check versioning suspended:
+    this.getExpandCollapseElement(name).click();
+
+    return cy.get('@versioningValueCell').should('have.text', this.versioningStateSuspended);
+  }
+
+  testInvalidCreate() {
+    this.navigateTo('create');
+    cy.get('#bid').as('nameInputField'); // Grabs name box field
+
+    // Gives an invalid name (too short), then waits for dashboard to determine validity
+    cy.get('@nameInputField').type('rq');
+
+    cy.contains('button', 'Create Bucket').click(); // To trigger a validation
+
+    // Waiting for website to decide if name is valid or not
+    // Check that name input field was marked invalid in the css
+    cy.get('@nameInputField')
+      .should('not.have.class', 'ng-pending')
+      .and('have.class', 'ng-invalid');
+
+    // Check that error message was printed under name input field
+    cy.get('#bid + .invalid-feedback').should(
+      'have.text',
+      'Bucket names must be 3 to 63 characters long.'
+    );
+
+    // Test invalid owner input
+    // select some valid option. The owner drop down error message will not appear unless a valid user was selected at
+    // one point before the invalid placeholder user is selected.
+    this.selectOwner(BucketsPageHelper.USERS[1]);
+
+    // select the first option, which is invalid because it is a placeholder
+    this.selectOwner('-- Select a user --');
+
+    cy.get('@nameInputField').click();
+
+    // Check that owner drop down field was marked invalid in the css
+    cy.get('#owner').should('have.class', 'ng-invalid');
+
+    // Check that error message was printed under owner drop down field
+    cy.get('#owner + .invalid-feedback').should('have.text', 'This field is required.');
+
+    // Check invalid placement target input
+    this.selectOwner(BucketsPageHelper.USERS[1]);
+    // The drop down error message will not appear unless a valid option is previsously selected.
+    this.selectPlacementTarget('default-placement');
+    this.selectPlacementTarget('-- Select a placement target --');
+    cy.get('@nameInputField').click(); // Trigger validation
+    cy.get('#placement-target').should('have.class', 'ng-invalid');
+    cy.get('#placement-target + .invalid-feedback').should('have.text', 'This field is required.');
+
+    // Clicks the Create Bucket button but the page doesn't move.
+    // Done by testing for the breadcrumb
+    cy.contains('button', 'Create Bucket').click(); // Clicks Create Bucket button
+    this.expectBreadcrumbText('Create');
+    // content in fields seems to subsist through tests if not cleared, so it is cleared
+    cy.get('@nameInputField').clear();
+    return cy.contains('button', 'Cancel').click();
+  }
+
+  testInvalidEdit(name: string) {
+    this.navigateEdit(name);
+
+    cy.get('input[id=versioning]').should('exist').and('not.be.checked');
+
+    // Chooses 'Select a user' rather than a valid owner on Edit Bucket page
+    // and checks if it's an invalid input
+
+    // select the first option, which is invalid because it is a placeholder
+    this.selectOwner('-- Select a user --');
+
+    cy.contains('button', 'Edit Bucket').click();
+
+    // Check that owner drop down field was marked invalid in the css
+    cy.get('#owner').should('have.class', 'ng-invalid');
+
+    // Check that error message was printed under owner drop down field
+    cy.get('#owner + .invalid-feedback').should('have.text', 'This field is required.');
+
+    this.expectBreadcrumbText('Edit');
+  }
+}
diff --git a/src/pybind/mgr/dashboard/frontend/cypress/e2e/rgw/daemons.e2e-spec.ts b/src/pybind/mgr/dashboard/frontend/cypress/e2e/rgw/daemons.e2e-spec.ts
new file mode 100644 (file)
index 0000000..f3129a7
--- /dev/null
@@ -0,0 +1,35 @@
+import { DaemonsPageHelper } from './daemons.po';
+
+describe('RGW daemons page', () => {
+  const daemons = new DaemonsPageHelper();
+
+  beforeEach(() => {
+    cy.login();
+    Cypress.Cookies.preserveOnce('token');
+    daemons.navigateTo();
+  });
+
+  describe('breadcrumb and tab tests', () => {
+    it('should open and show breadcrumb', () => {
+      daemons.expectBreadcrumbText('Gateways');
+    });
+
+    it('should show two tabs', () => {
+      daemons.getTabsCount().should('eq', 2);
+    });
+
+    it('should show daemons list tab at first', () => {
+      daemons.getTabText(0).should('eq', 'Gateways List');
+    });
+
+    it('should show overall performance as a second tab', () => {
+      daemons.getTabText(1).should('eq', 'Overall Performance');
+    });
+  });
+
+  describe('details and performance counters table tests', () => {
+    it('should check that details/performance tables are visible when daemon is selected', () => {
+      daemons.checkTables();
+    });
+  });
+});
diff --git a/src/pybind/mgr/dashboard/frontend/cypress/e2e/rgw/daemons.po.ts b/src/pybind/mgr/dashboard/frontend/cypress/e2e/rgw/daemons.po.ts
new file mode 100644 (file)
index 0000000..82a1794
--- /dev/null
@@ -0,0 +1,34 @@
+import { PageHelper } from '../page-helper.po';
+
+export class DaemonsPageHelper extends PageHelper {
+  pages = {
+    index: { url: '#/rgw/daemon', id: 'cd-rgw-daemon-list' }
+  };
+
+  getTableCell() {
+    return cy
+      .get('.tab-content')
+      .its(1)
+      .find('cd-table')
+      .should('have.length', 1) // Only 1 table should be renderer
+      .find('datatable-body-cell');
+  }
+
+  checkTables() {
+    // click on a daemon so details table appears
+    cy.get('.datatable-body-cell-label').first().click();
+
+    // check details table is visible
+    // check at least one field is present
+    this.getTableCell().should('be.visible').should('contain.text', 'ceph_version');
+
+    // click on performance counters tab and check table is loaded
+    cy.contains('.nav-link', 'Performance Counters').click();
+
+    // check at least one field is present
+    this.getTableCell().should('be.visible').should('contain.text', 'objecter.op_r');
+
+    // click on performance details tab
+    cy.contains('.nav-link', 'Performance Details').click();
+  }
+}
diff --git a/src/pybind/mgr/dashboard/frontend/cypress/e2e/rgw/users.e2e-spec.ts b/src/pybind/mgr/dashboard/frontend/cypress/e2e/rgw/users.e2e-spec.ts
new file mode 100644 (file)
index 0000000..b5f366a
--- /dev/null
@@ -0,0 +1,46 @@
+import { UsersPageHelper } from './users.po';
+
+describe('RGW users page', () => {
+  const users = new UsersPageHelper();
+  const tenant = 'e2e_000tenant';
+  const user_id = 'e2e_000user_create_edit_delete';
+  const user_name = tenant + '$' + user_id;
+
+  beforeEach(() => {
+    cy.login();
+    Cypress.Cookies.preserveOnce('token');
+    users.navigateTo();
+  });
+
+  describe('breadcrumb tests', () => {
+    it('should open and show breadcrumb', () => {
+      users.expectBreadcrumbText('Users');
+    });
+  });
+
+  describe('create, edit & delete user tests', () => {
+    it('should create user', () => {
+      users.navigateTo('create');
+      users.create(tenant, user_id, 'Some Name', 'original@website.com', '1200');
+      users.getFirstTableCell(user_id).should('exist');
+    });
+
+    it('should edit users full name, email and max buckets', () => {
+      users.edit(user_name, 'Another Identity', 'changed@othersite.com', '1969');
+    });
+
+    it('should delete user', () => {
+      users.delete(user_name);
+    });
+  });
+
+  describe('Invalid input tests', () => {
+    it('should put invalid input into user creation form and check fields are marked invalid', () => {
+      users.invalidCreate();
+    });
+
+    it('should put invalid input into user edit form and check fields are marked invalid', () => {
+      users.invalidEdit();
+    });
+  });
+});
diff --git a/src/pybind/mgr/dashboard/frontend/cypress/e2e/rgw/users.po.ts b/src/pybind/mgr/dashboard/frontend/cypress/e2e/rgw/users.po.ts
new file mode 100644 (file)
index 0000000..980cced
--- /dev/null
@@ -0,0 +1,139 @@
+import { PageHelper } from '../page-helper.po';
+
+const pages = {
+  index: { url: '#/rgw/user', id: 'cd-rgw-user-list' },
+  create: { url: '#/rgw/user/create', id: 'cd-rgw-user-form' }
+};
+
+export class UsersPageHelper extends PageHelper {
+  pages = pages;
+
+  @PageHelper.restrictTo(pages.create.url)
+  create(tenant: string, user_id: string, fullname: string, email: string, maxbuckets: string) {
+    // Enter in user_id
+    cy.get('#user_id').type(user_id);
+    // Show Tenanat
+    cy.get('#show_tenant').click({ force: true });
+    // Enter in tenant
+    cy.get('#tenant').type(tenant);
+    // Enter in full name
+    cy.get('#display_name').click().type(fullname);
+
+    // Enter in email
+    cy.get('#email').click().type(email);
+
+    // Enter max buckets
+    this.selectOption('max_buckets_mode', 'Custom');
+    cy.get('#max_buckets').should('exist').should('have.value', '1000');
+    cy.get('#max_buckets').click().clear().type(maxbuckets);
+
+    // Click the create button and wait for user to be made
+    cy.contains('button', 'Create User').click();
+    this.getFirstTableCell(tenant + '$' + user_id).should('exist');
+  }
+
+  @PageHelper.restrictTo(pages.index.url)
+  edit(name: string, new_fullname: string, new_email: string, new_maxbuckets: string) {
+    this.navigateEdit(name);
+
+    // Change the full name field
+    cy.get('#display_name').click().clear().type(new_fullname);
+
+    // Change the email field
+    cy.get('#email').click().clear().type(new_email);
+
+    // Change the max buckets field
+    this.selectOption('max_buckets_mode', 'Custom');
+    cy.get('#max_buckets').click().clear().type(new_maxbuckets);
+
+    cy.contains('button', 'Edit User').click();
+
+    // Click the user and check its details table for updated content
+    this.getExpandCollapseElement(name).click();
+    cy.get('.datatable-row-detail')
+      .should('contain.text', new_fullname)
+      .and('contain.text', new_email)
+      .and('contain.text', new_maxbuckets);
+  }
+
+  invalidCreate() {
+    const tenant = '000invalid_tenant';
+    const uname = '000invalid_create_user';
+    // creating this user in order to check that you can't give two users the same name
+    this.navigateTo('create');
+    this.create(tenant, uname, 'xxx', 'xxx@xxx', '1');
+
+    this.navigateTo('create');
+
+    // Username
+    cy.get('#user_id')
+      // No username had been entered. Field should be invalid
+      .should('have.class', 'ng-invalid')
+      // Try to give user already taken name. Should make field invalid.
+      .type(uname);
+    cy.get('#show_tenant').click({ force: true });
+    cy.get('#tenant').type(tenant).should('have.class', 'ng-invalid');
+    cy.contains('#tenant + .invalid-feedback', 'The chosen user ID exists in this tenant.');
+
+    // check that username field is marked invalid if username has been cleared off
+    cy.get('#user_id').clear().blur().should('have.class', 'ng-invalid');
+    cy.contains('#user_id + .invalid-feedback', 'This field is required.');
+
+    // Full name
+    cy.get('#display_name')
+      // No display name has been given so field should be invalid
+      .should('have.class', 'ng-invalid')
+      // display name field should also be marked invalid if given input then emptied
+      .type('a')
+      .clear()
+      .blur()
+      .should('have.class', 'ng-invalid');
+    cy.contains('#display_name + .invalid-feedback', 'This field is required.');
+
+    // put invalid email to make field invalid
+    cy.get('#email').type('a').blur().should('have.class', 'ng-invalid');
+    cy.contains('#email + .invalid-feedback', 'This is not a valid email address.');
+
+    // put negative max buckets to make field invalid
+    this.expectSelectOption('max_buckets_mode', 'Custom');
+    cy.get('#max_buckets').clear().type('-5').blur().should('have.class', 'ng-invalid');
+    cy.contains('#max_buckets + .invalid-feedback', 'The entered value must be >= 1.');
+
+    this.navigateTo();
+    this.delete(tenant + '$' + uname);
+  }
+
+  invalidEdit() {
+    const tenant = '000invalid_tenant';
+    const uname = '000invalid_edit_user';
+    // creating this user to edit for the test
+    this.navigateTo('create');
+    this.create(tenant, uname, 'xxx', 'xxx@xxx', '50');
+    const name = tenant + '$' + uname;
+    this.navigateEdit(name);
+
+    // put invalid email to make field invalid
+    cy.get('#email')
+      .clear()
+      .type('a')
+      .blur()
+      .should('not.have.class', 'ng-pending')
+      .should('have.class', 'ng-invalid');
+    cy.contains('#email + .invalid-feedback', 'This is not a valid email address.');
+
+    // empty the display name field making it invalid
+    cy.get('#display_name').clear().blur().should('have.class', 'ng-invalid');
+    cy.contains('#display_name + .invalid-feedback', 'This field is required.');
+
+    // put negative max buckets to make field invalid
+    this.selectOption('max_buckets_mode', 'Disabled');
+    cy.get('#max_buckets').should('not.exist');
+    this.selectOption('max_buckets_mode', 'Custom');
+    cy.get('#max_buckets').should('exist').should('have.value', '50');
+    cy.get('#max_buckets').clear().type('-5').blur().should('have.class', 'ng-invalid');
+    cy.contains('#max_buckets + .invalid-feedback', 'The entered value must be >= 1.');
+
+    this.navigateTo();
+    this.delete(tenant + '$' + uname);
+  }
+}
diff --git a/src/pybind/mgr/dashboard/frontend/cypress/e2e/ui/api-docs.e2e-spec.ts b/src/pybind/mgr/dashboard/frontend/cypress/e2e/ui/api-docs.e2e-spec.ts
new file mode 100644 (file)
index 0000000..5299485
--- /dev/null
@@ -0,0 +1,15 @@
+import { ApiDocsPageHelper } from '../ui/api-docs.po';
+
+describe('Api Docs Page', () => {
+  const apiDocs = new ApiDocsPageHelper();
+
+  beforeEach(() => {
+    cy.login();
+    Cypress.Cookies.preserveOnce('token');
+    apiDocs.navigateTo();
+  });
+
+  it('should show the API Docs description', () => {
+    cy.get('.renderedMarkdown').first().contains('This is the official Ceph REST API');
+  });
+});
diff --git a/src/pybind/mgr/dashboard/frontend/cypress/e2e/ui/api-docs.po.ts b/src/pybind/mgr/dashboard/frontend/cypress/e2e/ui/api-docs.po.ts
new file mode 100644 (file)
index 0000000..c7a8d22
--- /dev/null
@@ -0,0 +1,5 @@
+import { PageHelper } from '../page-helper.po';
+
+export class ApiDocsPageHelper extends PageHelper {
+  pages = { index: { url: '#/api-docs', id: 'cd-api-docs' } };
+}
diff --git a/src/pybind/mgr/dashboard/frontend/cypress/e2e/ui/dashboard-v3.e2e-spec.ts b/src/pybind/mgr/dashboard/frontend/cypress/e2e/ui/dashboard-v3.e2e-spec.ts
new file mode 100644 (file)
index 0000000..8fa0013
--- /dev/null
@@ -0,0 +1,50 @@
+import { ManagerModulesPageHelper } from '../cluster/mgr-modules.po';
+import { DashboardV3PageHelper } from './dashboard-v3.po';
+
+describe('Dashboard-v3 Main Page', () => {
+  const dashboard = new DashboardV3PageHelper();
+  const mgrmodules = new ManagerModulesPageHelper();
+
+  before(() => {
+    cy.login();
+    mgrmodules.navigateTo();
+    mgrmodules.navigateEdit('dashboard');
+    cy.get('#FEATURE_TOGGLE_DASHBOARD').check();
+    cy.contains('button', 'Update').click();
+  });
+
+  beforeEach(() => {
+    cy.login();
+    Cypress.Cookies.preserveOnce('token');
+    dashboard.navigateTo();
+  });
+
+  describe('Check that all hyperlinks on inventory card lead to the correct page and fields exist', () => {
+    it('should ensure that all linked pages in the inventory card lead to correct page', () => {
+      const expectationMap = {
+        Host: 'Hosts',
+        Monitor: 'Monitors',
+        OSDs: 'OSDs',
+        Pool: 'Pools',
+        'Object Gateway': 'Gateways'
+      };
+
+      for (const [linkText, breadcrumbText] of Object.entries(expectationMap)) {
+        cy.location('hash').should('eq', '#/dashboard');
+        dashboard.clickInventoryCardLink(linkText);
+        dashboard.expectBreadcrumbText(breadcrumbText);
+        dashboard.navigateBack();
+      }
+    });
+
+    it('should verify that cards exist on dashboard in proper order', () => {
+      // Ensures that cards are all displayed on the dashboard tab while being in the proper
+      // order, checks for card title and position via indexing into a list of all cards.
+      const order = ['Details', 'Status', 'Capacity', 'Inventory', 'Cluster utilization'];
+
+      for (let i = 0; i < order.length; i++) {
+        dashboard.card(i).should('contain.text', order[i]);
+      }
+    });
+  });
+});
diff --git a/src/pybind/mgr/dashboard/frontend/cypress/e2e/ui/dashboard-v3.po.ts b/src/pybind/mgr/dashboard/frontend/cypress/e2e/ui/dashboard-v3.po.ts
new file mode 100644 (file)
index 0000000..597d2db
--- /dev/null
@@ -0,0 +1,20 @@
+import { PageHelper } from '../page-helper.po';
+
+export class DashboardV3PageHelper extends PageHelper {
+  pages = { index: { url: '#/dashboard', id: 'cd-dashboard-v3' } };
+
+  cardTitle(index: number) {
+    return cy.get('.card-title').its(index).text();
+  }
+
+  clickInventoryCardLink(link: string) {
+    console.log(link);
+    cy.get(`cd-card[cardTitle="Inventory"]`).contains('a', link).click();
+  }
+
+  card(indexOrTitle: number) {
+    cy.get('cd-card').as('cards');
+
+    return cy.get('@cards').its(indexOrTitle);
+  }
+}
diff --git a/src/pybind/mgr/dashboard/frontend/cypress/e2e/ui/dashboard.e2e-spec.ts b/src/pybind/mgr/dashboard/frontend/cypress/e2e/ui/dashboard.e2e-spec.ts
new file mode 100644 (file)
index 0000000..43def20
--- /dev/null
@@ -0,0 +1,142 @@
+import { IscsiPageHelper } from '../block/iscsi.po';
+import { HostsPageHelper } from '../cluster/hosts.po';
+import { ManagerModulesPageHelper } from '../cluster/mgr-modules.po';
+import { MonitorsPageHelper } from '../cluster/monitors.po';
+import { OSDsPageHelper } from '../cluster/osds.po';
+import { PageHelper } from '../page-helper.po';
+import { PoolPageHelper } from '../pools/pools.po';
+import { DaemonsPageHelper } from '../rgw/daemons.po';
+import { DashboardPageHelper } from './dashboard.po';
+
+describe('Dashboard Main Page', () => {
+  const dashboard = new DashboardPageHelper();
+  const daemons = new DaemonsPageHelper();
+  const hosts = new HostsPageHelper();
+  const osds = new OSDsPageHelper();
+  const pools = new PoolPageHelper();
+  const monitors = new MonitorsPageHelper();
+  const iscsi = new IscsiPageHelper();
+  const mgrmodules = new ManagerModulesPageHelper();
+
+  before(() => {
+    cy.login();
+    mgrmodules.navigateTo();
+    mgrmodules.navigateEdit('dashboard');
+    cy.get('#FEATURE_TOGGLE_DASHBOARD').uncheck();
+    cy.contains('button', 'Update').click();
+  });
+
+  beforeEach(() => {
+    cy.login();
+    Cypress.Cookies.preserveOnce('token');
+    dashboard.navigateTo();
+  });
+
+  describe('Check that all hyperlinks on info cards lead to the correct page and fields exist', () => {
+    it('should ensure that all linked info cards lead to correct page', () => {
+      const expectationMap = {
+        Monitors: 'Monitors',
+        OSDs: 'OSDs',
+        Hosts: 'Hosts',
+        'Object Gateways': 'Gateways',
+        'iSCSI Gateways': 'Overview',
+        Pools: 'Pools'
+      };
+
+      for (const [linkText, breadcrumbText] of Object.entries(expectationMap)) {
+        cy.location('hash').should('eq', '#/dashboard');
+        dashboard.clickInfoCardLink(linkText);
+        dashboard.expectBreadcrumbText(breadcrumbText);
+        dashboard.navigateBack();
+      }
+    });
+
+    it('should verify that info cards exist on dashboard in proper order', () => {
+      // Ensures that info cards are all displayed on the dashboard tab while being in the proper
+      // order, checks for card title and position via indexing into a list of all info cards.
+      const order = [
+        'Cluster Status',
+        'Hosts',
+        'Monitors',
+        'OSDs',
+        'Managers',
+        'Object Gateways',
+        'Metadata Servers',
+        'iSCSI Gateways',
+        'Raw Capacity',
+        'Objects',
+        'PG Status',
+        'Pools',
+        'PGs per OSD',
+        'Client Read/Write',
+        'Client Throughput',
+        'Recovery Throughput',
+        'Scrubbing'
+      ];
+
+      for (let i = 0; i < order.length; i++) {
+        dashboard.infoCard(i).should('contain.text', order[i]);
+      }
+    });
+
+    it('should verify that info card group titles are present and in the right order', () => {
+      cy.location('hash').should('eq', '#/dashboard');
+      dashboard.infoGroupTitle(0).should('eq', 'Status');
+      dashboard.infoGroupTitle(1).should('eq', 'Capacity');
+      dashboard.infoGroupTitle(2).should('eq', 'Performance');
+    });
+  });
+
+  it('Should check that dashboard cards have correct information', () => {
+    interface TestSpec {
+      cardName: string;
+      regexMatcher?: RegExp;
+      pageObject: PageHelper;
+    }
+    const testSpecs: TestSpec[] = [
+      { cardName: 'Object Gateways', regexMatcher: /(\d+)\s+total/, pageObject: daemons },
+      { cardName: 'Monitors', regexMatcher: /(\d+)\s+\(quorum/, pageObject: monitors },
+      { cardName: 'Hosts', regexMatcher: /(\d+)\s+total/, pageObject: hosts },
+      { cardName: 'OSDs', regexMatcher: /(\d+)\s+total/, pageObject: osds },
+      { cardName: 'Pools', pageObject: pools },
+      { cardName: 'iSCSI Gateways', regexMatcher: /(\d+)\s+total/, pageObject: iscsi }
+    ];
+    for (let i = 0; i < testSpecs.length; i++) {
+      const spec = testSpecs[i];
+      dashboard.navigateTo();
+
+      dashboard.infoCardBodyText(spec.cardName).then((infoCardBodyText: string) => {
+        let dashCount = 0;
+
+        if (spec.regexMatcher) {
+          const match = infoCardBodyText.match(new RegExp(spec.regexMatcher));
+          expect(match).to.length.gt(
+            1,
+            `Regex ${spec.regexMatcher} did not find a match for card with name ` +
+              `${spec.cardName}`
+          );
+          dashCount = Number(match[1]);
+        } else {
+          dashCount = Number(infoCardBodyText);
+        }
+
+        spec.pageObject.navigateTo();
+        spec.pageObject.getTableCount('total').then((tableCount) => {
+          expect(tableCount).to.eq(
+            dashCount,
+            `Text of card "${spec.cardName}" and regex "${spec.regexMatcher}" resulted in ${dashCount} ` +
+              `but did not match table count ${tableCount}`
+          );
+        });
+      });
+    }
+  });
+
+  after(() => {
+    cy.login();
+    mgrmodules.navigateTo();
+    mgrmodules.navigateEdit('dashboard');
+    cy.get('#FEATURE_TOGGLE_DASHBOARD').click();
+    cy.contains('button', 'Update').click();
+  });
+});
diff --git a/src/pybind/mgr/dashboard/frontend/cypress/e2e/ui/dashboard.po.ts b/src/pybind/mgr/dashboard/frontend/cypress/e2e/ui/dashboard.po.ts
new file mode 100644 (file)
index 0000000..42d63ef
--- /dev/null
@@ -0,0 +1,31 @@
+import { PageHelper } from '../page-helper.po';
+
+export class DashboardPageHelper extends PageHelper {
+  pages = { index: { url: '#/dashboard', id: 'cd-dashboard' } };
+
+  infoGroupTitle(index: number) {
+    return cy.get('.info-group-title').its(index).text();
+  }
+
+  clickInfoCardLink(cardName: string) {
+    cy.get(`cd-info-card[cardtitle="${cardName}"]`).contains('a', cardName).click();
+  }
+
+  infoCard(indexOrTitle: number | string) {
+    cy.get('cd-info-card').as('infoCards');
+
+    if (typeof indexOrTitle === 'number') {
+      return cy.get('@infoCards').its(indexOrTitle);
+    } else {
+      return cy.contains('cd-info-card a', indexOrTitle).parent().parent().parent().parent();
+    }
+  }
+
+  infoCardBodyText(infoCard: string) {
+    return this.infoCard(infoCard).find('.card-text').text();
+  }
+
+  infoCardBody(infoCard: string) {
+    return this.infoCard(infoCard).find('.card-text');
+  }
+}
diff --git a/src/pybind/mgr/dashboard/frontend/cypress/e2e/ui/language.e2e-spec.ts b/src/pybind/mgr/dashboard/frontend/cypress/e2e/ui/language.e2e-spec.ts
new file mode 100644 (file)
index 0000000..ccf16c2
--- /dev/null
@@ -0,0 +1,20 @@
+import { LanguagePageHelper } from './language.po';
+
+describe('Shared pages', () => {
+  const language = new LanguagePageHelper();
+
+  beforeEach(() => {
+    cy.login();
+    Cypress.Cookies.preserveOnce('token');
+    language.navigateTo();
+  });
+
+  it('should check default language', () => {
+    language.getLanguageBtn().should('contain.text', 'English');
+  });
+
+  it('should check all available languages', () => {
+    language.getLanguageBtn().click();
+    language.getAllLanguages().should('have.length', 1).should('contain.text', 'English');
+  });
+});
diff --git a/src/pybind/mgr/dashboard/frontend/cypress/e2e/ui/language.po.ts b/src/pybind/mgr/dashboard/frontend/cypress/e2e/ui/language.po.ts
new file mode 100644 (file)
index 0000000..80e21ba
--- /dev/null
@@ -0,0 +1,15 @@
+import { PageHelper } from '../page-helper.po';
+
+export class LanguagePageHelper extends PageHelper {
+  pages = {
+    index: { url: '#/dashboard', id: 'cd-dashboard' }
+  };
+
+  getLanguageBtn() {
+    return cy.get('cd-language-selector a').first();
+  }
+
+  getAllLanguages() {
+    return cy.get('cd-language-selector button');
+  }
+}
diff --git a/src/pybind/mgr/dashboard/frontend/cypress/e2e/ui/login.e2e-spec.ts b/src/pybind/mgr/dashboard/frontend/cypress/e2e/ui/login.e2e-spec.ts
new file mode 100644 (file)
index 0000000..2b337e6
--- /dev/null
@@ -0,0 +1,23 @@
+import { LoginPageHelper } from './login.po';
+
+describe('Login page', () => {
+  const login = new LoginPageHelper();
+
+  it('should login and navigate to dashboard page', () => {
+    login.navigateTo();
+    login.doLogin();
+  });
+
+  it('should logout when clicking the button', () => {
+    login.navigateTo();
+    login.doLogin();
+
+    login.doLogout();
+  });
+
+  it('should have no accessibility violations', () => {
+    login.navigateTo();
+    cy.injectAxe();
+    cy.checkA11y();
+  });
+});
diff --git a/src/pybind/mgr/dashboard/frontend/cypress/e2e/ui/login.po.ts b/src/pybind/mgr/dashboard/frontend/cypress/e2e/ui/login.po.ts
new file mode 100644 (file)
index 0000000..d4d2c69
--- /dev/null
@@ -0,0 +1,22 @@
+import { PageHelper } from '../page-helper.po';
+
+export class LoginPageHelper extends PageHelper {
+  pages = {
+    index: { url: '#/login', id: 'cd-login' },
+    dashboard: { url: '#/dashboard', id: 'cd-dashboard' }
+  };
+
+  doLogin() {
+    cy.get('[name=username]').type('admin');
+    cy.get('#password').type('admin');
+    cy.get('[type=submit]').click();
+    cy.get('cd-dashboard').should('exist');
+  }
+
+  doLogout() {
+    cy.get('cd-identity a').click();
+    cy.contains('cd-identity span', 'Sign out').click();
+    cy.get('cd-login').should('exist');
+    cy.location('hash').should('eq', '#/login');
+  }
+}
diff --git a/src/pybind/mgr/dashboard/frontend/cypress/e2e/ui/navigation.e2e-spec.ts b/src/pybind/mgr/dashboard/frontend/cypress/e2e/ui/navigation.e2e-spec.ts
new file mode 100644 (file)
index 0000000..fee2d2d
--- /dev/null
@@ -0,0 +1,24 @@
+import { NavigationPageHelper } from './navigation.po';
+
+describe('Shared pages', () => {
+  const shared = new NavigationPageHelper();
+
+  beforeEach(() => {
+    cy.login();
+    Cypress.Cookies.preserveOnce('token');
+    shared.navigateTo();
+  });
+
+  it('should display the vertical menu by default', () => {
+    shared.getVerticalMenu().should('not.have.class', 'active');
+  });
+
+  it('should hide the vertical menu', () => {
+    shared.getMenuToggler().click();
+    shared.getVerticalMenu().should('have.class', 'active');
+  });
+
+  it('should navigate to the correct page', () => {
+    shared.checkNavigations(shared.navigations);
+  });
+});
diff --git a/src/pybind/mgr/dashboard/frontend/cypress/e2e/ui/navigation.po.ts b/src/pybind/mgr/dashboard/frontend/cypress/e2e/ui/navigation.po.ts
new file mode 100644 (file)
index 0000000..f797bbc
--- /dev/null
@@ -0,0 +1,78 @@
+import { PageHelper } from '../page-helper.po';
+
+export class NavigationPageHelper extends PageHelper {
+  pages = {
+    index: { url: '#/dashboard', id: 'cd-dashboard' }
+  };
+
+  navigations = [
+    { menu: 'NFS', component: 'cd-error' },
+    {
+      menu: 'Object Gateway',
+      submenus: [
+        { menu: 'Gateways', component: 'cd-rgw-daemon-list' },
+        { menu: 'Users', component: 'cd-rgw-user-list' },
+        { menu: 'Buckets', component: 'cd-rgw-bucket-list' }
+      ]
+    },
+    { menu: 'Dashboard', component: 'cd-dashboard' },
+    {
+      menu: 'Cluster',
+      submenus: [
+        { menu: 'Hosts', component: 'cd-hosts' },
+        { menu: 'Physical Disks', component: 'cd-error' },
+        { menu: 'Monitors', component: 'cd-monitor' },
+        { menu: 'Services', component: 'cd-error' },
+        { menu: 'OSDs', component: 'cd-osd-list' },
+        { menu: 'Configuration', component: 'cd-configuration' },
+        { menu: 'CRUSH map', component: 'cd-crushmap' },
+        { menu: 'Manager Modules', component: 'cd-mgr-module-list' },
+        { menu: 'Ceph Users', component: 'cd-crud-table' },
+        { menu: 'Logs', component: 'cd-logs' },
+        { menu: 'Alerts', component: 'cd-prometheus-tabs' }
+      ]
+    },
+    { menu: 'Pools', component: 'cd-pool-list' },
+    {
+      menu: 'Block',
+      submenus: [
+        { menu: 'Images', component: 'cd-error' },
+        { menu: 'Mirroring', component: 'cd-mirroring' },
+        { menu: 'iSCSI', component: 'cd-iscsi' }
+      ]
+    },
+    { menu: 'File Systems', component: 'cd-cephfs-list' }
+  ];
+
+  getVerticalMenu() {
+    return cy.get('nav[id=sidebar]');
+  }
+
+  getMenuToggler() {
+    return cy.get('[aria-label="toggle sidebar visibility"]');
+  }
+
+  checkNavigations(navs: any) {
+    // The nfs-ganesha, RGW, and block/rbd status requests are mocked to ensure that this method runs in time
+    cy.intercept('/ui-api/nfs-ganesha/status', { fixture: 'nfs-ganesha-status.json' });
+    cy.intercept('/ui-api/rgw/status', { fixture: 'rgw-status.json' });
+    cy.intercept('/ui-api/block/rbd/status', { fixture: 'block-rbd-status.json' });
+
+    navs.forEach((nav: any) => {
+      cy.contains('.simplebar-content li.nav-item a', nav.menu).click();
+      if (nav.submenus) {
+        this.checkNavSubMenu(nav.menu, nav.submenus);
+      } else {
+        cy.get(nav.component).should('exist');
+      }
+    });
+  }
+
+  checkNavSubMenu(menu: any, submenu: any) {
+    submenu.forEach((nav: any) => {
+      cy.contains('.simplebar-content li.nav-item', menu).within(() => {
+        cy.contains(`ul.list-unstyled li a`, nav.menu).click();
+      });
+    });
+  }
+}
diff --git a/src/pybind/mgr/dashboard/frontend/cypress/e2e/ui/notification.e2e-spec.ts b/src/pybind/mgr/dashboard/frontend/cypress/e2e/ui/notification.e2e-spec.ts
new file mode 100644 (file)
index 0000000..2ee73a7
--- /dev/null
@@ -0,0 +1,59 @@
+import { PoolPageHelper } from '../pools/pools.po';
+import { NotificationSidebarPageHelper } from './notification.po';
+
+describe('Notification page', () => {
+  const notification = new NotificationSidebarPageHelper();
+  const pools = new PoolPageHelper();
+  const poolName = 'e2e_notification_pool';
+
+  before(() => {
+    cy.login();
+    Cypress.Cookies.preserveOnce('token');
+    pools.navigateTo('create');
+    pools.create(poolName, 8);
+    pools.edit_pool_pg(poolName, 4, false);
+  });
+
+  after(() => {
+    cy.login();
+    Cypress.Cookies.preserveOnce('token');
+    pools.navigateTo();
+    pools.delete(poolName);
+  });
+
+  beforeEach(() => {
+    cy.login();
+    Cypress.Cookies.preserveOnce('token');
+    pools.navigateTo();
+  });
+
+  it('should open notification sidebar', () => {
+    notification.getSidebar().should('not.be.visible');
+    notification.open();
+    notification.getSidebar().should('be.visible');
+  });
+
+  it('should display a running task', () => {
+    notification.getToast().should('not.exist');
+
+    // Check that running task is shown.
+    notification.open();
+    notification.getTasks().contains(poolName).should('exist');
+
+    // Delete pool after task is complete (otherwise we get an error).
+    notification.getTasks().contains(poolName, { timeout: 300000 }).should('not.exist');
+  });
+
+  it('should have notifications', () => {
+    notification.open();
+    notification.getNotifications().should('have.length.gt', 0);
+  });
+
+  it('should clear notifications', () => {
+    notification.getToast().should('not.exist');
+    notification.open();
+    notification.getNotifications().should('have.length.gt', 0);
+    notification.getClearNotficationsBtn().should('be.visible');
+    notification.clearNotifications();
+  });
+});
diff --git a/src/pybind/mgr/dashboard/frontend/cypress/e2e/ui/notification.po.ts b/src/pybind/mgr/dashboard/frontend/cypress/e2e/ui/notification.po.ts
new file mode 100644 (file)
index 0000000..12c424e
--- /dev/null
@@ -0,0 +1,45 @@
+import { PageHelper } from '../page-helper.po';
+
+export class NotificationSidebarPageHelper extends PageHelper {
+  getNotificatinoIcon() {
+    return cy.get('cd-notifications a');
+  }
+
+  getSidebar() {
+    return cy.get('cd-notifications-sidebar');
+  }
+
+  getTasks() {
+    return this.getSidebar().find('.card.tc_task');
+  }
+
+  getNotifications() {
+    return this.getSidebar().find('.card.tc_notification');
+  }
+
+  getClearNotficationsBtn() {
+    return this.getSidebar().find('button.btn-block');
+  }
+
+  getCloseBtn() {
+    return this.getSidebar().find('button.close');
+  }
+
+  open() {
+    this.getNotificatinoIcon().click();
+    this.getSidebar().should('be.visible');
+  }
+
+  clearNotifications() {
+    // It can happen that although notifications are cleared, by the time we check the notifications
+    // amount, another notification can appear, so we check it more than once (if needed).
+    this.getClearNotficationsBtn().click();
+    this.getNotifications()
+      .should('have.length.gte', 0)
+      .then(($elems) => {
+        if ($elems.length > 0) {
+          this.clearNotifications();
+        }
+      });
+  }
+}
diff --git a/src/pybind/mgr/dashboard/frontend/cypress/e2e/ui/role-mgmt.e2e-spec.ts b/src/pybind/mgr/dashboard/frontend/cypress/e2e/ui/role-mgmt.e2e-spec.ts
new file mode 100644 (file)
index 0000000..c3f325d
--- /dev/null
@@ -0,0 +1,37 @@
+import { RoleMgmtPageHelper } from './role-mgmt.po';
+
+describe('Role Management page', () => {
+  const roleMgmt = new RoleMgmtPageHelper();
+  const role_name = 'e2e_role_mgmt_role';
+
+  beforeEach(() => {
+    cy.login();
+    Cypress.Cookies.preserveOnce('token');
+    roleMgmt.navigateTo();
+  });
+
+  describe('breadcrumb tests', () => {
+    it('should check breadcrumb on roles tab on user management page', () => {
+      roleMgmt.expectBreadcrumbText('Roles');
+    });
+
+    it('should check breadcrumb on role creation page', () => {
+      roleMgmt.navigateTo('create');
+      roleMgmt.expectBreadcrumbText('Create');
+    });
+  });
+
+  describe('role create, edit & delete test', () => {
+    it('should create a role', () => {
+      roleMgmt.create(role_name, 'An interesting description');
+    });
+
+    it('should edit a role', () => {
+      roleMgmt.edit(role_name, 'A far more interesting description');
+    });
+
+    it('should delete a role', () => {
+      roleMgmt.delete(role_name);
+    });
+  });
+});
diff --git a/src/pybind/mgr/dashboard/frontend/cypress/e2e/ui/role-mgmt.po.ts b/src/pybind/mgr/dashboard/frontend/cypress/e2e/ui/role-mgmt.po.ts
new file mode 100644 (file)
index 0000000..1cc3630
--- /dev/null
@@ -0,0 +1,40 @@
+import { PageHelper } from '../page-helper.po';
+
+export class RoleMgmtPageHelper extends PageHelper {
+  pages = {
+    index: { url: '#/user-management/roles', id: 'cd-role-list' },
+    create: { url: '#/user-management/roles/create', id: 'cd-role-form' }
+  };
+
+  create(name: string, description: string) {
+    this.navigateTo('create');
+    // Waits for data to load
+    cy.contains('grafana');
+
+    // fill in fields
+    cy.get('#name').type(name);
+    cy.get('#description').type(description);
+
+    // Click the create button and wait for role to be made
+    cy.get('[data-cy=submitBtn]').click();
+    cy.get('.breadcrumb-item.active').should('not.have.text', 'Create');
+
+    this.getFirstTableCell(name).should('exist');
+  }
+
+  edit(name: string, description: string) {
+    this.navigateEdit(name);
+    // Waits for data to load
+    cy.contains('grafana');
+
+    // fill in fields with new values
+    cy.get('#description').clear().type(description);
+
+    // Click the edit button and check new values are present in table
+    cy.get('[data-cy=submitBtn]').click();
+    cy.get('.breadcrumb-item.active').should('not.have.text', 'Edit');
+
+    this.getFirstTableCell(name).should('exist');
+    this.getFirstTableCell(description).should('exist');
+  }
+}
diff --git a/src/pybind/mgr/dashboard/frontend/cypress/e2e/ui/user-mgmt.e2e-spec.ts b/src/pybind/mgr/dashboard/frontend/cypress/e2e/ui/user-mgmt.e2e-spec.ts
new file mode 100644 (file)
index 0000000..92dc772
--- /dev/null
@@ -0,0 +1,37 @@
+import { UserMgmtPageHelper } from './user-mgmt.po';
+
+describe('User Management page', () => {
+  const userMgmt = new UserMgmtPageHelper();
+  const user_name = 'e2e_user_mgmt_user';
+
+  beforeEach(() => {
+    cy.login();
+    Cypress.Cookies.preserveOnce('token');
+    userMgmt.navigateTo();
+  });
+
+  describe('breadcrumb tests', () => {
+    it('should check breadcrumb on users tab of user management page', () => {
+      userMgmt.expectBreadcrumbText('Users');
+    });
+
+    it('should check breadcrumb on user creation page', () => {
+      userMgmt.navigateTo('create');
+      userMgmt.expectBreadcrumbText('Create');
+    });
+  });
+
+  describe('user create, edit & delete test', () => {
+    it('should create a user', () => {
+      userMgmt.create(user_name, 'cool_password', 'Jeff', 'realemail@realwebsite.com');
+    });
+
+    it('should edit a user', () => {
+      userMgmt.edit(user_name, 'cool_password_number_2', 'Geoff', 'w@m');
+    });
+
+    it('should delete a user', () => {
+      userMgmt.delete(user_name);
+    });
+  });
+});
diff --git a/src/pybind/mgr/dashboard/frontend/cypress/e2e/ui/user-mgmt.po.ts b/src/pybind/mgr/dashboard/frontend/cypress/e2e/ui/user-mgmt.po.ts
new file mode 100644 (file)
index 0000000..fb2b791
--- /dev/null
@@ -0,0 +1,39 @@
+import { PageHelper } from '../page-helper.po';
+
+export class UserMgmtPageHelper extends PageHelper {
+  pages = {
+    index: { url: '#/user-management/users', id: 'cd-user-list' },
+    create: { url: '#/user-management/users/create', id: 'cd-user-form' }
+  };
+
+  create(username: string, password: string, name: string, email: string) {
+    this.navigateTo('create');
+
+    // fill in fields
+    cy.get('#username').type(username);
+    cy.get('#password').type(password);
+    cy.get('#confirmpassword').type(password);
+    cy.get('#name').type(name);
+    cy.get('#email').type(email);
+
+    // Click the create button and wait for user to be made
+    cy.get('[data-cy=submitBtn]').click();
+    this.getFirstTableCell(username).should('exist');
+  }
+
+  edit(username: string, password: string, name: string, email: string) {
+    this.navigateEdit(username);
+
+    // fill in fields with new values
+    cy.get('#password').clear().type(password);
+    cy.get('#confirmpassword').clear().type(password);
+    cy.get('#name').clear().type(name);
+    cy.get('#email').clear().type(email);
+
+    // Click the edit button and check new values are present in table
+    const editButton = cy.get('[data-cy=submitBtn]');
+    editButton.click();
+    this.getFirstTableCell(email).should('exist');
+    this.getFirstTableCell(name).should('exist');
+  }
+}
diff --git a/src/pybind/mgr/dashboard/frontend/cypress/e2e/visualTests/dashboard.vrt-spec.ts b/src/pybind/mgr/dashboard/frontend/cypress/e2e/visualTests/dashboard.vrt-spec.ts
new file mode 100644 (file)
index 0000000..450cff8
--- /dev/null
@@ -0,0 +1,24 @@
+import { LoginPageHelper } from '../ui/login.po';
+
+describe('Dashboard Landing Page', () => {
+  const login = new LoginPageHelper();
+
+  beforeEach(() => {
+    cy.eyesOpen({
+      testName: 'Dashboard Component'
+    });
+  });
+
+  afterEach(() => {
+    cy.eyesClose();
+  });
+
+  it('should take screenshot of dashboard landing page', () => {
+    login.navigateTo();
+    login.doLogin();
+    cy.get('[aria-label="Status card"]').should('be.visible');
+    cy.get('[aria-label="Inventory card"]').should('be.visible');
+    cy.get('[aria-label="Cluster utilization card"]').should('be.visible');
+    cy.eyesCheckWindow({ tag: 'Dashboard landing page' });
+  });
+});
diff --git a/src/pybind/mgr/dashboard/frontend/cypress/e2e/visualTests/login.vrt-spec.ts b/src/pybind/mgr/dashboard/frontend/cypress/e2e/visualTests/login.vrt-spec.ts
new file mode 100644 (file)
index 0000000..ea74f1d
--- /dev/null
@@ -0,0 +1,19 @@
+describe('Login Page', () => {
+  beforeEach(() => {
+    cy.visit('#/login');
+    cy.eyesOpen({
+      appName: 'Ceph',
+      testName: 'Login Component Check'
+    });
+  });
+
+  afterEach(() => {
+    cy.eyesClose();
+  });
+
+  it('types login credentials and takes screenshot', () => {
+    cy.get('[name=username]').type('admin');
+    cy.get('#password').type('admin');
+    cy.eyesCheckWindow({ tag: 'Login Screen with credentials typed' });
+  });
+});
diff --git a/src/pybind/mgr/dashboard/frontend/cypress/integration/a11y/dashboard.e2e-spec.ts b/src/pybind/mgr/dashboard/frontend/cypress/integration/a11y/dashboard.e2e-spec.ts
deleted file mode 100644 (file)
index 4feea0d..0000000
+++ /dev/null
@@ -1,27 +0,0 @@
-import { DashboardPageHelper } from '../ui/dashboard.po';
-
-describe('Dashboard Main Page', { retries: 0 }, () => {
-  const dashboard = new DashboardPageHelper();
-
-  beforeEach(() => {
-    cy.login();
-    Cypress.Cookies.preserveOnce('token');
-    dashboard.navigateTo();
-  });
-
-  describe('Dashboard accessibility', () => {
-    it('should have no accessibility violations', () => {
-      cy.injectAxe();
-      cy.checkAccessibility(
-        {
-          exclude: [['.cd-navbar-main']]
-        },
-        {
-          rules: {
-            'page-has-heading-one': { enabled: false }
-          }
-        }
-      );
-    });
-  });
-});
diff --git a/src/pybind/mgr/dashboard/frontend/cypress/integration/a11y/navigation.e2e-spec.ts b/src/pybind/mgr/dashboard/frontend/cypress/integration/a11y/navigation.e2e-spec.ts
deleted file mode 100644 (file)
index 2a0c5c5..0000000
+++ /dev/null
@@ -1,21 +0,0 @@
-import { NavigationPageHelper } from '../ui/navigation.po';
-
-describe('Navigation accessibility', { retries: 0 }, () => {
-  const shared = new NavigationPageHelper();
-
-  beforeEach(() => {
-    cy.login();
-    Cypress.Cookies.preserveOnce('token');
-    shared.navigateTo();
-  });
-
-  it('top-nav should have no accessibility violations', () => {
-    cy.injectAxe();
-    cy.checkAccessibility('.cd-navbar-top');
-  });
-
-  it('sidebar should have no accessibility violations', () => {
-    cy.injectAxe();
-    cy.checkAccessibility('nav[id=sidebar]');
-  });
-});
diff --git a/src/pybind/mgr/dashboard/frontend/cypress/integration/block/images.e2e-spec.ts b/src/pybind/mgr/dashboard/frontend/cypress/integration/block/images.e2e-spec.ts
deleted file mode 100644 (file)
index 5c89359..0000000
+++ /dev/null
@@ -1,95 +0,0 @@
-import { PoolPageHelper } from '../pools/pools.po';
-import { ImagesPageHelper } from './images.po';
-
-describe('Images page', () => {
-  const pools = new PoolPageHelper();
-  const images = new ImagesPageHelper();
-
-  const poolName = 'e2e_images_pool';
-
-  before(() => {
-    cy.login();
-    Cypress.Cookies.preserveOnce('token');
-    // Need pool for image testing
-    pools.navigateTo('create');
-    pools.create(poolName, 8, 'rbd');
-    pools.existTableCell(poolName);
-  });
-
-  after(() => {
-    // Deletes images test pool
-    pools.navigateTo();
-    pools.delete(poolName);
-    pools.navigateTo();
-    pools.existTableCell(poolName, false);
-  });
-
-  beforeEach(() => {
-    cy.login();
-    Cypress.Cookies.preserveOnce('token');
-    images.navigateTo();
-  });
-
-  it('should open and show breadcrumb', () => {
-    images.expectBreadcrumbText('Images');
-  });
-
-  it('should show four tabs', () => {
-    images.getTabsCount().should('eq', 4);
-  });
-
-  it('should show text for all tabs', () => {
-    images.getTabText(0).should('eq', 'Images');
-    images.getTabText(1).should('eq', 'Namespaces');
-    images.getTabText(2).should('eq', 'Trash');
-    images.getTabText(3).should('eq', 'Overall Performance');
-  });
-
-  describe('create, edit & delete image test', () => {
-    const imageName = 'e2e_images#image';
-    const newImageName = 'e2e_images#image_new';
-
-    it('should create image', () => {
-      images.createImage(imageName, poolName, '1');
-      images.getFirstTableCell(imageName).should('exist');
-    });
-
-    it('should edit image', () => {
-      images.editImage(imageName, poolName, newImageName, '2');
-      images.getFirstTableCell(newImageName).should('exist');
-    });
-
-    it('should delete image', () => {
-      images.delete(newImageName);
-    });
-  });
-
-  describe('move to trash, restore and purge image tests', () => {
-    const imageName = 'e2e_trash#image';
-    const newImageName = 'e2e_newtrash#image';
-
-    before(() => {
-      cy.login();
-      Cypress.Cookies.preserveOnce('token');
-      // Need image for trash testing
-      images.createImage(imageName, poolName, '1');
-      images.getFirstTableCell(imageName).should('exist');
-    });
-
-    it('should move the image to the trash', () => {
-      images.moveToTrash(imageName);
-      images.getFirstTableCell(imageName).should('exist');
-    });
-
-    it('should restore image to images table', () => {
-      images.restoreImage(imageName, newImageName);
-      images.getFirstTableCell(newImageName).should('exist');
-    });
-
-    it('should purge trash in images trash tab', () => {
-      images.getFirstTableCell(newImageName).should('exist');
-      images.moveToTrash(newImageName);
-      images.purgeTrash(newImageName, poolName);
-    });
-  });
-});
diff --git a/src/pybind/mgr/dashboard/frontend/cypress/integration/block/images.po.ts b/src/pybind/mgr/dashboard/frontend/cypress/integration/block/images.po.ts
deleted file mode 100644 (file)
index bf6cbc0..0000000
+++ /dev/null
@@ -1,110 +0,0 @@
-import { PageHelper } from '../page-helper.po';
-
-export class ImagesPageHelper extends PageHelper {
-  pages = {
-    index: { url: '#/block/rbd', id: 'cd-rbd-list' },
-    create: { url: '#/block/rbd/create', id: 'cd-rbd-form' }
-  };
-
-  // Creates a block image and fills in the name, pool, and size fields.
-  // Then checks if the image is present in the Images table.
-  createImage(name: string, pool: string, size: string) {
-    this.navigateTo('create');
-
-    cy.get('#name').type(name); // Enter in image name
-
-    // Select image pool
-    cy.contains('Loading...').should('not.exist');
-    this.selectOption('pool', pool);
-    cy.get('#pool').should('have.class', 'ng-valid'); // check if selected
-
-    // Enter in the size of the image
-    cy.get('#size').type(size);
-
-    // Click the create button and wait for image to be made
-    cy.get('[data-cy=submitBtn]').click();
-    this.getFirstTableCell(name).should('exist');
-  }
-
-  editImage(name: string, pool: string, newName: string, newSize: string) {
-    this.navigateEdit(name);
-
-    // Wait until data is loaded
-    cy.get('#pool').should('contain.value', pool);
-
-    cy.get('#name').clear().type(newName);
-    cy.get('#size').clear().type(newSize); // click the size box and send new size
-
-    cy.get('[data-cy=submitBtn]').click();
-
-    this.getExpandCollapseElement(newName).click();
-    cy.get('.table.table-striped.table-bordered').contains('td', newSize);
-  }
-
-  // Selects RBD image and moves it to the trash,
-  // checks that it is present in the trash table
-  moveToTrash(name: string) {
-    // wait for image to be created
-    cy.get('.datatable-body').first().should('not.contain.text', '(Creating...)');
-
-    this.getFirstTableCell(name).click();
-
-    // click on the drop down and selects the move to trash option
-    cy.get('.table-actions button.dropdown-toggle').first().click();
-    cy.get('button.move-to-trash').click();
-
-    cy.get('[data-cy=submitBtn]').should('be.visible').click();
-
-    // Clicks trash tab
-    cy.contains('.nav-link', 'Trash').click();
-    this.getFirstTableCell(name).should('exist');
-  }
-
-  // Checks trash tab table for image and then restores it to the RBD Images table
-  // (could change name if new name is given)
-  restoreImage(name: string, newName?: string) {
-    // clicks on trash tab
-    cy.contains('.nav-link', 'Trash').click();
-
-    // wait for table to load
-    this.getFirstTableCell(name).click();
-    cy.contains('button', 'Restore').click();
-
-    // wait for pop-up to be visible (checks for title of pop-up)
-    cy.get('cd-modal #name').should('be.visible');
-
-    // If a new name for the image is passed, it changes the name of the image
-    if (newName !== undefined) {
-      // click name box and send new name
-      cy.get('cd-modal #name').clear().type(newName);
-    }
-
-    cy.get('[data-cy=submitBtn]').click();
-
-    // clicks images tab
-    cy.contains('.nav-link', 'Images').click();
-
-    this.getFirstTableCell(newName).should('exist');
-  }
-
-  // Enters trash tab and purges trash, thus emptying the trash table.
-  // Checks if Image is still in the table.
-  purgeTrash(name: string, pool?: string) {
-    // clicks trash tab
-    cy.contains('.nav-link', 'Trash').click();
-    cy.contains('button', 'Purge Trash').click();
-
-    // Check for visibility of modal container
-    cy.get('.modal-header').should('be.visible');
-
-    // If purgeing a specific pool, selects that pool if given
-    if (pool !== undefined) {
-      this.selectOption('poolName', pool);
-      cy.get('#poolName').should('have.class', 'ng-valid'); // check if pool is selected
-    }
-    cy.get('[data-cy=submitBtn]').click();
-    // Wait for image to delete and check it is not present
-
-    this.getFirstTableCell(name).should('not.exist');
-  }
-}
diff --git a/src/pybind/mgr/dashboard/frontend/cypress/integration/block/iscsi.e2e-spec.ts b/src/pybind/mgr/dashboard/frontend/cypress/integration/block/iscsi.e2e-spec.ts
deleted file mode 100644 (file)
index cef4874..0000000
+++ /dev/null
@@ -1,25 +0,0 @@
-import { IscsiPageHelper } from './iscsi.po';
-
-describe('Iscsi Page', () => {
-  const iscsi = new IscsiPageHelper();
-
-  beforeEach(() => {
-    cy.login();
-    Cypress.Cookies.preserveOnce('token');
-    iscsi.navigateTo();
-  });
-
-  it('should open and show breadcrumb', () => {
-    iscsi.expectBreadcrumbText('Overview');
-  });
-
-  it('should check that tables are displayed and legends are correct', () => {
-    // Check tables are displayed
-    iscsi.getDataTables().its(0).should('be.visible');
-    iscsi.getDataTables().its(1).should('be.visible');
-
-    // Check that legends are correct
-    iscsi.getLegends().its(0).should('contain.text', 'Gateways');
-    iscsi.getLegends().its(1).should('contain.text', 'Images');
-  });
-});
diff --git a/src/pybind/mgr/dashboard/frontend/cypress/integration/block/iscsi.po.ts b/src/pybind/mgr/dashboard/frontend/cypress/integration/block/iscsi.po.ts
deleted file mode 100644 (file)
index 08efa64..0000000
+++ /dev/null
@@ -1,7 +0,0 @@
-import { PageHelper } from '../page-helper.po';
-
-export class IscsiPageHelper extends PageHelper {
-  pages = {
-    index: { url: '#/block/iscsi/overview', id: 'cd-iscsi' }
-  };
-}
diff --git a/src/pybind/mgr/dashboard/frontend/cypress/integration/block/mirroring.e2e-spec.ts b/src/pybind/mgr/dashboard/frontend/cypress/integration/block/mirroring.e2e-spec.ts
deleted file mode 100644 (file)
index 4e17840..0000000
+++ /dev/null
@@ -1,115 +0,0 @@
-import { PoolPageHelper } from '../pools/pools.po';
-import { MirroringPageHelper } from './mirroring.po';
-
-describe('Mirroring page', () => {
-  const pools = new PoolPageHelper();
-  const mirroring = new MirroringPageHelper();
-
-  beforeEach(() => {
-    cy.login();
-    Cypress.Cookies.preserveOnce('token');
-    mirroring.navigateTo();
-  });
-
-  it('should open and show breadcrumb', () => {
-    mirroring.expectBreadcrumbText('Mirroring');
-  });
-
-  it('should show three tabs', () => {
-    mirroring.getTabsCount().should('eq', 3);
-  });
-
-  it('should show text for all tabs', () => {
-    mirroring.getTabText(0).should('eq', 'Issues (0)');
-    mirroring.getTabText(1).should('eq', 'Syncing (0)');
-    mirroring.getTabText(2).should('eq', 'Ready (0)');
-  });
-
-  describe('rbd mirroring bootstrap', () => {
-    const poolName = 'rbd-mirror';
-
-    beforeEach(() => {
-      cy.login();
-      Cypress.Cookies.preserveOnce('token');
-      pools.navigateTo('create');
-      pools.create(poolName, 8, 'rbd');
-      pools.navigateTo();
-      pools.existTableCell(poolName, true);
-      mirroring.navigateTo();
-    });
-
-    it('should generate and import the bootstrap token between clusters', () => {
-      const url: string = Cypress.env('CEPH2_URL');
-      mirroring.navigateTo();
-      mirroring.generateToken(poolName);
-      cy.get('@token').then((bootstrapToken) => {
-        // pass the token to the origin as an arg
-        const args = { name: poolName, token: String(bootstrapToken) };
-
-        // login to the second ceph cluster
-        cy.ceph2Login();
-
-        // can't use any imports or functions inside the origin
-        // so writing the code to copy the token inside the origin manually
-        // rather than using a function call
-        // @ts-ignore
-        cy.origin(url, { args }, ({ name, token }: any) => {
-          // Create an rbd pool in the second cluster
-          cy.visit('#/pool/create').wait(100);
-          cy.get('input[name=name]').clear().type(name);
-          cy.get(`select[name=poolType]`).select('replicated');
-          cy.get(`select[name=poolType] option:checked`).contains('replicated');
-          cy.get('.float-start.me-2.select-menu-edit').click();
-          cy.get('.popover-body').should('be.visible');
-          // Choose rbd as the application label
-          cy.get('.select-menu-item-content').contains('rbd').click();
-          cy.get('cd-submit-button').click();
-          cy.get('cd-pool-list').should('exist');
-
-          cy.visit('#/block/mirroring').wait(1000);
-          cy.get('.table-actions button.dropdown-toggle').first().click();
-          cy.get('[aria-label="Import Bootstrap Token"]').click();
-          cy.get('cd-bootstrap-import-modal').within(() => {
-            cy.get(`label[for=${name}]`).click();
-            cy.get('textarea[id=token]').wait(100).type(token);
-            cy.get('button[type=submit]').click();
-          });
-        });
-      });
-
-      // login again since origin removes all the cookies
-      // sessions, localStorage items etc..
-      cy.login();
-      Cypress.Cookies.preserveOnce('token');
-      mirroring.navigateTo();
-      mirroring.checkPoolHealthStatus(poolName, 'OK');
-    });
-  });
-
-  describe('checks that edit mode functionality shows in the pools table', () => {
-    const poolName = 'mirroring_test';
-
-    beforeEach(() => {
-      pools.navigateTo('create'); // Need pool for mirroring testing
-      pools.create(poolName, 8, 'rbd');
-      pools.navigateTo();
-      pools.existTableCell(poolName, true);
-    });
-
-    it('tests editing mode for pools', () => {
-      mirroring.navigateTo();
-
-      mirroring.editMirror(poolName, 'Pool');
-      mirroring.getFirstTableCell('pool').should('be.visible');
-      mirroring.editMirror(poolName, 'Image');
-      mirroring.getFirstTableCell('image').should('be.visible');
-      mirroring.editMirror(poolName, 'Disabled');
-      mirroring.getFirstTableCell('disabled').should('be.visible');
-    });
-
-    afterEach(() => {
-      pools.navigateTo();
-      pools.delete(poolName);
-    });
-  });
-});
diff --git a/src/pybind/mgr/dashboard/frontend/cypress/integration/block/mirroring.po.ts b/src/pybind/mgr/dashboard/frontend/cypress/integration/block/mirroring.po.ts
deleted file mode 100644 (file)
index c4adca8..0000000
+++ /dev/null
@@ -1,61 +0,0 @@
-import { PageHelper } from '../page-helper.po';
-
-const pages = {
-  index: { url: '#/block/mirroring', id: 'cd-mirroring' }
-};
-
-export class MirroringPageHelper extends PageHelper {
-  pages = pages;
-
-  poolsColumnIndex = {
-    name: 1,
-    health: 6
-  };
-
-  /**
-   * Goes to the mirroring page and edits a pool in the Pool table. Clicks on the
-   * pool and chooses an option (either pool, image, or disabled)
-   */
-  @PageHelper.restrictTo(pages.index.url)
-  editMirror(name: string, option: string) {
-    // Clicks the pool in the table
-    this.getFirstTableCell(name).click();
-
-    // Clicks the Edit Mode button
-    cy.contains('button', 'Edit Mode').click();
-
-    // Clicks the drop down in the edit pop-up, then clicks the Update button
-    cy.get('.modal-content').should('be.visible');
-    this.selectOption('mirrorMode', option);
-
-    // Clicks update button and checks if the mode has been changed
-    cy.contains('button', 'Update').click();
-    cy.contains('.modal-dialog', 'Edit pool mirror mode').should('not.exist');
-    const val = option.toLowerCase(); // used since entries in table are lower case
-    this.getFirstTableCell(val).should('be.visible');
-  }
-
-  @PageHelper.restrictTo(pages.index.url)
-  generateToken(poolName: string) {
-    cy.get('[aria-label="Create Bootstrap Token"]').first().click();
-    cy.get('cd-bootstrap-create-modal').within(() => {
-      cy.get(`label[for=${poolName}]`).click();
-      cy.get('button[type=submit]').click();
-      cy.get('textarea[id=token]').wait(200).invoke('val').as('token');
-      cy.get('[aria-label="Back"]').click();
-    });
-  }
-
-  @PageHelper.restrictTo(pages.index.url)
-  checkPoolHealthStatus(poolName: string, status: string) {
-    cy.get('cd-mirroring-pools').within(() => {
-      this.getTableCell(this.poolsColumnIndex.name, poolName)
-        .parent()
-        .find(`datatable-body-cell:nth-child(${this.poolsColumnIndex.health}) .badge`)
-        .should(($ele) => {
-          const newLabels = $ele.toArray().map((v) => v.innerText);
-          expect(newLabels).to.include(status);
-        });
-    });
-  }
-}
diff --git a/src/pybind/mgr/dashboard/frontend/cypress/integration/cluster/configuration.e2e-spec.ts b/src/pybind/mgr/dashboard/frontend/cypress/integration/cluster/configuration.e2e-spec.ts
deleted file mode 100644 (file)
index d022d59..0000000
+++ /dev/null
@@ -1,78 +0,0 @@
-import { ConfigurationPageHelper } from './configuration.po';
-
-describe('Configuration page', () => {
-  const configuration = new ConfigurationPageHelper();
-
-  beforeEach(() => {
-    cy.login();
-    Cypress.Cookies.preserveOnce('token');
-    configuration.navigateTo();
-  });
-
-  describe('breadcrumb test', () => {
-    it('should open and show breadcrumb', () => {
-      configuration.expectBreadcrumbText('Configuration');
-    });
-  });
-
-  describe('fields check', () => {
-    beforeEach(() => {
-      configuration.getExpandCollapseElement().click();
-    });
-
-    it('should check that details table opens (w/o tab header)', () => {
-      configuration.getStatusTables().should('be.visible');
-      configuration.getTabs().should('not.exist');
-    });
-  });
-
-  describe('edit configuration test', () => {
-    const configName = 'client_cache_size';
-
-    beforeEach(() => {
-      configuration.clearTableSearchInput();
-      configuration.getTableCount('found').as('configFound');
-    });
-
-    after(() => {
-      configuration.configClear(configName);
-    });
-
-    it('should click and edit a configuration and results should appear in the table', () => {
-      configuration.edit(
-        configName,
-        ['global', '1'],
-        ['mon', '2'],
-        ['mgr', '3'],
-        ['osd', '4'],
-        ['mds', '5'],
-        ['client', '6']
-      );
-    });
-
-    it('should verify modified filter is applied properly', () => {
-      configuration.filterTable('Modified', 'no');
-      configuration.getTableCount('found').as('unmodifiedConfigs');
-
-      // Modified filter value to yes
-      configuration.filterTable('Modified', 'yes');
-      configuration.getTableCount('found').as('modifiedConfigs');
-
-      cy.get('@configFound').then((configFound) => {
-        cy.get('@unmodifiedConfigs').then((unmodifiedConfigs) => {
-          const modifiedConfigs = Number(configFound) - Number(unmodifiedConfigs);
-          configuration.getTableCount('found').should('eq', modifiedConfigs);
-        });
-      });
-
-      // Modified filter value to no
-      configuration.filterTable('Modified', 'no');
-      cy.get('@configFound').then((configFound) => {
-        cy.get('@modifiedConfigs').then((modifiedConfigs) => {
-          const unmodifiedConfigs = Number(configFound) - Number(modifiedConfigs);
-          configuration.getTableCount('found').should('eq', unmodifiedConfigs);
-        });
-      });
-    });
-  });
-});
diff --git a/src/pybind/mgr/dashboard/frontend/cypress/integration/cluster/configuration.po.ts b/src/pybind/mgr/dashboard/frontend/cypress/integration/cluster/configuration.po.ts
deleted file mode 100644 (file)
index 0133dc3..0000000
+++ /dev/null
@@ -1,75 +0,0 @@
-import { PageHelper } from '../page-helper.po';
-
-export class ConfigurationPageHelper extends PageHelper {
-  pages = {
-    index: { url: '#/configuration', id: 'cd-configuration' }
-  };
-
-  /**
-   * Clears out all the values in a config to reset before and after testing
-   * Does not work for configs with checkbox only, possible future PR
-   */
-  configClear(name: string) {
-    const valList = ['global', 'mon', 'mgr', 'osd', 'mds', 'client']; // Editable values
-
-    this.navigateEdit(name);
-    // Waits for the data to load
-    cy.contains('.card-header', `Edit ${name}`);
-
-    for (const i of valList) {
-      cy.get(`#${i}`).clear();
-    }
-    // Clicks save button and checks that values are not present for the selected config
-    cy.get('[data-cy=submitBtn]').click();
-
-    // Enter config setting name into filter box
-    this.searchTable(name);
-
-    // Expand row
-    this.getExpandCollapseElement(name).click();
-
-    // Checks for visibility of details tab
-    this.getStatusTables().should('be.visible');
-
-    for (const i of valList) {
-      // Waits until values are not present in the details table
-      this.getStatusTables().should('not.contain.text', i + ':');
-    }
-  }
-
-  /**
-   * Clicks the designated config, then inputs the values passed into the edit function.
-   * Then checks if the edit is reflected in the config table.
-   * Takes in name of config and a list of tuples of values the user wants edited,
-   * each tuple having the desired value along with the number tehey want for that value.
-   * Ex: [global, '2'] is the global value with an input of 2
-   */
-  edit(name: string, ...values: [string, string][]) {
-    this.navigateEdit(name);
-
-    // Waits for data to load
-    cy.contains('.card-header', `Edit ${name}`);
-
-    values.forEach((valtuple) => {
-      // Finds desired value based off given list
-      cy.get(`#${valtuple[0]}`).type(valtuple[1]); // of values and inserts the given number for the value
-    });
-
-    // Clicks save button then waits until the desired config is visible, clicks it,
-    // then checks that each desired value appears with the desired number
-    cy.get('[data-cy=submitBtn]').click();
-
-    // Enter config setting name into filter box
-    this.searchTable(name);
-
-    // Checks for visibility of config in table
-    this.getExpandCollapseElement(name).should('be.visible').click();
-
-    // Clicks config
-    values.forEach((value) => {
-      // iterates through list of values and
-      // checks if the value appears in details with the correct number attatched
-      cy.contains('.table.table-striped.table-bordered', `${value[0]}\: ${value[1]}`);
-    });
-  }
-}
diff --git a/src/pybind/mgr/dashboard/frontend/cypress/integration/cluster/create-cluster.po.ts b/src/pybind/mgr/dashboard/frontend/cypress/integration/cluster/create-cluster.po.ts
deleted file mode 100644 (file)
index 300eddb..0000000
+++ /dev/null
@@ -1,56 +0,0 @@
-import { PageHelper } from '../page-helper.po';
-import { NotificationSidebarPageHelper } from '../ui/notification.po';
-import { HostsPageHelper } from './hosts.po';
-import { ServicesPageHelper } from './services.po';
-
-const pages = {
-  index: { url: '#/expand-cluster', id: 'cd-create-cluster' }
-};
-export class CreateClusterWizardHelper extends PageHelper {
-  pages = pages;
-
-  createCluster() {
-    cy.get('cd-create-cluster').should('contain.text', 'Please expand your cluster first');
-    cy.get('[name=expand-cluster]').click();
-    cy.get('cd-wizard').should('exist');
-  }
-
-  doSkip() {
-    cy.get('[name=skip-cluster-creation]').click();
-    cy.contains('cd-modal button', 'Continue').click();
-
-    cy.get('cd-dashboard').should('exist');
-    const notification = new NotificationSidebarPageHelper();
-    notification.open();
-    notification.getNotifications().should('contain', 'Cluster expansion skipped by user');
-  }
-}
-
-export class CreateClusterHostPageHelper extends HostsPageHelper {
-  pages = {
-    index: { url: '#/expand-cluster', id: 'cd-wizard' },
-    add: { url: '', id: 'cd-host-form' }
-  };
-
-  columnIndex = {
-    hostname: 1,
-    labels: 2,
-    status: 3,
-    services: 0
-  };
-}
-
-export class CreateClusterServicePageHelper extends ServicesPageHelper {
-  pages = {
-    index: { url: '#/expand-cluster', id: 'cd-wizard' },
-    create: { url: '', id: 'cd-service-form' }
-  };
-
-  columnIndex = {
-    service_name: 1,
-    placement: 2,
-    running: 0,
-    size: 0,
-    last_refresh: 0
-  };
-}
diff --git a/src/pybind/mgr/dashboard/frontend/cypress/integration/cluster/crush-map.e2e-spec.ts b/src/pybind/mgr/dashboard/frontend/cypress/integration/cluster/crush-map.e2e-spec.ts
deleted file mode 100644 (file)
index 0a45473..0000000
+++ /dev/null
@@ -1,37 +0,0 @@
-import { CrushMapPageHelper } from './crush-map.po';
-
-describe('CRUSH map page', () => {
-  const crushmap = new CrushMapPageHelper();
-
-  beforeEach(() => {
-    cy.login();
-    Cypress.Cookies.preserveOnce('token');
-    crushmap.navigateTo();
-  });
-
-  describe('breadcrumb test', () => {
-    it('should open and show breadcrumb', () => {
-      crushmap.expectBreadcrumbText('CRUSH map');
-    });
-  });
-
-  describe('fields check', () => {
-    it('should check that title & table appears', () => {
-      // Check that title (CRUSH map viewer) appears
-      crushmap.getPageTitle().should('equal', 'CRUSH map viewer');
-
-      // Check that title appears once OSD is clicked
-      crushmap.getCrushNode(0).click();
-
-      crushmap
-        .getLegends()
-        .invoke('text')
-        .then((legend) => {
-          crushmap.getCrushNode(0).should('have.text', legend);
-        });
-
-      // Check that table appears once OSD is clicked
-      crushmap.getDataTables().should('be.visible');
-    });
-  });
-});
diff --git a/src/pybind/mgr/dashboard/frontend/cypress/integration/cluster/crush-map.po.ts b/src/pybind/mgr/dashboard/frontend/cypress/integration/cluster/crush-map.po.ts
deleted file mode 100644 (file)
index a5d2d59..0000000
+++ /dev/null
@@ -1,13 +0,0 @@
-import { PageHelper } from '../page-helper.po';
-
-export class CrushMapPageHelper extends PageHelper {
-  pages = { index: { url: '#/crush-map', id: 'cd-crushmap' } };
-
-  getPageTitle() {
-    return cy.get('cd-crushmap .card-header').text();
-  }
-
-  getCrushNode(idx: number) {
-    return cy.get('.node-name.type-osd').eq(idx);
-  }
-}
diff --git a/src/pybind/mgr/dashboard/frontend/cypress/integration/cluster/hosts.e2e-spec.ts b/src/pybind/mgr/dashboard/frontend/cypress/integration/cluster/hosts.e2e-spec.ts
deleted file mode 100644 (file)
index e4f9936..0000000
+++ /dev/null
@@ -1,35 +0,0 @@
-import { HostsPageHelper } from './hosts.po';
-
-describe('Hosts page', () => {
-  const hosts = new HostsPageHelper();
-
-  beforeEach(() => {
-    cy.login();
-    Cypress.Cookies.preserveOnce('token');
-    hosts.navigateTo();
-  });
-
-  describe('breadcrumb and tab tests', () => {
-    it('should open and show breadcrumb', () => {
-      hosts.expectBreadcrumbText('Hosts');
-    });
-
-    it('should show two tabs', () => {
-      hosts.getTabsCount().should('eq', 2);
-    });
-
-    it('should show hosts list tab at first', () => {
-      hosts.getTabText(0).should('eq', 'Hosts List');
-    });
-
-    it('should show overall performance as a second tab', () => {
-      hosts.getTabText(1).should('eq', 'Overall Performance');
-    });
-  });
-
-  describe('services link test', () => {
-    it('should check at least one host is present', () => {
-      hosts.check_for_host();
-    });
-  });
-});
diff --git a/src/pybind/mgr/dashboard/frontend/cypress/integration/cluster/hosts.po.ts b/src/pybind/mgr/dashboard/frontend/cypress/integration/cluster/hosts.po.ts
deleted file mode 100644 (file)
index 9511142..0000000
+++ /dev/null
@@ -1,182 +0,0 @@
-import { PageHelper } from '../page-helper.po';
-
-const pages = {
-  index: { url: '#/hosts', id: 'cd-hosts' },
-  add: { url: '#/hosts/(modal:add)', id: 'cd-host-form' }
-};
-
-export class HostsPageHelper extends PageHelper {
-  pages = pages;
-
-  columnIndex = {
-    hostname: 2,
-    services: 3,
-    labels: 4,
-    status: 5
-  };
-
-  check_for_host() {
-    this.getTableCount('total').should('not.be.eq', 0);
-  }
-
-  add(hostname: string, exist?: boolean, maintenance?: boolean, labels: string[] = []) {
-    cy.get(`${this.pages.add.id}`).within(() => {
-      cy.get('#hostname').type(hostname);
-      if (maintenance) {
-        cy.get('label[for=maintenance]').click();
-      }
-      if (exist) {
-        cy.get('#hostname').should('have.class', 'ng-invalid');
-      }
-    });
-
-    if (labels.length) {
-      this.selectPredefinedLabels(labels);
-    }
-
-    cy.get('cd-submit-button').click();
-    // back to host list
-    cy.get(`${this.pages.index.id}`);
-  }
-
-  selectPredefinedLabels(labels: string[]) {
-    cy.get('a[data-testid=select-menu-edit]').click();
-    for (const label of labels) {
-      cy.get('.popover-body div.select-menu-item-content').contains(label).click();
-    }
-  }
-
-  checkExist(hostname: string, exist: boolean) {
-    this.getTableCell(this.columnIndex.hostname, hostname).should(($elements) => {
-      const hosts = $elements.map((_, el) => el.textContent).get();
-      if (exist) {
-        expect(hosts).to.include(hostname);
-      } else {
-        expect(hosts).to.not.include(hostname);
-      }
-    });
-  }
-
-  remove(hostname: string) {
-    super.delete(hostname, this.columnIndex.hostname, 'hosts');
-  }
-
-  // Add or remove labels on a host, then verify labels in the table
-  editLabels(hostname: string, labels: string[], add: boolean) {
-    this.getTableCell(this.columnIndex.hostname, hostname).click();
-    this.clickActionButton('edit');
-
-    // add or remove label badges
-    if (add) {
-      cy.get('cd-modal').find('.select-menu-edit').click();
-      for (const label of labels) {
-        cy.contains('cd-modal .badge', new RegExp(`^${label}$`)).should('not.exist');
-        cy.get('.popover-body input').type(`${label}{enter}`);
-      }
-    } else {
-      for (const label of labels) {
-        cy.contains('cd-modal .badge', new RegExp(`^${label}$`))
-          .find('.badge-remove')
-          .click();
-      }
-    }
-    cy.get('cd-modal cd-submit-button').click();
-    this.checkLabelExists(hostname, labels, add);
-  }
-
-  checkLabelExists(hostname: string, labels: string[], add: boolean) {
-    // Verify labels are added or removed from Labels column
-    // First find row with hostname, then find labels in the row
-    this.getTableCell(this.columnIndex.hostname, hostname)
-      .click()
-      .parent()
-      .find(`datatable-body-cell:nth-child(${this.columnIndex.labels}) .badge`)
-      .should(($ele) => {
-        const newLabels = $ele.toArray().map((v) => v.innerText);
-        for (const label of labels) {
-          if (add) {
-            expect(newLabels).to.include(label);
-          } else {
-            expect(newLabels).to.not.include(label);
-          }
-        }
-      });
-  }
-
-  @PageHelper.restrictTo(pages.index.url)
-  maintenance(hostname: string, exit = false, force = false) {
-    this.clearTableSearchInput();
-    if (force) {
-      this.getTableCell(this.columnIndex.hostname, hostname).click();
-      this.clickActionButton('enter-maintenance');
-
-      cy.get('cd-modal').within(() => {
-        cy.contains('button', 'Continue').click();
-      });
-
-      this.getTableCell(this.columnIndex.hostname, hostname)
-        .parent()
-        .find(`datatable-body-cell:nth-child(${this.columnIndex.status}) .badge`)
-        .should(($ele) => {
-          const status = $ele.toArray().map((v) => v.innerText);
-          expect(status).to.include('maintenance');
-        });
-    }
-    if (exit) {
-      this.getTableCell(this.columnIndex.hostname, hostname)
-        .click()
-        .parent()
-        .find(`datatable-body-cell:nth-child(${this.columnIndex.status})`)
-        .then(($ele) => {
-          const status = $ele.toArray().map((v) => v.innerText);
-          if (status[0].includes('maintenance')) {
-            this.clickActionButton('exit-maintenance');
-          }
-        });
-
-      this.getTableCell(this.columnIndex.hostname, hostname)
-        .parent()
-        .find(`datatable-body-cell:nth-child(${this.columnIndex.status})`)
-        .should(($ele) => {
-          const status = $ele.toArray().map((v) => v.innerText);
-          expect(status).to.not.include('maintenance');
-        });
-    } else {
-      this.getTableCell(this.columnIndex.hostname, hostname).click();
-      this.clickActionButton('enter-maintenance');
-
-      this.getTableCell(this.columnIndex.hostname, hostname)
-        .parent()
-        .find(`datatable-body-cell:nth-child(${this.columnIndex.status}) .badge`)
-        .should(($ele) => {
-          const status = $ele.toArray().map((v) => v.innerText);
-          expect(status).to.include('maintenance');
-        });
-    }
-  }
-
-  @PageHelper.restrictTo(pages.index.url)
-  drain(hostname: string) {
-    this.getTableCell(this.columnIndex.hostname, hostname).click();
-    this.clickActionButton('start-drain');
-    this.checkLabelExists(hostname, ['_no_schedule'], true);
-
-    this.clickTab('cd-host-details', hostname, 'Daemons');
-    cy.get('cd-host-details').within(() => {
-      cy.wait(20000);
-      this.expectTableCount('total', 0);
-    });
-  }
-
-  checkServiceInstancesExist(hostname: string, instances: string[]) {
-    this.getTableCell(this.columnIndex.hostname, hostname)
-      .parent()
-      .find(`datatable-body-cell:nth-child(${this.columnIndex.services}) .badge`)
-      .should(($ele) => {
-        const serviceInstances = $ele.toArray().map((v) => v.innerText);
-        for (const instance of instances) {
-          expect(serviceInstances).to.include(instance);
-        }
-      });
-  }
-}
diff --git a/src/pybind/mgr/dashboard/frontend/cypress/integration/cluster/inventory.po.ts b/src/pybind/mgr/dashboard/frontend/cypress/integration/cluster/inventory.po.ts
deleted file mode 100644 (file)
index 5a9abdc..0000000
+++ /dev/null
@@ -1,22 +0,0 @@
-import { PageHelper } from '../page-helper.po';
-
-const pages = {
-  index: { url: '#/inventory', id: 'cd-inventory' }
-};
-
-export class InventoryPageHelper extends PageHelper {
-  pages = pages;
-
-  identify() {
-    // Nothing we can do, just verify the form is there
-    this.getFirstTableCell().click();
-    cy.contains('cd-table-actions button', 'Identify').click();
-    cy.get('cd-modal').within(() => {
-      cy.get('#duration').select('15 minutes');
-      cy.get('#duration').select('10 minutes');
-      cy.get('cd-back-button').click();
-    });
-    cy.get('cd-modal').should('not.exist');
-    cy.get(`${this.pages.index.id}`);
-  }
-}
diff --git a/src/pybind/mgr/dashboard/frontend/cypress/integration/cluster/logs.e2e-spec.ts b/src/pybind/mgr/dashboard/frontend/cypress/integration/cluster/logs.e2e-spec.ts
deleted file mode 100644 (file)
index ecc3cc1..0000000
+++ /dev/null
@@ -1,62 +0,0 @@
-import { PoolPageHelper } from '../pools/pools.po';
-import { LogsPageHelper } from './logs.po';
-
-describe('Logs page', () => {
-  const logs = new LogsPageHelper();
-  const pools = new PoolPageHelper();
-
-  const poolname = 'e2e_logs_test_pool';
-  const today = new Date();
-  let hour = today.getHours();
-  if (hour > 12) {
-    hour = hour - 12;
-  }
-  const minute = today.getMinutes();
-
-  beforeEach(() => {
-    cy.login();
-    Cypress.Cookies.preserveOnce('token');
-  });
-
-  describe('breadcrumb and tab tests', () => {
-    beforeEach(() => {
-      logs.navigateTo();
-    });
-
-    it('should open and show breadcrumb', () => {
-      logs.expectBreadcrumbText('Logs');
-    });
-
-    it('should show three tabs', () => {
-      logs.getTabsCount().should('eq', 3);
-    });
-
-    it('should show cluster logs tab at first', () => {
-      logs.getTabText(0).should('eq', 'Cluster Logs');
-    });
-
-    it('should show audit logs as a second tab', () => {
-      logs.getTabText(1).should('eq', 'Audit Logs');
-    });
-
-    it('should show daemon logs as a third tab', () => {
-      logs.getTabText(2).should('eq', 'Daemon Logs');
-    });
-  });
-
-  describe('audit logs respond to pool creation and deletion test', () => {
-    it('should create pool and check audit logs reacted', () => {
-      pools.navigateTo('create');
-      pools.create(poolname, 8);
-      pools.navigateTo();
-      pools.existTableCell(poolname, true);
-      logs.checkAuditForPoolFunction(poolname, 'create', hour, minute);
-    });
-
-    it('should delete pool and check audit logs reacted', () => {
-      pools.navigateTo();
-      pools.delete(poolname);
-      logs.checkAuditForPoolFunction(poolname, 'delete', hour, minute);
-    });
-  });
-});
diff --git a/src/pybind/mgr/dashboard/frontend/cypress/integration/cluster/logs.po.ts b/src/pybind/mgr/dashboard/frontend/cypress/integration/cluster/logs.po.ts
deleted file mode 100644 (file)
index 7efd8a6..0000000
+++ /dev/null
@@ -1,70 +0,0 @@
-import { PageHelper } from '../page-helper.po';
-
-export class LogsPageHelper extends PageHelper {
-  pages = {
-    index: { url: '#/logs', id: 'cd-logs' }
-  };
-
-  checkAuditForPoolFunction(poolname: string, poolfunction: string, hour: number, minute: number) {
-    this.navigateTo();
-
-    // sometimes the modal from deleting pool is still present at this point.
-    // This wait makes sure it isn't
-    cy.contains('.modal-dialog', 'Delete Pool').should('not.exist');
-
-    // go to audit logs tab
-    cy.contains('.nav-link', 'Audit Logs').click();
-
-    // Enter an earliest time so that no old messages with the same pool name show up
-    cy.get('.ngb-tp-input').its(0).clear();
-
-    if (hour < 10) {
-      cy.get('.ngb-tp-input').its(0).type('0');
-    }
-    cy.get('.ngb-tp-input').its(0).type(`${hour}`);
-
-    cy.get('.ngb-tp-input').its(1).clear();
-    if (minute < 10) {
-      cy.get('.ngb-tp-input').its(1).type('0');
-    }
-    cy.get('.ngb-tp-input').its(1).type(`${minute}`);
-
-    // Enter the pool name into the filter box
-    cy.get('input.form-control.ng-valid').first().clear().type(poolname);
-
-    cy.get('.tab-pane.active')
-      .get('.card-body')
-      .get('.message')
-      .should('contain.text', poolname)
-      .and('contain.text', `pool ${poolfunction}`);
-  }
-
-  checkAuditForConfigChange(configname: string, setting: string, hour: number, minute: number) {
-    this.navigateTo();
-
-    // go to audit logs tab
-    cy.contains('.nav-link', 'Audit Logs').click();
-
-    // Enter an earliest time so that no old messages with the same config name show up
-    cy.get('.ngb-tp-input').its(0).clear();
-    if (hour < 10) {
-      cy.get('.ngb-tp-input').its(0).type('0');
-    }
-    cy.get('.ngb-tp-input').its(0).type(`${hour}`);
-
-    cy.get('.ngb-tp-input').its(1).clear();
-    if (minute < 10) {
-      cy.get('.ngb-tp-input').its(1).type('0');
-    }
-    cy.get('.ngb-tp-input').its(1).type(`${minute}`);
-
-    // Enter the config name into the filter box
-    cy.get('input.form-control.ng-valid').first().clear().type(configname);
-
-    cy.get('.tab-pane.active')
-      .get('.card-body')
-      .get('.message')
-      .should('contain.text', configname)
-      .and('contain.text', setting);
-  }
-}
diff --git a/src/pybind/mgr/dashboard/frontend/cypress/integration/cluster/mgr-modules.e2e-spec.ts b/src/pybind/mgr/dashboard/frontend/cypress/integration/cluster/mgr-modules.e2e-spec.ts
deleted file mode 100644 (file)
index 0a2aa81..0000000
+++ /dev/null
@@ -1,78 +0,0 @@
-import { Input, ManagerModulesPageHelper } from './mgr-modules.po';
-
-describe('Manager modules page', () => {
-  const mgrmodules = new ManagerModulesPageHelper();
-
-  beforeEach(() => {
-    cy.login();
-    Cypress.Cookies.preserveOnce('token');
-    mgrmodules.navigateTo();
-  });
-
-  describe('breadcrumb test', () => {
-    it('should open and show breadcrumb', () => {
-      mgrmodules.expectBreadcrumbText('Manager Modules');
-    });
-  });
-
-  describe('verifies editing functionality for manager modules', () => {
-    it('should test editing on balancer module', () => {
-      const balancerArr: Input[] = [
-        {
-          id: 'crush_compat_max_iterations',
-          newValue: '123',
-          oldValue: '25'
-        }
-      ];
-      mgrmodules.editMgrModule('balancer', balancerArr);
-    });
-
-    it('should test editing on dashboard module', () => {
-      const dashboardArr: Input[] = [
-        {
-          id: 'GRAFANA_API_PASSWORD',
-          newValue: 'rafa',
-          oldValue: ''
-        }
-      ];
-      mgrmodules.editMgrModule('dashboard', dashboardArr);
-    });
-
-    it('should test editing on devicehealth module', () => {
-      const devHealthArray: Input[] = [
-        {
-          id: 'mark_out_threshold',
-          newValue: '1987',
-          oldValue: '2419200'
-        },
-        {
-          id: 'pool_name',
-          newValue: 'sox',
-          oldValue: '.mgr'
-        },
-        {
-          id: 'retention_period',
-          newValue: '1999',
-          oldValue: '15552000'
-        },
-        {
-          id: 'scrape_frequency',
-          newValue: '2020',
-          oldValue: '86400'
-        },
-        {
-          id: 'sleep_interval',
-          newValue: '456',
-          oldValue: '600'
-        },
-        {
-          id: 'warn_threshold',
-          newValue: '567',
-          oldValue: '7257600'
-        }
-      ];
-
-      mgrmodules.editMgrModule('devicehealth', devHealthArray);
-    });
-  });
-});
diff --git a/src/pybind/mgr/dashboard/frontend/cypress/integration/cluster/mgr-modules.po.ts b/src/pybind/mgr/dashboard/frontend/cypress/integration/cluster/mgr-modules.po.ts
deleted file mode 100644 (file)
index 04d2eee..0000000
+++ /dev/null
@@ -1,57 +0,0 @@
-import { PageHelper } from '../page-helper.po';
-
-export class Input {
-  id: string;
-  oldValue: string;
-  newValue: string;
-}
-
-export class ManagerModulesPageHelper extends PageHelper {
-  pages = { index: { url: '#/mgr-modules', id: 'cd-mgr-module-list' } };
-
-  /**
-   * Selects the Manager Module and then fills in the desired fields.
-   */
-  editMgrModule(name: string, inputs: Input[]) {
-    this.navigateEdit(name);
-
-    for (const input of inputs) {
-      // Clears fields and adds edits
-      cy.get(`#${input.id}`).clear().type(input.newValue);
-    }
-
-    cy.contains('button', 'Update').click();
-    // Checks if edits appear
-    this.getExpandCollapseElement(name).should('be.visible').click();
-
-    for (const input of inputs) {
-      cy.get('.datatable-body').last().contains(input.newValue);
-    }
-
-    // Clear mgr module of all edits made to it
-    this.navigateEdit(name);
-
-    // Clears the editable fields
-    for (const input of inputs) {
-      if (input.oldValue) {
-        const id = `#${input.id}`;
-        cy.get(id).clear();
-        if (input.oldValue) {
-          cy.get(id).type(input.oldValue);
-        }
-      }
-    }
-
-    // Checks that clearing represents in details tab of module
-    cy.contains('button', 'Update').click();
-    this.getExpandCollapseElement(name).should('be.visible').click();
-    for (const input of inputs) {
-      if (input.oldValue) {
-        cy.get('.datatable-body')
-          .eq(1)
-          .should('contain', input.id)
-          .and('not.contain', input.newValue);
-      }
-    }
-  }
-}
diff --git a/src/pybind/mgr/dashboard/frontend/cypress/integration/cluster/monitors.e2e-spec.ts b/src/pybind/mgr/dashboard/frontend/cypress/integration/cluster/monitors.e2e-spec.ts
deleted file mode 100644 (file)
index a23d071..0000000
+++ /dev/null
@@ -1,62 +0,0 @@
-import { MonitorsPageHelper } from './monitors.po';
-
-describe('Monitors page', () => {
-  const monitors = new MonitorsPageHelper();
-
-  beforeEach(() => {
-    cy.login();
-    Cypress.Cookies.preserveOnce('token');
-    monitors.navigateTo();
-  });
-
-  describe('breadcrumb test', () => {
-    it('should open and show breadcrumb', () => {
-      monitors.expectBreadcrumbText('Monitors');
-    });
-  });
-
-  describe('fields check', () => {
-    it('should check status table is present', () => {
-      // check for table header 'Status'
-      monitors.getLegends().its(0).should('have.text', 'Status');
-
-      // check for fields in table
-      monitors
-        .getStatusTables()
-        .should('contain.text', 'Cluster ID')
-        .and('contain.text', 'monmap modified')
-        .and('contain.text', 'monmap epoch')
-        .and('contain.text', 'quorum con')
-        .and('contain.text', 'quorum mon')
-        .and('contain.text', 'required con')
-        .and('contain.text', 'required mon');
-    });
-
-    it('should check In Quorum and Not In Quorum tables are present', () => {
-      // check for there to be two tables
-      monitors.getDataTables().should('have.length', 2);
-
-      // check for table header 'In Quorum'
-      monitors.getLegends().its(1).should('have.text', 'In Quorum');
-
-      // check for table header 'Not In Quorum'
-      monitors.getLegends().its(2).should('have.text', 'Not In Quorum');
-
-      // verify correct columns on In Quorum table
-      monitors.getDataTableHeaders(0).contains('Name');
-
-      monitors.getDataTableHeaders(0).contains('Rank');
-
-      monitors.getDataTableHeaders(0).contains('Public Address');
-
-      monitors.getDataTableHeaders(0).contains('Open Sessions');
-
-      // verify correct columns on Not In Quorum table
-      monitors.getDataTableHeaders(1).contains('Name');
-
-      monitors.getDataTableHeaders(1).contains('Rank');
-
-      monitors.getDataTableHeaders(1).contains('Public Address');
-    });
-  });
-});
diff --git a/src/pybind/mgr/dashboard/frontend/cypress/integration/cluster/monitors.po.ts b/src/pybind/mgr/dashboard/frontend/cypress/integration/cluster/monitors.po.ts
deleted file mode 100644 (file)
index 4113b99..0000000
+++ /dev/null
@@ -1,7 +0,0 @@
-import { PageHelper } from '../page-helper.po';
-
-export class MonitorsPageHelper extends PageHelper {
-  pages = {
-    index: { url: '#/monitor', id: 'cd-monitor' }
-  };
-}
diff --git a/src/pybind/mgr/dashboard/frontend/cypress/integration/cluster/osds.e2e-spec.ts b/src/pybind/mgr/dashboard/frontend/cypress/integration/cluster/osds.e2e-spec.ts
deleted file mode 100644 (file)
index 2fc148a..0000000
+++ /dev/null
@@ -1,57 +0,0 @@
-import { OSDsPageHelper } from './osds.po';
-
-describe('OSDs page', () => {
-  const osds = new OSDsPageHelper();
-
-  beforeEach(() => {
-    cy.login();
-    Cypress.Cookies.preserveOnce('token');
-    osds.navigateTo();
-  });
-
-  describe('breadcrumb and tab tests', () => {
-    it('should open and show breadcrumb', () => {
-      osds.expectBreadcrumbText('OSDs');
-    });
-
-    it('should show two tabs', () => {
-      osds.getTabsCount().should('eq', 2);
-      osds.getTabText(0).should('eq', 'OSDs List');
-      osds.getTabText(1).should('eq', 'Overall Performance');
-    });
-  });
-
-  describe('check existence of fields on OSD page', () => {
-    it('should check that number of rows and count in footer match', () => {
-      osds.getTableCount('total').then((text) => {
-        osds.getTableRows().its('length').should('equal', text);
-      });
-    });
-
-    it('should verify that buttons exist', () => {
-      cy.contains('button', 'Create');
-      cy.contains('button', 'Cluster-wide configuration');
-    });
-
-    describe('by selecting one row in OSDs List', () => {
-      beforeEach(() => {
-        osds.getExpandCollapseElement().click();
-      });
-
-      it('should show the correct text for the tab labels', () => {
-        cy.get('#tabset-osd-details > a').then(($tabs) => {
-          const tabHeadings = $tabs.map((_i, e) => e.textContent).get();
-
-          expect(tabHeadings).to.eql([
-            'Devices',
-            'Attributes (OSD map)',
-            'Metadata',
-            'Device health',
-            'Performance counter',
-            'Performance Details'
-          ]);
-        });
-      });
-    });
-  });
-});
diff --git a/src/pybind/mgr/dashboard/frontend/cypress/integration/cluster/osds.po.ts b/src/pybind/mgr/dashboard/frontend/cypress/integration/cluster/osds.po.ts
deleted file mode 100644 (file)
index cd812f4..0000000
+++ /dev/null
@@ -1,84 +0,0 @@
-import { PageHelper } from '../page-helper.po';
-
-const pages = {
-  index: { url: '#/osd', id: 'cd-osd-list' },
-  create: { url: '#/osd/create', id: 'cd-osd-form' }
-};
-
-export class OSDsPageHelper extends PageHelper {
-  pages = pages;
-
-  columnIndex = {
-    id: 3,
-    status: 5
-  };
-
-  create(deviceType: 'hdd' | 'ssd', hostname?: string, expandCluster = false) {
-    cy.get('[aria-label="toggle advanced mode"]').click();
-    // Click Primary devices Add button
-    cy.get('cd-osd-devices-selection-groups[name="Primary"]').as('primaryGroups');
-    cy.get('@primaryGroups').find('button').click();
-
-    // Select all devices with `deviceType`
-    cy.get('cd-osd-devices-selection-modal').within(() => {
-      cy.get('.modal-footer .tc_submitButton').as('addButton').should('be.disabled');
-      this.filterTable('Type', deviceType);
-      if (hostname) {
-        this.filterTable('Hostname', hostname);
-      }
-
-      if (expandCluster) {
-        this.getTableCount('total').should('be.gte', 1);
-      }
-      cy.get('@addButton').click();
-    });
-
-    if (!expandCluster) {
-      cy.get('@primaryGroups').within(() => {
-        this.getTableCount('total').as('newOSDCount');
-      });
-
-      cy.get(`${pages.create.id} .card-footer .tc_submitButton`).click();
-      cy.get(`cd-osd-creation-preview-modal .modal-footer .tc_submitButton`).click();
-    }
-  }
-
-  @PageHelper.restrictTo(pages.index.url)
-  checkStatus(id: number, status: string[]) {
-    this.searchTable(`id:${id}`);
-    this.expectTableCount('found', 1);
-    cy.get(`datatable-body-cell:nth-child(${this.columnIndex.status}) .badge`).should(($ele) => {
-      const allStatus = $ele.toArray().map((v) => v.innerText);
-      for (const s of status) {
-        expect(allStatus).to.include(s);
-      }
-    });
-  }
-
-  @PageHelper.restrictTo(pages.index.url)
-  ensureNoOsd(id: number) {
-    this.searchTable(`id:${id}`);
-    this.expectTableCount('found', 0);
-    this.clearTableSearchInput();
-  }
-
-  @PageHelper.restrictTo(pages.index.url)
-  deleteByIDs(osdIds: number[], replace?: boolean) {
-    this.getTableRows().each(($el) => {
-      const rowOSD = Number(
-        $el.find('datatable-body-cell .datatable-body-cell-label').get(this.columnIndex.id - 1)
-          .textContent
-      );
-      if (osdIds.includes(rowOSD)) {
-        cy.wrap($el).click();
-      }
-    });
-    this.clickActionButton('delete');
-    if (replace) {
-      cy.get('cd-modal label[for="preserve"]').click();
-    }
-    cy.get('cd-modal label[for="confirmation"]').click();
-    cy.contains('cd-modal button', 'Delete').click();
-    cy.get('cd-modal').should('not.exist');
-  }
-}
diff --git a/src/pybind/mgr/dashboard/frontend/cypress/integration/cluster/services.po.ts b/src/pybind/mgr/dashboard/frontend/cypress/integration/cluster/services.po.ts
deleted file mode 100644 (file)
index c464a3f..0000000
+++ /dev/null
@@ -1,200 +0,0 @@
-import { PageHelper } from '../page-helper.po';
-
-const pages = {
-  index: { url: '#/services', id: 'cd-services' },
-  create: { url: '#/services/(modal:create)', id: 'cd-service-form' }
-};
-
-export class ServicesPageHelper extends PageHelper {
-  pages = pages;
-
-  columnIndex = {
-    service_name: 2,
-    placement: 3,
-    running: 4,
-    size: 5,
-    last_refresh: 6
-  };
-
-  serviceDetailColumnIndex = {
-    daemonName: 2,
-    status: 4
-  };
-
-  check_for_service() {
-    this.getTableCount('total').should('not.be.eq', 0);
-  }
-
-  private selectServiceType(serviceType: string) {
-    return this.selectOption('service_type', serviceType);
-  }
-
-  clickServiceTab(serviceName: string, tabName: string) {
-    this.getExpandCollapseElement(serviceName).click();
-    cy.get('cd-service-details').within(() => {
-      this.getTab(tabName).click();
-    });
-  }
-
-  addService(
-    serviceType: string,
-    exist?: boolean,
-    count = 1,
-    snmpVersion?: string,
-    snmpPrivProtocol?: boolean,
-    unmanaged = false
-  ) {
-    cy.get(`${this.pages.create.id}`).within(() => {
-      this.selectServiceType(serviceType);
-      switch (serviceType) {
-        case 'rgw':
-          cy.get('#service_id').type('foo');
-          unmanaged ? cy.get('label[for=unmanaged]').click() : cy.get('#count').type(String(count));
-          break;
-
-        case 'ingress':
-          if (unmanaged) {
-            cy.get('label[for=unmanaged]').click();
-          }
-          this.selectOption('backend_service', 'rgw.foo');
-          cy.get('#service_id').should('have.value', 'rgw.foo');
-          cy.get('#virtual_ip').type('192.168.100.1/24');
-          cy.get('#frontend_port').type('8081');
-          cy.get('#monitor_port').type('8082');
-          break;
-
-        case 'nfs':
-          cy.get('#service_id').type('testnfs');
-          unmanaged ? cy.get('label[for=unmanaged]').click() : cy.get('#count').type(String(count));
-          break;
-
-        case 'snmp-gateway':
-          this.selectOption('snmp_version', snmpVersion);
-          cy.get('#snmp_destination').type('192.168.0.1:8443');
-          if (snmpVersion === 'V2c') {
-            cy.get('#snmp_community').type('public');
-          } else {
-            cy.get('#engine_id').type('800C53F00000');
-            this.selectOption('auth_protocol', 'SHA');
-            if (snmpPrivProtocol) {
-              this.selectOption('privacy_protocol', 'DES');
-              cy.get('#snmp_v3_priv_password').type('testencrypt');
-            }
-
-            // Credentials
-            cy.get('#snmp_v3_auth_username').type('test');
-            cy.get('#snmp_v3_auth_password').type('testpass');
-          }
-          break;
-
-        default:
-          cy.get('#service_id').type('test');
-          unmanaged ? cy.get('label[for=unmanaged]').click() : cy.get('#count').type(String(count));
-          break;
-      }
-      if (serviceType === 'snmp-gateway') {
-        cy.get('cd-submit-button').dblclick();
-      } else {
-        cy.get('cd-submit-button').click();
-      }
-    });
-    if (exist) {
-      cy.get('#service_id').should('have.class', 'ng-invalid');
-    } else {
-      // back to service list
-      cy.get(`${this.pages.index.id}`);
-    }
-  }
-
-  editService(name: string, daemonCount: string) {
-    this.navigateEdit(name, true, false);
-    cy.get(`${this.pages.create.id}`).within(() => {
-      cy.get('#service_type').should('be.disabled');
-      cy.get('#service_id').should('be.disabled');
-      cy.get('#count').clear().type(daemonCount);
-      cy.get('cd-submit-button').click();
-    });
-  }
-
-  checkServiceStatus(daemon: string, expectedStatus = 'running') {
-    let daemonNameIndex = this.serviceDetailColumnIndex.daemonName;
-    let statusIndex = this.serviceDetailColumnIndex.status;
-
-    // since hostname row is hidden from the hosts details table,
-    // we'll need to manually override the indexes when this check is being
-    // done for the daemons in host details page. So we'll get the url and
-    // verify if the current page is not the services index page
-    cy.url().then((url) => {
-      if (!url.includes(pages.index.url)) {
-        daemonNameIndex = 1;
-        statusIndex = 3;
-      }
-
-      cy.get('cd-service-daemon-list').within(() => {
-        this.getTableCell(daemonNameIndex, daemon, true)
-          .parent()
-          .find(`datatable-body-cell:nth-child(${statusIndex}) .badge`)
-          .should(($ele) => {
-            const status = $ele.toArray().map((v) => v.innerText);
-            expect(status).to.include(expectedStatus);
-          });
-      });
-    });
-  }
-
-  expectPlacementCount(serviceName: string, expectedCount: string) {
-    this.getTableCell(this.columnIndex.service_name, serviceName)
-      .parent()
-      .find(`datatable-body-cell:nth-child(${this.columnIndex.placement})`)
-      .should(($ele) => {
-        const running = $ele.text().split(';');
-        expect(running).to.include(`count:${expectedCount}`);
-      });
-  }
-
-  checkExist(serviceName: string, exist: boolean) {
-    this.getTableCell(this.columnIndex.service_name, serviceName).should(($elements) => {
-      const services = $elements.map((_, el) => el.textContent).get();
-      if (exist) {
-        expect(services).to.include(serviceName);
-      } else {
-        expect(services).to.not.include(serviceName);
-      }
-    });
-  }
-
-  isUnmanaged(serviceName: string, unmanaged: boolean) {
-    this.getTableCell(this.columnIndex.service_name, serviceName)
-      .parent()
-      .find(`datatable-body-cell:nth-child(${this.columnIndex.placement})`)
-      .should(($ele) => {
-        const placement = $ele.text().split(';');
-        unmanaged
-          ? expect(placement).to.include('unmanaged')
-          : expect(placement).to.not.include('unmanaged');
-      });
-  }
-
-  deleteService(serviceName: string) {
-    const getRow = this.getTableCell.bind(this, this.columnIndex.service_name);
-    getRow(serviceName).click();
-
-    // Clicks on table Delete button
-    this.clickActionButton('delete');
-
-    // Confirms deletion
-    cy.get('cd-modal .custom-control-label').click();
-    cy.contains('cd-modal button', 'Delete').click();
-
-    // Wait for modal to close
-    cy.get('cd-modal').should('not.exist');
-    this.checkExist(serviceName, false);
-  }
-
-  daemonAction(daemon: string, action: string) {
-    cy.get('cd-service-daemon-list').within(() => {
-      this.getTableRow(daemon).click();
-      this.clickActionButton(action);
-    });
-  }
-}
diff --git a/src/pybind/mgr/dashboard/frontend/cypress/integration/cluster/users.e2e-spec.ts b/src/pybind/mgr/dashboard/frontend/cypress/integration/cluster/users.e2e-spec.ts
deleted file mode 100644 (file)
index 87acda9..0000000
+++ /dev/null
@@ -1,27 +0,0 @@
-import { UsersPageHelper } from './users.po';
-
-describe('Cluster Ceph Users', () => {
-  const users = new UsersPageHelper();
-
-  beforeEach(() => {
-    cy.login();
-    Cypress.Cookies.preserveOnce('token');
-    users.navigateTo();
-  });
-
-  describe('breadcrumb and tab tests', () => {
-    it('should open and show breadcrumb', () => {
-      users.expectBreadcrumbText('Ceph Users');
-    });
-  });
-
-  describe('Cluster users table', () => {
-    it('should verify the table is not empty', () => {
-      users.checkForUsers();
-    });
-
-    it('should verify the keys are hidden', () => {
-      users.verifyKeysAreHidden();
-    });
-  });
-});
diff --git a/src/pybind/mgr/dashboard/frontend/cypress/integration/cluster/users.po.ts b/src/pybind/mgr/dashboard/frontend/cypress/integration/cluster/users.po.ts
deleted file mode 100644 (file)
index 8778384..0000000
+++ /dev/null
@@ -1,29 +0,0 @@
-import { PageHelper } from '../page-helper.po';
-
-const pages = {
-  index: { url: '#/ceph-users', id: 'cd-crud-table' }
-};
-
-export class UsersPageHelper extends PageHelper {
-  pages = pages;
-
-  columnIndex = {
-    entity: 1,
-    capabilities: 2,
-    key: 3
-  };
-
-  checkForUsers() {
-    this.getTableCount('total').should('not.be.eq', 0);
-  }
-
-  verifyKeysAreHidden() {
-    this.getTableCell(this.columnIndex.entity, 'osd.0')
-      .parent()
-      .find(`datatable-body-cell:nth-child(${this.columnIndex.key}) span`)
-      .should(($ele) => {
-        const serviceInstances = $ele.toArray().map((v) => v.innerText);
-        expect(serviceInstances).not.contains(/^[a-z0-9]+$/i);
-      });
-  }
-}
diff --git a/src/pybind/mgr/dashboard/frontend/cypress/integration/common/01-global.feature.po.ts b/src/pybind/mgr/dashboard/frontend/cypress/integration/common/01-global.feature.po.ts
deleted file mode 100644 (file)
index d5b4645..0000000
+++ /dev/null
@@ -1,188 +0,0 @@
-import { And, Given, Then, When } from 'cypress-cucumber-preprocessor/steps';
-
-import { UrlsCollection } from './urls.po';
-
-const urlsCollection = new UrlsCollection();
-
-Given('I am logged in', () => {
-  cy.login();
-  Cypress.Cookies.preserveOnce('token');
-});
-
-Given('I am on the {string} page', (page: string) => {
-  cy.visit(urlsCollection.pages[page].url);
-  cy.get(urlsCollection.pages[page].id).should('exist');
-});
-
-Then('I should be on the {string} page', (page: string) => {
-  cy.get(urlsCollection.pages[page].id).should('exist');
-});
-
-And('I should see a button to {string}', (button: string) => {
-  cy.get(`[aria-label="${button}"]`).should('be.visible');
-});
-
-When('I click on {string} button', (button: string) => {
-  cy.get(`[aria-label="${button}"]`).first().click();
-});
-
-// When you are clicking on an action in the table actions dropdown button
-When('I click on {string} button from the table actions', (button: string) => {
-  cy.get('.table-actions button.dropdown-toggle').first().click();
-  cy.get(`[aria-label="${button}"]`).first().click();
-});
-
-And('select options {string}', (labels: string) => {
-  if (labels) {
-    cy.get('a[data-testid=select-menu-edit]').click();
-    for (const label of labels.split(', ')) {
-      cy.get('.popover-body div.select-menu-item-content').contains(label).click();
-    }
-  }
-});
-
-And('{string} option {string}', (action: string, labels: string) => {
-  if (labels) {
-    if (action === 'add') {
-      cy.get('cd-modal').find('.select-menu-edit').click();
-      for (const label of labels.split(', ')) {
-        cy.get('.popover-body input').type(`${label}{enter}`);
-      }
-    } else {
-      for (const label of labels.split(', ')) {
-        cy.contains('cd-modal .badge', new RegExp(`^${label}$`))
-          .find('.badge-remove')
-          .click();
-      }
-    }
-  }
-});
-
-/**
- * Fills in the given field using the value provided
- * @param field ID of the field that needs to be filled out.
- * @param value Value that should be filled in the field.
- */
-And('enter {string} {string}', (field: string, value: string) => {
-  cy.get('cd-modal').within(() => {
-    cy.get(`input[id=${field}]`).type(value);
-  });
-});
-
-And('I click on submit button', () => {
-  cy.get('[data-cy=submitBtn]').click();
-});
-
-/**
- * Selects any row on the datatable if it matches the given name
- */
-When('I select a row {string}', (row: string) => {
-  cy.get('cd-table .search input').first().clear().type(row);
-  cy.contains(`datatable-body-row datatable-body-cell .datatable-body-cell-label`, row).click();
-});
-
-Then('I should see the modal', () => {
-  cy.get('cd-modal').should('exist');
-});
-
-Then('I should not see the modal', () => {
-  cy.get('cd-modal').should('not.exist');
-});
-
-/**
- * Some modals have an additional confirmation to be provided
- * by ticking the 'Are you sure?' box.
- */
-Then('I check the tick box in modal', () => {
-  cy.get('cd-modal .custom-control-label').click();
-});
-
-And('I confirm to {string}', (action: string) => {
-  cy.contains('cd-modal button', action).click();
-  cy.get('cd-modal').should('not.exist');
-});
-
-Then('I should see an error in {string} field', (field: string) => {
-  cy.get('cd-modal').within(() => {
-    cy.get(`input[id=${field}]`).should('have.class', 'ng-invalid');
-  });
-});
-
-Then('I should see a row with {string}', (row: string) => {
-  cy.get('cd-table .search input').first().clear().type(row);
-  cy.contains(`datatable-body-row datatable-body-cell .datatable-body-cell-label`, row).should(
-    'exist'
-  );
-});
-
-Then('I should not see a row with {string}', (row: string) => {
-  cy.get('cd-table .search input').first().clear().type(row);
-  cy.contains(`datatable-body-row datatable-body-cell .datatable-body-cell-label`, row).should(
-    'not.exist'
-  );
-});
-
-Then('I should see rows with following entries', (entries) => {
-  entries.hashes().forEach((entry: any) => {
-    cy.get('cd-table .search input').first().clear().type(entry.hostname);
-    cy.contains(
-      `datatable-body-row datatable-body-cell .datatable-body-cell-label`,
-      entry.hostname
-    ).should('exist');
-  });
-});
-
-And('I should see row {string} have {string}', (row: string, options: string) => {
-  if (options) {
-    cy.get('cd-table .search input').first().clear().type(row);
-    for (const option of options.split(',')) {
-      cy.contains(
-        `datatable-body-row datatable-body-cell .datatable-body-cell-label .badge`,
-        option
-      ).should('exist');
-    }
-  }
-});
-
-And('I should see row {string} does not have {string}', (row: string, options: string) => {
-  if (options) {
-    cy.get('cd-table .search input').first().clear().type(row);
-    for (const option of options.split(',')) {
-      cy.contains(
-        `datatable-body-row datatable-body-cell .datatable-body-cell-label .badge`,
-        option
-      ).should('not.exist');
-    }
-  }
-});
-
-And('I go to the {string} tab', (names: string) => {
-  for (const name of names.split(', ')) {
-    cy.contains('.nav.nav-tabs a', name).click();
-  }
-});
-
-And('select {string} {string}', (selectionName: string, option: string) => {
-  cy.get(`select[name=${selectionName}]`).select(option);
-  cy.get(`select[name=${selectionName}] option:checked`).contains(option);
-});
-
-When('I expand the row {string}', (row: string) => {
-  cy.contains('.datatable-body-row', row).first().find('.tc_expand-collapse').click();
-});
-
-And('I should see row {string} have {string} on this tab', (row: string, options: string) => {
-  if (options) {
-    cy.get('cd-table').should('exist');
-    cy.get('datatable-scroller, .empty-row');
-    cy.get('.datatable-row-detail').within(() => {
-      cy.get('cd-table .search input').first().clear().type(row);
-      for (const option of options.split(',')) {
-        cy.contains(
-          `datatable-body-row datatable-body-cell .datatable-body-cell-label span`,
-          option
-        ).should('exist');
-      }
-    });
-  }
-});
diff --git a/src/pybind/mgr/dashboard/frontend/cypress/integration/common/create-cluster/create-cluster.feature.po.ts b/src/pybind/mgr/dashboard/frontend/cypress/integration/common/create-cluster/create-cluster.feature.po.ts
deleted file mode 100644 (file)
index d18c348..0000000
+++ /dev/null
@@ -1,12 +0,0 @@
-import { Given, Then } from 'cypress-cucumber-preprocessor/steps';
-
-Given('I am on the {string} section', (page: string) => {
-  cy.get('cd-wizard').within(() => {
-    cy.get('.nav-link').should('contain.text', page).first().click();
-    cy.get('.nav-link.active').should('contain.text', page);
-  });
-});
-
-Then('I should see a message {string}', () => {
-  cy.get('cd-create-cluster').should('contain.text', 'Please expand your cluster first');
-});
diff --git a/src/pybind/mgr/dashboard/frontend/cypress/integration/common/grafana.feature.po.ts b/src/pybind/mgr/dashboard/frontend/cypress/integration/common/grafana.feature.po.ts
deleted file mode 100644 (file)
index 7366f8b..0000000
+++ /dev/null
@@ -1,86 +0,0 @@
-import { e2e } from '@grafana/e2e';
-import { Then, When } from 'cypress-cucumber-preprocessor/steps';
-import 'cypress-iframe';
-
-function getIframe() {
-  cy.frameLoaded('#iframe');
-  return cy.iframe();
-}
-
-Then('I should see the grafana panel {string}', (panels: string) => {
-  getIframe().within(() => {
-    for (const panel of panels.split(', ')) {
-      cy.get('.grafana-app')
-        .wait(100)
-        .within(() => {
-          e2e.components.Panels.Panel.title(panel).should('be.visible');
-        });
-    }
-  });
-});
-
-When('I view the grafana panel {string}', (panels: string) => {
-  getIframe().within(() => {
-    for (const panel of panels.split(', ')) {
-      cy.get('.grafana-app')
-        .wait(100)
-        .within(() => {
-          e2e.components.Panels.Panel.title(panel).should('be.visible').click();
-          e2e.components.Panels.Panel.headerItems('View').should('be.visible').click();
-        });
-    }
-  });
-});
-
-Then('I should not see {string} in the panel {string}', (value: string, panels: string) => {
-  getIframe().within(() => {
-    for (const panel of panels.split(', ')) {
-      cy.get('.grafana-app')
-        .wait(100)
-        .within(() => {
-          cy.get(`[aria-label="${panel} panel"]`)
-            .should('be.visible')
-            .within(() => {
-              cy.get('span').first().should('not.have.text', value);
-            });
-        });
-    }
-  });
-});
-
-Then(
-  'I should see the legends {string} in the graph {string}',
-  (legends: string, panels: string) => {
-    getIframe().within(() => {
-      for (const panel of panels.split(', ')) {
-        cy.get('.grafana-app')
-          .wait(100)
-          .within(() => {
-            cy.get(`[aria-label="${panel} panel"]`)
-              .should('be.visible')
-              .within(() => {
-                for (const legend of legends.split(', ')) {
-                  cy.get('a').contains(legend);
-                }
-              });
-          });
-      }
-    });
-  }
-);
-
-Then('I should not see No Data in the graph {string}', (panels: string) => {
-  getIframe().within(() => {
-    for (const panel of panels.split(', ')) {
-      cy.get('.grafana-app')
-        .wait(100)
-        .within(() => {
-          cy.get(`[aria-label="${panel} panel"]`)
-            .should('be.visible')
-            .within(() => {
-              cy.get('div.datapoints-warning').should('not.exist');
-            });
-        });
-    }
-  });
-});
diff --git a/src/pybind/mgr/dashboard/frontend/cypress/integration/common/urls.po.ts b/src/pybind/mgr/dashboard/frontend/cypress/integration/common/urls.po.ts
deleted file mode 100644 (file)
index 2863550..0000000
+++ /dev/null
@@ -1,44 +0,0 @@
-import { PageHelper } from '../page-helper.po';
-
-export class UrlsCollection extends PageHelper {
-  pages = {
-    // Cluster expansion
-    welcome: { url: '#/expand-cluster', id: 'cd-create-cluster' },
-
-    // Landing page
-    dashboard: { url: '#/dashboard', id: 'cd-dashboard' },
-
-    // Hosts
-    hosts: { url: '#/hosts', id: 'cd-hosts' },
-    'add hosts': { url: '#/hosts/(modal:add)', id: 'cd-host-form' },
-
-    // Services
-    services: { url: '#/services', id: 'cd-services' },
-    'create services': { url: '#/services/(modal:create)', id: 'cd-service-form' },
-
-    // Physical Disks
-    'physical disks': { url: '#/inventory', id: 'cd-inventory' },
-
-    // Monitors
-    monitors: { url: '#/monitor', id: 'cd-monitor' },
-
-    // OSDs
-    osds: { url: '#/osd', id: 'cd-osd-list' },
-    'create osds': { url: '#/osd/create', id: 'cd-osd-form' },
-
-    // Configuration
-    configuration: { url: '#/configuration', id: 'cd-configuration' },
-
-    // Crush Map
-    'crush map': { url: '#/crush-map', id: 'cd-crushmap' },
-
-    // Mgr modules
-    'mgr-modules': { url: '#/mgr-modules', id: 'cd-mgr-module-list' },
-
-    // Logs
-    logs: { url: '#/logs', id: 'cd-logs' },
-
-    // RGW Daemons
-    'rgw daemons': { url: '#/rgw/daemon', id: 'cd-rgw-daemon-list' }
-  };
-}
diff --git a/src/pybind/mgr/dashboard/frontend/cypress/integration/filesystems/filesystems.e2e-spec.ts b/src/pybind/mgr/dashboard/frontend/cypress/integration/filesystems/filesystems.e2e-spec.ts
deleted file mode 100644 (file)
index e623475..0000000
+++ /dev/null
@@ -1,17 +0,0 @@
-import { FilesystemsPageHelper } from './filesystems.po';
-
-describe('File Systems page', () => {
-  const filesystems = new FilesystemsPageHelper();
-
-  beforeEach(() => {
-    cy.login();
-    Cypress.Cookies.preserveOnce('token');
-    filesystems.navigateTo();
-  });
-
-  describe('breadcrumb test', () => {
-    it('should open and show breadcrumb', () => {
-      filesystems.expectBreadcrumbText('File Systems');
-    });
-  });
-});
diff --git a/src/pybind/mgr/dashboard/frontend/cypress/integration/filesystems/filesystems.po.ts b/src/pybind/mgr/dashboard/frontend/cypress/integration/filesystems/filesystems.po.ts
deleted file mode 100644 (file)
index bd6e5b8..0000000
+++ /dev/null
@@ -1,5 +0,0 @@
-import { PageHelper } from '../page-helper.po';
-
-export class FilesystemsPageHelper extends PageHelper {
-  pages = { index: { url: '#/cephfs', id: 'cd-cephfs-list' } };
-}
diff --git a/src/pybind/mgr/dashboard/frontend/cypress/integration/orchestrator/01-hosts.e2e-spec.ts b/src/pybind/mgr/dashboard/frontend/cypress/integration/orchestrator/01-hosts.e2e-spec.ts
deleted file mode 100644 (file)
index aca36ad..0000000
+++ /dev/null
@@ -1,86 +0,0 @@
-import { HostsPageHelper } from '../cluster/hosts.po';
-
-describe('Hosts page', () => {
-  const hosts = new HostsPageHelper();
-
-  beforeEach(() => {
-    cy.login();
-    Cypress.Cookies.preserveOnce('token');
-    hosts.navigateTo();
-  });
-
-  describe('when Orchestrator is available', () => {
-    beforeEach(function () {
-      cy.fixture('orchestrator/inventory.json').as('hosts');
-      cy.fixture('orchestrator/services.json').as('services');
-    });
-
-    it('should not add an exsiting host', function () {
-      const hostname = Cypress._.sample(this.hosts).name;
-      hosts.navigateTo('add');
-      hosts.add(hostname, true);
-    });
-
-    it('should drain and remove a host and then add it back', function () {
-      const hostname = Cypress._.last(this.hosts)['name'];
-
-      // should drain the host first before deleting
-      hosts.drain(hostname);
-      hosts.remove(hostname);
-
-      // add it back
-      hosts.navigateTo('add');
-      hosts.add(hostname);
-      hosts.checkExist(hostname, true);
-    });
-
-    it('should display inventory', function () {
-      for (const host of this.hosts) {
-        hosts.clickTab('cd-host-details', host.name, 'Physical Disks');
-        cy.get('cd-host-details').within(() => {
-          hosts.expectTableCount('total', host.devices.length);
-        });
-      }
-    });
-
-    it('should display daemons', function () {
-      for (const host of this.hosts) {
-        hosts.clickTab('cd-host-details', host.name, 'Daemons');
-        cy.get('cd-host-details').within(() => {
-          hosts.getTableCount('total').should('be.gte', 0);
-        });
-      }
-    });
-
-    it('should edit host labels', function () {
-      const hostname = Cypress._.sample(this.hosts).name;
-      const labels = ['foo', 'bar'];
-      hosts.editLabels(hostname, labels, true);
-      hosts.editLabels(hostname, labels, false);
-    });
-
-    it('should enter host into maintenance', function () {
-      const hostname = Cypress._.sample(this.hosts).name;
-      const serviceList = new Array();
-      this.services.forEach((service: any) => {
-        if (hostname === service.hostname) {
-          serviceList.push(service.daemon_type);
-        }
-      });
-      let enterMaintenance = true;
-      serviceList.forEach((service: string) => {
-        if (service === 'mgr' || service === 'alertmanager') {
-          enterMaintenance = false;
-        }
-      });
-      if (enterMaintenance) {
-        hosts.maintenance(hostname);
-      }
-    });
-
-    it('should exit host from maintenance', function () {
-      const hostname = Cypress._.sample(this.hosts).name;
-      hosts.maintenance(hostname, true);
-    });
-  });
-});
diff --git a/src/pybind/mgr/dashboard/frontend/cypress/integration/orchestrator/03-inventory.e2e-spec.ts b/src/pybind/mgr/dashboard/frontend/cypress/integration/orchestrator/03-inventory.e2e-spec.ts
deleted file mode 100644 (file)
index a64e3bc..0000000
+++ /dev/null
@@ -1,26 +0,0 @@
-import { InventoryPageHelper } from '../cluster/inventory.po';
-
-describe('Physical Disks page', () => {
-  const inventory = new InventoryPageHelper();
-
-  beforeEach(() => {
-    cy.login();
-    Cypress.Cookies.preserveOnce('token');
-    inventory.navigateTo();
-  });
-
-  it('should have correct devices', () => {
-    cy.fixture('orchestrator/inventory.json').then((hosts) => {
-      const totalDiskCount = Cypress._.sumBy(hosts, 'devices.length');
-      inventory.expectTableCount('total', totalDiskCount);
-      for (const host of hosts) {
-        inventory.filterTable('Hostname', host['name']);
-        inventory.getTableCount('found').should('be.eq', host.devices.length);
-      }
-    });
-  });
-
-  it('should identify device', () => {
-    inventory.identify();
-  });
-});
diff --git a/src/pybind/mgr/dashboard/frontend/cypress/integration/orchestrator/04-osds.e2e-spec.ts b/src/pybind/mgr/dashboard/frontend/cypress/integration/orchestrator/04-osds.e2e-spec.ts
deleted file mode 100644 (file)
index 41f0933..0000000
+++ /dev/null
@@ -1,50 +0,0 @@
-import { OSDsPageHelper } from '../cluster/osds.po';
-import { DashboardPageHelper } from '../ui/dashboard.po';
-
-describe('OSDs page', () => {
-  const osds = new OSDsPageHelper();
-  const dashboard = new DashboardPageHelper();
-
-  beforeEach(() => {
-    cy.login();
-    Cypress.Cookies.preserveOnce('token');
-    osds.navigateTo();
-  });
-
-  describe('when Orchestrator is available', () => {
-    it('should create and delete OSDs', () => {
-      osds.getTableCount('total').as('initOSDCount');
-      osds.navigateTo('create');
-      osds.create('hdd');
-
-      cy.get('@newOSDCount').then((newCount) => {
-        cy.get('@initOSDCount').then((oldCount) => {
-          const expectedCount = Number(oldCount) + Number(newCount);
-
-          // check total rows
-          osds.expectTableCount('total', expectedCount);
-
-          // landing page is easier to check OSD status
-          dashboard.navigateTo();
-          dashboard.infoCardBody('OSDs').should('contain.text', `${expectedCount} total`);
-          dashboard.infoCardBody('OSDs').should('contain.text', `${expectedCount} up`);
-          dashboard.infoCardBody('OSDs').should('contain.text', `${expectedCount} in`);
-
-          cy.wait(30000);
-          expect(Number(newCount)).to.be.gte(2);
-          // Delete the first OSD we created
-          osds.navigateTo();
-          const deleteOsdId = Number(oldCount);
-          osds.deleteByIDs([deleteOsdId], false);
-          osds.ensureNoOsd(deleteOsdId);
-
-          cy.wait(30000);
-          // Replace the second OSD we created
-          const replaceID = Number(oldCount) + 1;
-          osds.deleteByIDs([replaceID], true);
-          osds.checkStatus(replaceID, ['destroyed']);
-        });
-      });
-    });
-  });
-});
diff --git a/src/pybind/mgr/dashboard/frontend/cypress/integration/orchestrator/05-services.e2e-spec.ts b/src/pybind/mgr/dashboard/frontend/cypress/integration/orchestrator/05-services.e2e-spec.ts
deleted file mode 100644 (file)
index fb5e6ac..0000000
+++ /dev/null
@@ -1,36 +0,0 @@
-import { ServicesPageHelper } from '../cluster/services.po';
-
-describe('Services page', () => {
-  const services = new ServicesPageHelper();
-  const serviceName = 'rgw.foo';
-
-  beforeEach(() => {
-    cy.login();
-    Cypress.Cookies.preserveOnce('token');
-    services.navigateTo();
-  });
-
-  describe('when Orchestrator is available', () => {
-    it('should create an rgw service', () => {
-      services.navigateTo('create');
-      services.addService('rgw');
-
-      services.checkExist(serviceName, true);
-    });
-
-    it('should edit a service', () => {
-      const count = '2';
-      services.editService(serviceName, count);
-      services.expectPlacementCount(serviceName, count);
-    });
-
-    it('should create and delete an ingress service', () => {
-      services.navigateTo('create');
-      services.addService('ingress');
-
-      services.checkExist('ingress.rgw.foo', true);
-
-      services.deleteService('ingress.rgw.foo');
-    });
-  });
-});
diff --git a/src/pybind/mgr/dashboard/frontend/cypress/integration/orchestrator/grafana/grafana.feature b/src/pybind/mgr/dashboard/frontend/cypress/integration/orchestrator/grafana/grafana.feature
deleted file mode 100644 (file)
index 62476ad..0000000
+++ /dev/null
@@ -1,63 +0,0 @@
-Feature: Grafana panels
-
-    Go to some of the grafana performance section and check if
-    panels are populated without any issues
-
-    Background: Log in
-        Given I am logged in
-
-    Scenario Outline: Hosts Overall Performance
-        Given I am on the "hosts" page
-        When I go to the "Overall Performance" tab
-        Then I should see the grafana panel "<panel>"
-        When I view the grafana panel "<panel>"
-        Then I should not see "No Data" in the panel "<panel>"
-
-        Examples:
-            | panel |
-            | OSD Hosts |
-            | AVG CPU Busy |
-            | AVG RAM Utilization |
-            | Physical IOPS |
-            | AVG Disk Utilization |
-            | Network Load |
-            | CPU Busy - Top 10 Hosts |
-            | Network Load - Top 10 Hosts |
-
-    Scenario Outline: RGW Daemon Overall Performance
-        Given I am on the "rgw daemons" page
-        When I go to the "Overall Performance" tab
-        Then I should see the grafana panel "<panel>"
-        When I view the grafana panel "<panel>"
-        Then I should not see No Data in the graph "<panel>"
-        And I should see the legends "<legends>" in the graph "<panel>"
-
-        Examples:
-            | panel | legends |
-            | Total Requests/sec by RGW Instance | foo.ceph-node-00, foo.ceph-node-01, foo.ceph-node-02 |
-            | GET Latencies by RGW Instance | foo.ceph-node-00, foo.ceph-node-01, foo.ceph-node-02 |
-            | Bandwidth by RGW Instance | foo.ceph-node-00, foo.ceph-node-01, foo.ceph-node-02 |
-            | PUT Latencies by RGW Instance | foo.ceph-node-00, foo.ceph-node-01, foo.ceph-node-02 |
-            | Average GET/PUT Latencies | GET AVG, PUT AVG |
-            | Bandwidth Consumed by Type | GETs, PUTs |
-
-    Scenario Outline: RGW per Daemon Performance
-        Given I am on the "rgw daemons" page
-        When I expand the row "<name>"
-        And I go to the "Performance Details" tab
-        Then I should see the grafana panel "<panel>"
-        When I view the grafana panel "<panel>"
-        Then I should not see No Data in the graph "<panel>"
-        And I should see the legends "<name>" in the graph "<panel>"
-
-        Examples:
-            | name | panel |
-            | foo.ceph-node-00 | Bandwidth by HTTP Operation |
-            | foo.ceph-node-00 | HTTP Request Breakdown |
-            | foo.ceph-node-00 | Workload Breakdown |
-            | foo.ceph-node-01 | Bandwidth by HTTP Operation |
-            | foo.ceph-node-01 | HTTP Request Breakdown |
-            | foo.ceph-node-01 | Workload Breakdown |
-            | foo.ceph-node-02 | Bandwidth by HTTP Operation |
-            | foo.ceph-node-02 | HTTP Request Breakdown |
-            | foo.ceph-node-02 | Workload Breakdown |
diff --git a/src/pybind/mgr/dashboard/frontend/cypress/integration/orchestrator/workflow/01-create-cluster-welcome.feature b/src/pybind/mgr/dashboard/frontend/cypress/integration/orchestrator/workflow/01-create-cluster-welcome.feature
deleted file mode 100644 (file)
index 6ba2fc4..0000000
+++ /dev/null
@@ -1,26 +0,0 @@
-Feature: Cluster expansion welcome screen
-
-    Go to the welcome screen and decide whether
-    to proceed to wizard or skips to landing page
-
-    Background: Login
-        Given I am logged in
-
-    Scenario: Cluster expansion welcome screen
-        Given I am on the "welcome" page
-        And I should see a button to "Expand Cluster"
-        And I should see a button to "Skip"
-        And I should see a message "Please expand your cluster first"
-
-    Scenario: Go to the Cluster expansion wizard
-        Given I am on the "welcome" page
-        And I should see a button to "Expand Cluster"
-        When I click on "Expand Cluster" button
-        Then I am on the "Add Hosts" section
-
-    Scenario: Skips the process and go to the landing page
-        Given I am on the "welcome" page
-        And I should see a button to "Skip"
-        When I click on "Skip" button
-        And I confirm to "Continue"
-        Then I should be on the "dashboard" page
diff --git a/src/pybind/mgr/dashboard/frontend/cypress/integration/orchestrator/workflow/02-create-cluster-add-host.feature b/src/pybind/mgr/dashboard/frontend/cypress/integration/orchestrator/workflow/02-create-cluster-add-host.feature
deleted file mode 100644 (file)
index be49fcb..0000000
+++ /dev/null
@@ -1,76 +0,0 @@
-Feature: Cluster expansion host addition
-
-    Add some hosts and perform some host related actions like editing the labels
-    and removing the hosts from the cluster and verify all of the actions are performed
-    as expected
-
-    Background: Cluster expansion wizard
-        Given I am logged in
-        And I am on the "welcome" page
-        And I click on "Expand Cluster" button
-
-    Scenario Outline: Add hosts
-        Given I am on the "Add Hosts" section
-        When I click on "Add" button
-        And enter "hostname" "<hostname>"
-        And select options "<labels>"
-        And I click on "Add Host" button
-        Then I should not see the modal
-        And I should see a row with "<hostname>"
-        And I should see row "<hostname>" have "<labels>"
-
-        Examples:
-            | hostname | labels |
-            | ceph-node-01 | mon, mgr |
-            | ceph-node-02 ||
-
-    Scenario Outline: Remove hosts
-        Given I am on the "Add Hosts" section
-        And I should see a row with "<hostname>"
-        When I select a row "<hostname>"
-        And I click on "Remove" button from the table actions
-        Then I should see the modal
-        And I check the tick box in modal
-        And I click on "Remove Host" button
-        Then I should not see the modal
-        And I should not see a row with "<hostname>"
-
-        Examples:
-            | hostname |
-            | ceph-node-01 |
-            | ceph-node-02 |
-
-    Scenario: Add hosts using pattern 'ceph-node-[01-02]'
-        Given I am on the "Add Hosts" section
-        When I click on "Add" button
-        And enter "hostname" "ceph-node-[01-02]"
-        And I click on "Add Host" button
-        Then I should not see the modal
-        And I should see rows with following entries
-            | hostname |
-            | ceph-node-01 |
-            | ceph-node-02 |
-
-    Scenario: Add exisiting host and verify it failed
-        Given I am on the "Add Hosts" section
-        And I should see a row with "ceph-node-00"
-        When I click on "Add" button
-        And enter "hostname" "ceph-node-00"
-        Then I should see an error in "hostname" field
-
-    Scenario Outline: Add and remove labels on host
-        Given I am on the "Add Hosts" section
-        When I select a row "<hostname>"
-        And I click on "Edit" button from the table actions
-        And "add" option "<labels>"
-        And I click on "Edit Host" button
-        Then I should see row "<hostname>" have "<labels>"
-        When I select a row "<hostname>"
-        And I click on "Edit" button from the table actions
-        And "remove" option "<labels>"
-        And I click on "Edit Host" button
-        Then I should see row "<hostname>" does not have "<labels>"
-
-        Examples:
-            | hostname | labels |
-            | ceph-node-01 | foo |
diff --git a/src/pybind/mgr/dashboard/frontend/cypress/integration/orchestrator/workflow/03-create-cluster-create-services.e2e-spec.ts b/src/pybind/mgr/dashboard/frontend/cypress/integration/orchestrator/workflow/03-create-cluster-create-services.e2e-spec.ts
deleted file mode 100644 (file)
index 745a2ec..0000000
+++ /dev/null
@@ -1,47 +0,0 @@
-/* tslint:disable*/
-import {
-  CreateClusterServicePageHelper,
-  CreateClusterWizardHelper
-} from '../../cluster/create-cluster.po';
-/* tslint:enable*/
-
-describe('Create cluster create services page', () => {
-  const createCluster = new CreateClusterWizardHelper();
-  const createClusterServicePage = new CreateClusterServicePageHelper();
-
-  const createService = (serviceType: string, serviceName: string, count = 1) => {
-    cy.get('[aria-label=Create]').first().click();
-    createClusterServicePage.addService(serviceType, false, count);
-    createClusterServicePage.checkExist(serviceName, true);
-  };
-
-  beforeEach(() => {
-    cy.login();
-    Cypress.Cookies.preserveOnce('token');
-    createCluster.navigateTo();
-    createCluster.createCluster();
-    cy.get('.nav-link').contains('Create Services').click();
-  });
-
-  it('should check if title contains Create Services', () => {
-    cy.get('.title').should('contain.text', 'Create Services');
-  });
-
-  describe('when Orchestrator is available', () => {
-    const serviceName = 'mds.test';
-
-    it('should create an mds service', () => {
-      createService('mds', serviceName);
-    });
-
-    it('should edit a service', () => {
-      const daemonCount = '2';
-      createClusterServicePage.editService(serviceName, daemonCount);
-      createClusterServicePage.expectPlacementCount(serviceName, daemonCount);
-    });
-
-    it('should delete mds service', () => {
-      createClusterServicePage.deleteService('mds.test');
-    });
-  });
-});
diff --git a/src/pybind/mgr/dashboard/frontend/cypress/integration/orchestrator/workflow/04-create-cluster-create-osds.e2e-spec.ts b/src/pybind/mgr/dashboard/frontend/cypress/integration/orchestrator/workflow/04-create-cluster-create-osds.e2e-spec.ts
deleted file mode 100644 (file)
index 2426243..0000000
+++ /dev/null
@@ -1,41 +0,0 @@
-/* tslint:disable*/
-import { CreateClusterWizardHelper } from '../../cluster/create-cluster.po';
-import { OSDsPageHelper } from '../../cluster/osds.po';
-/* tslint:enable*/
-
-const osds = new OSDsPageHelper();
-
-describe('Create cluster create osds page', () => {
-  const createCluster = new CreateClusterWizardHelper();
-
-  beforeEach(() => {
-    cy.login();
-    Cypress.Cookies.preserveOnce('token');
-    createCluster.navigateTo();
-    createCluster.createCluster();
-    cy.get('.nav-link').contains('Create OSDs').click();
-  });
-
-  it('should check if title contains Create OSDs', () => {
-    cy.get('.title').should('contain.text', 'Create OSDs');
-  });
-
-  describe('when Orchestrator is available', () => {
-    it('should create OSDs', () => {
-      const hostnames = ['ceph-node-00', 'ceph-node-01'];
-      for (const hostname of hostnames) {
-        osds.create('hdd', hostname, true);
-
-        // Go to the Review section and Expand the cluster
-        // because the drive group spec is only stored
-        // in frontend and will be lost when refreshed
-        cy.get('.nav-link').contains('Review').click();
-        cy.get('button[aria-label="Next"]').click();
-        cy.get('cd-dashboard').should('exist');
-        createCluster.navigateTo();
-        createCluster.createCluster();
-        cy.get('.nav-link').contains('Create OSDs').click();
-      }
-    });
-  });
-});
diff --git a/src/pybind/mgr/dashboard/frontend/cypress/integration/orchestrator/workflow/05-create-cluster-review.e2e-spec.ts b/src/pybind/mgr/dashboard/frontend/cypress/integration/orchestrator/workflow/05-create-cluster-review.e2e-spec.ts
deleted file mode 100644 (file)
index f93ad7a..0000000
+++ /dev/null
@@ -1,67 +0,0 @@
-/* tslint:disable*/
-import {
-  CreateClusterHostPageHelper,
-  CreateClusterWizardHelper
-} from '../../cluster/create-cluster.po';
-/* tslint:enable*/
-
-describe('Create Cluster Review page', () => {
-  const createCluster = new CreateClusterWizardHelper();
-  const createClusterHostPage = new CreateClusterHostPageHelper();
-
-  beforeEach(() => {
-    cy.login();
-    Cypress.Cookies.preserveOnce('token');
-    createCluster.navigateTo();
-    createCluster.createCluster();
-
-    cy.get('.nav-link').contains('Review').click();
-  });
-
-  describe('navigation link test', () => {
-    it('should check if active nav-link is of Review section', () => {
-      cy.get('.nav-link.active').should('contain.text', 'Review');
-    });
-  });
-
-  describe('fields check', () => {
-    it('should check cluster resources table is present', () => {
-      // check for table header 'Cluster Resources'
-      createCluster.getLegends().its(0).should('have.text', 'Cluster Resources');
-
-      // check for fields in table
-      createCluster.getStatusTables().should('contain.text', 'Hosts');
-      createCluster.getStatusTables().should('contain.text', 'Storage Capacity');
-      createCluster.getStatusTables().should('contain.text', 'CPUs');
-      createCluster.getStatusTables().should('contain.text', 'Memory');
-    });
-
-    it('should check Host Details table is present', () => {
-      // check for there to be two tables
-      createCluster.getDataTables().should('have.length', 1);
-
-      // verify correct columns on Host Details table
-      createCluster.getDataTableHeaders(0).contains('Hostname');
-
-      createCluster.getDataTableHeaders(0).contains('Labels');
-
-      createCluster.getDataTableHeaders(0).contains('CPUs');
-
-      createCluster.getDataTableHeaders(0).contains('Cores');
-
-      createCluster.getDataTableHeaders(0).contains('Total Memory');
-
-      createCluster.getDataTableHeaders(0).contains('Raw Capacity');
-
-      createCluster.getDataTableHeaders(0).contains('HDDs');
-
-      createCluster.getDataTableHeaders(0).contains('Flash');
-
-      createCluster.getDataTableHeaders(0).contains('NICs');
-    });
-
-    it('should check default host name is present', () => {
-      createClusterHostPage.check_for_host();
-    });
-  });
-});
diff --git a/src/pybind/mgr/dashboard/frontend/cypress/integration/orchestrator/workflow/06-cluster-check.e2e-spec.ts b/src/pybind/mgr/dashboard/frontend/cypress/integration/orchestrator/workflow/06-cluster-check.e2e-spec.ts
deleted file mode 100644 (file)
index 94cb36c..0000000
+++ /dev/null
@@ -1,83 +0,0 @@
-/* tslint:disable*/
-import { CreateClusterWizardHelper } from '../../cluster/create-cluster.po';
-import { HostsPageHelper } from '../../cluster/hosts.po';
-import { ServicesPageHelper } from '../../cluster/services.po';
-/* tslint:enable*/
-
-describe('when cluster creation is completed', () => {
-  const createCluster = new CreateClusterWizardHelper();
-  const services = new ServicesPageHelper();
-  const hosts = new HostsPageHelper();
-
-  const hostnames = ['ceph-node-00', 'ceph-node-01', 'ceph-node-02', 'ceph-node-03'];
-
-  beforeEach(() => {
-    cy.login();
-    Cypress.Cookies.preserveOnce('token');
-  });
-
-  it('should redirect to dashboard landing page after cluster creation', () => {
-    createCluster.navigateTo();
-    createCluster.createCluster();
-
-    // Explicitly skip OSD Creation Step so that it prevents from
-    // deploying OSDs to the hosts automatically.
-    cy.get('.nav-link').contains('Create OSDs').click();
-    cy.get('button[aria-label="Skip this step"]').click();
-
-    cy.get('.nav-link').contains('Review').click();
-    cy.get('button[aria-label="Next"]').click();
-    cy.get('cd-dashboard').should('exist');
-  });
-
-  describe('Hosts page', () => {
-    beforeEach(() => {
-      hosts.navigateTo();
-    });
-
-    it('should add one more host', () => {
-      hosts.navigateTo('add');
-      hosts.add(hostnames[3]);
-      hosts.checkExist(hostnames[3], true);
-    });
-
-    it('should check if monitoring stacks are running on the root host', { retries: 2 }, () => {
-      const monitoringStack = ['alertmanager', 'grafana', 'node-exporter', 'prometheus'];
-      hosts.clickTab('cd-host-details', 'ceph-node-00', 'Daemons');
-      for (const daemon of monitoringStack) {
-        cy.get('cd-host-details').within(() => {
-          services.checkServiceStatus(daemon);
-        });
-      }
-    });
-
-    it('should have removed "_no_schedule" label', () => {
-      for (const hostname of hostnames) {
-        hosts.checkLabelExists(hostname, ['_no_schedule'], false);
-      }
-    });
-
-    it('should display inventory', () => {
-      hosts.clickTab('cd-host-details', hostnames[1], 'Physical Disks');
-      cy.get('cd-host-details').within(() => {
-        hosts.getTableCount('total').should('be.gte', 0);
-      });
-    });
-
-    it('should display daemons', () => {
-      hosts.clickTab('cd-host-details', hostnames[1], 'Daemons');
-      cy.get('cd-host-details').within(() => {
-        hosts.getTableCount('total').should('be.gte', 0);
-      });
-    });
-
-    it('should check if mon daemon is running on all hosts', () => {
-      for (const hostname of hostnames) {
-        hosts.clickTab('cd-host-details', hostname, 'Daemons');
-        cy.get('cd-host-details').within(() => {
-          services.checkServiceStatus('mon');
-        });
-      }
-    });
-  });
-});
diff --git a/src/pybind/mgr/dashboard/frontend/cypress/integration/orchestrator/workflow/07-osds.e2e-spec.ts b/src/pybind/mgr/dashboard/frontend/cypress/integration/orchestrator/workflow/07-osds.e2e-spec.ts
deleted file mode 100644 (file)
index a0a1dd0..0000000
+++ /dev/null
@@ -1,24 +0,0 @@
-/* tslint:disable*/
-import { OSDsPageHelper } from '../../cluster/osds.po';
-/* tslint:enable*/
-
-describe('OSDs page', () => {
-  const osds = new OSDsPageHelper();
-
-  beforeEach(() => {
-    cy.login();
-    Cypress.Cookies.preserveOnce('token');
-    osds.navigateTo();
-  });
-
-  it('should check if atleast 3 osds are created', { retries: 3 }, () => {
-    // we have created a total of more than 3 osds throughout
-    // the whole tests so ensuring that atleast
-    // 3 osds are listed in the table. Since the OSD
-    // creation can take more time going with
-    // retry of 3
-    for (let id = 0; id < 3; id++) {
-      osds.checkStatus(id, ['in', 'up']);
-    }
-  });
-});
diff --git a/src/pybind/mgr/dashboard/frontend/cypress/integration/orchestrator/workflow/08-hosts.e2e-spec.ts b/src/pybind/mgr/dashboard/frontend/cypress/integration/orchestrator/workflow/08-hosts.e2e-spec.ts
deleted file mode 100644 (file)
index 6e8c632..0000000
+++ /dev/null
@@ -1,49 +0,0 @@
-/* tslint:disable*/
-import { HostsPageHelper } from '../../cluster/hosts.po';
-import { ServicesPageHelper } from '../../cluster/services.po';
-/* tslint:enable*/
-
-describe('Host Page', () => {
-  const hosts = new HostsPageHelper();
-  const services = new ServicesPageHelper();
-
-  const hostnames = ['ceph-node-00', 'ceph-node-01', 'ceph-node-02', 'ceph-node-03'];
-
-  beforeEach(() => {
-    cy.login();
-    Cypress.Cookies.preserveOnce('token');
-    hosts.navigateTo();
-  });
-
-  // rgw is needed for testing the force maintenance
-  it('should create rgw services', () => {
-    services.navigateTo('create');
-    services.addService('rgw', false, 4);
-    services.checkExist('rgw.foo', true);
-  });
-
-  it('should check if rgw daemon is running on all hosts', () => {
-    for (const hostname of hostnames) {
-      hosts.clickTab('cd-host-details', hostname, 'Daemons');
-      cy.get('cd-host-details').within(() => {
-        services.checkServiceStatus('rgw');
-      });
-    }
-  });
-
-  it('should force maintenance and exit', () => {
-    hosts.maintenance(hostnames[3], true, true);
-  });
-
-  it('should drain, remove and add the host back', () => {
-    hosts.drain(hostnames[3]);
-    hosts.remove(hostnames[3]);
-    hosts.navigateTo('add');
-    hosts.add(hostnames[3]);
-    hosts.checkExist(hostnames[3], true);
-  });
-
-  it('should show the exact count of daemons', () => {
-    hosts.checkServiceInstancesExist(hostnames[0], ['mgr: 1', 'prometheus: 1']);
-  });
-});
diff --git a/src/pybind/mgr/dashboard/frontend/cypress/integration/orchestrator/workflow/09-services.e2e-spec.ts b/src/pybind/mgr/dashboard/frontend/cypress/integration/orchestrator/workflow/09-services.e2e-spec.ts
deleted file mode 100644 (file)
index 91f2de5..0000000
+++ /dev/null
@@ -1,133 +0,0 @@
-/* tslint:disable*/
-import { ServicesPageHelper } from '../../cluster/services.po';
-/* tslint:enable*/
-
-describe('Services page', () => {
-  const services = new ServicesPageHelper();
-  const mdsDaemonName = 'mds.test';
-  beforeEach(() => {
-    cy.login();
-    Cypress.Cookies.preserveOnce('token');
-    services.navigateTo();
-  });
-
-  it('should check if rgw service is created', () => {
-    services.checkExist('rgw.foo', true);
-  });
-
-  it('should create an mds service', () => {
-    services.navigateTo('create');
-    services.addService('mds', false);
-    services.checkExist(mdsDaemonName, true);
-
-    services.clickServiceTab(mdsDaemonName, 'Daemons');
-    cy.get('cd-service-details').within(() => {
-      services.checkServiceStatus(mdsDaemonName);
-    });
-  });
-
-  it('should stop a daemon', () => {
-    services.clickServiceTab(mdsDaemonName, 'Daemons');
-    services.checkServiceStatus(mdsDaemonName);
-
-    services.daemonAction('mds', 'stop');
-    cy.get('cd-service-details').within(() => {
-      services.checkServiceStatus(mdsDaemonName, 'stopped');
-    });
-  });
-
-  it('should restart a daemon', () => {
-    services.checkExist(mdsDaemonName, true);
-    services.clickServiceTab(mdsDaemonName, 'Daemons');
-    services.daemonAction('mds', 'restart');
-    cy.get('cd-service-details').within(() => {
-      services.checkServiceStatus(mdsDaemonName, 'running');
-    });
-  });
-
-  it('should redeploy a daemon', () => {
-    services.checkExist(mdsDaemonName, true);
-    services.clickServiceTab(mdsDaemonName, 'Daemons');
-
-    services.daemonAction('mds', 'stop');
-    cy.get('cd-service-details').within(() => {
-      services.checkServiceStatus(mdsDaemonName, 'stopped');
-    });
-    services.daemonAction('mds', 'redeploy');
-    cy.get('cd-service-details').within(() => {
-      services.checkServiceStatus(mdsDaemonName, 'running');
-    });
-  });
-
-  it('should start a daemon', () => {
-    services.checkExist(mdsDaemonName, true);
-    services.clickServiceTab(mdsDaemonName, 'Daemons');
-
-    services.daemonAction('mds', 'stop');
-    cy.get('cd-service-details').within(() => {
-      services.checkServiceStatus(mdsDaemonName, 'stopped');
-    });
-    services.daemonAction('mds', 'start');
-    cy.get('cd-service-details').within(() => {
-      services.checkServiceStatus(mdsDaemonName, 'running');
-    });
-  });
-
-  it('should delete an mds service', () => {
-    services.deleteService(mdsDaemonName);
-  });
-
-  it('should create and delete snmp-gateway service with version V2c', () => {
-    services.navigateTo('create');
-    services.addService('snmp-gateway', false, 1, 'V2c');
-    services.checkExist('snmp-gateway', true);
-
-    services.clickServiceTab('snmp-gateway', 'Daemons');
-    cy.get('cd-service-details').within(() => {
-      services.checkServiceStatus('snmp-gateway');
-    });
-
-    services.deleteService('snmp-gateway');
-  });
-
-  it('should create and delete snmp-gateway service with version V3', () => {
-    services.navigateTo('create');
-    services.addService('snmp-gateway', false, 1, 'V3', true);
-    services.checkExist('snmp-gateway', true);
-
-    services.clickServiceTab('snmp-gateway', 'Daemons');
-    cy.get('cd-service-details').within(() => {
-      services.checkServiceStatus('snmp-gateway');
-    });
-
-    services.deleteService('snmp-gateway');
-  });
-
-  it('should create and delete snmp-gateway service with version V3 and w/o privacy protocol', () => {
-    services.navigateTo('create');
-    services.addService('snmp-gateway', false, 1, 'V3', false);
-    services.checkExist('snmp-gateway', true);
-
-    services.clickServiceTab('snmp-gateway', 'Daemons');
-    cy.get('cd-service-details').within(() => {
-      services.checkServiceStatus('snmp-gateway');
-    });
-
-    services.deleteService('snmp-gateway');
-  });
-
-  it('should create ingress as unmanaged', () => {
-    services.navigateTo('create');
-    services.addService('ingress', false, undefined, undefined, undefined, true);
-    services.checkExist('ingress.rgw.foo', true);
-    services.isUnmanaged('ingress.rgw.foo', true);
-    services.deleteService('ingress.rgw.foo');
-  });
-
-  it('should check if exporter daemons are running', () => {
-    services.clickServiceTab('ceph-exporter', 'Daemons');
-    cy.get('cd-service-details').within(() => {
-      services.checkServiceStatus('ceph-exporter', 'running');
-    });
-  });
-});
diff --git a/src/pybind/mgr/dashboard/frontend/cypress/integration/orchestrator/workflow/10-nfs-exports.e2e-spec.ts b/src/pybind/mgr/dashboard/frontend/cypress/integration/orchestrator/workflow/10-nfs-exports.e2e-spec.ts
deleted file mode 100644 (file)
index f97509d..0000000
+++ /dev/null
@@ -1,83 +0,0 @@
-/* tslint:disable*/
-import { ServicesPageHelper } from '../../cluster/services.po';
-import { NFSPageHelper } from '../../orchestrator/workflow/nfs/nfs-export.po';
-import { BucketsPageHelper } from '../../rgw/buckets.po';
-/* tslint:enable*/
-
-describe('nfsExport page', () => {
-  const nfsExport = new NFSPageHelper();
-  const services = new ServicesPageHelper();
-  const buckets = new BucketsPageHelper();
-  const bucketName = 'e2e.nfs.bucket';
-  // @TODO: uncomment this when a CephFS volume can be created through Dashboard.
-  // const fsPseudo = '/fsPseudo';
-  const rgwPseudo = '/rgwPseudo';
-  const editPseudo = '/editPseudo';
-  const backends = ['CephFS', 'Object Gateway'];
-  const squash = 'no_root_squash';
-  const client: object = { addresses: '192.168.0.10' };
-
-  beforeEach(() => {
-    cy.login();
-    Cypress.Cookies.preserveOnce('token');
-    nfsExport.navigateTo();
-  });
-
-  describe('breadcrumb test', () => {
-    it('should open and show breadcrumb', () => {
-      nfsExport.expectBreadcrumbText('NFS');
-    });
-  });
-
-  describe('Create, edit and delete', () => {
-    it('should create an NFS cluster', () => {
-      services.navigateTo('create');
-
-      services.addService('nfs');
-
-      services.checkExist('nfs.testnfs', true);
-      services.clickServiceTab('nfs.testnfs', 'Daemons');
-      services.checkServiceStatus('nfs');
-    });
-
-    it('should create a nfs-export with RGW backend', () => {
-      buckets.navigateTo('create');
-      buckets.create(bucketName, 'dashboard', 'default-placement');
-
-      nfsExport.navigateTo();
-      nfsExport.existTableCell(rgwPseudo, false);
-      nfsExport.navigateTo('create');
-      nfsExport.create(backends[1], squash, client, rgwPseudo, bucketName);
-      nfsExport.existTableCell(rgwPseudo);
-    });
-
-    // @TODO: uncomment this when a CephFS volume can be created through Dashboard.
-    // it('should create a nfs-export with CephFS backend', () => {
-    //   nfsExport.navigateTo();
-    //   nfsExport.existTableCell(fsPseudo, false);
-    //   nfsExport.navigateTo('create');
-    //   nfsExport.create(backends[0], squash, client, fsPseudo);
-    //   nfsExport.existTableCell(fsPseudo);
-    // });
-
-    it('should show Clients', () => {
-      nfsExport.clickTab('cd-nfs-details', rgwPseudo, 'Clients (1)');
-      cy.get('cd-nfs-details').within(() => {
-        nfsExport.getTableCount('total').should('be.gte', 0);
-      });
-    });
-
-    it('should edit an export', () => {
-      nfsExport.editExport(rgwPseudo, editPseudo);
-
-      nfsExport.existTableCell(editPseudo);
-    });
-
-    it('should delete exports and bucket', () => {
-      nfsExport.delete(editPseudo);
-
-      buckets.navigateTo();
-      buckets.delete(bucketName);
-    });
-  });
-});
diff --git a/src/pybind/mgr/dashboard/frontend/cypress/integration/orchestrator/workflow/nfs/nfs-export.po.ts b/src/pybind/mgr/dashboard/frontend/cypress/integration/orchestrator/workflow/nfs/nfs-export.po.ts
deleted file mode 100644 (file)
index c700ef0..0000000
+++ /dev/null
@@ -1,52 +0,0 @@
-/* tslint:disable*/
-import { PageHelper } from '../../../page-helper.po';
-/* tslint:enable*/
-
-const pages = {
-  index: { url: '#/nfs', id: 'cd-nfs-list' },
-  create: { url: '#/nfs/create', id: 'cd-nfs-form' }
-};
-
-export class NFSPageHelper extends PageHelper {
-  pages = pages;
-
-  @PageHelper.restrictTo(pages.create.url)
-  create(backend: string, squash: string, client: object, pseudo: string, rgwPath?: string) {
-    this.selectOption('cluster_id', 'testnfs');
-    // select a storage backend
-    this.selectOption('name', backend);
-    if (backend === 'CephFS') {
-      this.selectOption('fs_name', 'myfs');
-
-      cy.get('#security_label').click({ force: true });
-    } else {
-      cy.get('input[data-testid=rgw_path]').type(rgwPath);
-    }
-
-    cy.get('input[name=pseudo]').type(pseudo);
-    this.selectOption('squash', squash);
-
-    // Add clients
-    cy.get('button[name=add_client]').click({ force: true });
-    cy.get('input[name=addresses]').type(client['addresses']);
-
-    // Check if we can remove clients and add it again
-    cy.get('span[name=remove_client]').click({ force: true });
-    cy.get('button[name=add_client]').click({ force: true });
-    cy.get('input[name=addresses]').type(client['addresses']);
-
-    cy.get('cd-submit-button').click();
-  }
-
-  editExport(pseudo: string, editPseudo: string) {
-    this.navigateEdit(pseudo);
-
-    cy.get('input[name=pseudo]').clear().type(editPseudo);
-
-    cy.get('cd-submit-button').click();
-
-    // Click the export and check its details table for updated content
-    this.getExpandCollapseElement(editPseudo).click();
-    cy.get('.active.tab-pane').should('contain.text', editPseudo);
-  }
-}
diff --git a/src/pybind/mgr/dashboard/frontend/cypress/integration/page-helper.po.ts b/src/pybind/mgr/dashboard/frontend/cypress/integration/page-helper.po.ts
deleted file mode 100644 (file)
index e4bbd3f..0000000
+++ /dev/null
@@ -1,309 +0,0 @@
-interface Page {
-  url: string;
-  id: string;
-}
-
-export abstract class PageHelper {
-  pages: Record<string, Page>;
-
-  /**
-   * Decorator to be used on Helper methods to restrict access to one particular URL.  This shall
-   * help developers to prevent and highlight mistakes.  It also reduces boilerplate code and by
-   * thus, increases readability.
-   */
-  static restrictTo(page: string): Function {
-    return (target: any, propertyKey: string, descriptor: PropertyDescriptor) => {
-      const fn: Function = descriptor.value;
-      descriptor.value = function (...args: any) {
-        cy.location('hash').should((url) => {
-          expect(url).to.eq(
-            page,
-            `Method ${target.constructor.name}::${propertyKey} is supposed to be ` +
-              `run on path "${page}", but was run on URL "${url}"`
-          );
-        });
-        fn.apply(this, args);
-      };
-    };
-  }
-
-  /**
-   * Navigates to the given page or to index.
-   * Waits until the page component is loaded
-   */
-  navigateTo(name: string = null) {
-    name = name || 'index';
-    const page = this.pages[name];
-
-    cy.visit(page.url);
-    cy.get(page.id);
-  }
-
-  /**
-   * Navigates back and waits for the hash to change
-   */
-  navigateBack() {
-    cy.location('hash').then((hash) => {
-      cy.go('back');
-      cy.location('hash').should('not.be', hash);
-    });
-  }
-
-  /**
-   * Navigates to the edit page
-   */
-  navigateEdit(name: string, select = true, breadcrumb = true) {
-    if (select) {
-      this.navigateTo();
-      this.getFirstTableCell(name).click();
-    }
-    cy.contains('Creating...').should('not.exist');
-    cy.contains('button', 'Edit').click();
-    if (breadcrumb) {
-      this.expectBreadcrumbText('Edit');
-    }
-  }
-
-  /**
-   * Checks the active breadcrumb value.
-   */
-  expectBreadcrumbText(text: string) {
-    cy.get('.breadcrumb-item.active').should('have.text', text);
-  }
-
-  getTabs() {
-    return cy.get('.nav.nav-tabs a');
-  }
-
-  getTab(tabName: string) {
-    return cy.contains('.nav.nav-tabs a', tabName);
-  }
-
-  getTabText(index: number) {
-    return this.getTabs().its(index).text();
-  }
-
-  getTabsCount(): any {
-    return this.getTabs().its('length');
-  }
-
-  /**
-   * Helper method to navigate/click a tab inside the expanded table row.
-   * @param selector The selector of the expanded table row.
-   * @param name The name of the row which should expand.
-   * @param tabName Name of the tab to be navigated/clicked.
-   */
-  clickTab(selector: string, name: string, tabName: string) {
-    this.getExpandCollapseElement(name).click();
-    cy.get(selector).within(() => {
-      this.getTab(tabName).click();
-    });
-  }
-
-  /**
-   * Helper method to select an option inside a select element.
-   * This method will also expect that the option was set.
-   * @param option The option text (not value) to be selected.
-   */
-  selectOption(selectionName: string, option: string) {
-    cy.get(`select[name=${selectionName}]`).select(option);
-    return this.expectSelectOption(selectionName, option);
-  }
-
-  /**
-   * Helper method to expect a set option inside a select element.
-   * @param option The selected option text (not value) that is to
-   *   be expected.
-   */
-  expectSelectOption(selectionName: string, option: string) {
-    return cy.get(`select[name=${selectionName}] option:checked`).contains(option);
-  }
-
-  getLegends() {
-    return cy.get('legend');
-  }
-
-  getToast() {
-    return cy.get('.ngx-toastr');
-  }
-
-  /**
-   * Waits for the table to load its data
-   * Should be used in all methods that access the datatable
-   */
-  private waitDataTableToLoad() {
-    cy.get('cd-table').should('exist');
-    cy.get('datatable-scroller, .empty-row');
-  }
-
-  getDataTables() {
-    this.waitDataTableToLoad();
-
-    return cy.get('cd-table .dataTables_wrapper');
-  }
-
-  private getTableCountSpan(spanType: 'selected' | 'found' | 'total') {
-    return cy.contains('.datatable-footer-inner .page-count span', spanType);
-  }
-
-  // Get 'selected', 'found', or 'total' row count of a table.
-  getTableCount(spanType: 'selected' | 'found' | 'total') {
-    this.waitDataTableToLoad();
-    return this.getTableCountSpan(spanType).then(($elem) => {
-      const text = $elem
-        .filter((_i, e) => e.innerText.includes(spanType))
-        .first()
-        .text();
-
-      return Number(text.match(/(\d+)\s+\w*/)[1]);
-    });
-  }
-
-  // Wait until selected', 'found', or 'total' row count of a table equal to a number.
-  expectTableCount(spanType: 'selected' | 'found' | 'total', count: number) {
-    this.waitDataTableToLoad();
-    this.getTableCountSpan(spanType).should(($elem) => {
-      const text = $elem.first().text();
-      expect(Number(text.match(/(\d+)\s+\w*/)[1])).to.equal(count);
-    });
-  }
-
-  getTableRow(content: string) {
-    this.waitDataTableToLoad();
-
-    this.searchTable(content);
-    return cy.contains('.datatable-body-row', content);
-  }
-
-  getTableRows() {
-    this.waitDataTableToLoad();
-
-    return cy.get('datatable-row-wrapper');
-  }
-
-  /**
-   * Returns the first table cell.
-   * Optionally, you can specify the content of the cell.
-   */
-  getFirstTableCell(content?: string) {
-    this.waitDataTableToLoad();
-
-    if (content) {
-      this.searchTable(content);
-      return cy.contains('.datatable-body-cell-label', content);
-    } else {
-      return cy.get('.datatable-body-cell-label').first();
-    }
-  }
-
-  getTableCell(columnIndex: number, exactContent: string, partialMatch = false) {
-    this.waitDataTableToLoad();
-    this.clearTableSearchInput();
-    this.searchTable(exactContent);
-    if (partialMatch) {
-      return cy.contains(
-        `datatable-body-row datatable-body-cell:nth-child(${columnIndex})`,
-        exactContent
-      );
-    }
-    return cy.contains(
-      `datatable-body-row datatable-body-cell:nth-child(${columnIndex})`,
-      new RegExp(`^${exactContent}$`)
-    );
-  }
-
-  existTableCell(name: string, oughtToBePresent = true) {
-    const waitRule = oughtToBePresent ? 'be.visible' : 'not.exist';
-    this.getFirstTableCell(name).should(waitRule);
-  }
-
-  getExpandCollapseElement(content?: string) {
-    this.waitDataTableToLoad();
-
-    if (content) {
-      return cy.contains('.datatable-body-row', content).find('.tc_expand-collapse');
-    } else {
-      return cy.get('.tc_expand-collapse').first();
-    }
-  }
-
-  /**
-   * Gets column headers of table
-   */
-  getDataTableHeaders(index = 0) {
-    this.waitDataTableToLoad();
-
-    return cy.get('.datatable-header').its(index).find('.datatable-header-cell');
-  }
-
-  /**
-   * Grabs striped tables
-   */
-  getStatusTables() {
-    return cy.get('.table.table-striped');
-  }
-
-  filterTable(name: string, option: string) {
-    this.waitDataTableToLoad();
-
-    cy.get('.tc_filter_name > button').click();
-    cy.contains(`.tc_filter_name .dropdown-item`, name).click();
-
-    cy.get('.tc_filter_option > button').click();
-    cy.contains(`.tc_filter_option .dropdown-item`, option).click();
-  }
-
-  setPageSize(size: string) {
-    cy.get('cd-table .dataTables_paginate input').first().clear({ force: true }).type(size);
-  }
-
-  searchTable(text: string) {
-    this.waitDataTableToLoad();
-
-    this.setPageSize('10');
-    cy.get('[aria-label=search]').first().clear({ force: true }).type(text);
-  }
-
-  clearTableSearchInput() {
-    this.waitDataTableToLoad();
-
-    return cy.get('cd-table .search button').first().click();
-  }
-
-  // Click the action button
-  clickActionButton(action: string) {
-    cy.get('.table-actions button.dropdown-toggle').first().click(); // open submenu
-    cy.get(`button.${action}`).click(); // click on "action" menu item
-  }
-
-  /**
-   * This is a generic method to delete table rows.
-   * It will select the first row that contains the provided name and delete it.
-   * After that it will wait until the row is no longer displayed.
-   * @param name The string to search in table cells.
-   * @param columnIndex If provided, search string in columnIndex column.
-   */
-  delete(name: string, columnIndex?: number, section?: string) {
-    // Selects row
-    const getRow = columnIndex
-      ? this.getTableCell.bind(this, columnIndex)
-      : this.getFirstTableCell.bind(this);
-    getRow(name).click();
-    let action: string;
-    section === 'hosts' ? (action = 'remove') : (action = 'delete');
-
-    // Clicks on table Delete/Remove button
-    this.clickActionButton(action);
-
-    // Convert action to SentenceCase and Confirms deletion
-    const actionUpperCase = action.charAt(0).toUpperCase() + action.slice(1);
-    cy.get('cd-modal .custom-control-label').click();
-    cy.contains('cd-modal button', actionUpperCase).click();
-
-    // Wait for modal to close
-    cy.get('cd-modal').should('not.exist');
-
-    // Waits for item to be removed from table
-    getRow(name).should('not.exist');
-  }
-}
diff --git a/src/pybind/mgr/dashboard/frontend/cypress/integration/pools/pools.e2e-spec.ts b/src/pybind/mgr/dashboard/frontend/cypress/integration/pools/pools.e2e-spec.ts
deleted file mode 100644 (file)
index b4c3c75..0000000
+++ /dev/null
@@ -1,54 +0,0 @@
-import { PoolPageHelper } from './pools.po';
-
-describe('Pools page', () => {
-  const pools = new PoolPageHelper();
-  const poolName = 'pool_e2e_pool-test';
-
-  beforeEach(() => {
-    cy.login();
-    Cypress.Cookies.preserveOnce('token');
-    pools.navigateTo();
-  });
-
-  describe('breadcrumb and tab tests', () => {
-    it('should open and show breadcrumb', () => {
-      pools.expectBreadcrumbText('Pools');
-    });
-
-    it('should show two tabs', () => {
-      pools.getTabsCount().should('equal', 2);
-    });
-
-    it('should show pools list tab at first', () => {
-      pools.getTabText(0).should('eq', 'Pools List');
-    });
-
-    it('should show overall performance as a second tab', () => {
-      pools.getTabText(1).should('eq', 'Overall Performance');
-    });
-  });
-
-  describe('Create, update and destroy', () => {
-    it('should create a pool', () => {
-      pools.existTableCell(poolName, false);
-      pools.navigateTo('create');
-      pools.create(poolName, 8, 'rbd');
-      pools.existTableCell(poolName);
-    });
-
-    it('should edit a pools placement group', () => {
-      pools.existTableCell(poolName);
-      pools.edit_pool_pg(poolName, 32);
-    });
-
-    it('should show updated configuration field values', () => {
-      pools.existTableCell(poolName);
-      const bpsLimit = '4 B/s';
-      pools.edit_pool_configuration(poolName, bpsLimit);
-    });
-
-    it('should delete a pool', () => {
-      pools.delete(poolName);
-    });
-  });
-});
diff --git a/src/pybind/mgr/dashboard/frontend/cypress/integration/pools/pools.po.ts b/src/pybind/mgr/dashboard/frontend/cypress/integration/pools/pools.po.ts
deleted file mode 100644 (file)
index 7cca96a..0000000
+++ /dev/null
@@ -1,70 +0,0 @@
-import { PageHelper } from '../page-helper.po';
-
-const pages = {
-  index: { url: '#/pool', id: 'cd-pool-list' },
-  create: { url: '#/pool/create', id: 'cd-pool-form' }
-};
-
-export class PoolPageHelper extends PageHelper {
-  pages = pages;
-
-  private isPowerOf2(n: number) {
-    // tslint:disable-next-line: no-bitwise
-    return expect((n & (n - 1)) === 0, `Placement groups ${n} are not a power of 2`).to.be.true;
-  }
-
-  @PageHelper.restrictTo(pages.create.url)
-  create(name: string, placement_groups: number, ...apps: string[]) {
-    cy.get('input[name=name]').clear().type(name);
-
-    this.isPowerOf2(placement_groups);
-
-    this.selectOption('poolType', 'replicated');
-
-    this.expectSelectOption('pgAutoscaleMode', 'on');
-    this.selectOption('pgAutoscaleMode', 'off'); // To show pgNum field
-    cy.get('input[name=pgNum]').clear().type(`${placement_groups}`);
-    this.setApplications(apps);
-    cy.get('cd-submit-button').click();
-  }
-
-  edit_pool_pg(name: string, new_pg: number, wait = true) {
-    this.isPowerOf2(new_pg);
-    this.navigateEdit(name);
-
-    cy.get('input[name=pgNum]').clear().type(`${new_pg}`);
-    cy.get('cd-submit-button').click();
-    const str = `${new_pg} active+clean`;
-    this.getTableRow(name);
-    if (wait) {
-      this.getTableRow(name).contains(str);
-    }
-  }
-
-  edit_pool_configuration(name: string, bpsLimit: string) {
-    this.navigateEdit(name);
-
-    cy.get('.collapsible').click();
-    cy.get('cd-rbd-configuration-form')
-      .get('input[name=rbd_qos_bps_limit]')
-      .clear()
-      .type(`${bpsLimit}`);
-    cy.get('cd-submit-button').click();
-
-    this.navigateEdit(name);
-
-    cy.get('.collapsible').click();
-    cy.get('cd-rbd-configuration-form')
-      .get('input[name=rbd_qos_bps_limit]')
-      .should('have.value', bpsLimit);
-  }
-
-  private setApplications(apps: string[]) {
-    if (!apps || apps.length === 0) {
-      return;
-    }
-    cy.get('.float-start.me-2.select-menu-edit').click();
-    cy.get('.popover-body').should('be.visible');
-    apps.forEach((app) => cy.get('.select-menu-item-content').contains(app).click());
-  }
-}
diff --git a/src/pybind/mgr/dashboard/frontend/cypress/integration/rgw/buckets.e2e-spec.ts b/src/pybind/mgr/dashboard/frontend/cypress/integration/rgw/buckets.e2e-spec.ts
deleted file mode 100644 (file)
index 6c50b48..0000000
+++ /dev/null
@@ -1,67 +0,0 @@
-import { BucketsPageHelper } from './buckets.po';
-
-describe('RGW buckets page', () => {
-  const buckets = new BucketsPageHelper();
-  const bucket_name = 'e2ebucket';
-
-  beforeEach(() => {
-    cy.login();
-    Cypress.Cookies.preserveOnce('token');
-    buckets.navigateTo();
-  });
-
-  describe('breadcrumb tests', () => {
-    it('should open and show breadcrumb', () => {
-      buckets.expectBreadcrumbText('Buckets');
-    });
-  });
-
-  describe('create, edit & delete bucket tests', () => {
-    it('should create bucket', () => {
-      buckets.navigateTo('create');
-      buckets.create(bucket_name, BucketsPageHelper.USERS[0], 'default-placement');
-      buckets.getFirstTableCell(bucket_name).should('exist');
-    });
-
-    it('should edit bucket', () => {
-      buckets.edit(bucket_name, BucketsPageHelper.USERS[1]);
-      buckets.getDataTables().should('contain.text', BucketsPageHelper.USERS[1]);
-    });
-
-    it('should delete bucket', () => {
-      buckets.delete(bucket_name);
-    });
-
-    it('should check default encryption is SSE-S3', () => {
-      buckets.navigateTo('create');
-      buckets.checkForDefaultEncryption();
-    });
-
-    it('should create bucket with object locking enabled', () => {
-      buckets.navigateTo('create');
-      buckets.create(bucket_name, BucketsPageHelper.USERS[0], 'default-placement', true);
-      buckets.getFirstTableCell(bucket_name).should('exist');
-    });
-
-    it('should not allow to edit versioning if object locking is enabled', () => {
-      buckets.edit(bucket_name, BucketsPageHelper.USERS[1], true);
-      buckets.getDataTables().should('contain.text', BucketsPageHelper.USERS[1]);
-
-      buckets.delete(bucket_name);
-    });
-  });
-
-  describe('Invalid Input in Create and Edit tests', () => {
-    it('should test invalid inputs in create fields', () => {
-      buckets.testInvalidCreate();
-    });
-
-    it('should test invalid input in edit owner field', () => {
-      buckets.navigateTo('create');
-      buckets.create(bucket_name, BucketsPageHelper.USERS[0], 'default-placement');
-      buckets.testInvalidEdit(bucket_name);
-      buckets.navigateTo();
-      buckets.delete(bucket_name);
-    });
-  });
-});
diff --git a/src/pybind/mgr/dashboard/frontend/cypress/integration/rgw/buckets.po.ts b/src/pybind/mgr/dashboard/frontend/cypress/integration/rgw/buckets.po.ts
deleted file mode 100644 (file)
index a27be3c..0000000
+++ /dev/null
@@ -1,202 +0,0 @@
-import { PageHelper } from '../page-helper.po';
-
-const pages = {
-  index: { url: '#/rgw/bucket', id: 'cd-rgw-bucket-list' },
-  create: { url: '#/rgw/bucket/create', id: 'cd-rgw-bucket-form' }
-};
-
-export class BucketsPageHelper extends PageHelper {
-  static readonly USERS = ['dashboard', 'testid'];
-
-  pages = pages;
-
-  versioningStateEnabled = 'Enabled';
-  versioningStateSuspended = 'Suspended';
-
-  private selectOwner(owner: string) {
-    return this.selectOption('owner', owner);
-  }
-
-  private selectPlacementTarget(placementTarget: string) {
-    return this.selectOption('placement-target', placementTarget);
-  }
-
-  private selectLockMode(lockMode: string) {
-    return this.selectOption('lock_mode', lockMode);
-  }
-
-  @PageHelper.restrictTo(pages.create.url)
-  create(name: string, owner: string, placementTarget: string, isLocking = false) {
-    // Enter in bucket name
-    cy.get('#bid').type(name);
-
-    // Select bucket owner
-    this.selectOwner(owner);
-    cy.get('#owner').should('have.class', 'ng-valid');
-
-    // Select bucket placement target:
-    this.selectPlacementTarget(placementTarget);
-    cy.get('#placement-target').should('have.class', 'ng-valid');
-
-    if (isLocking) {
-      cy.get('#lock_enabled').click({ force: true });
-      // Select lock mode:
-      this.selectLockMode('Compliance');
-      cy.get('#lock_mode').should('have.class', 'ng-valid');
-      cy.get('#lock_retention_period_days').type('3');
-    }
-
-    // Click the create button and wait for bucket to be made
-    cy.contains('button', 'Create Bucket').click();
-
-    this.getFirstTableCell(name).should('exist');
-  }
-
-  @PageHelper.restrictTo(pages.create.url)
-  checkForDefaultEncryption() {
-    cy.get("cd-helper[aria-label='toggle encryption helper']").click();
-    cy.get("a[aria-label='click here']").click();
-    cy.get('cd-modal').within(() => {
-      cy.get('input[id=s3Enabled]').should('be.checked');
-    });
-  }
-
-  @PageHelper.restrictTo(pages.index.url)
-  edit(name: string, new_owner: string, isLocking = false) {
-    this.navigateEdit(name);
-
-    cy.get('input[name=placement-target]').should('have.value', 'default-placement');
-    this.selectOwner(new_owner);
-
-    // If object locking is enabled versioning shouldn't be visible
-    if (isLocking) {
-      cy.get('input[id=versioning]').should('be.disabled');
-      cy.contains('button', 'Edit Bucket').click();
-
-      // wait to be back on buckets page with table visible and click
-      this.getExpandCollapseElement(name).click();
-
-      // check its details table for edited owner field
-      cy.get('.table.table-striped.table-bordered')
-        .first()
-        .should('contains.text', new_owner)
-        .as('bucketDataTable');
-
-      // Check versioning enabled:
-      cy.get('@bucketDataTable').find('tr').its(2).find('td').last().should('have.text', new_owner);
-      cy.get('@bucketDataTable').find('tr').its(11).find('td').last().as('versioningValueCell');
-
-      return cy.get('@versioningValueCell').should('have.text', this.versioningStateEnabled);
-    }
-    // Enable versioning
-    cy.get('input[id=versioning]').should('not.be.checked');
-    cy.get('label[for=versioning]').click();
-    cy.get('input[id=versioning]').should('be.checked');
-
-    cy.contains('button', 'Edit Bucket').click();
-
-    // wait to be back on buckets page with table visible and click
-    this.getExpandCollapseElement(name).click();
-
-    // check its details table for edited owner field
-    cy.get('.table.table-striped.table-bordered')
-      .first()
-      .should('contains.text', new_owner)
-      .as('bucketDataTable');
-
-    // Check versioning enabled:
-    cy.get('@bucketDataTable').find('tr').its(2).find('td').last().should('have.text', new_owner);
-    cy.get('@bucketDataTable').find('tr').its(11).find('td').last().as('versioningValueCell');
-
-    cy.get('@versioningValueCell').should('have.text', this.versioningStateEnabled);
-
-    // Disable versioning:
-    this.navigateEdit(name);
-
-    cy.get('label[for=versioning]').click();
-    cy.get('input[id=versioning]').should('not.be.checked');
-    cy.contains('button', 'Edit Bucket').click();
-
-    // Check versioning suspended:
-    this.getExpandCollapseElement(name).click();
-
-    return cy.get('@versioningValueCell').should('have.text', this.versioningStateSuspended);
-  }
-
-  testInvalidCreate() {
-    this.navigateTo('create');
-    cy.get('#bid').as('nameInputField'); // Grabs name box field
-
-    // Gives an invalid name (too short), then waits for dashboard to determine validity
-    cy.get('@nameInputField').type('rq');
-
-    cy.contains('button', 'Create Bucket').click(); // To trigger a validation
-
-    // Waiting for website to decide if name is valid or not
-    // Check that name input field was marked invalid in the css
-    cy.get('@nameInputField')
-      .should('not.have.class', 'ng-pending')
-      .and('have.class', 'ng-invalid');
-
-    // Check that error message was printed under name input field
-    cy.get('#bid + .invalid-feedback').should(
-      'have.text',
-      'Bucket names must be 3 to 63 characters long.'
-    );
-
-    // Test invalid owner input
-    // select some valid option. The owner drop down error message will not appear unless a valid user was selected at
-    // one point before the invalid placeholder user is selected.
-    this.selectOwner(BucketsPageHelper.USERS[1]);
-
-    // select the first option, which is invalid because it is a placeholder
-    this.selectOwner('-- Select a user --');
-
-    cy.get('@nameInputField').click();
-
-    // Check that owner drop down field was marked invalid in the css
-    cy.get('#owner').should('have.class', 'ng-invalid');
-
-    // Check that error message was printed under owner drop down field
-    cy.get('#owner + .invalid-feedback').should('have.text', 'This field is required.');
-
-    // Check invalid placement target input
-    this.selectOwner(BucketsPageHelper.USERS[1]);
-    // The drop down error message will not appear unless a valid option is previsously selected.
-    this.selectPlacementTarget('default-placement');
-    this.selectPlacementTarget('-- Select a placement target --');
-    cy.get('@nameInputField').click(); // Trigger validation
-    cy.get('#placement-target').should('have.class', 'ng-invalid');
-    cy.get('#placement-target + .invalid-feedback').should('have.text', 'This field is required.');
-
-    // Clicks the Create Bucket button but the page doesn't move.
-    // Done by testing for the breadcrumb
-    cy.contains('button', 'Create Bucket').click(); // Clicks Create Bucket button
-    this.expectBreadcrumbText('Create');
-    // content in fields seems to subsist through tests if not cleared, so it is cleared
-    cy.get('@nameInputField').clear();
-    return cy.contains('button', 'Cancel').click();
-  }
-
-  testInvalidEdit(name: string) {
-    this.navigateEdit(name);
-
-    cy.get('input[id=versioning]').should('exist').and('not.be.checked');
-
-    // Chooses 'Select a user' rather than a valid owner on Edit Bucket page
-    // and checks if it's an invalid input
-
-    // select the first option, which is invalid because it is a placeholder
-    this.selectOwner('-- Select a user --');
-
-    cy.contains('button', 'Edit Bucket').click();
-
-    // Check that owner drop down field was marked invalid in the css
-    cy.get('#owner').should('have.class', 'ng-invalid');
-
-    // Check that error message was printed under owner drop down field
-    cy.get('#owner + .invalid-feedback').should('have.text', 'This field is required.');
-
-    this.expectBreadcrumbText('Edit');
-  }
-}
diff --git a/src/pybind/mgr/dashboard/frontend/cypress/integration/rgw/daemons.e2e-spec.ts b/src/pybind/mgr/dashboard/frontend/cypress/integration/rgw/daemons.e2e-spec.ts
deleted file mode 100644 (file)
index f3129a7..0000000
+++ /dev/null
@@ -1,35 +0,0 @@
-import { DaemonsPageHelper } from './daemons.po';
-
-describe('RGW daemons page', () => {
-  const daemons = new DaemonsPageHelper();
-
-  beforeEach(() => {
-    cy.login();
-    Cypress.Cookies.preserveOnce('token');
-    daemons.navigateTo();
-  });
-
-  describe('breadcrumb and tab tests', () => {
-    it('should open and show breadcrumb', () => {
-      daemons.expectBreadcrumbText('Gateways');
-    });
-
-    it('should show two tabs', () => {
-      daemons.getTabsCount().should('eq', 2);
-    });
-
-    it('should show daemons list tab at first', () => {
-      daemons.getTabText(0).should('eq', 'Gateways List');
-    });
-
-    it('should show overall performance as a second tab', () => {
-      daemons.getTabText(1).should('eq', 'Overall Performance');
-    });
-  });
-
-  describe('details and performance counters table tests', () => {
-    it('should check that details/performance tables are visible when daemon is selected', () => {
-      daemons.checkTables();
-    });
-  });
-});
diff --git a/src/pybind/mgr/dashboard/frontend/cypress/integration/rgw/daemons.po.ts b/src/pybind/mgr/dashboard/frontend/cypress/integration/rgw/daemons.po.ts
deleted file mode 100644 (file)
index 82a1794..0000000
+++ /dev/null
@@ -1,34 +0,0 @@
-import { PageHelper } from '../page-helper.po';
-
-export class DaemonsPageHelper extends PageHelper {
-  pages = {
-    index: { url: '#/rgw/daemon', id: 'cd-rgw-daemon-list' }
-  };
-
-  getTableCell() {
-    return cy
-      .get('.tab-content')
-      .its(1)
-      .find('cd-table')
-      .should('have.length', 1) // Only 1 table should be renderer
-      .find('datatable-body-cell');
-  }
-
-  checkTables() {
-    // click on a daemon so details table appears
-    cy.get('.datatable-body-cell-label').first().click();
-
-    // check details table is visible
-    // check at least one field is present
-    this.getTableCell().should('be.visible').should('contain.text', 'ceph_version');
-
-    // click on performance counters tab and check table is loaded
-    cy.contains('.nav-link', 'Performance Counters').click();
-
-    // check at least one field is present
-    this.getTableCell().should('be.visible').should('contain.text', 'objecter.op_r');
-
-    // click on performance details tab
-    cy.contains('.nav-link', 'Performance Details').click();
-  }
-}
diff --git a/src/pybind/mgr/dashboard/frontend/cypress/integration/rgw/users.e2e-spec.ts b/src/pybind/mgr/dashboard/frontend/cypress/integration/rgw/users.e2e-spec.ts
deleted file mode 100644 (file)
index b5f366a..0000000
+++ /dev/null
@@ -1,46 +0,0 @@
-import { UsersPageHelper } from './users.po';
-
-describe('RGW users page', () => {
-  const users = new UsersPageHelper();
-  const tenant = 'e2e_000tenant';
-  const user_id = 'e2e_000user_create_edit_delete';
-  const user_name = tenant + '$' + user_id;
-
-  beforeEach(() => {
-    cy.login();
-    Cypress.Cookies.preserveOnce('token');
-    users.navigateTo();
-  });
-
-  describe('breadcrumb tests', () => {
-    it('should open and show breadcrumb', () => {
-      users.expectBreadcrumbText('Users');
-    });
-  });
-
-  describe('create, edit & delete user tests', () => {
-    it('should create user', () => {
-      users.navigateTo('create');
-      users.create(tenant, user_id, 'Some Name', 'original@website.com', '1200');
-      users.getFirstTableCell(user_id).should('exist');
-    });
-
-    it('should edit users full name, email and max buckets', () => {
-      users.edit(user_name, 'Another Identity', 'changed@othersite.com', '1969');
-    });
-
-    it('should delete user', () => {
-      users.delete(user_name);
-    });
-  });
-
-  describe('Invalid input tests', () => {
-    it('should put invalid input into user creation form and check fields are marked invalid', () => {
-      users.invalidCreate();
-    });
-
-    it('should put invalid input into user edit form and check fields are marked invalid', () => {
-      users.invalidEdit();
-    });
-  });
-});
diff --git a/src/pybind/mgr/dashboard/frontend/cypress/integration/rgw/users.po.ts b/src/pybind/mgr/dashboard/frontend/cypress/integration/rgw/users.po.ts
deleted file mode 100644 (file)
index a4266f9..0000000
+++ /dev/null
@@ -1,139 +0,0 @@
-import { PageHelper } from '../page-helper.po';
-
-const pages = {
-  index: { url: '#/rgw/user', id: 'cd-rgw-user-list' },
-  create: { url: '#/rgw/user/create', id: 'cd-rgw-user-form' }
-};
-
-export class UsersPageHelper extends PageHelper {
-  pages = pages;
-
-  @PageHelper.restrictTo(pages.create.url)
-  create(tenant: string, user_id: string, fullname: string, email: string, maxbuckets: string) {
-    // Enter in user_id
-    cy.get('#user_id').type(user_id);
-    // Show Tenanat
-    cy.get('#show_tenant').click({ force: true });
-    // Enter in tenant
-    cy.get('#tenant').type(tenant);
-    // Enter in full name
-    cy.get('#display_name').click().type(fullname);
-
-    // Enter in email
-    cy.get('#email').click().type(email);
-
-    // Enter max buckets
-    this.selectOption('max_buckets_mode', 'Custom');
-    cy.get('#max_buckets').should('exist').should('have.value', '1000');
-    cy.get('#max_buckets').click().clear().type(maxbuckets);
-
-    // Click the create button and wait for user to be made
-    cy.contains('button', 'Create User').click();
-    this.getFirstTableCell(tenant + '$' + user_id).should('exist');
-  }
-
-  @PageHelper.restrictTo(pages.index.url)
-  edit(name: string, new_fullname: string, new_email: string, new_maxbuckets: string) {
-    this.navigateEdit(name);
-
-    // Change the full name field
-    cy.get('#display_name').click().clear().type(new_fullname);
-
-    // Change the email field
-    cy.get('#email').click().clear().type(new_email);
-
-    // Change the max buckets field
-    this.selectOption('max_buckets_mode', 'Custom');
-    cy.get('#max_buckets').click().clear().type(new_maxbuckets);
-
-    cy.contains('button', 'Edit User').click();
-
-    // Click the user and check its details table for updated content
-    this.getExpandCollapseElement(name).click();
-    cy.get('.active.tab-pane')
-      .should('contain.text', new_fullname)
-      .and('contain.text', new_email)
-      .and('contain.text', new_maxbuckets);
-  }
-
-  invalidCreate() {
-    const tenant = '000invalid_tenant';
-    const uname = '000invalid_create_user';
-    // creating this user in order to check that you can't give two users the same name
-    this.navigateTo('create');
-    this.create(tenant, uname, 'xxx', 'xxx@xxx', '1');
-
-    this.navigateTo('create');
-
-    // Username
-    cy.get('#user_id')
-      // No username had been entered. Field should be invalid
-      .should('have.class', 'ng-invalid')
-      // Try to give user already taken name. Should make field invalid.
-      .type(uname);
-    cy.get('#show_tenant').click({ force: true });
-    cy.get('#tenant').type(tenant).should('have.class', 'ng-invalid');
-    cy.contains('#tenant + .invalid-feedback', 'The chosen user ID exists in this tenant.');
-
-    // check that username field is marked invalid if username has been cleared off
-    cy.get('#user_id').clear().blur().should('have.class', 'ng-invalid');
-    cy.contains('#user_id + .invalid-feedback', 'This field is required.');
-
-    // Full name
-    cy.get('#display_name')
-      // No display name has been given so field should be invalid
-      .should('have.class', 'ng-invalid')
-      // display name field should also be marked invalid if given input then emptied
-      .type('a')
-      .clear()
-      .blur()
-      .should('have.class', 'ng-invalid');
-    cy.contains('#display_name + .invalid-feedback', 'This field is required.');
-
-    // put invalid email to make field invalid
-    cy.get('#email').type('a').blur().should('have.class', 'ng-invalid');
-    cy.contains('#email + .invalid-feedback', 'This is not a valid email address.');
-
-    // put negative max buckets to make field invalid
-    this.expectSelectOption('max_buckets_mode', 'Custom');
-    cy.get('#max_buckets').clear().type('-5').blur().should('have.class', 'ng-invalid');
-    cy.contains('#max_buckets + .invalid-feedback', 'The entered value must be >= 1.');
-
-    this.navigateTo();
-    this.delete(tenant + '$' + uname);
-  }
-
-  invalidEdit() {
-    const tenant = '000invalid_tenant';
-    const uname = '000invalid_edit_user';
-    // creating this user to edit for the test
-    this.navigateTo('create');
-    this.create(tenant, uname, 'xxx', 'xxx@xxx', '50');
-    const name = tenant + '$' + uname;
-    this.navigateEdit(name);
-
-    // put invalid email to make field invalid
-    cy.get('#email')
-      .clear()
-      .type('a')
-      .blur()
-      .should('not.have.class', 'ng-pending')
-      .should('have.class', 'ng-invalid');
-    cy.contains('#email + .invalid-feedback', 'This is not a valid email address.');
-
-    // empty the display name field making it invalid
-    cy.get('#display_name').clear().blur().should('have.class', 'ng-invalid');
-    cy.contains('#display_name + .invalid-feedback', 'This field is required.');
-
-    // put negative max buckets to make field invalid
-    this.selectOption('max_buckets_mode', 'Disabled');
-    cy.get('#max_buckets').should('not.exist');
-    this.selectOption('max_buckets_mode', 'Custom');
-    cy.get('#max_buckets').should('exist').should('have.value', '50');
-    cy.get('#max_buckets').clear().type('-5').blur().should('have.class', 'ng-invalid');
-    cy.contains('#max_buckets + .invalid-feedback', 'The entered value must be >= 1.');
-
-    this.navigateTo();
-    this.delete(tenant + '$' + uname);
-  }
-}
diff --git a/src/pybind/mgr/dashboard/frontend/cypress/integration/ui/api-docs.e2e-spec.ts b/src/pybind/mgr/dashboard/frontend/cypress/integration/ui/api-docs.e2e-spec.ts
deleted file mode 100644 (file)
index 5299485..0000000
+++ /dev/null
@@ -1,15 +0,0 @@
-import { ApiDocsPageHelper } from '../ui/api-docs.po';
-
-describe('Api Docs Page', () => {
-  const apiDocs = new ApiDocsPageHelper();
-
-  beforeEach(() => {
-    cy.login();
-    Cypress.Cookies.preserveOnce('token');
-    apiDocs.navigateTo();
-  });
-
-  it('should show the API Docs description', () => {
-    cy.get('.renderedMarkdown').first().contains('This is the official Ceph REST API');
-  });
-});
diff --git a/src/pybind/mgr/dashboard/frontend/cypress/integration/ui/api-docs.po.ts b/src/pybind/mgr/dashboard/frontend/cypress/integration/ui/api-docs.po.ts
deleted file mode 100644 (file)
index c7a8d22..0000000
+++ /dev/null
@@ -1,5 +0,0 @@
-import { PageHelper } from '../page-helper.po';
-
-export class ApiDocsPageHelper extends PageHelper {
-  pages = { index: { url: '#/api-docs', id: 'cd-api-docs' } };
-}
diff --git a/src/pybind/mgr/dashboard/frontend/cypress/integration/ui/dashboard-v3.e2e-spec.ts b/src/pybind/mgr/dashboard/frontend/cypress/integration/ui/dashboard-v3.e2e-spec.ts
deleted file mode 100644 (file)
index cb72824..0000000
+++ /dev/null
@@ -1,50 +0,0 @@
-import { ManagerModulesPageHelper } from '../cluster/mgr-modules.po';
-import { DashboardV3PageHelper } from './dashboard-v3.po';
-
-describe('Dashboard-v3 Main Page', () => {
-  const dashboard = new DashboardV3PageHelper();
-  const mgrmodules = new ManagerModulesPageHelper();
-
-  before(() => {
-    cy.login();
-    mgrmodules.navigateTo();
-    mgrmodules.navigateEdit('dashboard');
-    cy.get('#FEATURE_TOGGLE_DASHBOARD').check();
-    cy.contains('button', 'Update').click();
-  });
-
-  beforeEach(() => {
-    cy.login();
-    Cypress.Cookies.preserveOnce('token');
-    dashboard.navigateTo();
-  });
-
-  describe('Check that all hyperlinks on inventory card lead to the correct page and fields exist', () => {
-    it('should ensure that all linked pages in the inventory card lead to correct page', () => {
-      const expectationMap = {
-        Host: 'Hosts',
-        Monitor: 'Monitors',
-        OSDs: 'OSDs',
-        Pool: 'Pools',
-        'Object Gateway': 'Daemons'
-      };
-
-      for (const [linkText, breadcrumbText] of Object.entries(expectationMap)) {
-        cy.location('hash').should('eq', '#/dashboard');
-        dashboard.clickInventoryCardLink(linkText);
-        dashboard.expectBreadcrumbText(breadcrumbText);
-        dashboard.navigateBack();
-      }
-    });
-
-    it('should verify that cards exist on dashboard in proper order', () => {
-      // Ensures that cards are all displayed on the dashboard tab while being in the proper
-      // order, checks for card title and position via indexing into a list of all cards.
-      const order = ['Details', 'Status', 'Capacity', 'Inventory', 'Cluster utilization'];
-
-      for (let i = 0; i < order.length; i++) {
-        dashboard.card(i).should('contain.text', order[i]);
-      }
-    });
-  });
-});
diff --git a/src/pybind/mgr/dashboard/frontend/cypress/integration/ui/dashboard-v3.po.ts b/src/pybind/mgr/dashboard/frontend/cypress/integration/ui/dashboard-v3.po.ts
deleted file mode 100644 (file)
index e720724..0000000
+++ /dev/null
@@ -1,20 +0,0 @@
-import { PageHelper } from '../page-helper.po';
-
-export class DashboardV3PageHelper extends PageHelper {
-  pages = { index: { url: '#/dashboard', id: 'cd-dashboard-v3' } };
-
-  cardTitle(index: number) {
-    return cy.get('.card-title').its(index).text();
-  }
-
-  clickInventoryCardLink(link: string) {
-    console.log(link);
-    cy.get(`cd-card[title="Inventory"]`).contains('a', link).click();
-  }
-
-  card(indexOrTitle: number) {
-    cy.get('cd-card').as('cards');
-
-    return cy.get('@cards').its(indexOrTitle);
-  }
-}
diff --git a/src/pybind/mgr/dashboard/frontend/cypress/integration/ui/dashboard.e2e-spec.ts b/src/pybind/mgr/dashboard/frontend/cypress/integration/ui/dashboard.e2e-spec.ts
deleted file mode 100644 (file)
index 110b496..0000000
+++ /dev/null
@@ -1,142 +0,0 @@
-import { IscsiPageHelper } from '../block/iscsi.po';
-import { HostsPageHelper } from '../cluster/hosts.po';
-import { ManagerModulesPageHelper } from '../cluster/mgr-modules.po';
-import { MonitorsPageHelper } from '../cluster/monitors.po';
-import { OSDsPageHelper } from '../cluster/osds.po';
-import { PageHelper } from '../page-helper.po';
-import { PoolPageHelper } from '../pools/pools.po';
-import { DaemonsPageHelper } from '../rgw/daemons.po';
-import { DashboardPageHelper } from './dashboard.po';
-
-describe('Dashboard Main Page', () => {
-  const dashboard = new DashboardPageHelper();
-  const daemons = new DaemonsPageHelper();
-  const hosts = new HostsPageHelper();
-  const osds = new OSDsPageHelper();
-  const pools = new PoolPageHelper();
-  const monitors = new MonitorsPageHelper();
-  const iscsi = new IscsiPageHelper();
-  const mgrmodules = new ManagerModulesPageHelper();
-
-  before(() => {
-    cy.login();
-    mgrmodules.navigateTo();
-    mgrmodules.navigateEdit('dashboard');
-    cy.get('#FEATURE_TOGGLE_DASHBOARD').uncheck();
-    cy.contains('button', 'Update').click();
-  });
-
-  beforeEach(() => {
-    cy.login();
-    Cypress.Cookies.preserveOnce('token');
-    dashboard.navigateTo();
-  });
-
-  describe('Check that all hyperlinks on info cards lead to the correct page and fields exist', () => {
-    it('should ensure that all linked info cards lead to correct page', () => {
-      const expectationMap = {
-        Monitors: 'Monitors',
-        OSDs: 'OSDs',
-        Hosts: 'Hosts',
-        'Object Gateways': 'Daemons',
-        'iSCSI Gateways': 'Overview',
-        Pools: 'Pools'
-      };
-
-      for (const [linkText, breadcrumbText] of Object.entries(expectationMap)) {
-        cy.location('hash').should('eq', '#/dashboard');
-        dashboard.clickInfoCardLink(linkText);
-        dashboard.expectBreadcrumbText(breadcrumbText);
-        dashboard.navigateBack();
-      }
-    });
-
-    it('should verify that info cards exist on dashboard in proper order', () => {
-      // Ensures that info cards are all displayed on the dashboard tab while being in the proper
-      // order, checks for card title and position via indexing into a list of all info cards.
-      const order = [
-        'Cluster Status',
-        'Hosts',
-        'Monitors',
-        'OSDs',
-        'Managers',
-        'Object Gateways',
-        'Metadata Servers',
-        'iSCSI Gateways',
-        'Raw Capacity',
-        'Objects',
-        'PG Status',
-        'Pools',
-        'PGs per OSD',
-        'Client Read/Write',
-        'Client Throughput',
-        'Recovery Throughput',
-        'Scrubbing'
-      ];
-
-      for (let i = 0; i < order.length; i++) {
-        dashboard.infoCard(i).should('contain.text', order[i]);
-      }
-    });
-
-    it('should verify that info card group titles are present and in the right order', () => {
-      cy.location('hash').should('eq', '#/dashboard');
-      dashboard.infoGroupTitle(0).should('eq', 'Status');
-      dashboard.infoGroupTitle(1).should('eq', 'Capacity');
-      dashboard.infoGroupTitle(2).should('eq', 'Performance');
-    });
-  });
-
-  it('Should check that dashboard cards have correct information', () => {
-    interface TestSpec {
-      cardName: string;
-      regexMatcher?: RegExp;
-      pageObject: PageHelper;
-    }
-    const testSpecs: TestSpec[] = [
-      { cardName: 'Object Gateways', regexMatcher: /(\d+)\s+total/, pageObject: daemons },
-      { cardName: 'Monitors', regexMatcher: /(\d+)\s+\(quorum/, pageObject: monitors },
-      { cardName: 'Hosts', regexMatcher: /(\d+)\s+total/, pageObject: hosts },
-      { cardName: 'OSDs', regexMatcher: /(\d+)\s+total/, pageObject: osds },
-      { cardName: 'Pools', pageObject: pools },
-      { cardName: 'iSCSI Gateways', regexMatcher: /(\d+)\s+total/, pageObject: iscsi }
-    ];
-    for (let i = 0; i < testSpecs.length; i++) {
-      const spec = testSpecs[i];
-      dashboard.navigateTo();
-
-      dashboard.infoCardBodyText(spec.cardName).then((infoCardBodyText: string) => {
-        let dashCount = 0;
-
-        if (spec.regexMatcher) {
-          const match = infoCardBodyText.match(new RegExp(spec.regexMatcher));
-          expect(match).to.length.gt(
-            1,
-            `Regex ${spec.regexMatcher} did not find a match for card with name ` +
-              `${spec.cardName}`
-          );
-          dashCount = Number(match[1]);
-        } else {
-          dashCount = Number(infoCardBodyText);
-        }
-
-        spec.pageObject.navigateTo();
-        spec.pageObject.getTableCount('total').then((tableCount) => {
-          expect(tableCount).to.eq(
-            dashCount,
-            `Text of card "${spec.cardName}" and regex "${spec.regexMatcher}" resulted in ${dashCount} ` +
-              `but did not match table count ${tableCount}`
-          );
-        });
-      });
-    }
-  });
-
-  after(() => {
-    cy.login();
-    mgrmodules.navigateTo();
-    mgrmodules.navigateEdit('dashboard');
-    cy.get('#FEATURE_TOGGLE_DASHBOARD').click();
-    cy.contains('button', 'Update').click();
-  });
-});
diff --git a/src/pybind/mgr/dashboard/frontend/cypress/integration/ui/dashboard.po.ts b/src/pybind/mgr/dashboard/frontend/cypress/integration/ui/dashboard.po.ts
deleted file mode 100644 (file)
index 42d63ef..0000000
+++ /dev/null
@@ -1,31 +0,0 @@
-import { PageHelper } from '../page-helper.po';
-
-export class DashboardPageHelper extends PageHelper {
-  pages = { index: { url: '#/dashboard', id: 'cd-dashboard' } };
-
-  infoGroupTitle(index: number) {
-    return cy.get('.info-group-title').its(index).text();
-  }
-
-  clickInfoCardLink(cardName: string) {
-    cy.get(`cd-info-card[cardtitle="${cardName}"]`).contains('a', cardName).click();
-  }
-
-  infoCard(indexOrTitle: number | string) {
-    cy.get('cd-info-card').as('infoCards');
-
-    if (typeof indexOrTitle === 'number') {
-      return cy.get('@infoCards').its(indexOrTitle);
-    } else {
-      return cy.contains('cd-info-card a', indexOrTitle).parent().parent().parent().parent();
-    }
-  }
-
-  infoCardBodyText(infoCard: string) {
-    return this.infoCard(infoCard).find('.card-text').text();
-  }
-
-  infoCardBody(infoCard: string) {
-    return this.infoCard(infoCard).find('.card-text');
-  }
-}
diff --git a/src/pybind/mgr/dashboard/frontend/cypress/integration/ui/language.e2e-spec.ts b/src/pybind/mgr/dashboard/frontend/cypress/integration/ui/language.e2e-spec.ts
deleted file mode 100644 (file)
index ccf16c2..0000000
+++ /dev/null
@@ -1,20 +0,0 @@
-import { LanguagePageHelper } from './language.po';
-
-describe('Shared pages', () => {
-  const language = new LanguagePageHelper();
-
-  beforeEach(() => {
-    cy.login();
-    Cypress.Cookies.preserveOnce('token');
-    language.navigateTo();
-  });
-
-  it('should check default language', () => {
-    language.getLanguageBtn().should('contain.text', 'English');
-  });
-
-  it('should check all available languages', () => {
-    language.getLanguageBtn().click();
-    language.getAllLanguages().should('have.length', 1).should('contain.text', 'English');
-  });
-});
diff --git a/src/pybind/mgr/dashboard/frontend/cypress/integration/ui/language.po.ts b/src/pybind/mgr/dashboard/frontend/cypress/integration/ui/language.po.ts
deleted file mode 100644 (file)
index 80e21ba..0000000
+++ /dev/null
@@ -1,15 +0,0 @@
-import { PageHelper } from '../page-helper.po';
-
-export class LanguagePageHelper extends PageHelper {
-  pages = {
-    index: { url: '#/dashboard', id: 'cd-dashboard' }
-  };
-
-  getLanguageBtn() {
-    return cy.get('cd-language-selector a').first();
-  }
-
-  getAllLanguages() {
-    return cy.get('cd-language-selector button');
-  }
-}
diff --git a/src/pybind/mgr/dashboard/frontend/cypress/integration/ui/login.e2e-spec.ts b/src/pybind/mgr/dashboard/frontend/cypress/integration/ui/login.e2e-spec.ts
deleted file mode 100644 (file)
index 2b337e6..0000000
+++ /dev/null
@@ -1,23 +0,0 @@
-import { LoginPageHelper } from './login.po';
-
-describe('Login page', () => {
-  const login = new LoginPageHelper();
-
-  it('should login and navigate to dashboard page', () => {
-    login.navigateTo();
-    login.doLogin();
-  });
-
-  it('should logout when clicking the button', () => {
-    login.navigateTo();
-    login.doLogin();
-
-    login.doLogout();
-  });
-
-  it('should have no accessibility violations', () => {
-    login.navigateTo();
-    cy.injectAxe();
-    cy.checkA11y();
-  });
-});
diff --git a/src/pybind/mgr/dashboard/frontend/cypress/integration/ui/login.po.ts b/src/pybind/mgr/dashboard/frontend/cypress/integration/ui/login.po.ts
deleted file mode 100644 (file)
index d4d2c69..0000000
+++ /dev/null
@@ -1,22 +0,0 @@
-import { PageHelper } from '../page-helper.po';
-
-export class LoginPageHelper extends PageHelper {
-  pages = {
-    index: { url: '#/login', id: 'cd-login' },
-    dashboard: { url: '#/dashboard', id: 'cd-dashboard' }
-  };
-
-  doLogin() {
-    cy.get('[name=username]').type('admin');
-    cy.get('#password').type('admin');
-    cy.get('[type=submit]').click();
-    cy.get('cd-dashboard').should('exist');
-  }
-
-  doLogout() {
-    cy.get('cd-identity a').click();
-    cy.contains('cd-identity span', 'Sign out').click();
-    cy.get('cd-login').should('exist');
-    cy.location('hash').should('eq', '#/login');
-  }
-}
diff --git a/src/pybind/mgr/dashboard/frontend/cypress/integration/ui/navigation.e2e-spec.ts b/src/pybind/mgr/dashboard/frontend/cypress/integration/ui/navigation.e2e-spec.ts
deleted file mode 100644 (file)
index fee2d2d..0000000
+++ /dev/null
@@ -1,24 +0,0 @@
-import { NavigationPageHelper } from './navigation.po';
-
-describe('Shared pages', () => {
-  const shared = new NavigationPageHelper();
-
-  beforeEach(() => {
-    cy.login();
-    Cypress.Cookies.preserveOnce('token');
-    shared.navigateTo();
-  });
-
-  it('should display the vertical menu by default', () => {
-    shared.getVerticalMenu().should('not.have.class', 'active');
-  });
-
-  it('should hide the vertical menu', () => {
-    shared.getMenuToggler().click();
-    shared.getVerticalMenu().should('have.class', 'active');
-  });
-
-  it('should navigate to the correct page', () => {
-    shared.checkNavigations(shared.navigations);
-  });
-});
diff --git a/src/pybind/mgr/dashboard/frontend/cypress/integration/ui/navigation.po.ts b/src/pybind/mgr/dashboard/frontend/cypress/integration/ui/navigation.po.ts
deleted file mode 100644 (file)
index 3bad055..0000000
+++ /dev/null
@@ -1,78 +0,0 @@
-import { PageHelper } from '../page-helper.po';
-
-export class NavigationPageHelper extends PageHelper {
-  pages = {
-    index: { url: '#/dashboard', id: 'cd-dashboard' }
-  };
-
-  navigations = [
-    { menu: 'NFS', component: 'cd-error' },
-    {
-      menu: 'Object Gateway',
-      submenus: [
-        { menu: 'Daemons', component: 'cd-rgw-daemon-list' },
-        { menu: 'Users', component: 'cd-rgw-user-list' },
-        { menu: 'Buckets', component: 'cd-rgw-bucket-list' }
-      ]
-    },
-    { menu: 'Dashboard', component: 'cd-dashboard' },
-    {
-      menu: 'Cluster',
-      submenus: [
-        { menu: 'Hosts', component: 'cd-hosts' },
-        { menu: 'Physical Disks', component: 'cd-error' },
-        { menu: 'Monitors', component: 'cd-monitor' },
-        { menu: 'Services', component: 'cd-error' },
-        { menu: 'OSDs', component: 'cd-osd-list' },
-        { menu: 'Configuration', component: 'cd-configuration' },
-        { menu: 'CRUSH map', component: 'cd-crushmap' },
-        { menu: 'Manager Modules', component: 'cd-mgr-module-list' },
-        { menu: 'Ceph Users', component: 'cd-crud-table' },
-        { menu: 'Logs', component: 'cd-logs' },
-        { menu: 'Alerts', component: 'cd-prometheus-tabs' }
-      ]
-    },
-    { menu: 'Pools', component: 'cd-pool-list' },
-    {
-      menu: 'Block',
-      submenus: [
-        { menu: 'Images', component: 'cd-error' },
-        { menu: 'Mirroring', component: 'cd-mirroring' },
-        { menu: 'iSCSI', component: 'cd-iscsi' }
-      ]
-    },
-    { menu: 'File Systems', component: 'cd-cephfs-list' }
-  ];
-
-  getVerticalMenu() {
-    return cy.get('nav[id=sidebar]');
-  }
-
-  getMenuToggler() {
-    return cy.get('[aria-label="toggle sidebar visibility"]');
-  }
-
-  checkNavigations(navs: any) {
-    // The nfs-ganesha, RGW, and block/rbd status requests are mocked to ensure that this method runs in time
-    cy.intercept('/ui-api/nfs-ganesha/status', { fixture: 'nfs-ganesha-status.json' });
-    cy.intercept('/ui-api/rgw/status', { fixture: 'rgw-status.json' });
-    cy.intercept('/ui-api/block/rbd/status', { fixture: 'block-rbd-status.json' });
-
-    navs.forEach((nav: any) => {
-      cy.contains('.simplebar-content li.nav-item a', nav.menu).click();
-      if (nav.submenus) {
-        this.checkNavSubMenu(nav.menu, nav.submenus);
-      } else {
-        cy.get(nav.component).should('exist');
-      }
-    });
-  }
-
-  checkNavSubMenu(menu: any, submenu: any) {
-    submenu.forEach((nav: any) => {
-      cy.contains('.simplebar-content li.nav-item', menu).within(() => {
-        cy.contains(`ul.list-unstyled li a`, nav.menu).click();
-      });
-    });
-  }
-}
diff --git a/src/pybind/mgr/dashboard/frontend/cypress/integration/ui/notification.e2e-spec.ts b/src/pybind/mgr/dashboard/frontend/cypress/integration/ui/notification.e2e-spec.ts
deleted file mode 100644 (file)
index 2ee73a7..0000000
+++ /dev/null
@@ -1,59 +0,0 @@
-import { PoolPageHelper } from '../pools/pools.po';
-import { NotificationSidebarPageHelper } from './notification.po';
-
-describe('Notification page', () => {
-  const notification = new NotificationSidebarPageHelper();
-  const pools = new PoolPageHelper();
-  const poolName = 'e2e_notification_pool';
-
-  before(() => {
-    cy.login();
-    Cypress.Cookies.preserveOnce('token');
-    pools.navigateTo('create');
-    pools.create(poolName, 8);
-    pools.edit_pool_pg(poolName, 4, false);
-  });
-
-  after(() => {
-    cy.login();
-    Cypress.Cookies.preserveOnce('token');
-    pools.navigateTo();
-    pools.delete(poolName);
-  });
-
-  beforeEach(() => {
-    cy.login();
-    Cypress.Cookies.preserveOnce('token');
-    pools.navigateTo();
-  });
-
-  it('should open notification sidebar', () => {
-    notification.getSidebar().should('not.be.visible');
-    notification.open();
-    notification.getSidebar().should('be.visible');
-  });
-
-  it('should display a running task', () => {
-    notification.getToast().should('not.exist');
-
-    // Check that running task is shown.
-    notification.open();
-    notification.getTasks().contains(poolName).should('exist');
-
-    // Delete pool after task is complete (otherwise we get an error).
-    notification.getTasks().contains(poolName, { timeout: 300000 }).should('not.exist');
-  });
-
-  it('should have notifications', () => {
-    notification.open();
-    notification.getNotifications().should('have.length.gt', 0);
-  });
-
-  it('should clear notifications', () => {
-    notification.getToast().should('not.exist');
-    notification.open();
-    notification.getNotifications().should('have.length.gt', 0);
-    notification.getClearNotficationsBtn().should('be.visible');
-    notification.clearNotifications();
-  });
-});
diff --git a/src/pybind/mgr/dashboard/frontend/cypress/integration/ui/notification.po.ts b/src/pybind/mgr/dashboard/frontend/cypress/integration/ui/notification.po.ts
deleted file mode 100644 (file)
index 12c424e..0000000
+++ /dev/null
@@ -1,45 +0,0 @@
-import { PageHelper } from '../page-helper.po';
-
-export class NotificationSidebarPageHelper extends PageHelper {
-  getNotificatinoIcon() {
-    return cy.get('cd-notifications a');
-  }
-
-  getSidebar() {
-    return cy.get('cd-notifications-sidebar');
-  }
-
-  getTasks() {
-    return this.getSidebar().find('.card.tc_task');
-  }
-
-  getNotifications() {
-    return this.getSidebar().find('.card.tc_notification');
-  }
-
-  getClearNotficationsBtn() {
-    return this.getSidebar().find('button.btn-block');
-  }
-
-  getCloseBtn() {
-    return this.getSidebar().find('button.close');
-  }
-
-  open() {
-    this.getNotificatinoIcon().click();
-    this.getSidebar().should('be.visible');
-  }
-
-  clearNotifications() {
-    // It can happen that although notifications are cleared, by the time we check the notifications
-    // amount, another notification can appear, so we check it more than once (if needed).
-    this.getClearNotficationsBtn().click();
-    this.getNotifications()
-      .should('have.length.gte', 0)
-      .then(($elems) => {
-        if ($elems.length > 0) {
-          this.clearNotifications();
-        }
-      });
-  }
-}
diff --git a/src/pybind/mgr/dashboard/frontend/cypress/integration/ui/role-mgmt.e2e-spec.ts b/src/pybind/mgr/dashboard/frontend/cypress/integration/ui/role-mgmt.e2e-spec.ts
deleted file mode 100644 (file)
index c3f325d..0000000
+++ /dev/null
@@ -1,37 +0,0 @@
-import { RoleMgmtPageHelper } from './role-mgmt.po';
-
-describe('Role Management page', () => {
-  const roleMgmt = new RoleMgmtPageHelper();
-  const role_name = 'e2e_role_mgmt_role';
-
-  beforeEach(() => {
-    cy.login();
-    Cypress.Cookies.preserveOnce('token');
-    roleMgmt.navigateTo();
-  });
-
-  describe('breadcrumb tests', () => {
-    it('should check breadcrumb on roles tab on user management page', () => {
-      roleMgmt.expectBreadcrumbText('Roles');
-    });
-
-    it('should check breadcrumb on role creation page', () => {
-      roleMgmt.navigateTo('create');
-      roleMgmt.expectBreadcrumbText('Create');
-    });
-  });
-
-  describe('role create, edit & delete test', () => {
-    it('should create a role', () => {
-      roleMgmt.create(role_name, 'An interesting description');
-    });
-
-    it('should edit a role', () => {
-      roleMgmt.edit(role_name, 'A far more interesting description');
-    });
-
-    it('should delete a role', () => {
-      roleMgmt.delete(role_name);
-    });
-  });
-});
diff --git a/src/pybind/mgr/dashboard/frontend/cypress/integration/ui/role-mgmt.po.ts b/src/pybind/mgr/dashboard/frontend/cypress/integration/ui/role-mgmt.po.ts
deleted file mode 100644 (file)
index 1cc3630..0000000
+++ /dev/null
@@ -1,40 +0,0 @@
-import { PageHelper } from '../page-helper.po';
-
-export class RoleMgmtPageHelper extends PageHelper {
-  pages = {
-    index: { url: '#/user-management/roles', id: 'cd-role-list' },
-    create: { url: '#/user-management/roles/create', id: 'cd-role-form' }
-  };
-
-  create(name: string, description: string) {
-    this.navigateTo('create');
-    // Waits for data to load
-    cy.contains('grafana');
-
-    // fill in fields
-    cy.get('#name').type(name);
-    cy.get('#description').type(description);
-
-    // Click the create button and wait for role to be made
-    cy.get('[data-cy=submitBtn]').click();
-    cy.get('.breadcrumb-item.active').should('not.have.text', 'Create');
-
-    this.getFirstTableCell(name).should('exist');
-  }
-
-  edit(name: string, description: string) {
-    this.navigateEdit(name);
-    // Waits for data to load
-    cy.contains('grafana');
-
-    // fill in fields with new values
-    cy.get('#description').clear().type(description);
-
-    // Click the edit button and check new values are present in table
-    cy.get('[data-cy=submitBtn]').click();
-    cy.get('.breadcrumb-item.active').should('not.have.text', 'Edit');
-
-    this.getFirstTableCell(name).should('exist');
-    this.getFirstTableCell(description).should('exist');
-  }
-}
diff --git a/src/pybind/mgr/dashboard/frontend/cypress/integration/ui/user-mgmt.e2e-spec.ts b/src/pybind/mgr/dashboard/frontend/cypress/integration/ui/user-mgmt.e2e-spec.ts
deleted file mode 100644 (file)
index 92dc772..0000000
+++ /dev/null
@@ -1,37 +0,0 @@
-import { UserMgmtPageHelper } from './user-mgmt.po';
-
-describe('User Management page', () => {
-  const userMgmt = new UserMgmtPageHelper();
-  const user_name = 'e2e_user_mgmt_user';
-
-  beforeEach(() => {
-    cy.login();
-    Cypress.Cookies.preserveOnce('token');
-    userMgmt.navigateTo();
-  });
-
-  describe('breadcrumb tests', () => {
-    it('should check breadcrumb on users tab of user management page', () => {
-      userMgmt.expectBreadcrumbText('Users');
-    });
-
-    it('should check breadcrumb on user creation page', () => {
-      userMgmt.navigateTo('create');
-      userMgmt.expectBreadcrumbText('Create');
-    });
-  });
-
-  describe('user create, edit & delete test', () => {
-    it('should create a user', () => {
-      userMgmt.create(user_name, 'cool_password', 'Jeff', 'realemail@realwebsite.com');
-    });
-
-    it('should edit a user', () => {
-      userMgmt.edit(user_name, 'cool_password_number_2', 'Geoff', 'w@m');
-    });
-
-    it('should delete a user', () => {
-      userMgmt.delete(user_name);
-    });
-  });
-});
diff --git a/src/pybind/mgr/dashboard/frontend/cypress/integration/ui/user-mgmt.po.ts b/src/pybind/mgr/dashboard/frontend/cypress/integration/ui/user-mgmt.po.ts
deleted file mode 100644 (file)
index fb2b791..0000000
+++ /dev/null
@@ -1,39 +0,0 @@
-import { PageHelper } from '../page-helper.po';
-
-export class UserMgmtPageHelper extends PageHelper {
-  pages = {
-    index: { url: '#/user-management/users', id: 'cd-user-list' },
-    create: { url: '#/user-management/users/create', id: 'cd-user-form' }
-  };
-
-  create(username: string, password: string, name: string, email: string) {
-    this.navigateTo('create');
-
-    // fill in fields
-    cy.get('#username').type(username);
-    cy.get('#password').type(password);
-    cy.get('#confirmpassword').type(password);
-    cy.get('#name').type(name);
-    cy.get('#email').type(email);
-
-    // Click the create button and wait for user to be made
-    cy.get('[data-cy=submitBtn]').click();
-    this.getFirstTableCell(username).should('exist');
-  }
-
-  edit(username: string, password: string, name: string, email: string) {
-    this.navigateEdit(username);
-
-    // fill in fields with new values
-    cy.get('#password').clear().type(password);
-    cy.get('#confirmpassword').clear().type(password);
-    cy.get('#name').clear().type(name);
-    cy.get('#email').clear().type(email);
-
-    // Click the edit button and check new values are present in table
-    const editButton = cy.get('[data-cy=submitBtn]');
-    editButton.click();
-    this.getFirstTableCell(email).should('exist');
-    this.getFirstTableCell(name).should('exist');
-  }
-}
diff --git a/src/pybind/mgr/dashboard/frontend/cypress/integration/visualTests/dashboard.vrt-spec.ts b/src/pybind/mgr/dashboard/frontend/cypress/integration/visualTests/dashboard.vrt-spec.ts
deleted file mode 100644 (file)
index 450cff8..0000000
+++ /dev/null
@@ -1,24 +0,0 @@
-import { LoginPageHelper } from '../ui/login.po';
-
-describe('Dashboard Landing Page', () => {
-  const login = new LoginPageHelper();
-
-  beforeEach(() => {
-    cy.eyesOpen({
-      testName: 'Dashboard Component'
-    });
-  });
-
-  afterEach(() => {
-    cy.eyesClose();
-  });
-
-  it('should take screenshot of dashboard landing page', () => {
-    login.navigateTo();
-    login.doLogin();
-    cy.get('[aria-label="Status card"]').should('be.visible');
-    cy.get('[aria-label="Inventory card"]').should('be.visible');
-    cy.get('[aria-label="Cluster utilization card"]').should('be.visible');
-    cy.eyesCheckWindow({ tag: 'Dashboard landing page' });
-  });
-});
diff --git a/src/pybind/mgr/dashboard/frontend/cypress/integration/visualTests/login.vrt-spec.ts b/src/pybind/mgr/dashboard/frontend/cypress/integration/visualTests/login.vrt-spec.ts
deleted file mode 100644 (file)
index ea74f1d..0000000
+++ /dev/null
@@ -1,19 +0,0 @@
-describe('Login Page', () => {
-  beforeEach(() => {
-    cy.visit('#/login');
-    cy.eyesOpen({
-      appName: 'Ceph',
-      testName: 'Login Component Check'
-    });
-  });
-
-  afterEach(() => {
-    cy.eyesClose();
-  });
-
-  it('types login credentials and takes screenshot', () => {
-    cy.get('[name=username]').type('admin');
-    cy.get('#password').type('admin');
-    cy.eyesCheckWindow({ tag: 'Login Screen with credentials typed' });
-  });
-});
index 04eabb8abbd8808e4884699b066660aace736a4c..2ab1b50025b8497f368b78669be4bf25d295bc94 100644 (file)
@@ -30,43 +30,47 @@ const fillAuth = () => {
 };
 
 Cypress.Commands.add('login', (username, password) => {
-  requestAuth(username, password).then((resp) => {
-    auth = resp.body;
-    auth.permissions = JSON.stringify(new Permissions(auth.permissions));
-    auth.pwdExpirationDate = String(auth.pwdExpirationDate);
-    auth.pwdUpdateRequired = String(auth.pwdUpdateRequired);
-    auth.sso = String(auth.sso);
-    fillAuth();
+  cy.session([username, password], () => {
+    requestAuth(username, password).then((resp) => {
+      auth = resp.body;
+      auth.permissions = JSON.stringify(new Permissions(auth.permissions));
+      auth.pwdExpirationDate = String(auth.pwdExpirationDate);
+      auth.pwdUpdateRequired = String(auth.pwdUpdateRequired);
+      auth.sso = String(auth.sso);
+      fillAuth();
+    });
   });
 });
 
 Cypress.Commands.add('ceph2Login', (username, password) => {
   const url: string = Cypress.env('CEPH2_URL');
-  requestAuth(username, password, url).then((resp) => {
-    auth = resp.body;
-    auth.permissions = JSON.stringify(new Permissions(auth.permissions));
-    auth.pwdExpirationDate = String(auth.pwdExpirationDate);
-    auth.pwdUpdateRequired = String(auth.pwdUpdateRequired);
-    auth.sso = String(auth.sso);
-    const args = {
-      username: auth.username,
-      permissions: auth.permissions,
-      pwdExpirationDate: auth.pwdExpirationDate,
-      pwdUpdateRequired: auth.pwdUpdateRequired,
-      sso: auth.sso
-    };
-    // @ts-ignore
-    cy.origin(
-      url,
-      { args },
-      ({ uname, permissions, pwdExpirationDate, pwdUpdateRequired, sso }: any) => {
-        window.localStorage.setItem('dashboard_username', uname);
-        window.localStorage.setItem('dashboard_permissions', permissions);
-        window.localStorage.setItem('user_pwd_expiration_date', pwdExpirationDate);
-        window.localStorage.setItem('user_pwd_update_required', pwdUpdateRequired);
-        window.localStorage.setItem('sso', sso);
-      }
-    );
+  cy.session([username, password, url], () => {
+    requestAuth(username, password, url).then((resp) => {
+      auth = resp.body;
+      auth.permissions = JSON.stringify(new Permissions(auth.permissions));
+      auth.pwdExpirationDate = String(auth.pwdExpirationDate);
+      auth.pwdUpdateRequired = String(auth.pwdUpdateRequired);
+      auth.sso = String(auth.sso);
+      const args = {
+        username: auth.username,
+        permissions: auth.permissions,
+        pwdExpirationDate: auth.pwdExpirationDate,
+        pwdUpdateRequired: auth.pwdUpdateRequired,
+        sso: auth.sso
+      };
+      // @ts-ignore
+      cy.origin(
+        url,
+        { args },
+        ({ uname, permissions, pwdExpirationDate, pwdUpdateRequired, sso }: any) => {
+          window.localStorage.setItem('dashboard_username', uname);
+          window.localStorage.setItem('dashboard_permissions', permissions);
+          window.localStorage.setItem('user_pwd_expiration_date', pwdExpirationDate);
+          window.localStorage.setItem('user_pwd_update_required', pwdUpdateRequired);
+          window.localStorage.setItem('sso', sso);
+        }
+      );
+    });
   });
 });
 
diff --git a/src/pybind/mgr/dashboard/frontend/cypress/support/e2e.ts b/src/pybind/mgr/dashboard/frontend/cypress/support/e2e.ts
new file mode 100644 (file)
index 0000000..4db2c6a
--- /dev/null
@@ -0,0 +1,19 @@
+import '@applitools/eyes-cypress/commands';
+import 'cypress-axe';
+
+import './commands';
+
+afterEach(() => {
+  cy.visit('#/403');
+});
+
+Cypress.on('uncaught:exception', (err: Error) => {
+  if (
+    err.message.includes('ResizeObserver loop limit exceeded') ||
+    err.message.includes('api/prometheus/rules') ||
+    err.message.includes('NG0100: ExpressionChangedAfterItHasBeenCheckedError')
+  ) {
+    return false;
+  }
+  return true;
+});
diff --git a/src/pybind/mgr/dashboard/frontend/cypress/support/index.ts b/src/pybind/mgr/dashboard/frontend/cypress/support/index.ts
deleted file mode 100644 (file)
index 4db2c6a..0000000
+++ /dev/null
@@ -1,19 +0,0 @@
-import '@applitools/eyes-cypress/commands';
-import 'cypress-axe';
-
-import './commands';
-
-afterEach(() => {
-  cy.visit('#/403');
-});
-
-Cypress.on('uncaught:exception', (err: Error) => {
-  if (
-    err.message.includes('ResizeObserver loop limit exceeded') ||
-    err.message.includes('api/prometheus/rules') ||
-    err.message.includes('NG0100: ExpressionChangedAfterItHasBeenCheckedError')
-  ) {
-    return false;
-  }
-  return true;
-});
index 2668137b6c058323dcd115590f2ef6d3b113aa36..0b48ce40bd9ddd4c53ef92a4e881f5427c091ba5 100644 (file)
@@ -5,27 +5,27 @@
   "requires": true,
   "dependencies": {
     "@aduh95/viz.js": {
-      "version": "3.7.0",
-      "resolved": "https://registry.npmjs.org/@aduh95/viz.js/-/viz.js-3.7.0.tgz",
-      "integrity": "sha512-20Pk2Z98fbPLkECcrZSJszKos/OgtvJJR3NcbVfgCJ6EQjDNzW2P1BKqImOz3tJ952dvO2DWEhcLhQ1Wz1e9ng==",
+      "version": "3.4.0",
+      "resolved": "https://registry.npmjs.org/@aduh95/viz.js/-/viz.js-3.4.0.tgz",
+      "integrity": "sha512-KI2nVf9JdwWCXqK6RVf+9/096G7VWN4Z84mnynlyZKao2xQENW8WNEjLmvdlxS5X8PNWXFC1zqwm7tveOXw/4A==",
       "dev": true
     },
     "@ampproject/remapping": {
-      "version": "2.2.0",
-      "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.0.tgz",
-      "integrity": "sha512-qRmjj8nj9qmLTQXXmaR1cck3UXSRMPrbsLJAasZpF+t3riI71BXed5ebIOYwQntykeZuhjsdweEc9BxH5Jc26w==",
+      "version": "2.2.1",
+      "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.1.tgz",
+      "integrity": "sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg==",
       "requires": {
-        "@jridgewell/gen-mapping": "^0.1.0",
+        "@jridgewell/gen-mapping": "^0.3.0",
         "@jridgewell/trace-mapping": "^0.3.9"
       }
     },
     "@angular-devkit/architect": {
-      "version": "0.1303.9",
-      "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1303.9.tgz",
-      "integrity": "sha512-RMHqCGDxbLqT+250A0a8vagsoTdqGjAxjhrvTeq7PJmClI7uJ/uA1Fs18+t85toIqVKn2hovdY9sNf42nBDD2Q==",
+      "version": "0.1303.11",
+      "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1303.11.tgz",
+      "integrity": "sha512-JwrWomNqNGjAeKlqV2pimUFlCgFxQy+Vioz9+QAPIrUkvvjbkQ1dZKOe8Ul8eosb1N3Ln282U6qzOpHKfJ4TOg==",
       "dev": true,
       "requires": {
-        "@angular-devkit/core": "13.3.9",
+        "@angular-devkit/core": "13.3.11",
         "rxjs": "6.6.7"
       },
       "dependencies": {
         "webpack-subresource-integrity": "5.1.0"
       },
       "dependencies": {
-        "@angular-devkit/architect": {
-          "version": "0.1303.11",
-          "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1303.11.tgz",
-          "integrity": "sha512-JwrWomNqNGjAeKlqV2pimUFlCgFxQy+Vioz9+QAPIrUkvvjbkQ1dZKOe8Ul8eosb1N3Ln282U6qzOpHKfJ4TOg==",
-          "dev": true,
-          "requires": {
-            "@angular-devkit/core": "13.3.11",
-            "rxjs": "6.6.7"
-          }
-        },
-        "@angular-devkit/core": {
-          "version": "13.3.11",
-          "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-13.3.11.tgz",
-          "integrity": "sha512-rfqoLMRYhlz0wzKlHx7FfyIyQq8dKTsmbCoIVU1cEIH0gyTMVY7PbVzwRRcO6xp5waY+0hA+0Brriujpuhkm4w==",
+        "@ampproject/remapping": {
+          "version": "2.2.0",
+          "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.0.tgz",
+          "integrity": "sha512-qRmjj8nj9qmLTQXXmaR1cck3UXSRMPrbsLJAasZpF+t3riI71BXed5ebIOYwQntykeZuhjsdweEc9BxH5Jc26w==",
           "dev": true,
           "requires": {
-            "ajv": "8.9.0",
-            "ajv-formats": "2.1.1",
-            "fast-json-stable-stringify": "2.1.0",
-            "magic-string": "0.25.7",
-            "rxjs": "6.6.7",
-            "source-map": "0.7.3"
+            "@jridgewell/gen-mapping": "^0.1.0",
+            "@jridgewell/trace-mapping": "^0.3.9"
           }
         },
         "@babel/core": {
               "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
               "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==",
               "dev": true
-            },
-            "source-map": {
-              "version": "0.5.7",
-              "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
-              "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==",
-              "dev": true
             }
           }
         },
             "@babel/types": "^7.16.8",
             "jsesc": "^2.5.1",
             "source-map": "^0.5.0"
-          },
-          "dependencies": {
-            "source-map": {
-              "version": "0.5.7",
-              "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
-              "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==",
-              "dev": true
-            }
           }
         },
         "@babel/runtime": {
             "@babel/types": "^7.16.7"
           }
         },
+        "@jridgewell/gen-mapping": {
+          "version": "0.1.1",
+          "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.1.1.tgz",
+          "integrity": "sha512-sQXCasFk+U8lWYEe66WxRDOE9PjVz4vSM51fTu3Hw+ClTpUSQb718772vH3pyS5pShp6lvQM7SxgIDXXXmOX7w==",
+          "dev": true,
+          "requires": {
+            "@jridgewell/set-array": "^1.0.0",
+            "@jridgewell/sourcemap-codec": "^1.4.10"
+          }
+        },
         "core-js": {
           "version": "3.20.3",
           "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.20.3.tgz",
           "integrity": "sha512-vVl8j8ph6tRS3B8qir40H7yw7voy17xL0piAjlbBUsH7WIfzoedL/ZOr1OV9FyZQLWXsayOJyV4tnRyXR85/ag==",
           "dev": true
         },
+        "esbuild": {
+          "version": "0.14.22",
+          "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.14.22.tgz",
+          "integrity": "sha512-CjFCFGgYtbFOPrwZNJf7wsuzesx8kqwAffOlbYcFDLFuUtP8xloK1GH+Ai13Qr0RZQf9tE7LMTHJ2iVGJ1SKZA==",
+          "dev": true,
+          "optional": true,
+          "requires": {
+            "esbuild-android-arm64": "0.14.22",
+            "esbuild-darwin-64": "0.14.22",
+            "esbuild-darwin-arm64": "0.14.22",
+            "esbuild-freebsd-64": "0.14.22",
+            "esbuild-freebsd-arm64": "0.14.22",
+            "esbuild-linux-32": "0.14.22",
+            "esbuild-linux-64": "0.14.22",
+            "esbuild-linux-arm": "0.14.22",
+            "esbuild-linux-arm64": "0.14.22",
+            "esbuild-linux-mips64le": "0.14.22",
+            "esbuild-linux-ppc64le": "0.14.22",
+            "esbuild-linux-riscv64": "0.14.22",
+            "esbuild-linux-s390x": "0.14.22",
+            "esbuild-netbsd-64": "0.14.22",
+            "esbuild-openbsd-64": "0.14.22",
+            "esbuild-sunos-64": "0.14.22",
+            "esbuild-windows-32": "0.14.22",
+            "esbuild-windows-64": "0.14.22",
+            "esbuild-windows-arm64": "0.14.22"
+          }
+        },
         "lru-cache": {
           "version": "6.0.0",
           "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
             "lru-cache": "^6.0.0"
           }
         },
+        "source-map": {
+          "version": "0.5.7",
+          "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
+          "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==",
+          "dev": true
+        },
         "yallist": {
           "version": "4.0.0",
           "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
         "rxjs": "6.6.7"
       },
       "dependencies": {
-        "@angular-devkit/architect": {
-          "version": "0.1303.11",
-          "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1303.11.tgz",
-          "integrity": "sha512-JwrWomNqNGjAeKlqV2pimUFlCgFxQy+Vioz9+QAPIrUkvvjbkQ1dZKOe8Ul8eosb1N3Ln282U6qzOpHKfJ4TOg==",
-          "dev": true,
-          "requires": {
-            "@angular-devkit/core": "13.3.11",
-            "rxjs": "6.6.7"
-          }
-        },
-        "@angular-devkit/core": {
-          "version": "13.3.11",
-          "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-13.3.11.tgz",
-          "integrity": "sha512-rfqoLMRYhlz0wzKlHx7FfyIyQq8dKTsmbCoIVU1cEIH0gyTMVY7PbVzwRRcO6xp5waY+0hA+0Brriujpuhkm4w==",
-          "dev": true,
-          "requires": {
-            "ajv": "8.9.0",
-            "ajv-formats": "2.1.1",
-            "fast-json-stable-stringify": "2.1.0",
-            "magic-string": "0.25.7",
-            "rxjs": "6.6.7",
-            "source-map": "0.7.3"
-          }
-        },
         "rxjs": {
           "version": "6.6.7",
           "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz",
       }
     },
     "@angular-devkit/core": {
-      "version": "13.3.9",
-      "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-13.3.9.tgz",
-      "integrity": "sha512-XqCuIWyoqIsLABjV3GQL/+EiBCt3xVPPtNp3Mg4gjBsDLW7PEnvbb81yGkiZQmIsq4EIyQC/6fQa3VdjsCshGg==",
+      "version": "13.3.11",
+      "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-13.3.11.tgz",
+      "integrity": "sha512-rfqoLMRYhlz0wzKlHx7FfyIyQq8dKTsmbCoIVU1cEIH0gyTMVY7PbVzwRRcO6xp5waY+0hA+0Brriujpuhkm4w==",
       "dev": true,
       "requires": {
         "ajv": "8.9.0",
         "rxjs": "6.6.7"
       },
       "dependencies": {
+        "@angular-devkit/core": {
+          "version": "13.3.9",
+          "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-13.3.9.tgz",
+          "integrity": "sha512-XqCuIWyoqIsLABjV3GQL/+EiBCt3xVPPtNp3Mg4gjBsDLW7PEnvbb81yGkiZQmIsq4EIyQC/6fQa3VdjsCshGg==",
+          "dev": true,
+          "requires": {
+            "ajv": "8.9.0",
+            "ajv-formats": "2.1.1",
+            "fast-json-stable-stringify": "2.1.0",
+            "magic-string": "0.25.7",
+            "rxjs": "6.6.7",
+            "source-map": "0.7.3"
+          }
+        },
         "rxjs": {
           "version": "6.6.7",
           "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz",
           "integrity": "sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==",
           "dev": true
         },
+        "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==",
+          "dev": true
+        },
         "tmp": {
           "version": "0.2.1",
           "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz",
         "uuid": "8.3.2"
       },
       "dependencies": {
+        "@angular-devkit/architect": {
+          "version": "0.1303.9",
+          "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1303.9.tgz",
+          "integrity": "sha512-RMHqCGDxbLqT+250A0a8vagsoTdqGjAxjhrvTeq7PJmClI7uJ/uA1Fs18+t85toIqVKn2hovdY9sNf42nBDD2Q==",
+          "dev": true,
+          "requires": {
+            "@angular-devkit/core": "13.3.9",
+            "rxjs": "6.6.7"
+          }
+        },
+        "@angular-devkit/core": {
+          "version": "13.3.9",
+          "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-13.3.9.tgz",
+          "integrity": "sha512-XqCuIWyoqIsLABjV3GQL/+EiBCt3xVPPtNp3Mg4gjBsDLW7PEnvbb81yGkiZQmIsq4EIyQC/6fQa3VdjsCshGg==",
+          "dev": true,
+          "requires": {
+            "ajv": "8.9.0",
+            "ajv-formats": "2.1.1",
+            "fast-json-stable-stringify": "2.1.0",
+            "magic-string": "0.25.7",
+            "rxjs": "6.6.7",
+            "source-map": "0.7.3"
+          }
+        },
         "debug": {
           "version": "4.3.3",
           "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz",
             "ms": "2.1.2"
           }
         },
+        "ini": {
+          "version": "2.0.0",
+          "resolved": "https://registry.npmjs.org/ini/-/ini-2.0.0.tgz",
+          "integrity": "sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA==",
+          "dev": true
+        },
         "lru-cache": {
           "version": "6.0.0",
           "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
             "supports-preserve-symlinks-flag": "^1.0.0"
           }
         },
+        "rxjs": {
+          "version": "6.6.7",
+          "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz",
+          "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==",
+          "dev": true,
+          "requires": {
+            "tslib": "^1.9.0"
+          }
+        },
         "semver": {
           "version": "7.3.5",
           "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz",
             "lru-cache": "^6.0.0"
           }
         },
+        "tslib": {
+          "version": "1.14.1",
+          "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
+          "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==",
+          "dev": true
+        },
         "yallist": {
           "version": "4.0.0",
           "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
           }
         },
         "semver": {
-          "version": "7.3.8",
-          "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz",
-          "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==",
+          "version": "7.5.1",
+          "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.1.tgz",
+          "integrity": "sha512-Wvss5ivl8TMRZXXESstBA4uR5iXgEN/VC5/sOcuXdVLzcdkz4HWetIoRfG5gb5X+ij/G9rw9YoGn3QoQ8OCSpw==",
           "dev": true,
           "requires": {
             "lru-cache": "^6.0.0"
             "ms": "2.1.2"
           }
         },
+        "deepmerge": {
+          "version": "4.2.2",
+          "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz",
+          "integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==",
+          "dev": true
+        },
         "has-flag": {
           "version": "4.0.0",
           "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
       "dev": true
     },
     "@babel/code-frame": {
-      "version": "7.18.6",
-      "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz",
-      "integrity": "sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==",
+      "version": "7.21.4",
+      "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.21.4.tgz",
+      "integrity": "sha512-LYvhNKfwWSPpocw8GI7gpK2nq3HSDuEPC/uSYaALSJu9xjsalaaYFOq0Pwt5KmVqwEbZlDu81aLXwBOmD/Fv9g==",
       "requires": {
         "@babel/highlight": "^7.18.6"
       }
     },
     "@babel/compat-data": {
-      "version": "7.21.0",
-      "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.21.0.tgz",
-      "integrity": "sha512-gMuZsmsgxk/ENC3O/fRw5QY8A9/uxQbbCEypnLIiYYc/qVJtEV7ouxC3EllIIwNzMqAQee5tanFabWsUOutS7g=="
+      "version": "7.21.9",
+      "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.21.9.tgz",
+      "integrity": "sha512-FUGed8kfhyWvbYug/Un/VPJD41rDIgoVVcR+FuzhzOYyRz5uED+Gd3SLZml0Uw2l2aHFb7ZgdW5mGA3G2cCCnQ=="
     },
     "@babel/core": {
       "version": "7.17.2",
       }
     },
     "@babel/generator": {
-      "version": "7.21.1",
-      "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.21.1.tgz",
-      "integrity": "sha512-1lT45bAYlQhFn/BHivJs43AiW2rg3/UbLyShGfF3C0KmHvO5fSghWd5kBJy30kpRRucGzXStvnnCFniCR2kXAA==",
+      "version": "7.21.9",
+      "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.21.9.tgz",
+      "integrity": "sha512-F3fZga2uv09wFdEjEQIJxXALXfz0+JaOb7SabvVMmjHxeVTuGW8wgE8Vp1Hd7O+zMTYtcfEISGRzPkeiaPPsvg==",
       "requires": {
-        "@babel/types": "^7.21.0",
+        "@babel/types": "^7.21.5",
         "@jridgewell/gen-mapping": "^0.3.2",
         "@jridgewell/trace-mapping": "^0.3.17",
         "jsesc": "^2.5.1"
-      },
-      "dependencies": {
-        "@jridgewell/gen-mapping": {
-          "version": "0.3.2",
-          "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz",
-          "integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==",
-          "requires": {
-            "@jridgewell/set-array": "^1.0.1",
-            "@jridgewell/sourcemap-codec": "^1.4.10",
-            "@jridgewell/trace-mapping": "^0.3.9"
-          }
-        }
       }
     },
     "@babel/helper-annotate-as-pure": {
       }
     },
     "@babel/helper-builder-binary-assignment-operator-visitor": {
-      "version": "7.18.9",
-      "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.18.9.tgz",
-      "integrity": "sha512-yFQ0YCHoIqarl8BCRwBL8ulYUaZpz3bNsA7oFepAzee+8/+ImtADXNOmO5vJvsPff3qi+hvpkY/NYBTrBQgdNw==",
+      "version": "7.21.5",
+      "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.21.5.tgz",
+      "integrity": "sha512-uNrjKztPLkUk7bpCNC0jEKDJzzkvel/W+HguzbN8krA+LPfC1CEobJEvAvGka2A/M+ViOqXdcRL0GqPUJSjx9g==",
       "dev": true,
       "requires": {
-        "@babel/helper-explode-assignable-expression": "^7.18.6",
-        "@babel/types": "^7.18.9"
+        "@babel/types": "^7.21.5"
       }
     },
     "@babel/helper-compilation-targets": {
-      "version": "7.20.7",
-      "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.20.7.tgz",
-      "integrity": "sha512-4tGORmfQcrc+bvrjb5y3dG9Mx1IOZjsHqQVUz7XCNHO+iTmqxWnVg3KRygjGmpRLJGdQSKuvFinbIb0CnZwHAQ==",
+      "version": "7.21.5",
+      "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.21.5.tgz",
+      "integrity": "sha512-1RkbFGUKex4lvsB9yhIfWltJM5cZKUftB2eNajaDv3dCMEp49iBG0K14uH8NnX9IPux2+mK7JGEOB0jn48/J6w==",
       "requires": {
-        "@babel/compat-data": "^7.20.5",
-        "@babel/helper-validator-option": "^7.18.6",
+        "@babel/compat-data": "^7.21.5",
+        "@babel/helper-validator-option": "^7.21.0",
         "browserslist": "^4.21.3",
         "lru-cache": "^5.1.1",
         "semver": "^6.3.0"
       }
     },
     "@babel/helper-create-class-features-plugin": {
-      "version": "7.21.0",
-      "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.21.0.tgz",
-      "integrity": "sha512-Q8wNiMIdwsv5la5SPxNYzzkPnjgC0Sy0i7jLkVOCdllu/xcVNkr3TeZzbHBJrj+XXRqzX5uCyCoV9eu6xUG7KQ==",
+      "version": "7.21.8",
+      "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.21.8.tgz",
+      "integrity": "sha512-+THiN8MqiH2AczyuZrnrKL6cAxFRRQDKW9h1YkBvbgKmAm6mwiacig1qT73DHIWMGo40GRnsEfN3LA+E6NtmSw==",
       "dev": true,
       "requires": {
         "@babel/helper-annotate-as-pure": "^7.18.6",
-        "@babel/helper-environment-visitor": "^7.18.9",
+        "@babel/helper-environment-visitor": "^7.21.5",
         "@babel/helper-function-name": "^7.21.0",
-        "@babel/helper-member-expression-to-functions": "^7.21.0",
+        "@babel/helper-member-expression-to-functions": "^7.21.5",
         "@babel/helper-optimise-call-expression": "^7.18.6",
-        "@babel/helper-replace-supers": "^7.20.7",
+        "@babel/helper-replace-supers": "^7.21.5",
         "@babel/helper-skip-transparent-expression-wrappers": "^7.20.0",
-        "@babel/helper-split-export-declaration": "^7.18.6"
+        "@babel/helper-split-export-declaration": "^7.18.6",
+        "semver": "^6.3.0"
       },
       "dependencies": {
         "@babel/helper-annotate-as-pure": {
       }
     },
     "@babel/helper-create-regexp-features-plugin": {
-      "version": "7.21.0",
-      "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.21.0.tgz",
-      "integrity": "sha512-N+LaFW/auRSWdx7SHD/HiARwXQju1vXTW4fKr4u5SgBUTm51OKEjKgj+cs00ggW3kEvNqwErnlwuq7Y3xBe4eg==",
+      "version": "7.21.8",
+      "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.21.8.tgz",
+      "integrity": "sha512-zGuSdedkFtsFHGbexAvNuipg1hbtitDLo2XE8/uf6Y9sOQV1xsYX/2pNbtedp/X0eU1pIt+kGvaqHCowkRbS5g==",
       "dev": true,
       "requires": {
         "@babel/helper-annotate-as-pure": "^7.18.6",
-        "regexpu-core": "^5.3.1"
+        "regexpu-core": "^5.3.1",
+        "semver": "^6.3.0"
       },
       "dependencies": {
         "@babel/helper-annotate-as-pure": {
       }
     },
     "@babel/helper-environment-visitor": {
-      "version": "7.18.9",
-      "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.9.tgz",
-      "integrity": "sha512-3r/aACDJ3fhQ/EVgFy0hpj8oHyHpQc+LPtJoY9SzTThAsStm4Ptegq92vqKoE3vD706ZVFWITnMnxucw+S9Ipg=="
-    },
-    "@babel/helper-explode-assignable-expression": {
-      "version": "7.18.6",
-      "resolved": "https://registry.npmjs.org/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.18.6.tgz",
-      "integrity": "sha512-eyAYAsQmB80jNfg4baAtLeWAQHfHFiR483rzFK+BhETlGZaQC9bsfrugfXDCbRHLQbIA7U5NxhhOxN7p/dWIcg==",
-      "dev": true,
-      "requires": {
-        "@babel/types": "^7.18.6"
-      }
+      "version": "7.21.5",
+      "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.21.5.tgz",
+      "integrity": "sha512-IYl4gZ3ETsWocUWgsFZLM5i1BYx9SoemminVEXadgLBa9TdeorzgLKm8wWLA6J1N/kT3Kch8XIk1laNzYoHKvQ=="
     },
     "@babel/helper-function-name": {
       "version": "7.21.0",
       }
     },
     "@babel/helper-member-expression-to-functions": {
-      "version": "7.21.0",
-      "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.21.0.tgz",
-      "integrity": "sha512-Muu8cdZwNN6mRRNG6lAYErJ5X3bRevgYR2O8wN0yn7jJSnGDu6eG59RfT29JHxGUovyfrh6Pj0XzmR7drNVL3Q==",
+      "version": "7.21.5",
+      "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.21.5.tgz",
+      "integrity": "sha512-nIcGfgwpH2u4n9GG1HpStW5Ogx7x7ekiFHbjjFRKXbn5zUvqO9ZgotCO4x1aNbKn/x/xOUaXEhyNHCwtFCpxWg==",
       "dev": true,
       "requires": {
-        "@babel/types": "^7.21.0"
+        "@babel/types": "^7.21.5"
       }
     },
     "@babel/helper-module-imports": {
-      "version": "7.18.6",
-      "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.18.6.tgz",
-      "integrity": "sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA==",
+      "version": "7.21.4",
+      "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.21.4.tgz",
+      "integrity": "sha512-orajc5T2PsRYUN3ZryCEFeMDYwyw09c/pZeaQEZPH0MpKzSvn3e0uXsDBu3k03VI+9DBiRo+l22BfKTpKwa/Wg==",
       "requires": {
-        "@babel/types": "^7.18.6"
+        "@babel/types": "^7.21.4"
       }
     },
     "@babel/helper-module-transforms": {
-      "version": "7.21.2",
-      "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.21.2.tgz",
-      "integrity": "sha512-79yj2AR4U/Oqq/WOV7Lx6hUjau1Zfo4cI+JLAVYeMV5XIlbOhmjEk5ulbTc9fMpmlojzZHkUUxAiK+UKn+hNQQ==",
+      "version": "7.21.5",
+      "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.21.5.tgz",
+      "integrity": "sha512-bI2Z9zBGY2q5yMHoBvJ2a9iX3ZOAzJPm7Q8Yz6YeoUjU/Cvhmi2G4QyTNyPBqqXSgTjUxRg3L0xV45HvkNWWBw==",
       "requires": {
-        "@babel/helper-environment-visitor": "^7.18.9",
-        "@babel/helper-module-imports": "^7.18.6",
-        "@babel/helper-simple-access": "^7.20.2",
+        "@babel/helper-environment-visitor": "^7.21.5",
+        "@babel/helper-module-imports": "^7.21.4",
+        "@babel/helper-simple-access": "^7.21.5",
         "@babel/helper-split-export-declaration": "^7.18.6",
         "@babel/helper-validator-identifier": "^7.19.1",
         "@babel/template": "^7.20.7",
-        "@babel/traverse": "^7.21.2",
-        "@babel/types": "^7.21.2"
+        "@babel/traverse": "^7.21.5",
+        "@babel/types": "^7.21.5"
       }
     },
     "@babel/helper-optimise-call-expression": {
       }
     },
     "@babel/helper-plugin-utils": {
-      "version": "7.20.2",
-      "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.20.2.tgz",
-      "integrity": "sha512-8RvlJG2mj4huQ4pZ+rU9lqKi9ZKiRmuvGuM2HlWmkmgOhbs6zEAw6IEiJ5cQqGbDzGZOhwuOQNtZMi/ENLjZoQ==",
+      "version": "7.21.5",
+      "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.21.5.tgz",
+      "integrity": "sha512-0WDaIlXKOX/3KfBK/dwP1oQGiPh6rjMkT7HIRv7i5RR2VUMwrx5ZL0dwBkKx7+SW1zwNdgjHd34IMk5ZjTeHVg==",
       "dev": true
     },
     "@babel/helper-remap-async-to-generator": {
       }
     },
     "@babel/helper-replace-supers": {
-      "version": "7.20.7",
-      "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.20.7.tgz",
-      "integrity": "sha512-vujDMtB6LVfNW13jhlCrp48QNslK6JXi7lQG736HVbHz/mbf4Dc7tIRh1Xf5C0rF7BP8iiSxGMCmY6Ci1ven3A==",
+      "version": "7.21.5",
+      "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.21.5.tgz",
+      "integrity": "sha512-/y7vBgsr9Idu4M6MprbOVUfH3vs7tsIfnVWv/Ml2xgwvyH6LTngdfbf5AdsKwkJy4zgy1X/kuNrEKvhhK28Yrg==",
       "dev": true,
       "requires": {
-        "@babel/helper-environment-visitor": "^7.18.9",
-        "@babel/helper-member-expression-to-functions": "^7.20.7",
+        "@babel/helper-environment-visitor": "^7.21.5",
+        "@babel/helper-member-expression-to-functions": "^7.21.5",
         "@babel/helper-optimise-call-expression": "^7.18.6",
         "@babel/template": "^7.20.7",
-        "@babel/traverse": "^7.20.7",
-        "@babel/types": "^7.20.7"
+        "@babel/traverse": "^7.21.5",
+        "@babel/types": "^7.21.5"
       }
     },
     "@babel/helper-simple-access": {
-      "version": "7.20.2",
-      "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.20.2.tgz",
-      "integrity": "sha512-+0woI/WPq59IrqDYbVGfshjT5Dmk/nnbdpcF8SnMhhXObpTq2KNBdLFRFrkVdbDOyUmHBCxzm5FHV1rACIkIbA==",
+      "version": "7.21.5",
+      "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.21.5.tgz",
+      "integrity": "sha512-ENPDAMC1wAjR0uaCUwliBdiSl1KBJAVnMTzXqi64c2MG8MPR6ii4qf7bSXDqSFbr4W6W028/rf5ivoHop5/mkg==",
       "requires": {
-        "@babel/types": "^7.20.2"
+        "@babel/types": "^7.21.5"
       }
     },
     "@babel/helper-skip-transparent-expression-wrappers": {
       }
     },
     "@babel/helper-string-parser": {
-      "version": "7.19.4",
-      "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.19.4.tgz",
-      "integrity": "sha512-nHtDoQcuqFmwYNYPz3Rah5ph2p8PFeFCsZk9A/48dPc/rGocJ5J3hAAZ7pb76VWX3fZKu+uEr/FhH5jLx7umrw=="
+      "version": "7.21.5",
+      "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.21.5.tgz",
+      "integrity": "sha512-5pTUx3hAJaZIdW99sJ6ZUUgWq/Y+Hja7TowEnLNMm1VivRgZQL3vpBY3qUACVsvw+yQU6+YgfBVmcbLaZtrA1w=="
     },
     "@babel/helper-validator-identifier": {
       "version": "7.19.1",
       }
     },
     "@babel/helpers": {
-      "version": "7.21.0",
-      "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.21.0.tgz",
-      "integrity": "sha512-XXve0CBtOW0pd7MRzzmoyuSj0e3SEzj8pgyFxnTT1NJZL38BD1MK7yYrm8yefRPIDvNNe14xR4FdbHwpInD4rA==",
+      "version": "7.21.5",
+      "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.21.5.tgz",
+      "integrity": "sha512-BSY+JSlHxOmGsPTydUkPf1MdMQ3M81x5xGCOVgWM3G8XH77sJ292Y2oqcp0CbbgxhqBuI46iUz1tT7hqP7EfgA==",
       "requires": {
         "@babel/template": "^7.20.7",
-        "@babel/traverse": "^7.21.0",
-        "@babel/types": "^7.21.0"
+        "@babel/traverse": "^7.21.5",
+        "@babel/types": "^7.21.5"
       }
     },
     "@babel/highlight": {
       }
     },
     "@babel/parser": {
-      "version": "7.21.2",
-      "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.21.2.tgz",
-      "integrity": "sha512-URpaIJQwEkEC2T9Kn+Ai6Xe/02iNaVCuT/PtoRz3GPVJVDpPd7mLo+VddTbhCRU9TXqW5mSrQfXZyi8kDKOVpQ=="
+      "version": "7.21.9",
+      "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.21.9.tgz",
+      "integrity": "sha512-q5PNg/Bi1OpGgx5jYlvWZwAorZepEudDMCLtj967aeS7WMont7dUZI46M2XwcIQqvUlMxWfdLFu4S/qSxeUu5g=="
     },
     "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": {
       "version": "7.18.6",
       }
     },
     "@babel/plugin-syntax-jsx": {
-      "version": "7.18.6",
-      "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.18.6.tgz",
-      "integrity": "sha512-6mmljtAedFGTWu2p/8WIORGwy+61PLgOMPOdazc7YoJ9ZCWUyFy3A6CpPkRKLKD1ToAesxX8KGEViAiLo9N+7Q==",
+      "version": "7.21.4",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.21.4.tgz",
+      "integrity": "sha512-5hewiLct5OKyh6PLKEYaFclcqtIgCb6bmELouxjF6up5q3Sov7rOayW4RwhbaBL0dit8rA80GNfY+UuDp2mBbQ==",
       "dev": true,
       "requires": {
-        "@babel/helper-plugin-utils": "^7.18.6"
+        "@babel/helper-plugin-utils": "^7.20.2"
       }
     },
     "@babel/plugin-syntax-logical-assignment-operators": {
       }
     },
     "@babel/plugin-syntax-typescript": {
-      "version": "7.20.0",
-      "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.20.0.tgz",
-      "integrity": "sha512-rd9TkG+u1CExzS4SM1BlMEhMXwFLKVjOAFFCDx9PbX5ycJWDoWMcwdJH9RhkPu1dOgn5TrxLot/Gx6lWFuAUNQ==",
+      "version": "7.21.4",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.21.4.tgz",
+      "integrity": "sha512-xz0D39NvhQn4t4RNsHmDnnsaQizIlUkdtYvLs8La1BlfjQ6JEwxkJGeqJMW2tAXx+q6H+WFuUTXNdYVpEya0YA==",
       "dev": true,
       "requires": {
-        "@babel/helper-plugin-utils": "^7.19.0"
+        "@babel/helper-plugin-utils": "^7.20.2"
       }
     },
     "@babel/plugin-transform-arrow-functions": {
-      "version": "7.20.7",
-      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.20.7.tgz",
-      "integrity": "sha512-3poA5E7dzDomxj9WXWwuD6A5F3kc7VXwIJO+E+J8qtDtS+pXPAhrgEyh+9GBwBgPq1Z+bB+/JD60lp5jsN7JPQ==",
+      "version": "7.21.5",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.21.5.tgz",
+      "integrity": "sha512-wb1mhwGOCaXHDTcsRYMKF9e5bbMgqwxtqa2Y1ifH96dXJPwbuLX9qHy3clhrxVqgMz7nyNXs8VkxdH8UBcjKqA==",
       "dev": true,
       "requires": {
-        "@babel/helper-plugin-utils": "^7.20.2"
+        "@babel/helper-plugin-utils": "^7.21.5"
       }
     },
     "@babel/plugin-transform-async-to-generator": {
       }
     },
     "@babel/plugin-transform-computed-properties": {
-      "version": "7.20.7",
-      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.20.7.tgz",
-      "integrity": "sha512-Lz7MvBK6DTjElHAmfu6bfANzKcxpyNPeYBGEafyA6E5HtRpjpZwU+u7Qrgz/2OR0z+5TvKYbPdphfSaAcZBrYQ==",
+      "version": "7.21.5",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.21.5.tgz",
+      "integrity": "sha512-TR653Ki3pAwxBxUe8srfF3e4Pe3FTA46uaNHYyQwIoM4oWKSoOZiDNyHJ0oIoDIUPSRQbQG7jzgVBX3FPVne1Q==",
       "dev": true,
       "requires": {
-        "@babel/helper-plugin-utils": "^7.20.2",
+        "@babel/helper-plugin-utils": "^7.21.5",
         "@babel/template": "^7.20.7"
       }
     },
     "@babel/plugin-transform-destructuring": {
-      "version": "7.20.7",
-      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.20.7.tgz",
-      "integrity": "sha512-Xwg403sRrZb81IVB79ZPqNQME23yhugYVqgTxAhT99h485F4f+GMELFhhOsscDUB7HCswepKeCKLn/GZvUKoBA==",
+      "version": "7.21.3",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.21.3.tgz",
+      "integrity": "sha512-bp6hwMFzuiE4HqYEyoGJ/V2LeIWn+hLVKc4pnj++E5XQptwhtcGmSayM029d/j2X1bPKGTlsyPwAubuU22KhMA==",
       "dev": true,
       "requires": {
         "@babel/helper-plugin-utils": "^7.20.2"
       }
     },
     "@babel/plugin-transform-for-of": {
-      "version": "7.21.0",
-      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.21.0.tgz",
-      "integrity": "sha512-LlUYlydgDkKpIY7mcBWvyPPmMcOphEyYA27Ef4xpbh1IiDNLr0kZsos2nf92vz3IccvJI25QUwp86Eo5s6HmBQ==",
+      "version": "7.21.5",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.21.5.tgz",
+      "integrity": "sha512-nYWpjKW/7j/I/mZkGVgHJXh4bA1sfdFnJoOXwJuj4m3Q2EraO/8ZyrkCau9P5tbHQk01RMSt6KYLCsW7730SXQ==",
       "dev": true,
       "requires": {
-        "@babel/helper-plugin-utils": "^7.20.2"
+        "@babel/helper-plugin-utils": "^7.21.5"
       }
     },
     "@babel/plugin-transform-function-name": {
       }
     },
     "@babel/plugin-transform-modules-commonjs": {
-      "version": "7.21.2",
-      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.21.2.tgz",
-      "integrity": "sha512-Cln+Yy04Gxua7iPdj6nOV96smLGjpElir5YwzF0LBPKoPlLDNJePNlrGGaybAJkd0zKRnOVXOgizSqPYMNYkzA==",
+      "version": "7.21.5",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.21.5.tgz",
+      "integrity": "sha512-OVryBEgKUbtqMoB7eG2rs6UFexJi6Zj6FDXx+esBLPTCxCNxAY9o+8Di7IsUGJ+AVhp5ncK0fxWUBd0/1gPhrQ==",
       "dev": true,
       "requires": {
-        "@babel/helper-module-transforms": "^7.21.2",
-        "@babel/helper-plugin-utils": "^7.20.2",
-        "@babel/helper-simple-access": "^7.20.2"
+        "@babel/helper-module-transforms": "^7.21.5",
+        "@babel/helper-plugin-utils": "^7.21.5",
+        "@babel/helper-simple-access": "^7.21.5"
       }
     },
     "@babel/plugin-transform-modules-systemjs": {
       }
     },
     "@babel/plugin-transform-parameters": {
-      "version": "7.20.7",
-      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.20.7.tgz",
-      "integrity": "sha512-WiWBIkeHKVOSYPO0pWkxGPfKeWrCJyD3NJ53+Lrp/QMSZbsVPovrVl2aWZ19D/LTVnaDv5Ap7GJ/B2CTOZdrfA==",
+      "version": "7.21.3",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.21.3.tgz",
+      "integrity": "sha512-Wxc+TvppQG9xWFYatvCGPvZ6+SIUxQ2ZdiBP+PHYMIjnPXD+uThCshaz4NZOnODAtBjjcVQQ/3OKs9LW28purQ==",
       "dev": true,
       "requires": {
         "@babel/helper-plugin-utils": "^7.20.2"
       }
     },
     "@babel/plugin-transform-react-jsx": {
-      "version": "7.21.0",
-      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.21.0.tgz",
-      "integrity": "sha512-6OAWljMvQrZjR2DaNhVfRz6dkCAVV+ymcLUmaf8bccGOHn2v5rHJK3tTpij0BuhdYWP4LLaqj5lwcdlpAAPuvg==",
+      "version": "7.21.5",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.21.5.tgz",
+      "integrity": "sha512-ELdlq61FpoEkHO6gFRpfj0kUgSwQTGoaEU8eMRoS8Dv3v6e7BjEAj5WMtIBRdHUeAioMhKP5HyxNzNnP+heKbA==",
       "dev": true,
       "requires": {
         "@babel/helper-annotate-as-pure": "^7.18.6",
-        "@babel/helper-module-imports": "^7.18.6",
-        "@babel/helper-plugin-utils": "^7.20.2",
-        "@babel/plugin-syntax-jsx": "^7.18.6",
-        "@babel/types": "^7.21.0"
+        "@babel/helper-module-imports": "^7.21.4",
+        "@babel/helper-plugin-utils": "^7.21.5",
+        "@babel/plugin-syntax-jsx": "^7.21.4",
+        "@babel/types": "^7.21.5"
       },
       "dependencies": {
         "@babel/helper-annotate-as-pure": {
       }
     },
     "@babel/plugin-transform-regenerator": {
-      "version": "7.20.5",
-      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.20.5.tgz",
-      "integrity": "sha512-kW/oO7HPBtntbsahzQ0qSE3tFvkFwnbozz3NWFhLGqH75vLEg+sCGngLlhVkePlCs3Jv0dBBHDzCHxNiFAQKCQ==",
+      "version": "7.21.5",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.21.5.tgz",
+      "integrity": "sha512-ZoYBKDb6LyMi5yCsByQ5jmXsHAQDDYeexT1Szvlmui+lADvfSecr5Dxd/PkrTC3pAD182Fcju1VQkB4oCp9M+w==",
       "dev": true,
       "requires": {
-        "@babel/helper-plugin-utils": "^7.20.2",
+        "@babel/helper-plugin-utils": "^7.21.5",
         "regenerator-transform": "^0.15.1"
       }
     },
       }
     },
     "@babel/plugin-transform-unicode-escapes": {
-      "version": "7.18.10",
-      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.18.10.tgz",
-      "integrity": "sha512-kKAdAI+YzPgGY/ftStBFXTI1LZFju38rYThnfMykS+IXy8BVx+res7s2fxf1l8I35DV2T97ezo6+SGrXz6B3iQ==",
+      "version": "7.21.5",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.21.5.tgz",
+      "integrity": "sha512-LYm/gTOwZqsYohlvFUe/8Tujz75LqqVC2w+2qPHLR+WyWHGCZPN1KBpJCJn+4Bk4gOkQy/IXKIge6az5MqwlOg==",
       "dev": true,
       "requires": {
-        "@babel/helper-plugin-utils": "^7.18.9"
+        "@babel/helper-plugin-utils": "^7.21.5"
       }
     },
     "@babel/plugin-transform-unicode-regex": {
       "dev": true
     },
     "@babel/runtime": {
-      "version": "7.21.0",
-      "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.21.0.tgz",
-      "integrity": "sha512-xwII0//EObnq89Ji5AKYQaRYiW/nZ3llSv29d49IuxPhKbtJoLP+9QUUZ4nVragQVtaVGeZrpB+ZtG/Pdy/POw==",
+      "version": "7.21.5",
+      "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.21.5.tgz",
+      "integrity": "sha512-8jI69toZqqcsnqGGqwGS4Qb1VwLOEp4hz+CXPywcvjs60u3B4Pom/U/7rm4W8tMOYEB+E9wgD0mW1l3r8qlI9Q==",
       "requires": {
         "regenerator-runtime": "^0.13.11"
       }
     },
     "@babel/runtime-corejs3": {
-      "version": "7.21.0",
-      "resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.21.0.tgz",
-      "integrity": "sha512-TDD4UJzos3JJtM+tHX+w2Uc+KWj7GV+VKKFdMVd2Rx8sdA19hcc3P3AHFYd5LVOw+pYuSd5lICC3gm52B6Rwxw==",
+      "version": "7.21.5",
+      "resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.21.5.tgz",
+      "integrity": "sha512-FRqFlFKNazWYykft5zvzuEl1YyTDGsIRrjV9rvxvYkUC7W/ueBng1X68Xd6uRMzAaJ0xMKn08/wem5YS1lpX8w==",
       "requires": {
         "core-js-pure": "^3.25.1",
         "regenerator-runtime": "^0.13.11"
       }
     },
     "@babel/template": {
-      "version": "7.20.7",
-      "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.20.7.tgz",
-      "integrity": "sha512-8SegXApWe6VoNw0r9JHpSteLKTpTiLZ4rMlGIm9JQ18KiCtyQiAMEazujAHrUS5flrcqYZa75ukev3P6QmUwUw==",
+      "version": "7.21.9",
+      "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.21.9.tgz",
+      "integrity": "sha512-MK0X5k8NKOuWRamiEfc3KEJiHMTkGZNUjzMipqCGDDc6ijRl/B7RGSKVGncu4Ro/HdyzzY6cmoXuKI2Gffk7vQ==",
       "requires": {
-        "@babel/code-frame": "^7.18.6",
-        "@babel/parser": "^7.20.7",
-        "@babel/types": "^7.20.7"
+        "@babel/code-frame": "^7.21.4",
+        "@babel/parser": "^7.21.9",
+        "@babel/types": "^7.21.5"
       }
     },
     "@babel/traverse": {
-      "version": "7.21.2",
-      "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.21.2.tgz",
-      "integrity": "sha512-ts5FFU/dSUPS13tv8XiEObDu9K+iagEKME9kAbaP7r0Y9KtZJZ+NGndDvWoRAYNpeWafbpFeki3q9QoMD6gxyw==",
+      "version": "7.21.5",
+      "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.21.5.tgz",
+      "integrity": "sha512-AhQoI3YjWi6u/y/ntv7k48mcrCXmus0t79J9qPNlk/lAsFlCiJ047RmbfMOawySTHtywXhbXgpx/8nXMYd+oFw==",
       "requires": {
-        "@babel/code-frame": "^7.18.6",
-        "@babel/generator": "^7.21.1",
-        "@babel/helper-environment-visitor": "^7.18.9",
+        "@babel/code-frame": "^7.21.4",
+        "@babel/generator": "^7.21.5",
+        "@babel/helper-environment-visitor": "^7.21.5",
         "@babel/helper-function-name": "^7.21.0",
         "@babel/helper-hoist-variables": "^7.18.6",
         "@babel/helper-split-export-declaration": "^7.18.6",
-        "@babel/parser": "^7.21.2",
-        "@babel/types": "^7.21.2",
+        "@babel/parser": "^7.21.5",
+        "@babel/types": "^7.21.5",
         "debug": "^4.1.0",
         "globals": "^11.1.0"
       }
     },
     "@babel/types": {
-      "version": "7.21.2",
-      "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.21.2.tgz",
-      "integrity": "sha512-3wRZSs7jiFaB8AjxiiD+VqN5DTG2iRvJGQ+qYFrs/654lg6kGTQWIOFjlBo5RaXuAZjBmP3+OQH4dmhqiiyYxw==",
+      "version": "7.21.5",
+      "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.21.5.tgz",
+      "integrity": "sha512-m4AfNvVF2mVC/F7fDEdH2El3HzUg9It/XsCxZiOTTA3m3qYfcSVSbTfM6Q9xG+hYDniZssYhlXKKUMD5m8tF4Q==",
       "requires": {
-        "@babel/helper-string-parser": "^7.19.4",
+        "@babel/helper-string-parser": "^7.21.5",
         "@babel/helper-validator-identifier": "^7.19.1",
         "to-fast-properties": "^2.0.0"
       }
           }
         },
         "semver": {
-          "version": "7.3.8",
-          "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz",
-          "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==",
+          "version": "7.5.1",
+          "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.1.tgz",
+          "integrity": "sha512-Wvss5ivl8TMRZXXESstBA4uR5iXgEN/VC5/sOcuXdVLzcdkz4HWetIoRfG5gb5X+ij/G9rw9YoGn3QoQ8OCSpw==",
           "dev": true,
           "requires": {
             "lru-cache": "^6.0.0"
       }
     },
     "@compodoc/ngd-core": {
-      "version": "2.1.0",
-      "resolved": "https://registry.npmjs.org/@compodoc/ngd-core/-/ngd-core-2.1.0.tgz",
-      "integrity": "sha512-nyBH7J7SJJ2AV6OeZhJ02kRtVB7ALnZJKgShjoL9CNmOFEj8AkdhP9qTBIgjaDrbsW5pF4nx32KQL2fT7RFnqw==",
+      "version": "2.1.1",
+      "resolved": "https://registry.npmjs.org/@compodoc/ngd-core/-/ngd-core-2.1.1.tgz",
+      "integrity": "sha512-Z+wE6wWZYVnudRYg6qunDlyh3Orw39Ib66Gvrz5kX5u7So+iu3tr6sQJdqH6yGS3hAjig5avlfhWLlgsb6/x1Q==",
       "dev": true,
       "requires": {
-        "ansi-colors": "^4.1.1",
-        "fancy-log": "^1.3.3",
-        "typescript": "^4.0.3"
+        "ansi-colors": "^4.1.3",
+        "fancy-log": "^2.0.0",
+        "typescript": "^5.0.4"
       },
       "dependencies": {
-        "fancy-log": {
-          "version": "1.3.3",
-          "resolved": "https://registry.npmjs.org/fancy-log/-/fancy-log-1.3.3.tgz",
-          "integrity": "sha512-k9oEhlyc0FrVh25qYuSELjr8oxsCoc4/LEZfg2iJJrfEk/tZL9bCoJE47gqAvI2m/AUjluCS4+3I0eTx8n3AEw==",
-          "dev": true,
-          "requires": {
-            "ansi-gray": "^0.1.1",
-            "color-support": "^1.1.3",
-            "parse-node-version": "^1.0.0",
-            "time-stamp": "^1.0.0"
-          }
+        "ansi-colors": {
+          "version": "4.1.3",
+          "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz",
+          "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==",
+          "dev": true
+        },
+        "typescript": {
+          "version": "5.0.4",
+          "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.0.4.tgz",
+          "integrity": "sha512-cW9T5W9xY37cc+jfEnaUvX91foxtHkza3Nw3wkoF4sSlKn0MONdkdEndig/qPBWXNkmplh3NzayQzCiHM4/hqw==",
+          "dev": true
         }
       }
     },
     "@compodoc/ngd-transformer": {
-      "version": "2.1.0",
-      "resolved": "https://registry.npmjs.org/@compodoc/ngd-transformer/-/ngd-transformer-2.1.0.tgz",
-      "integrity": "sha512-Jo4VCMzIUtgIAdRmhHhOoRRE01gCjc5CyrUERRx0VgEzkkCm1Wmu/XHSsQP6tSpCYHBjERghqaDqH5DabkR2oQ==",
+      "version": "2.1.3",
+      "resolved": "https://registry.npmjs.org/@compodoc/ngd-transformer/-/ngd-transformer-2.1.3.tgz",
+      "integrity": "sha512-oWxJza7CpWR8/FeWYfE6j+jgncnGBsTWnZLt5rD2GUpsGSQTuGrsFPnmbbaVLgRS5QIVWBJYke7QFBr/7qVMWg==",
       "dev": true,
       "requires": {
-        "@aduh95/viz.js": "^3.1.0",
-        "@compodoc/ngd-core": "~2.1.0",
-        "dot": "^1.1.3",
-        "fs-extra": "^9.0.1"
+        "@aduh95/viz.js": "3.4.0",
+        "@compodoc/ngd-core": "~2.1.1",
+        "dot": "^2.0.0-beta.1",
+        "fs-extra": "^11.1.1"
+      },
+      "dependencies": {
+        "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,
+          "requires": {
+            "graceful-fs": "^4.2.0",
+            "jsonfile": "^6.0.1",
+            "universalify": "^2.0.0"
+          }
+        }
       }
     },
     "@csstools/postcss-progressive-custom-properties": {
       }
     },
     "@csstools/selector-specificity": {
-      "version": "2.1.1",
-      "resolved": "https://registry.npmjs.org/@csstools/selector-specificity/-/selector-specificity-2.1.1.tgz",
-      "integrity": "sha512-jwx+WCqszn53YHOfvFMJJRd/B2GqkCBt+1MJSG6o5/s8+ytHMvDZXsJgUEWLk12UnLd7HYKac4BYU5i/Ron1Cw==",
+      "version": "2.2.0",
+      "resolved": "https://registry.npmjs.org/@csstools/selector-specificity/-/selector-specificity-2.2.0.tgz",
+      "integrity": "sha512-+OJ9konv95ClSTOJCmMZqpd5+YGsB2S+x6w3E1oaM8UuR5j8nTNHYSz8c9BEPGDOCMQYIEEGlVPj/VY64iTbGw==",
       "dev": true
     },
     "@cypress/browserify-preprocessor": {
       "dev": true
     },
     "@esbuild/android-arm": {
-      "version": "0.17.10",
-      "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.17.10.tgz",
-      "integrity": "sha512-7YEBfZ5lSem9Tqpsz+tjbdsEshlO9j/REJrfv4DXgKTt1+/MHqGwbtlyxQuaSlMeUZLxUKBaX8wdzlTfHkmnLw==",
+      "version": "0.17.19",
+      "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.17.19.tgz",
+      "integrity": "sha512-rIKddzqhmav7MSmoFCmDIb6e2W57geRsM94gV2l38fzhXMwq7hZoClug9USI2pFRGL06f4IOPHHpFNOkWieR8A==",
       "dev": true,
       "optional": true
     },
     "@esbuild/android-arm64": {
-      "version": "0.17.10",
-      "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.17.10.tgz",
-      "integrity": "sha512-ht1P9CmvrPF5yKDtyC+z43RczVs4rrHpRqrmIuoSvSdn44Fs1n6DGlpZKdK6rM83pFLbVaSUwle8IN+TPmkv7g==",
+      "version": "0.17.19",
+      "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.17.19.tgz",
+      "integrity": "sha512-KBMWvEZooR7+kzY0BtbTQn0OAYY7CsiydT63pVEaPtVYF0hXbUaOyZog37DKxK7NF3XacBJOpYT4adIJh+avxA==",
       "dev": true,
       "optional": true
     },
     "@esbuild/android-x64": {
-      "version": "0.17.10",
-      "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.17.10.tgz",
-      "integrity": "sha512-CYzrm+hTiY5QICji64aJ/xKdN70IK8XZ6iiyq0tZkd3tfnwwSWTYH1t3m6zyaaBxkuj40kxgMyj1km/NqdjQZA==",
+      "version": "0.17.19",
+      "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.17.19.tgz",
+      "integrity": "sha512-uUTTc4xGNDT7YSArp/zbtmbhO0uEEK9/ETW29Wk1thYUJBz3IVnvgEiEwEa9IeLyvnpKrWK64Utw2bgUmDveww==",
       "dev": true,
       "optional": true
     },
     "@esbuild/darwin-arm64": {
-      "version": "0.17.10",
-      "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.17.10.tgz",
-      "integrity": "sha512-3HaGIowI+nMZlopqyW6+jxYr01KvNaLB5znXfbyyjuo4lE0VZfvFGcguIJapQeQMS4cX/NEispwOekJt3gr5Dg==",
+      "version": "0.17.19",
+      "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.17.19.tgz",
+      "integrity": "sha512-80wEoCfF/hFKM6WE1FyBHc9SfUblloAWx6FJkFWTWiCoht9Mc0ARGEM47e67W9rI09YoUxJL68WHfDRYEAvOhg==",
       "dev": true,
       "optional": true
     },
     "@esbuild/darwin-x64": {
-      "version": "0.17.10",
-      "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.17.10.tgz",
-      "integrity": "sha512-J4MJzGchuCRG5n+B4EHpAMoJmBeAE1L3wGYDIN5oWNqX0tEr7VKOzw0ymSwpoeSpdCa030lagGUfnfhS7OvzrQ==",
+      "version": "0.17.19",
+      "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.17.19.tgz",
+      "integrity": "sha512-IJM4JJsLhRYr9xdtLytPLSH9k/oxR3boaUIYiHkAawtwNOXKE8KoU8tMvryogdcT8AU+Bflmh81Xn6Q0vTZbQw==",
       "dev": true,
       "optional": true
     },
     "@esbuild/freebsd-arm64": {
-      "version": "0.17.10",
-      "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.17.10.tgz",
-      "integrity": "sha512-ZkX40Z7qCbugeK4U5/gbzna/UQkM9d9LNV+Fro8r7HA7sRof5Rwxc46SsqeMvB5ZaR0b1/ITQ/8Y1NmV2F0fXQ==",
+      "version": "0.17.19",
+      "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.17.19.tgz",
+      "integrity": "sha512-pBwbc7DufluUeGdjSU5Si+P3SoMF5DQ/F/UmTSb8HXO80ZEAJmrykPyzo1IfNbAoaqw48YRpv8shwd1NoI0jcQ==",
       "dev": true,
       "optional": true
     },
     "@esbuild/freebsd-x64": {
-      "version": "0.17.10",
-      "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.17.10.tgz",
-      "integrity": "sha512-0m0YX1IWSLG9hWh7tZa3kdAugFbZFFx9XrvfpaCMMvrswSTvUZypp0NFKriUurHpBA3xsHVE9Qb/0u2Bbi/otg==",
+      "version": "0.17.19",
+      "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.17.19.tgz",
+      "integrity": "sha512-4lu+n8Wk0XlajEhbEffdy2xy53dpR06SlzvhGByyg36qJw6Kpfk7cp45DR/62aPH9mtJRmIyrXAS5UWBrJT6TQ==",
       "dev": true,
       "optional": true
     },
     "@esbuild/linux-arm": {
-      "version": "0.17.10",
-      "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.17.10.tgz",
-      "integrity": "sha512-whRdrrl0X+9D6o5f0sTZtDM9s86Xt4wk1bf7ltx6iQqrIIOH+sre1yjpcCdrVXntQPCNw/G+XqsD4HuxeS+2QA==",
+      "version": "0.17.19",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.17.19.tgz",
+      "integrity": "sha512-cdmT3KxjlOQ/gZ2cjfrQOtmhG4HJs6hhvm3mWSRDPtZ/lP5oe8FWceS10JaSJC13GBd4eH/haHnqf7hhGNLerA==",
       "dev": true,
       "optional": true
     },
     "@esbuild/linux-arm64": {
-      "version": "0.17.10",
-      "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.17.10.tgz",
-      "integrity": "sha512-g1EZJR1/c+MmCgVwpdZdKi4QAJ8DCLP5uTgLWSAVd9wlqk9GMscaNMEViG3aE1wS+cNMzXXgdWiW/VX4J+5nTA==",
+      "version": "0.17.19",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.17.19.tgz",
+      "integrity": "sha512-ct1Tg3WGwd3P+oZYqic+YZF4snNl2bsnMKRkb3ozHmnM0dGWuxcPTTntAF6bOP0Sp4x0PjSF+4uHQ1xvxfRKqg==",
       "dev": true,
       "optional": true
     },
     "@esbuild/linux-ia32": {
-      "version": "0.17.10",
-      "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.17.10.tgz",
-      "integrity": "sha512-1vKYCjfv/bEwxngHERp7huYfJ4jJzldfxyfaF7hc3216xiDA62xbXJfRlradiMhGZbdNLj2WA1YwYFzs9IWNPw==",
+      "version": "0.17.19",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.17.19.tgz",
+      "integrity": "sha512-w4IRhSy1VbsNxHRQpeGCHEmibqdTUx61Vc38APcsRbuVgK0OPEnQ0YD39Brymn96mOx48Y2laBQGqgZ0j9w6SQ==",
       "dev": true,
       "optional": true
     },
     "@esbuild/linux-loong64": {
-      "version": "0.17.10",
-      "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.17.10.tgz",
-      "integrity": "sha512-mvwAr75q3Fgc/qz3K6sya3gBmJIYZCgcJ0s7XshpoqIAIBszzfXsqhpRrRdVFAyV1G9VUjj7VopL2HnAS8aHFA==",
+      "version": "0.17.19",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.17.19.tgz",
+      "integrity": "sha512-2iAngUbBPMq439a+z//gE+9WBldoMp1s5GWsUSgqHLzLJ9WoZLZhpwWuym0u0u/4XmZ3gpHmzV84PonE+9IIdQ==",
       "dev": true,
       "optional": true
     },
     "@esbuild/linux-mips64el": {
-      "version": "0.17.10",
-      "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.17.10.tgz",
-      "integrity": "sha512-XilKPgM2u1zR1YuvCsFQWl9Fc35BqSqktooumOY2zj7CSn5czJn279j9TE1JEqSqz88izJo7yE4x3LSf7oxHzg==",
+      "version": "0.17.19",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.17.19.tgz",
+      "integrity": "sha512-LKJltc4LVdMKHsrFe4MGNPp0hqDFA1Wpt3jE1gEyM3nKUvOiO//9PheZZHfYRfYl6AwdTH4aTcXSqBerX0ml4A==",
       "dev": true,
       "optional": true
     },
     "@esbuild/linux-ppc64": {
-      "version": "0.17.10",
-      "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.17.10.tgz",
-      "integrity": "sha512-kM4Rmh9l670SwjlGkIe7pYWezk8uxKHX4Lnn5jBZYBNlWpKMBCVfpAgAJqp5doLobhzF3l64VZVrmGeZ8+uKmQ==",
+      "version": "0.17.19",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.17.19.tgz",
+      "integrity": "sha512-/c/DGybs95WXNS8y3Ti/ytqETiW7EU44MEKuCAcpPto3YjQbyK3IQVKfF6nbghD7EcLUGl0NbiL5Rt5DMhn5tg==",
       "dev": true,
       "optional": true
     },
     "@esbuild/linux-riscv64": {
-      "version": "0.17.10",
-      "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.17.10.tgz",
-      "integrity": "sha512-r1m9ZMNJBtOvYYGQVXKy+WvWd0BPvSxMsVq8Hp4GzdMBQvfZRvRr5TtX/1RdN6Va8JMVQGpxqde3O+e8+khNJQ==",
+      "version": "0.17.19",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.17.19.tgz",
+      "integrity": "sha512-FC3nUAWhvFoutlhAkgHf8f5HwFWUL6bYdvLc/TTuxKlvLi3+pPzdZiFKSWz/PF30TB1K19SuCxDTI5KcqASJqA==",
       "dev": true,
       "optional": true
     },
     "@esbuild/linux-s390x": {
-      "version": "0.17.10",
-      "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.17.10.tgz",
-      "integrity": "sha512-LsY7QvOLPw9WRJ+fU5pNB3qrSfA00u32ND5JVDrn/xG5hIQo3kvTxSlWFRP0NJ0+n6HmhPGG0Q4jtQsb6PFoyg==",
+      "version": "0.17.19",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.17.19.tgz",
+      "integrity": "sha512-IbFsFbxMWLuKEbH+7sTkKzL6NJmG2vRyy6K7JJo55w+8xDk7RElYn6xvXtDW8HCfoKBFK69f3pgBJSUSQPr+4Q==",
       "dev": true,
       "optional": true
     },
     "@esbuild/linux-x64": {
-      "version": "0.17.10",
-      "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.17.10.tgz",
-      "integrity": "sha512-zJUfJLebCYzBdIz/Z9vqwFjIA7iSlLCFvVi7glMgnu2MK7XYigwsonXshy9wP9S7szF+nmwrelNaP3WGanstEg==",
+      "version": "0.17.19",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.17.19.tgz",
+      "integrity": "sha512-68ngA9lg2H6zkZcyp22tsVt38mlhWde8l3eJLWkyLrp4HwMUr3c1s/M2t7+kHIhvMjglIBrFpncX1SzMckomGw==",
       "dev": true,
       "optional": true
     },
     "@esbuild/netbsd-x64": {
-      "version": "0.17.10",
-      "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.17.10.tgz",
-      "integrity": "sha512-lOMkailn4Ok9Vbp/q7uJfgicpDTbZFlXlnKT2DqC8uBijmm5oGtXAJy2ZZVo5hX7IOVXikV9LpCMj2U8cTguWA==",
+      "version": "0.17.19",
+      "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.17.19.tgz",
+      "integrity": "sha512-CwFq42rXCR8TYIjIfpXCbRX0rp1jo6cPIUPSaWwzbVI4aOfX96OXY8M6KNmtPcg7QjYeDmN+DD0Wp3LaBOLf4Q==",
       "dev": true,
       "optional": true
     },
     "@esbuild/openbsd-x64": {
-      "version": "0.17.10",
-      "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.17.10.tgz",
-      "integrity": "sha512-/VE0Kx6y7eekqZ+ZLU4AjMlB80ov9tEz4H067Y0STwnGOYL8CsNg4J+cCmBznk1tMpxMoUOf0AbWlb1d2Pkbig==",
+      "version": "0.17.19",
+      "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.17.19.tgz",
+      "integrity": "sha512-cnq5brJYrSZ2CF6c35eCmviIN3k3RczmHz8eYaVlNasVqsNY+JKohZU5MKmaOI+KkllCdzOKKdPs762VCPC20g==",
       "dev": true,
       "optional": true
     },
     "@esbuild/sunos-x64": {
-      "version": "0.17.10",
-      "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.17.10.tgz",
-      "integrity": "sha512-ERNO0838OUm8HfUjjsEs71cLjLMu/xt6bhOlxcJ0/1MG3hNqCmbWaS+w/8nFLa0DDjbwZQuGKVtCUJliLmbVgg==",
+      "version": "0.17.19",
+      "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.17.19.tgz",
+      "integrity": "sha512-vCRT7yP3zX+bKWFeP/zdS6SqdWB8OIpaRq/mbXQxTGHnIxspRtigpkUcDMlSCOejlHowLqII7K2JKevwyRP2rg==",
       "dev": true,
       "optional": true
     },
     "@esbuild/win32-arm64": {
-      "version": "0.17.10",
-      "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.17.10.tgz",
-      "integrity": "sha512-fXv+L+Bw2AeK+XJHwDAQ9m3NRlNemG6Z6ijLwJAAVdu4cyoFbBWbEtyZzDeL+rpG2lWI51cXeMt70HA8g2MqIg==",
+      "version": "0.17.19",
+      "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.17.19.tgz",
+      "integrity": "sha512-yYx+8jwowUstVdorcMdNlzklLYhPxjniHWFKgRqH7IFlUEa0Umu3KuYplf1HUZZ422e3NU9F4LGb+4O0Kdcaag==",
       "dev": true,
       "optional": true
     },
     "@esbuild/win32-ia32": {
-      "version": "0.17.10",
-      "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.17.10.tgz",
-      "integrity": "sha512-3s+HADrOdCdGOi5lnh5DMQEzgbsFsd4w57L/eLKKjMnN0CN4AIEP0DCP3F3N14xnxh3ruNc32A0Na9zYe1Z/AQ==",
+      "version": "0.17.19",
+      "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.17.19.tgz",
+      "integrity": "sha512-eggDKanJszUtCdlVs0RB+h35wNlb5v4TWEkq4vZcmVt5u/HiDZrTXe2bWFQUez3RgNHwx/x4sk5++4NSSicKkw==",
       "dev": true,
       "optional": true
     },
     "@esbuild/win32-x64": {
-      "version": "0.17.10",
-      "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.17.10.tgz",
-      "integrity": "sha512-oP+zFUjYNaMNmjTwlFtWep85hvwUu19cZklB3QsBOcZSs6y7hmH4LNCJ7075bsqzYaNvZFXJlAVaQ2ApITDXtw==",
+      "version": "0.17.19",
+      "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.17.19.tgz",
+      "integrity": "sha512-lAhycmKnVOuRYNtRtatQR1LPQf2oYCkRGkSFnseDAKPl8lu5SOsK/e1sXe5a0Pc5kHIHe6P2I/ilntNv2xf3cA==",
       "dev": true,
       "optional": true
     },
           "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
           "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
           "dev": true
+        },
+        "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==",
+          "dev": true
         }
       }
     },
           }
         },
         "@types/node": {
-          "version": "14.18.37",
-          "resolved": "https://registry.npmjs.org/@types/node/-/node-14.18.37.tgz",
-          "integrity": "sha512-7GgtHCs/QZrBrDzgIJnQtuSvhFSwhyYSI2uafSwZoNt1iOGhEN5fwNrQMjtONyHm9+/LoA4453jH0CMYcr06Pg==",
+          "version": "14.18.47",
+          "resolved": "https://registry.npmjs.org/@types/node/-/node-14.18.47.tgz",
+          "integrity": "sha512-OuJi8bIng4wYHHA3YpKauL58dZrPxro3d0tabPHyiNF8rKfGKuVfr83oFlPLmKri1cX+Z3cJP39GXmnqkP11Gw==",
           "dev": true
         },
         "ansi-styles": {
       }
     },
     "@jridgewell/gen-mapping": {
-      "version": "0.1.1",
-      "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.1.1.tgz",
-      "integrity": "sha512-sQXCasFk+U8lWYEe66WxRDOE9PjVz4vSM51fTu3Hw+ClTpUSQb718772vH3pyS5pShp6lvQM7SxgIDXXXmOX7w==",
+      "version": "0.3.3",
+      "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz",
+      "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==",
       "requires": {
-        "@jridgewell/set-array": "^1.0.0",
-        "@jridgewell/sourcemap-codec": "^1.4.10"
+        "@jridgewell/set-array": "^1.0.1",
+        "@jridgewell/sourcemap-codec": "^1.4.10",
+        "@jridgewell/trace-mapping": "^0.3.9"
       }
     },
     "@jridgewell/resolve-uri": {
       "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw=="
     },
     "@jridgewell/source-map": {
-      "version": "0.3.2",
-      "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.2.tgz",
-      "integrity": "sha512-m7O9o2uR8k2ObDysZYzdfhb08VuEml5oWGiosa1VdaPZ/A6QyPkAJuwN0Q1lhULOf6B7MtQmHENS743hWtCrgw==",
+      "version": "0.3.3",
+      "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.3.tgz",
+      "integrity": "sha512-b+fsZXeLYi9fEULmfBrhxn4IrPlINf8fiNarzTof004v3lFdntdwa9PF7vFJqm3mg7s+ScJMxXaE3Acp1irZcg==",
       "dev": true,
       "requires": {
         "@jridgewell/gen-mapping": "^0.3.0",
         "@jridgewell/trace-mapping": "^0.3.9"
-      },
-      "dependencies": {
-        "@jridgewell/gen-mapping": {
-          "version": "0.3.2",
-          "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz",
-          "integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==",
-          "dev": true,
-          "requires": {
-            "@jridgewell/set-array": "^1.0.1",
-            "@jridgewell/sourcemap-codec": "^1.4.10",
-            "@jridgewell/trace-mapping": "^0.3.9"
-          }
-        }
       }
     },
     "@jridgewell/sourcemap-codec": {
-      "version": "1.4.14",
-      "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz",
-      "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw=="
+      "version": "1.4.15",
+      "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz",
+      "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg=="
     },
     "@jridgewell/trace-mapping": {
-      "version": "0.3.17",
-      "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.17.tgz",
-      "integrity": "sha512-MCNzAp77qzKca9+W/+I0+sEpaUnZoeasnghNeVc41VZCEKaCH73Vq3BZZ/SzWIgrqE4H4ceI+p+b6C0mHf9T4g==",
+      "version": "0.3.18",
+      "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.18.tgz",
+      "integrity": "sha512-w+niJYzMHdd7USdiH2U6869nqhD2nbfZXND5Yp93qIbEmnDNk7PD48o+YchRVpzMU7M6jVCbenTR7PA1FLQ9pA==",
       "requires": {
         "@jridgewell/resolve-uri": "3.1.0",
         "@jridgewell/sourcemap-codec": "1.4.14"
+      },
+      "dependencies": {
+        "@jridgewell/sourcemap-codec": {
+          "version": "1.4.14",
+          "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz",
+          "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw=="
+        }
       }
     },
     "@juggle/resize-observer": {
           }
         },
         "semver": {
-          "version": "7.3.8",
-          "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz",
-          "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==",
+          "version": "7.5.1",
+          "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.1.tgz",
+          "integrity": "sha512-Wvss5ivl8TMRZXXESstBA4uR5iXgEN/VC5/sOcuXdVLzcdkz4HWetIoRfG5gb5X+ij/G9rw9YoGn3QoQ8OCSpw==",
           "dev": true,
           "requires": {
             "lru-cache": "^6.0.0"
           }
         },
         "semver": {
-          "version": "7.3.8",
-          "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz",
-          "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==",
+          "version": "7.5.1",
+          "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.1.tgz",
+          "integrity": "sha512-Wvss5ivl8TMRZXXESstBA4uR5iXgEN/VC5/sOcuXdVLzcdkz4HWetIoRfG5gb5X+ij/G9rw9YoGn3QoQ8OCSpw==",
           "dev": true,
           "requires": {
             "lru-cache": "^6.0.0"
       }
     },
     "@nrwl/cli": {
-      "version": "15.8.1",
-      "resolved": "https://registry.npmjs.org/@nrwl/cli/-/cli-15.8.1.tgz",
-      "integrity": "sha512-MB4anWQJ/yqKfusF2b+wYim2/aJGERcEkK/xT9Q7nXwJR/x76cIdggHz/C4zasFAjEHcilwajmF5chk0vArbkA==",
+      "version": "15.9.3",
+      "resolved": "https://registry.npmjs.org/@nrwl/cli/-/cli-15.9.3.tgz",
+      "integrity": "sha512-qiAKHkov3iBx6hroPTitUrkRSUZFQqVgNJiF9gXRFC6pNJe9RS4rlmcIaoUFOboi9CnH5jwblNJVcz8YSVYOvA==",
       "dev": true,
       "requires": {
-        "nx": "15.8.1"
+        "nx": "15.9.3"
       },
       "dependencies": {
         "@nrwl/tao": {
-          "version": "15.8.1",
-          "resolved": "https://registry.npmjs.org/@nrwl/tao/-/tao-15.8.1.tgz",
-          "integrity": "sha512-wSoSTSF3tjVjNk6L1vtdSLHCIxEDl232aZN2LMpsVgU2yHnvrik2s9UJQrSDltsMM6vQ5VNKS5Li/AZWmfI79g==",
+          "version": "15.9.3",
+          "resolved": "https://registry.npmjs.org/@nrwl/tao/-/tao-15.9.3.tgz",
+          "integrity": "sha512-NcjFCbuMa53C3fBrK7qLUImUBySyr9EVwmiZuAv9sZZtm4eILK8w3qihjrB4FFUuLjPU/SViriYXi+hF2tbP4w==",
           "dev": true,
           "requires": {
-            "nx": "15.8.1"
+            "nx": "15.9.3"
           }
         },
         "@zkochan/js-yaml": {
           }
         },
         "fs-extra": {
-          "version": "11.1.0",
-          "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.1.0.tgz",
-          "integrity": "sha512-0rcTq621PD5jM/e0a3EJoGC/1TC5ZBCERW82LQuwfGnCa1V8w7dpYH1yNu+SLb6E5dkeCBzKEyLGlFrnr+dUyw==",
+          "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,
           "requires": {
             "graceful-fs": "^4.2.0",
           }
         },
         "nx": {
-          "version": "15.8.1",
-          "resolved": "https://registry.npmjs.org/nx/-/nx-15.8.1.tgz",
-          "integrity": "sha512-aXFZw2KKf1wQOKmUod+Q6CEBStnu44P5FODOTZ5CRepmn6msFyvxtSwDgOflYW07VvZbNMuLT4mNWgDiQE0BbA==",
-          "dev": true,
-          "requires": {
-            "@nrwl/cli": "15.8.1",
-            "@nrwl/nx-darwin-arm64": "15.8.1",
-            "@nrwl/nx-darwin-x64": "15.8.1",
-            "@nrwl/nx-linux-arm-gnueabihf": "15.8.1",
-            "@nrwl/nx-linux-arm64-gnu": "15.8.1",
-            "@nrwl/nx-linux-arm64-musl": "15.8.1",
-            "@nrwl/nx-linux-x64-gnu": "15.8.1",
-            "@nrwl/nx-linux-x64-musl": "15.8.1",
-            "@nrwl/nx-win32-arm64-msvc": "15.8.1",
-            "@nrwl/nx-win32-x64-msvc": "15.8.1",
-            "@nrwl/tao": "15.8.1",
+          "version": "15.9.3",
+          "resolved": "https://registry.npmjs.org/nx/-/nx-15.9.3.tgz",
+          "integrity": "sha512-GLwbykfTABc7/UZjQEEnV1bQbTVC53W+Zj4xWY640/45I4iZf/TUqKMBCgtLZ9v89gEsKOM4zsx55CqHT3bekA==",
+          "dev": true,
+          "requires": {
+            "@nrwl/cli": "15.9.3",
+            "@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",
+            "@nrwl/tao": "15.9.3",
             "@parcel/watcher": "2.0.4",
             "@yarnpkg/lockfile": "^1.1.0",
             "@yarnpkg/parsers": "^3.0.0-rc.18",
       }
     },
     "@nrwl/nx-darwin-arm64": {
-      "version": "15.8.1",
-      "resolved": "https://registry.npmjs.org/@nrwl/nx-darwin-arm64/-/nx-darwin-arm64-15.8.1.tgz",
-      "integrity": "sha512-nuubQcXVrxxxjpKHmfQ4bEgCwKsiey1/o9+FB98DoOv70Y2PuxMu/tjXbfNiJBFP72ySrrDmD/vRMKmduOsRSg==",
+      "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==",
       "dev": true,
       "optional": true
     },
     "@nrwl/nx-darwin-x64": {
-      "version": "15.8.1",
-      "resolved": "https://registry.npmjs.org/@nrwl/nx-darwin-x64/-/nx-darwin-x64-15.8.1.tgz",
-      "integrity": "sha512-qYtTKFoq5i0mtulZs7eWXBFV5OhI/fnZ9RGSD/mPoFt4cHV0MAgqIafiGpQ+Py/a36gIIfdR5U9MLyMRLOMNUQ==",
+      "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==",
       "dev": true,
       "optional": true
     },
     "@nrwl/nx-linux-arm-gnueabihf": {
-      "version": "15.8.1",
-      "resolved": "https://registry.npmjs.org/@nrwl/nx-linux-arm-gnueabihf/-/nx-linux-arm-gnueabihf-15.8.1.tgz",
-      "integrity": "sha512-m4lX38fcrhyzG8K+4Ds8StCIQ0hHxRFFeFUMMR3ZR/RYpGwO5nRcF22l4gRm9DnsL48Vv8qjZ7v9bTNKiD2O7g==",
+      "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==",
       "dev": true,
       "optional": true
     },
     "@nrwl/nx-linux-arm64-gnu": {
-      "version": "15.8.1",
-      "resolved": "https://registry.npmjs.org/@nrwl/nx-linux-arm64-gnu/-/nx-linux-arm64-gnu-15.8.1.tgz",
-      "integrity": "sha512-nCiObtfk/TBPblhMx7IG5jchgPg9RL5wE38GNf/yl1jjfRTfkvwrS+c0T5VyEBoUjGw7KDlZ/7CY0RBN+usdUg==",
+      "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==",
       "dev": true,
       "optional": true
     },
     "@nrwl/nx-linux-arm64-musl": {
-      "version": "15.8.1",
-      "resolved": "https://registry.npmjs.org/@nrwl/nx-linux-arm64-musl/-/nx-linux-arm64-musl-15.8.1.tgz",
-      "integrity": "sha512-7AguRwMwdNYtYnThm3jMqIaqhA03cJztZqIgxvWwJpbteBANfa6+HtTUeCwN27elHCmSex301Q9K8cIY8irtmg==",
+      "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==",
       "dev": true,
       "optional": true
     },
     "@nrwl/nx-linux-x64-gnu": {
-      "version": "15.8.1",
-      "resolved": "https://registry.npmjs.org/@nrwl/nx-linux-x64-gnu/-/nx-linux-x64-gnu-15.8.1.tgz",
-      "integrity": "sha512-GkJaYuGhW1P9FF8YLMjs6VnMSXZmJz6z0Bsv6GxtZ+6f0W9fOEzoSpXBmPF8aXi3z02uRPAgJC9iKeRh3Xmkdg==",
+      "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==",
       "dev": true,
       "optional": true
     },
     "@nrwl/nx-linux-x64-musl": {
-      "version": "15.8.1",
-      "resolved": "https://registry.npmjs.org/@nrwl/nx-linux-x64-musl/-/nx-linux-x64-musl-15.8.1.tgz",
-      "integrity": "sha512-YQNoPlKLVzvkxn4F3Pc5/wMc7FKoe9mAAME0KjL9DNAYGvOLHSO5fc82a+f0nIOflXlwBusnC/HiGUkcvD+Qxg==",
+      "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==",
       "dev": true,
       "optional": true
     },
     "@nrwl/nx-win32-arm64-msvc": {
-      "version": "15.8.1",
-      "resolved": "https://registry.npmjs.org/@nrwl/nx-win32-arm64-msvc/-/nx-win32-arm64-msvc-15.8.1.tgz",
-      "integrity": "sha512-g/zqZpVvmYt8/SjYjKkOxogMzL4XCjoRImXEBAW4t8+SIacLmZt+Rt8BCY1aqcb2TCSLwrZevPyvQGfTa6zfuA==",
+      "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==",
       "dev": true,
       "optional": true
     },
     "@nrwl/nx-win32-x64-msvc": {
-      "version": "15.8.1",
-      "resolved": "https://registry.npmjs.org/@nrwl/nx-win32-x64-msvc/-/nx-win32-x64-msvc-15.8.1.tgz",
-      "integrity": "sha512-obgXvryiY5IuvIIojLatGCB9gbzgG8LwlxpcqIIb/ywJ7K6ai74kNRdegPCjlsyca8MZSWqGncI0Hwo5fcGaDQ==",
+      "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==",
       "dev": true,
       "optional": true
     },
         "@angular-devkit/core": "13.3.9",
         "@angular-devkit/schematics": "13.3.9",
         "jsonc-parser": "3.0.0"
-      }
-    },
-    "@sideway/address": {
-      "version": "4.1.4",
-      "resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.4.tgz",
-      "integrity": "sha512-7vwq+rOHVWjyXxVlR76Agnvhy8I9rpzjosTESvmhNeXOXdZZB15Fl+TI9x1SiHZH5Jv2wTGduSxFDIaq0m3DUw==",
-      "dev": true,
-      "requires": {
-        "@hapi/hoek": "^9.0.0"
-      }
-    },
-    "@sideway/formula": {
-      "version": "3.0.1",
-      "resolved": "https://registry.npmjs.org/@sideway/formula/-/formula-3.0.1.tgz",
-      "integrity": "sha512-/poHZJJVjx3L+zVD6g9KgHfYnb443oi7wLu/XKojDviHy6HOEOA6z1Trk5aR1dGcmPenJEgb2sK2I80LeS3MIg==",
-      "dev": true
-    },
-    "@sideway/pinpoint": {
-      "version": "2.0.0",
+      },
+      "dependencies": {
+        "@angular-devkit/core": {
+          "version": "13.3.9",
+          "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-13.3.9.tgz",
+          "integrity": "sha512-XqCuIWyoqIsLABjV3GQL/+EiBCt3xVPPtNp3Mg4gjBsDLW7PEnvbb81yGkiZQmIsq4EIyQC/6fQa3VdjsCshGg==",
+          "dev": true,
+          "requires": {
+            "ajv": "8.9.0",
+            "ajv-formats": "2.1.1",
+            "fast-json-stable-stringify": "2.1.0",
+            "magic-string": "0.25.7",
+            "rxjs": "6.6.7",
+            "source-map": "0.7.3"
+          }
+        },
+        "rxjs": {
+          "version": "6.6.7",
+          "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz",
+          "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==",
+          "dev": true,
+          "requires": {
+            "tslib": "^1.9.0"
+          }
+        },
+        "tslib": {
+          "version": "1.14.1",
+          "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
+          "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==",
+          "dev": true
+        }
+      }
+    },
+    "@sideway/address": {
+      "version": "4.1.4",
+      "resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.4.tgz",
+      "integrity": "sha512-7vwq+rOHVWjyXxVlR76Agnvhy8I9rpzjosTESvmhNeXOXdZZB15Fl+TI9x1SiHZH5Jv2wTGduSxFDIaq0m3DUw==",
+      "dev": true,
+      "requires": {
+        "@hapi/hoek": "^9.0.0"
+      }
+    },
+    "@sideway/formula": {
+      "version": "3.0.1",
+      "resolved": "https://registry.npmjs.org/@sideway/formula/-/formula-3.0.1.tgz",
+      "integrity": "sha512-/poHZJJVjx3L+zVD6g9KgHfYnb443oi7wLu/XKojDviHy6HOEOA6z1Trk5aR1dGcmPenJEgb2sK2I80LeS3MIg==",
+      "dev": true
+    },
+    "@sideway/pinpoint": {
+      "version": "2.0.0",
       "resolved": "https://registry.npmjs.org/@sideway/pinpoint/-/pinpoint-2.0.0.tgz",
       "integrity": "sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==",
       "dev": true
       },
       "dependencies": {
         "@babel/core": {
-          "version": "7.21.0",
-          "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.21.0.tgz",
-          "integrity": "sha512-PuxUbxcW6ZYe656yL3EAhpy7qXKq0DmYsrJLpbB8XrsCP9Nm+XCg9XFMb5vIDliPD7+U/+M+QJlH17XOcB7eXA==",
+          "version": "7.21.8",
+          "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.21.8.tgz",
+          "integrity": "sha512-YeM22Sondbo523Sz0+CirSPnbj9bG3P0CdHcBZdqUuaeOaYEFbOLoGU7lebvGP6P5J/WE9wOn7u7C4J9HvS1xQ==",
           "dev": true,
           "requires": {
             "@ampproject/remapping": "^2.2.0",
-            "@babel/code-frame": "^7.18.6",
-            "@babel/generator": "^7.21.0",
-            "@babel/helper-compilation-targets": "^7.20.7",
-            "@babel/helper-module-transforms": "^7.21.0",
-            "@babel/helpers": "^7.21.0",
-            "@babel/parser": "^7.21.0",
+            "@babel/code-frame": "^7.21.4",
+            "@babel/generator": "^7.21.5",
+            "@babel/helper-compilation-targets": "^7.21.5",
+            "@babel/helper-module-transforms": "^7.21.5",
+            "@babel/helpers": "^7.21.5",
+            "@babel/parser": "^7.21.8",
             "@babel/template": "^7.20.7",
-            "@babel/traverse": "^7.21.0",
-            "@babel/types": "^7.21.0",
+            "@babel/traverse": "^7.21.5",
+            "@babel/types": "^7.21.5",
             "convert-source-map": "^1.7.0",
             "debug": "^4.1.0",
             "gensync": "^1.0.0-beta.2",
         "unist-util-find-all-after": "^3.0.2"
       }
     },
+    "@swagger-api/apidom-ast": {
+      "version": "0.69.3",
+      "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ast/-/apidom-ast-0.69.3.tgz",
+      "integrity": "sha512-orGw/gihk7RmorxibwalthDS58B7QaEBd31fK+/aFx6QqEO1tEO35F850BiL2B5C8TaK8C2Tey01AZTXCxYmfw==",
+      "requires": {
+        "@babel/runtime-corejs3": "^7.20.7",
+        "@types/ramda": "=0.29.0",
+        "ramda": "=0.29.0",
+        "ramda-adjunct": "=4.0.0",
+        "stampit": "=4.3.2",
+        "unraw": "=2.0.1"
+      }
+    },
+    "@swagger-api/apidom-core": {
+      "version": "0.69.3",
+      "resolved": "https://registry.npmjs.org/@swagger-api/apidom-core/-/apidom-core-0.69.3.tgz",
+      "integrity": "sha512-EIQiJUuT/V9nGkHOYYFP0QNgAW7Y4QwrQzldDzy9ltcHbOKY3TNh/QzYvO0+HvKSv9W7u7WTMH/kaRaSsaZsGw==",
+      "requires": {
+        "@babel/runtime-corejs3": "^7.20.7",
+        "@swagger-api/apidom-ast": "^0.69.3",
+        "@types/ramda": "=0.29.0",
+        "minim": "=0.23.8",
+        "ramda": "=0.29.0",
+        "ramda-adjunct": "=4.0.0",
+        "short-unique-id": "=4.4.4",
+        "stampit": "=4.3.2"
+      }
+    },
+    "@swagger-api/apidom-json-pointer": {
+      "version": "0.69.3",
+      "resolved": "https://registry.npmjs.org/@swagger-api/apidom-json-pointer/-/apidom-json-pointer-0.69.3.tgz",
+      "integrity": "sha512-VYXqLY8f2fkaS/d+vVQEMEOEZRXAgGm5tCMx4C7uaU+wC+SKPH/zTh+qElbkaXQr4nfLjbphBsHh31doCyBEjw==",
+      "requires": {
+        "@babel/runtime-corejs3": "^7.20.7",
+        "@swagger-api/apidom-core": "^0.69.3",
+        "@types/ramda": "=0.29.0",
+        "ramda": "=0.29.0",
+        "ramda-adjunct": "=4.0.0"
+      }
+    },
+    "@swagger-api/apidom-ns-api-design-systems": {
+      "version": "0.69.3",
+      "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ns-api-design-systems/-/apidom-ns-api-design-systems-0.69.3.tgz",
+      "integrity": "sha512-oTwIG8LyKnU4/m8BtAOc+X572+nH4gjxITYtw0L8f4a8Iv1b8LHS0KRzG7c/LVUGtMijpv3aBKPV6QvWhjrrtg==",
+      "optional": true,
+      "requires": {
+        "@babel/runtime-corejs3": "^7.20.7",
+        "@swagger-api/apidom-core": "^0.69.3",
+        "@swagger-api/apidom-ns-openapi-3-1": "^0.69.3",
+        "@types/ramda": "=0.29.0",
+        "ramda": "=0.29.0",
+        "ramda-adjunct": "=4.0.0",
+        "stampit": "=4.3.2"
+      }
+    },
+    "@swagger-api/apidom-ns-asyncapi-2": {
+      "version": "0.69.3",
+      "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ns-asyncapi-2/-/apidom-ns-asyncapi-2-0.69.3.tgz",
+      "integrity": "sha512-98HgNbZWqPHqf+EyXs/GcAnayA08/mfN7YlXIRRIys+rll4M/1b+ap+BkTnJ+le3Kra7DhIQ8ucQuEJ+Ik1Sxg==",
+      "optional": true,
+      "requires": {
+        "@babel/runtime-corejs3": "^7.20.7",
+        "@swagger-api/apidom-core": "^0.69.3",
+        "@swagger-api/apidom-ns-json-schema-draft-7": "^0.69.3",
+        "@types/ramda": "=0.29.0",
+        "ramda": "=0.29.0",
+        "ramda-adjunct": "=4.0.0",
+        "stampit": "=4.3.2"
+      }
+    },
+    "@swagger-api/apidom-ns-json-schema-draft-4": {
+      "version": "0.69.3",
+      "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ns-json-schema-draft-4/-/apidom-ns-json-schema-draft-4-0.69.3.tgz",
+      "integrity": "sha512-/tMeoz1IHEblc3OwWC812NQFLITOnexjGVujG5Wvsr9ZnTkRb+0g7CbXuooujwfcEY+++o2+kCUgy4SBQFIIlQ==",
+      "requires": {
+        "@babel/runtime-corejs3": "^7.20.7",
+        "@swagger-api/apidom-core": "^0.69.3",
+        "@types/ramda": "=0.29.0",
+        "ramda": "=0.29.0",
+        "ramda-adjunct": "=4.0.0",
+        "stampit": "=4.3.2"
+      }
+    },
+    "@swagger-api/apidom-ns-json-schema-draft-6": {
+      "version": "0.69.3",
+      "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ns-json-schema-draft-6/-/apidom-ns-json-schema-draft-6-0.69.3.tgz",
+      "integrity": "sha512-B/6zPFYW1xE66Uc/jOdZVZMEe0+444heTMlpGJGejTy6pNRxCTOsv+B63QzctJv410dHPxGeMRZMeff9wQPBDA==",
+      "optional": true,
+      "requires": {
+        "@babel/runtime-corejs3": "^7.20.7",
+        "@swagger-api/apidom-core": "^0.69.3",
+        "@swagger-api/apidom-ns-json-schema-draft-4": "^0.69.3",
+        "@types/ramda": "=0.29.0",
+        "ramda": "=0.29.0",
+        "ramda-adjunct": "=4.0.0",
+        "stampit": "=4.3.2"
+      }
+    },
+    "@swagger-api/apidom-ns-json-schema-draft-7": {
+      "version": "0.69.3",
+      "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ns-json-schema-draft-7/-/apidom-ns-json-schema-draft-7-0.69.3.tgz",
+      "integrity": "sha512-EkgPlKPQZ3XBkbxAFh2lXsLcyIwRikARFD3RlupsKjAHVFbH7cImbPxb+MnjacfwgVreMk34OWuXqjnGZ8lG1Q==",
+      "optional": true,
+      "requires": {
+        "@babel/runtime-corejs3": "^7.20.7",
+        "@swagger-api/apidom-core": "^0.69.3",
+        "@swagger-api/apidom-ns-json-schema-draft-6": "^0.69.3",
+        "@types/ramda": "=0.29.0",
+        "ramda": "=0.29.0",
+        "ramda-adjunct": "=4.0.0",
+        "stampit": "=4.3.2"
+      }
+    },
+    "@swagger-api/apidom-ns-openapi-3-0": {
+      "version": "0.69.3",
+      "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ns-openapi-3-0/-/apidom-ns-openapi-3-0-0.69.3.tgz",
+      "integrity": "sha512-iScwP+SzX8SJMrgChZbdS60Ode/zXfesNaDA+HkNBLbfSrri4/C5FTB0gfWOG0gCGPq+1K5dHlgLrEgogfAARw==",
+      "requires": {
+        "@babel/runtime-corejs3": "^7.20.7",
+        "@swagger-api/apidom-core": "^0.69.3",
+        "@swagger-api/apidom-ns-json-schema-draft-4": "^0.69.3",
+        "@types/ramda": "=0.29.0",
+        "ramda": "=0.29.0",
+        "ramda-adjunct": "=4.0.0",
+        "stampit": "=4.3.2"
+      }
+    },
+    "@swagger-api/apidom-ns-openapi-3-1": {
+      "version": "0.69.3",
+      "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ns-openapi-3-1/-/apidom-ns-openapi-3-1-0.69.3.tgz",
+      "integrity": "sha512-J+yIsTBTn7rzfj+vaCXRRdOCrL4kxwnS3P/h4lXb82EZnPU/EbJi+C0LK7O24/vXpeBVRr+OpDfnhpospanMJg==",
+      "requires": {
+        "@babel/runtime-corejs3": "^7.20.7",
+        "@swagger-api/apidom-core": "^0.69.3",
+        "@swagger-api/apidom-ns-openapi-3-0": "^0.69.3",
+        "@types/ramda": "=0.29.0",
+        "ramda": "=0.29.0",
+        "ramda-adjunct": "=4.0.0",
+        "stampit": "=4.3.2"
+      }
+    },
+    "@swagger-api/apidom-parser-adapter-api-design-systems-json": {
+      "version": "0.69.3",
+      "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-api-design-systems-json/-/apidom-parser-adapter-api-design-systems-json-0.69.3.tgz",
+      "integrity": "sha512-y8xOaSaZVphITajH12T1EHLuZB9kw79DTdQRYoMC+BqZQbsPv3/mLWXS1STQU9oR/PZBA9FJcgAFdThaRgi8UA==",
+      "optional": true,
+      "requires": {
+        "@babel/runtime-corejs3": "^7.20.7",
+        "@swagger-api/apidom-core": "^0.69.3",
+        "@swagger-api/apidom-ns-api-design-systems": "^0.69.3",
+        "@swagger-api/apidom-parser-adapter-json": "^0.69.3",
+        "@types/ramda": "=0.29.0",
+        "ramda": "=0.29.0",
+        "ramda-adjunct": "=4.0.0"
+      }
+    },
+    "@swagger-api/apidom-parser-adapter-api-design-systems-yaml": {
+      "version": "0.69.3",
+      "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-api-design-systems-yaml/-/apidom-parser-adapter-api-design-systems-yaml-0.69.3.tgz",
+      "integrity": "sha512-zMc+Dy7zl7cT8YduaUEpvLkRDVfZp8jZ1v13VjueX/VhsSXK0DW+OX/Mc8CU1Qx6Gg6tUMKxqmILdXZwxCrh6Q==",
+      "optional": true,
+      "requires": {
+        "@babel/runtime-corejs3": "^7.20.7",
+        "@swagger-api/apidom-core": "^0.69.3",
+        "@swagger-api/apidom-ns-api-design-systems": "^0.69.3",
+        "@swagger-api/apidom-parser-adapter-yaml-1-2": "^0.69.3",
+        "@types/ramda": "=0.29.0",
+        "ramda": "=0.29.0",
+        "ramda-adjunct": "=4.0.0"
+      }
+    },
+    "@swagger-api/apidom-parser-adapter-asyncapi-json-2": {
+      "version": "0.69.3",
+      "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-asyncapi-json-2/-/apidom-parser-adapter-asyncapi-json-2-0.69.3.tgz",
+      "integrity": "sha512-BbsBxxZxTMX2rKgVKJtqPoAsER0JvCe1pt3NUBLuQssugvpwaqimIsKC65dwspHFGSn0CVdBKA4n/XhZ3aT7tw==",
+      "optional": true,
+      "requires": {
+        "@babel/runtime-corejs3": "^7.20.7",
+        "@swagger-api/apidom-core": "^0.69.3",
+        "@swagger-api/apidom-ns-asyncapi-2": "^0.69.3",
+        "@swagger-api/apidom-parser-adapter-json": "^0.69.3",
+        "@types/ramda": "=0.29.0",
+        "ramda": "=0.29.0",
+        "ramda-adjunct": "=4.0.0"
+      }
+    },
+    "@swagger-api/apidom-parser-adapter-asyncapi-yaml-2": {
+      "version": "0.69.3",
+      "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-asyncapi-yaml-2/-/apidom-parser-adapter-asyncapi-yaml-2-0.69.3.tgz",
+      "integrity": "sha512-lae39qaKrkls+iVzYuc9CUyCbRl80wNK8iBWriSVETv5IwlVS6wywtTxCqtzcpd5K+m9KqXAlSkd+Z2AS9cWuQ==",
+      "optional": true,
+      "requires": {
+        "@babel/runtime-corejs3": "^7.20.7",
+        "@swagger-api/apidom-core": "^0.69.3",
+        "@swagger-api/apidom-ns-asyncapi-2": "^0.69.3",
+        "@swagger-api/apidom-parser-adapter-yaml-1-2": "^0.69.3",
+        "@types/ramda": "=0.29.0",
+        "ramda": "=0.29.0",
+        "ramda-adjunct": "=4.0.0"
+      }
+    },
+    "@swagger-api/apidom-parser-adapter-json": {
+      "version": "0.69.3",
+      "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-json/-/apidom-parser-adapter-json-0.69.3.tgz",
+      "integrity": "sha512-Z1CqG9OcV4WVESdQ1D0s5JUa2jeF8hpw4RupMDJ4lRoKTVeIDS5Qb7OOhIGeKpK2DgMep9SN2ULYJdgldPtq/A==",
+      "optional": true,
+      "requires": {
+        "@babel/runtime-corejs3": "^7.20.7",
+        "@swagger-api/apidom-ast": "^0.69.3",
+        "@swagger-api/apidom-core": "^0.69.3",
+        "@types/ramda": "=0.29.0",
+        "ramda": "=0.29.0",
+        "ramda-adjunct": "=4.0.0",
+        "stampit": "=4.3.2",
+        "tree-sitter": "=0.20.1",
+        "tree-sitter-json": "=0.20.0",
+        "web-tree-sitter": "=0.20.7"
+      }
+    },
+    "@swagger-api/apidom-parser-adapter-openapi-json-3-0": {
+      "version": "0.69.3",
+      "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-openapi-json-3-0/-/apidom-parser-adapter-openapi-json-3-0-0.69.3.tgz",
+      "integrity": "sha512-f5Xby5hAGy4VujkV74UA61UkSVRsNzhcBaW0IIapVVepFEclfU7J3dGvfkMIXv5Bg0infGeKddIUZUY61JN88w==",
+      "optional": true,
+      "requires": {
+        "@babel/runtime-corejs3": "^7.20.7",
+        "@swagger-api/apidom-core": "^0.69.3",
+        "@swagger-api/apidom-ns-openapi-3-0": "^0.69.3",
+        "@swagger-api/apidom-parser-adapter-json": "^0.69.3",
+        "@types/ramda": "=0.29.0",
+        "ramda": "=0.29.0",
+        "ramda-adjunct": "=4.0.0"
+      }
+    },
+    "@swagger-api/apidom-parser-adapter-openapi-json-3-1": {
+      "version": "0.69.3",
+      "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-openapi-json-3-1/-/apidom-parser-adapter-openapi-json-3-1-0.69.3.tgz",
+      "integrity": "sha512-Yq3k/d89Nmf+ePD5EIIkhXNti2Ru5XMqOXDbNQGKHH00e252Q+c+QF2A7Pgdy0xP3oA9OoYTGHLtL0ncmzYB9A==",
+      "optional": true,
+      "requires": {
+        "@babel/runtime-corejs3": "^7.20.7",
+        "@swagger-api/apidom-core": "^0.69.3",
+        "@swagger-api/apidom-ns-openapi-3-1": "^0.69.3",
+        "@swagger-api/apidom-parser-adapter-json": "^0.69.3",
+        "@types/ramda": "=0.29.0",
+        "ramda": "=0.29.0",
+        "ramda-adjunct": "=4.0.0"
+      }
+    },
+    "@swagger-api/apidom-parser-adapter-openapi-yaml-3-0": {
+      "version": "0.69.3",
+      "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-openapi-yaml-3-0/-/apidom-parser-adapter-openapi-yaml-3-0-0.69.3.tgz",
+      "integrity": "sha512-X4Qtg/0n0l2leWBBZC8+7Kj6eP3pqB4WCWlacoWuldz8WBDBuffTBmTV/qe6gKdI4DW6mX5ovxDf+tz2tr0ppQ==",
+      "optional": true,
+      "requires": {
+        "@babel/runtime-corejs3": "^7.20.7",
+        "@swagger-api/apidom-core": "^0.69.3",
+        "@swagger-api/apidom-ns-openapi-3-0": "^0.69.3",
+        "@swagger-api/apidom-parser-adapter-yaml-1-2": "^0.69.3",
+        "@types/ramda": "=0.29.0",
+        "ramda": "=0.29.0",
+        "ramda-adjunct": "=4.0.0"
+      }
+    },
+    "@swagger-api/apidom-parser-adapter-openapi-yaml-3-1": {
+      "version": "0.69.3",
+      "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-openapi-yaml-3-1/-/apidom-parser-adapter-openapi-yaml-3-1-0.69.3.tgz",
+      "integrity": "sha512-ZW/2T92HZT2RQOPW1VOa78VyDYD5wwR9EGNKXBsfMCnl0zVHwhwwkn/GgsYS0VDk56t43ww5DHM976q4ukF6Ew==",
+      "optional": true,
+      "requires": {
+        "@babel/runtime-corejs3": "^7.20.7",
+        "@swagger-api/apidom-core": "^0.69.3",
+        "@swagger-api/apidom-ns-openapi-3-1": "^0.69.3",
+        "@swagger-api/apidom-parser-adapter-yaml-1-2": "^0.69.3",
+        "@types/ramda": "=0.29.0",
+        "ramda": "=0.29.0",
+        "ramda-adjunct": "=4.0.0"
+      }
+    },
+    "@swagger-api/apidom-parser-adapter-yaml-1-2": {
+      "version": "0.69.3",
+      "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-yaml-1-2/-/apidom-parser-adapter-yaml-1-2-0.69.3.tgz",
+      "integrity": "sha512-HJ/OiXnVoUshwKrfaHDq4LfKeKxBsa6Bmo8NVdSZiRfeA1Y/fAx9mWW5xSzTADxmc6yA2MevnfIoq7W0NX6SSQ==",
+      "optional": true,
+      "requires": {
+        "@babel/runtime-corejs3": "^7.20.7",
+        "@swagger-api/apidom-ast": "^0.69.3",
+        "@swagger-api/apidom-core": "^0.69.3",
+        "@types/ramda": "=0.29.0",
+        "ramda": "=0.29.0",
+        "ramda-adjunct": "=4.0.0",
+        "stampit": "=4.3.2",
+        "tree-sitter": "=0.20.1",
+        "tree-sitter-yaml": "=0.5.0",
+        "web-tree-sitter": "=0.20.7"
+      }
+    },
+    "@swagger-api/apidom-reference": {
+      "version": "0.69.3",
+      "resolved": "https://registry.npmjs.org/@swagger-api/apidom-reference/-/apidom-reference-0.69.3.tgz",
+      "integrity": "sha512-dimoVsW4COR4TUTgOqTnXSZAIdYOepIudWOvca2fGOcXg85eBMS4xJlNHx1095Fm664y5y8DVxIYe1oLu9gjVA==",
+      "requires": {
+        "@babel/runtime-corejs3": "^7.20.7",
+        "@swagger-api/apidom-core": "^0.69.3",
+        "@swagger-api/apidom-json-pointer": "^0.69.3",
+        "@swagger-api/apidom-ns-asyncapi-2": "^0.69.3",
+        "@swagger-api/apidom-ns-openapi-3-0": "^0.69.3",
+        "@swagger-api/apidom-ns-openapi-3-1": "^0.69.3",
+        "@swagger-api/apidom-parser-adapter-api-design-systems-json": "^0.69.3",
+        "@swagger-api/apidom-parser-adapter-api-design-systems-yaml": "^0.69.3",
+        "@swagger-api/apidom-parser-adapter-asyncapi-json-2": "^0.69.3",
+        "@swagger-api/apidom-parser-adapter-asyncapi-yaml-2": "^0.69.3",
+        "@swagger-api/apidom-parser-adapter-json": "^0.69.3",
+        "@swagger-api/apidom-parser-adapter-openapi-json-3-0": "^0.69.3",
+        "@swagger-api/apidom-parser-adapter-openapi-json-3-1": "^0.69.3",
+        "@swagger-api/apidom-parser-adapter-openapi-yaml-3-0": "^0.69.3",
+        "@swagger-api/apidom-parser-adapter-openapi-yaml-3-1": "^0.69.3",
+        "@swagger-api/apidom-parser-adapter-yaml-1-2": "^0.69.3",
+        "@types/ramda": "=0.29.0",
+        "axios": "=1.3.6",
+        "minimatch": "=7.4.3",
+        "process": "=0.11.10",
+        "ramda": "=0.29.0",
+        "ramda-adjunct": "=4.0.0",
+        "stampit": "=4.3.2"
+      },
+      "dependencies": {
+        "brace-expansion": {
+          "version": "2.0.1",
+          "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
+          "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
+          "requires": {
+            "balanced-match": "^1.0.0"
+          }
+        },
+        "minimatch": {
+          "version": "7.4.3",
+          "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-7.4.3.tgz",
+          "integrity": "sha512-5UB4yYusDtkRPbRiy1cqZ1IpGNcJCGlEMG17RKzPddpyiPKoCdwohbED8g4QXT0ewCt8LTkQXuljsUfQ3FKM4A==",
+          "requires": {
+            "brace-expansion": "^2.0.1"
+          }
+        }
+      }
+    },
     "@swimlane/ngx-datatable": {
       "version": "18.0.0",
       "resolved": "https://registry.npmjs.org/@swimlane/ngx-datatable/-/ngx-datatable-18.0.0.tgz",
       }
     },
     "@types/babel__traverse": {
-      "version": "7.18.3",
-      "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.18.3.tgz",
-      "integrity": "sha512-1kbcJ40lLB7MHsj39U4Sh1uTd2E7rLEa79kmDpI6cy+XiXsteB3POdQomoq4FxszMrO3ZYchkhYJw7A2862b3w==",
+      "version": "7.18.5",
+      "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.18.5.tgz",
+      "integrity": "sha512-enCvTL8m/EHS/zIvJno9nE+ndYPh1/oNFzRYRmtUqJICG2VnCSBzMLW5VN2KCQU91f23tsNKR8v7VJJQMatl7Q==",
       "dev": true,
       "requires": {
         "@babel/types": "^7.3.0"
       }
     },
     "@types/connect-history-api-fallback": {
-      "version": "1.3.5",
-      "resolved": "https://registry.npmjs.org/@types/connect-history-api-fallback/-/connect-history-api-fallback-1.3.5.tgz",
-      "integrity": "sha512-h8QJa8xSb1WD4fpKBDcATDNGXghFj6/3GRWG6dhmRcu0RX1Ubasur2Uvx5aeEwlf0MwblEC2bMzzMQntxnw/Cw==",
+      "version": "1.5.0",
+      "resolved": "https://registry.npmjs.org/@types/connect-history-api-fallback/-/connect-history-api-fallback-1.5.0.tgz",
+      "integrity": "sha512-4x5FkPpLipqwthjPsF7ZRbOv3uoLUFkTA9G9v583qi4pACvq0uTELrB8OLUzPWUI4IJIyvM85vzkV1nyiI2Lig==",
       "dev": true,
       "requires": {
         "@types/express-serve-static-core": "*",
       "dev": true
     },
     "@types/eslint": {
-      "version": "8.21.2",
-      "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.21.2.tgz",
-      "integrity": "sha512-EMpxUyystd3uZVByZap1DACsMXvb82ypQnGn89e1Y0a+LYu3JJscUd/gqhRsVFDkaD2MIiWo0MT8EfXr3DGRKw==",
+      "version": "8.40.0",
+      "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.40.0.tgz",
+      "integrity": "sha512-nbq2mvc/tBrK9zQQuItvjJl++GTN5j06DaPtp3hZCpngmG6Q3xoyEmd0TwZI0gAy/G1X0zhGBbr2imsGFdFV0g==",
       "dev": true,
       "requires": {
         "@types/estree": "*",
       }
     },
     "@types/express-serve-static-core": {
-      "version": "4.17.33",
-      "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.33.tgz",
-      "integrity": "sha512-TPBqmR/HRYI3eC2E5hmiivIzv+bidAfXofM+sbonAGvyDhySGw9/PQZFt2BLOrjUUR++4eJVpx6KnLQK1Fk9tA==",
+      "version": "4.17.35",
+      "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.35.tgz",
+      "integrity": "sha512-wALWQwrgiB2AWTT91CB62b6Yt0sNHpznUXeZEcnPU3DRdlDIz74x8Qg1UUYKSVFi+va5vKOLYRBI1bRKiLLKIg==",
       "dev": true,
       "requires": {
         "@types/node": "*",
         "@types/qs": "*",
-        "@types/range-parser": "*"
+        "@types/range-parser": "*",
+        "@types/send": "*"
       }
     },
     "@types/file-saver": {
       }
     },
     "@types/http-proxy": {
-      "version": "1.17.10",
-      "resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.10.tgz",
-      "integrity": "sha512-Qs5aULi+zV1bwKAg5z1PWnDXWmsn+LxIvUGv6E2+OOMYhclZMO+OXd9pYVf2gLykf2I7IV2u7oTHwChPNsvJ7g==",
+      "version": "1.17.11",
+      "resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.11.tgz",
+      "integrity": "sha512-HC8G7c1WmaF2ekqpnFq626xd3Zz0uvaqFmBJNRZCGEZCXkvSdJoNFn/8Ygbd9fKNQj8UzLdCETaI0UWPAjK7IA==",
       "dev": true,
       "requires": {
         "@types/node": "*"
       "dev": true
     },
     "@types/mdast": {
-      "version": "3.0.10",
-      "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-3.0.10.tgz",
-      "integrity": "sha512-W864tg/Osz1+9f4lrGTZpCSO5/z4608eUp19tbozkq2HJK6i3z1kT0H9tlADXuYIb1YYOBByU4Jsqkk75q48qA==",
+      "version": "3.0.11",
+      "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-3.0.11.tgz",
+      "integrity": "sha512-Y/uImid8aAwrEA24/1tcRZwpxX3pIFTSilcNDKSPn+Y2iDywSEachzRuvgAYYLR3wpGXAsMbv5lvKLDZLeYPAw==",
       "dev": true,
       "requires": {
         "@types/unist": "*"
       }
     },
     "@types/mime": {
-      "version": "3.0.1",
-      "resolved": "https://registry.npmjs.org/@types/mime/-/mime-3.0.1.tgz",
-      "integrity": "sha512-Y4XFY5VJAuw0FgAqPNd6NNoV44jbq9Bz2L7Rh/J6jLTiHBSBJa9fxqQIvkIld4GsoDOcCbvzOUAbLPsSKKg+uA==",
+      "version": "1.3.2",
+      "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.2.tgz",
+      "integrity": "sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw==",
       "dev": true
     },
     "@types/minimatch": {
       "integrity": "sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==",
       "dev": true
     },
+    "@types/ramda": {
+      "version": "0.29.0",
+      "resolved": "https://registry.npmjs.org/@types/ramda/-/ramda-0.29.0.tgz",
+      "integrity": "sha512-TY9eKsklU43CmAbFJPKDUyBjleZ4EFAkbJeQRF4e8byGkOw1CjDcwg5EGa0Bgf0Kgs9BE9OU4UzQWnQDHnvMtA==",
+      "requires": {
+        "types-ramda": "^0.29.1"
+      }
+    },
     "@types/range-parser": {
       "version": "1.2.4",
       "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.4.tgz",
       "dev": true
     },
     "@types/react": {
-      "version": "18.0.28",
-      "resolved": "https://registry.npmjs.org/@types/react/-/react-18.0.28.tgz",
-      "integrity": "sha512-RD0ivG1kEztNBdoAK7lekI9M+azSnitIn85h4iOiaLjaTrMjzslhaqCGaI4IyCJ1RljWiLCEu4jyrLLgqxBTew==",
+      "version": "18.2.6",
+      "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.6.tgz",
+      "integrity": "sha512-wRZClXn//zxCFW+ye/D2qY65UsYP1Fpex2YXorHc8awoNamkMZSvBxwxdYVInsHOZZd2Ppq8isnSzJL5Mpf8OA==",
       "requires": {
         "@types/prop-types": "*",
         "@types/scheduler": "*",
       "dev": true
     },
     "@types/scheduler": {
-      "version": "0.16.2",
-      "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.2.tgz",
-      "integrity": "sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew=="
+      "version": "0.16.3",
+      "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.3.tgz",
+      "integrity": "sha512-5cJ8CB4yAx7BH1oMvdU0Jh9lrEXyPkar6F9G/ERswkCuvP4KQZfZkSjcMbAICCpQTN4OuZn8tz0HiKv9TGZgrQ=="
+    },
+    "@types/send": {
+      "version": "0.17.1",
+      "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.1.tgz",
+      "integrity": "sha512-Cwo8LE/0rnvX7kIIa3QHCkcuF21c05Ayb0ZfxPiv0W8VRiZiNW/WuRupHKpqqGVGf7SUA44QSOUKaEd9lIrd/Q==",
+      "dev": true,
+      "requires": {
+        "@types/mime": "^1",
+        "@types/node": "*"
+      }
     },
     "@types/serve-index": {
       "version": "1.9.1",
       }
     },
     "@types/yargs": {
-      "version": "17.0.22",
-      "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.22.tgz",
-      "integrity": "sha512-pet5WJ9U8yPVRhkwuEIp5ktAeAqRZOq4UdAyWLWzxbtpyXnzbtLdKiXAjJzi/KLmPGS9wk86lUFWZFN6sISo4g==",
+      "version": "17.0.24",
+      "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.24.tgz",
+      "integrity": "sha512-6i0aC7jV6QzQB8ne1joVZ0eSFIstHsCrobmOtghM11yGlH0j43FKL2UhWdELkyps0zuf7qVTUVCCR+tgSlyLLw==",
       "dev": true,
       "requires": {
         "@types/yargs-parser": "*"
           }
         },
         "semver": {
-          "version": "7.3.8",
-          "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz",
-          "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==",
+          "version": "7.5.1",
+          "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.1.tgz",
+          "integrity": "sha512-Wvss5ivl8TMRZXXESstBA4uR5iXgEN/VC5/sOcuXdVLzcdkz4HWetIoRfG5gb5X+ij/G9rw9YoGn3QoQ8OCSpw==",
           "dev": true,
           "requires": {
             "lru-cache": "^6.0.0"
           }
         },
         "semver": {
-          "version": "7.3.8",
-          "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz",
-          "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==",
+          "version": "7.5.1",
+          "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.1.tgz",
+          "integrity": "sha512-Wvss5ivl8TMRZXXESstBA4uR5iXgEN/VC5/sOcuXdVLzcdkz4HWetIoRfG5gb5X+ij/G9rw9YoGn3QoQ8OCSpw==",
           "dev": true,
           "requires": {
             "lru-cache": "^6.0.0"
       "dev": true
     },
     "@yarnpkg/parsers": {
-      "version": "3.0.0-rc.39",
-      "resolved": "https://registry.npmjs.org/@yarnpkg/parsers/-/parsers-3.0.0-rc.39.tgz",
-      "integrity": "sha512-BsD4zq3EVmaHqlynXTceNuEFAtrfToV4fI9GA54moKlWZL4Eb2eXrhgf1jV2nMYx18SZxYO4Jc5Kf1sCDNRjOg==",
+      "version": "3.0.0-rc.44",
+      "resolved": "https://registry.npmjs.org/@yarnpkg/parsers/-/parsers-3.0.0-rc.44.tgz",
+      "integrity": "sha512-UVAt9Icc8zfGXioeYJ8XMoSTxOYVmlal2TRNxy9Uh91taS72kQFalK7LpIslcvEBKy4XtarmfIwcFIU3ZY64lw==",
       "dev": true,
       "requires": {
         "js-yaml": "^3.10.0",
           }
         },
         "tslib": {
-          "version": "2.5.0",
-          "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz",
-          "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==",
+          "version": "2.5.2",
+          "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.2.tgz",
+          "integrity": "sha512-5svOrSA2w3iGFDs1HibEVBGbDrAY82bFQ3HZ3ixB+88nsbsWQoKqDRb5UBYAUPEzbBn6dAp5gRNXglySbx1MlA==",
           "dev": true
         }
       }
       }
     },
     "acorn-import-assertions": {
-      "version": "1.8.0",
-      "resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.8.0.tgz",
-      "integrity": "sha512-m7VZ3jwz4eK6A4Vtt8Ew1/mNbP24u0FhdyfA7fSvnJR6LMdfOYnmuIrrJAgrYfYJ10F/otaHTtrtrtmHdMNzEw==",
+      "version": "1.9.0",
+      "resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.9.0.tgz",
+      "integrity": "sha512-cmMwop9x+8KFhxvKrKfPYmN6/pKTYYHBqLa0DfvVZcKMJWNyWLnaqND7dx/qn66R7ewM1UX5XMaDVP5wlVTaVA==",
       "dev": true
     },
     "acorn-jsx": {
       }
     },
     "agentkeepalive": {
-      "version": "4.2.1",
-      "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.2.1.tgz",
-      "integrity": "sha512-Zn4cw2NEqd+9fiSVWMscnjyQ1a8Yfoc5oBajLeo5w+YBHgDUcEBY2hS4YpTz6iN5f/2zQiktcuM6tS8x1p9dpA==",
+      "version": "4.3.0",
+      "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.3.0.tgz",
+      "integrity": "sha512-7Epl1Blf4Sy37j4v9f9FjICCh4+KAQOyXgHEwlyBiAQLbhKdq/i2QQU3amQalS/wPhdPzDXPL5DMR5bkn+YeWg==",
       "dev": true,
       "requires": {
         "debug": "^4.1.0",
-        "depd": "^1.1.2",
+        "depd": "^2.0.0",
         "humanize-ms": "^1.2.1"
-      },
-      "dependencies": {
-        "depd": {
-          "version": "1.1.2",
-          "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz",
-          "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==",
-          "dev": true
-        }
       }
     },
     "aggregate-error": {
         }
       }
     },
-    "ansi-gray": {
-      "version": "0.1.1",
-      "resolved": "https://registry.npmjs.org/ansi-gray/-/ansi-gray-0.1.1.tgz",
-      "integrity": "sha512-HrgGIZUl8h2EHuZaU9hTR/cU5nhKxpVE1V6kdGsQ8e4zirElJ5fvtfc8N7Q1oq1aatO275i8pUFUCpNWCAnVWw==",
-      "dev": true,
-      "requires": {
-        "ansi-wrap": "0.1.0"
-      }
-    },
     "ansi-html-community": {
       "version": "0.0.8",
       "resolved": "https://registry.npmjs.org/ansi-html-community/-/ansi-html-community-0.0.8.tgz",
         "color-convert": "^1.9.0"
       }
     },
-    "ansi-wrap": {
-      "version": "0.1.0",
-      "resolved": "https://registry.npmjs.org/ansi-wrap/-/ansi-wrap-0.1.0.tgz",
-      "integrity": "sha512-ZyznvL8k/FZeQHr2T6LzcJ/+vBApDnMNZvfVFy3At0knswWd6rJ3/0Hhmpu8oqa6C92npmozs890sX9Dl6q+Qw==",
-      "dev": true
-    },
     "any-promise": {
       "version": "1.3.0",
       "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz",
       "dev": true
     },
     "aproba": {
-      "version": "2.0.0",
-      "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz",
-      "integrity": "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==",
-      "dev": true
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz",
+      "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw=="
     },
     "arch": {
       "version": "2.2.0",
       "dev": true
     },
     "are-we-there-yet": {
-      "version": "3.0.1",
-      "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-3.0.1.tgz",
-      "integrity": "sha512-QZW4EDmGwlYur0Yyf/b2uGucHQMa8aFUP7eu9ddR73vvhFyt4V0Vl3QHPcTNJ8l6qYOBdxgXdnBXQrHilfRQBg==",
-      "dev": true,
+      "version": "1.1.7",
+      "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.7.tgz",
+      "integrity": "sha512-nxwy40TuMiUGqMyRHgCSWZ9FM4VAoRP4xUYSTv5ImRog+h9yISPbVH7H8fASCIzYn9wlEv4zvFL7uKDMCFQm3g==",
+      "optional": true,
       "requires": {
         "delegates": "^1.0.0",
-        "readable-stream": "^3.6.0"
+        "readable-stream": "^2.0.6"
       }
     },
     "arg": {
       "integrity": "sha512-sKpyeERZ02v1FeCZT8lrfJq5u6goHCtpTAzPwJYe7c8SPFOboNjNg1vz2L4VTn9T4PQxEx13TbXLmYUcS6Ug7Q==",
       "dev": true
     },
+    "array-buffer-byte-length": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.0.tgz",
+      "integrity": "sha512-LPuwb2P+NrQw3XhxGc36+XSvuBPopovXYTR9Ew++Du9Yb/bx5AzBfrIsBoj0EZUifjQU+sHL21sseZ3jerWO/A==",
+      "dev": true,
+      "requires": {
+        "call-bind": "^1.0.2",
+        "is-array-buffer": "^3.0.1"
+      }
+    },
     "array-differ": {
       "version": "3.0.0",
       "resolved": "https://registry.npmjs.org/array-differ/-/array-differ-3.0.0.tgz",
     "asynckit": {
       "version": "0.4.0",
       "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
-      "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
-      "dev": true
+      "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="
     },
     "at-least-node": {
       "version": "1.0.0",
         "normalize-range": "^0.1.2",
         "picocolors": "^1.0.0",
         "postcss-value-parser": "^4.2.0"
-      },
-      "dependencies": {
-        "caniuse-lite": {
-          "version": "1.0.30001467",
-          "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001467.tgz",
-          "integrity": "sha512-cEdN/5e+RPikvl9AHm4uuLXxeCNq8rFsQ+lPHTfe/OtypP3WwnVVbjn+6uBV7PaFL6xUFzTh+sSCOz1rKhcO+Q==",
-          "dev": true
-        }
       }
     },
     "available-typed-arrays": {
       "dev": true
     },
     "axios": {
-      "version": "1.3.4",
-      "resolved": "https://registry.npmjs.org/axios/-/axios-1.3.4.tgz",
-      "integrity": "sha512-toYm+Bsyl6VC5wSkfkbbNB6ROv7KY93PEBBL6xyDczaIHasAiv4wPqQ/c4RjoQzipxRD2W5g21cOqQulZ7rHwQ==",
-      "dev": true,
+      "version": "1.3.6",
+      "resolved": "https://registry.npmjs.org/axios/-/axios-1.3.6.tgz",
+      "integrity": "sha512-PEcdkk7JcdPiMDkvM4K6ZBRYq9keuVJsToxm2zQIM70Qqo2WHTdJZMXcG9X+RmRp2VPNUQC8W1RAGbgt6b1yMg==",
       "requires": {
         "follow-redirects": "^1.15.0",
         "form-data": "^4.0.0",
       "version": "4.1.0",
       "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz",
       "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==",
-      "dev": true,
       "requires": {
         "buffer": "^5.5.0",
         "inherits": "^2.0.4",
         "readable-stream": "^3.4.0"
+      },
+      "dependencies": {
+        "readable-stream": {
+          "version": "3.6.2",
+          "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
+          "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==",
+          "requires": {
+            "inherits": "^2.0.3",
+            "string_decoder": "^1.1.1",
+            "util-deprecate": "^1.0.1"
+          }
+        }
       }
     },
     "blink-diff": {
           "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
           "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
           "dev": true
+        },
+        "qs": {
+          "version": "6.11.0",
+          "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz",
+          "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==",
+          "dev": true,
+          "requires": {
+            "side-channel": "^1.0.4"
+          }
         }
       }
     },
           "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-0.0.1.tgz",
           "integrity": "sha512-BapA40NHICOS+USX9SN4tyhq+A2RrN/Ws5F0Z5aMHDp98Fl86lX8Oti8B7uN93L4Ifv4fHOEA+pQw87gmMO/lQ==",
           "dev": true
-        },
-        "readable-stream": {
-          "version": "2.3.8",
-          "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz",
-          "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==",
-          "dev": true,
-          "requires": {
-            "core-util-is": "~1.0.0",
-            "inherits": "~2.0.3",
-            "isarray": "~1.0.0",
-            "process-nextick-args": "~2.0.0",
-            "safe-buffer": "~5.1.1",
-            "string_decoder": "~1.1.1",
-            "util-deprecate": "~1.0.1"
-          },
-          "dependencies": {
-            "string_decoder": {
-              "version": "1.1.1",
-              "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
-              "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
-              "dev": true,
-              "requires": {
-                "safe-buffer": "~5.1.0"
-              }
-            }
-          }
-        },
-        "safe-buffer": {
-          "version": "5.1.2",
-          "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
-          "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
-          "dev": true
         }
       }
     },
         "parse-asn1": "^5.1.5",
         "readable-stream": "^3.6.0",
         "safe-buffer": "^5.2.0"
+      },
+      "dependencies": {
+        "readable-stream": {
+          "version": "3.6.2",
+          "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
+          "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==",
+          "dev": true,
+          "requires": {
+            "inherits": "^2.0.3",
+            "string_decoder": "^1.1.1",
+            "util-deprecate": "^1.0.1"
+          }
+        }
       }
     },
     "browserify-zlib": {
       "version": "5.7.1",
       "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz",
       "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==",
-      "dev": true,
       "requires": {
         "base64-js": "^1.3.1",
         "ieee754": "^1.1.13"
         "unique-filename": "^1.1.1"
       },
       "dependencies": {
+        "chownr": {
+          "version": "2.0.0",
+          "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz",
+          "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==",
+          "dev": true
+        },
         "lru-cache": {
           "version": "6.0.0",
           "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
       "integrity": "sha512-ceOhN1DL7Y4O6M0j9ICgmTYziV89WMd96SvSl0REd8PMgrY0B/WBOPoed5S1KUmJqXgUXh8gzSe6E3ae27upsQ=="
     },
     "caniuse-lite": {
-      "version": "1.0.30001458",
-      "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001458.tgz",
-      "integrity": "sha512-lQ1VlUUq5q9ro9X+5gOEyH7i3vm+AYVT1WDCVB69XOZ17KZRhnZ9J0Sqz7wTHQaLBJccNCHq8/Ww5LlOIZbB0w=="
+      "version": "1.0.30001489",
+      "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001489.tgz",
+      "integrity": "sha512-x1mgZEXK8jHIfAxm+xgdpHpk50IN3z3q3zP261/WS+uvePxW8izXuCu6AHz0lkuYTlATDehiZ/tNyYBdSQsOUQ=="
     },
     "caseless": {
       "version": "0.12.0",
           }
         },
         "domutils": {
-          "version": "3.0.1",
-          "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.0.1.tgz",
-          "integrity": "sha512-z08c1l761iKhDFtfXO04C7kTdPBLi41zwOZl00WS8b5eiaebNpY00HKbztwBq+e3vyqWNwWF3mP9YLUeqIrF+Q==",
+          "version": "3.1.0",
+          "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.1.0.tgz",
+          "integrity": "sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA==",
           "dev": true,
           "requires": {
             "dom-serializer": "^2.0.0",
             "domelementtype": "^2.3.0",
-            "domhandler": "^5.0.1"
+            "domhandler": "^5.0.3"
           }
         },
         "entities": {
-          "version": "4.4.0",
-          "resolved": "https://registry.npmjs.org/entities/-/entities-4.4.0.tgz",
-          "integrity": "sha512-oYp7156SP8LkeGD0GF85ad1X9Ai79WtRsZ2gxJqtBuzH+98YUV6jkHEKlZkMbcrjJjIVJNIDP/3WL9wQkoPbWA==",
+          "version": "4.5.0",
+          "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
+          "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
           "dev": true
         },
         "parse5": {
           }
         },
         "domutils": {
-          "version": "3.0.1",
-          "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.0.1.tgz",
-          "integrity": "sha512-z08c1l761iKhDFtfXO04C7kTdPBLi41zwOZl00WS8b5eiaebNpY00HKbztwBq+e3vyqWNwWF3mP9YLUeqIrF+Q==",
+          "version": "3.1.0",
+          "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.1.0.tgz",
+          "integrity": "sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA==",
           "dev": true,
           "requires": {
             "dom-serializer": "^2.0.0",
             "domelementtype": "^2.3.0",
-            "domhandler": "^5.0.1"
+            "domhandler": "^5.0.3"
           }
         },
         "entities": {
-          "version": "4.4.0",
-          "resolved": "https://registry.npmjs.org/entities/-/entities-4.4.0.tgz",
-          "integrity": "sha512-oYp7156SP8LkeGD0GF85ad1X9Ai79WtRsZ2gxJqtBuzH+98YUV6jkHEKlZkMbcrjJjIVJNIDP/3WL9wQkoPbWA==",
+          "version": "4.5.0",
+          "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
+          "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
           "dev": true
         }
       }
       }
     },
     "chownr": {
-      "version": "2.0.0",
-      "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz",
-      "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==",
-      "dev": true
+      "version": "1.1.4",
+      "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz",
+      "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==",
+      "optional": true
     },
     "chrome-remote-interface": {
       "version": "0.31.1",
       }
     },
     "cli-spinners": {
-      "version": "2.7.0",
-      "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.7.0.tgz",
-      "integrity": "sha512-qu3pN8Y3qHNgE2AFweciB1IfMnmZ/fsNTEE+NOFjmGB2F/7rLhnhzppvpCnN4FovtP26k8lHyy9ptEbNwWFLzw==",
+      "version": "2.9.0",
+      "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.0.tgz",
+      "integrity": "sha512-4/aL9X3Wh0yiMQlE+eeRhWP6vclO3QRtw1JHKIT0FFUs5FjpFmESqtMvYZ0+lbzBw900b95mS0hohy+qn2VK/g==",
       "dev": true
     },
     "cli-table": {
     "code-point-at": {
       "version": "1.1.0",
       "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz",
-      "integrity": "sha512-RpAVKQA5T63xEj6/giIbUEtZwJ4UFIc3ZtvEkiaUERylqe8xb5IvqcgOurZLahv93CLKfxcw5YI+DZcUBRyLXA==",
-      "dev": true
+      "integrity": "sha512-RpAVKQA5T63xEj6/giIbUEtZwJ4UFIc3ZtvEkiaUERylqe8xb5IvqcgOurZLahv93CLKfxcw5YI+DZcUBRyLXA=="
     },
     "coffeeify": {
       "version": "3.0.1",
       "dev": true
     },
     "colorette": {
-      "version": "2.0.19",
-      "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.19.tgz",
-      "integrity": "sha512-3tlv/dIP7FWvj3BsbHrGLJ6l/oKh1O3TcgBqMn+yyCagOxc23fyzDS6HypQbgxWbkpDnf52p1LuR4eWDQ/K9WQ==",
+      "version": "2.0.20",
+      "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz",
+      "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==",
       "dev": true
     },
     "colors": {
       "version": "1.0.8",
       "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
       "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
-      "dev": true,
       "requires": {
         "delayed-stream": "~1.0.0"
       }
         "inherits": "^2.0.3",
         "readable-stream": "^2.2.2",
         "typedarray": "^0.0.6"
-      },
-      "dependencies": {
-        "readable-stream": {
-          "version": "2.3.8",
-          "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz",
-          "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==",
-          "dev": true,
-          "requires": {
-            "core-util-is": "~1.0.0",
-            "inherits": "~2.0.3",
-            "isarray": "~1.0.0",
-            "process-nextick-args": "~2.0.0",
-            "safe-buffer": "~5.1.1",
-            "string_decoder": "~1.1.1",
-            "util-deprecate": "~1.0.1"
-          }
-        },
-        "safe-buffer": {
-          "version": "5.1.2",
-          "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
-          "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
-          "dev": true
-        },
-        "string_decoder": {
-          "version": "1.1.1",
-          "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
-          "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
-          "dev": true,
-          "requires": {
-            "safe-buffer": "~5.1.0"
-          }
-        }
       }
     },
     "connect": {
     "console-control-strings": {
       "version": "1.1.0",
       "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz",
-      "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==",
-      "dev": true
+      "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ=="
     },
     "constants-browserify": {
       "version": "1.0.0",
           }
         },
         "schema-utils": {
-          "version": "4.0.0",
-          "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.0.0.tgz",
-          "integrity": "sha512-1edyXKgh6XnJsJSQ8mKWXnN/BVaIbFMLpouRUrXgVq7WYne5kw3MW7UPhO44uRXQSIpTSXoJbmrR2X0w9kUTyg==",
+          "version": "4.0.1",
+          "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.0.1.tgz",
+          "integrity": "sha512-lELhBAAly9NowEsX0yZBlw9ahZG+sK/1RJ21EpzdYHKEs13Vku3LJ+MIPhh4sMs0oCCeufZQEQbMekiA4vuVIQ==",
           "dev": true,
           "requires": {
             "@types/json-schema": "^7.0.9",
-            "ajv": "^8.8.0",
+            "ajv": "^8.9.0",
             "ajv-formats": "^2.1.1",
-            "ajv-keywords": "^5.0.0"
+            "ajv-keywords": "^5.1.0"
           }
         }
       }
     },
     "core-js": {
-      "version": "3.29.0",
-      "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.29.0.tgz",
-      "integrity": "sha512-VG23vuEisJNkGl6XQmFJd3rEG/so/CNatqeE+7uZAwTSwFeB/qaO0be8xZYUNWprJ/GIwL8aMt9cj1kvbpTZhg=="
+      "version": "3.30.2",
+      "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.30.2.tgz",
+      "integrity": "sha512-uBJiDmwqsbJCWHAwjrx3cvjbMXP7xD72Dmsn5LOJpiRmE3WbBbN5rCqQ2Qh6Ek6/eOrjlWngEynBWo4VxerQhg=="
     },
     "core-js-compat": {
-      "version": "3.29.0",
-      "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.29.0.tgz",
-      "integrity": "sha512-ScMn3uZNAFhK2DGoEfErguoiAHhV2Ju+oJo/jK08p7B3f3UhocUrCCkTvnZaiS+edl5nlIoiBXKcwMc6elv4KQ==",
+      "version": "3.30.2",
+      "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.30.2.tgz",
+      "integrity": "sha512-nriW1nuJjUgvkEjIot1Spwakz52V9YkYHZAQG6A1eCgC8AA1p0zngrQEP9R0+V6hji5XilWKG1Bd0YRppmGimA==",
       "dev": true,
       "requires": {
         "browserslist": "^4.21.5"
       }
     },
     "core-js-pure": {
-      "version": "3.29.0",
-      "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.29.0.tgz",
-      "integrity": "sha512-v94gUjN5UTe1n0yN/opTihJ8QBWD2O8i19RfTZR7foONPWArnjB96QA/wk5ozu1mm6ja3udQCzOzwQXTxi3xOQ=="
+      "version": "3.30.2",
+      "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.30.2.tgz",
+      "integrity": "sha512-p/npFUJXXBkCCTIlEGBdghofn00jWG6ZOtdoIXSJmAu2QBvN0IqpZXWweOytcwE6cfx8ZvVUy1vw8zxhe4Y2vg=="
     },
     "core-util-is": {
       "version": "1.0.3",
       "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz",
-      "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==",
-      "dev": true
+      "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ=="
     },
     "cors": {
       "version": "2.8.5",
       }
     },
     "cross-fetch": {
-      "version": "3.1.5",
-      "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.5.tgz",
-      "integrity": "sha512-lvb1SBsI0Z7GDwmuid+mU3kWVBwTVUbe7S0H52yaaAdQOXq2YktTCZdlAcNKFzE6QtRz0snpw9bNiPeOIkkQvw==",
+      "version": "3.1.6",
+      "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.6.tgz",
+      "integrity": "sha512-riRvo06crlE8HiqOwIpQhxwdOk4fOeR7FVM/wXoxchFEqMNUjvbs3bfo4OTgMEMHzppd4DxFBDbyySj8Cv781g==",
       "requires": {
-        "node-fetch": "2.6.7"
+        "node-fetch": "^2.6.11"
       }
     },
     "cross-spawn": {
           }
         },
         "semver": {
-          "version": "7.3.8",
-          "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz",
-          "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==",
+          "version": "7.5.1",
+          "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.1.tgz",
+          "integrity": "sha512-Wvss5ivl8TMRZXXESstBA4uR5iXgEN/VC5/sOcuXdVLzcdkz4HWetIoRfG5gb5X+ij/G9rw9YoGn3QoQ8OCSpw==",
           "dev": true,
           "requires": {
             "lru-cache": "^6.0.0"
       }
     },
     "csstype": {
-      "version": "3.1.1",
-      "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.1.tgz",
-      "integrity": "sha512-DJR/VvkAvSZW9bTouZue2sSxDwdTN92uHjqeKVm+0dAqdfNykRzQ95tay8aXMBAAPpUiq4Qcug2L7neoRh2Egw=="
+      "version": "3.1.2",
+      "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.2.tgz",
+      "integrity": "sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ=="
     },
     "cucumber": {
       "version": "4.2.1",
       "dev": true
     },
     "cypress": {
-      "version": "9.7.0",
-      "resolved": "https://registry.npmjs.org/cypress/-/cypress-9.7.0.tgz",
-      "integrity": "sha512-+1EE1nuuuwIt/N1KXRR2iWHU+OiIt7H28jJDyyI4tiUftId/DrXYEwoDa5+kH2pki1zxnA0r6HrUGHV5eLbF5Q==",
+      "version": "10.11.0",
+      "resolved": "https://registry.npmjs.org/cypress/-/cypress-10.11.0.tgz",
+      "integrity": "sha512-lsaE7dprw5DoXM00skni6W5ElVVLGAdRUUdZjX2dYsGjbY/QnpzWZ95Zom1mkGg0hAaO/QVTZoFVS7Jgr/GUPA==",
       "dev": true,
       "requires": {
         "@cypress/request": "^2.88.10",
         "dayjs": "^1.10.4",
         "debug": "^4.3.2",
         "enquirer": "^2.3.6",
-        "eventemitter2": "^6.4.3",
+        "eventemitter2": "6.4.7",
         "execa": "4.1.0",
         "executable": "^4.1.1",
         "extract-zip": "2.0.1",
       },
       "dependencies": {
         "@types/node": {
-          "version": "14.18.37",
-          "resolved": "https://registry.npmjs.org/@types/node/-/node-14.18.37.tgz",
-          "integrity": "sha512-7GgtHCs/QZrBrDzgIJnQtuSvhFSwhyYSI2uafSwZoNt1iOGhEN5fwNrQMjtONyHm9+/LoA4453jH0CMYcr06Pg==",
+          "version": "14.18.47",
+          "resolved": "https://registry.npmjs.org/@types/node/-/node-14.18.47.tgz",
+          "integrity": "sha512-OuJi8bIng4wYHHA3YpKauL58dZrPxro3d0tabPHyiNF8rKfGKuVfr83oFlPLmKri1cX+Z3cJP39GXmnqkP11Gw==",
           "dev": true
         },
         "ansi-styles": {
           "integrity": "sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==",
           "dev": true
         },
+        "eventemitter2": {
+          "version": "6.4.7",
+          "resolved": "https://registry.npmjs.org/eventemitter2/-/eventemitter2-6.4.7.tgz",
+          "integrity": "sha512-tYUSVOGeQPKt/eC1ABfhHy5Xd96N3oIijJvN3O9+TsC28T5V9yX9oEfEK5faP0EFSNVOG97qtAS68GBrQB2hDg==",
+          "dev": true
+        },
         "execa": {
           "version": "4.1.0",
           "resolved": "https://registry.npmjs.org/execa/-/execa-4.1.0.tgz",
           "dev": true
         },
         "semver": {
-          "version": "7.3.8",
-          "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz",
-          "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==",
+          "version": "7.5.1",
+          "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.1.tgz",
+          "integrity": "sha512-Wvss5ivl8TMRZXXESstBA4uR5iXgEN/VC5/sOcuXdVLzcdkz4HWetIoRfG5gb5X+ij/G9rw9YoGn3QoQ8OCSpw==",
           "dev": true,
           "requires": {
             "lru-cache": "^6.0.0"
       "integrity": "sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ==",
       "dev": true
     },
+    "decompress-response": {
+      "version": "4.2.1",
+      "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-4.2.1.tgz",
+      "integrity": "sha512-jOSne2qbyE+/r8G1VU+G/82LBs2Fs4LAsTiLSHOCOMZQl2OKZ6i8i4IyHemTe+/yIXOtTcRQMzPcgyhoFlqPkw==",
+      "optional": true,
+      "requires": {
+        "mimic-response": "^2.0.0"
+      }
+    },
     "dedent": {
       "version": "0.7.0",
       "resolved": "https://registry.npmjs.org/dedent/-/dedent-0.7.0.tgz",
       "dev": true
     },
     "deepmerge": {
-      "version": "4.2.2",
-      "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz",
-      "integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg=="
+      "version": "4.3.1",
+      "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz",
+      "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A=="
     },
     "default-gateway": {
       "version": "6.0.3",
     "delayed-stream": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
-      "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
-      "dev": true
+      "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ=="
     },
     "delegates": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz",
-      "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==",
-      "dev": true
+      "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ=="
     },
     "depd": {
       "version": "2.0.0",
       "integrity": "sha512-DtCOLG98P007x7wiiOmfI0fi3eIKyWiLTGJ2MDnVi/E04lWGbf+JzrRHMm0rgIIZJGtHpKpbVgLWHrv8xXpc3Q==",
       "dev": true
     },
+    "detect-libc": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz",
+      "integrity": "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==",
+      "optional": true
+    },
     "detect-newline": {
       "version": "3.1.0",
       "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz",
       }
     },
     "dot": {
-      "version": "1.1.3",
-      "resolved": "https://registry.npmjs.org/dot/-/dot-1.1.3.tgz",
-      "integrity": "sha512-/nt74Rm+PcfnirXGEdhZleTwGC2LMnuKTeeTIlI82xb5loBBoXNYzr2ezCroPSMtilK8EZIfcNZwOcHN+ib1Lg==",
+      "version": "2.0.0-beta.1",
+      "resolved": "https://registry.npmjs.org/dot/-/dot-2.0.0-beta.1.tgz",
+      "integrity": "sha512-kxM7fSnNQTXOmaeGuBSXM8O3fEsBb7XSDBllkGbRwa0lJSJTxxDE/4eSNGLKZUmlFw0f1vJ5qSV2BljrgQtgIA==",
       "dev": true
     },
     "dotenv": {
       "dev": true,
       "requires": {
         "readable-stream": "^2.0.2"
-      },
-      "dependencies": {
-        "readable-stream": {
-          "version": "2.3.8",
-          "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz",
-          "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==",
-          "dev": true,
-          "requires": {
-            "core-util-is": "~1.0.0",
-            "inherits": "~2.0.3",
-            "isarray": "~1.0.0",
-            "process-nextick-args": "~2.0.0",
-            "safe-buffer": "~5.1.1",
-            "string_decoder": "~1.1.1",
-            "util-deprecate": "~1.0.1"
-          }
-        },
-        "safe-buffer": {
-          "version": "5.1.2",
-          "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
-          "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
-          "dev": true
-        },
-        "string_decoder": {
-          "version": "1.1.1",
-          "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
-          "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
-          "dev": true,
-          "requires": {
-            "safe-buffer": "~5.1.0"
-          }
-        }
       }
     },
     "duration": {
       "dev": true
     },
     "ejs": {
-      "version": "3.1.8",
-      "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.8.tgz",
-      "integrity": "sha512-/sXZeMlhS0ArkfX2Aw780gJzXSMPnKjtspYZv+f3NiKLlubezAHDU5+9xz6gd3/NhG3txQCo6xlglmTS+oTGEQ==",
+      "version": "3.1.9",
+      "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.9.tgz",
+      "integrity": "sha512-rC+QVNMJWv+MtPgkt0y+0rVEIdbtxVADApW9JXrUVlzHetgcyczP/E7DJmWJ4fJCZF2cPcBk0laWO9ZHMG3DmQ==",
       "dev": true,
       "requires": {
         "jake": "^10.8.5"
       }
     },
     "electron-to-chromium": {
-      "version": "1.4.315",
-      "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.315.tgz",
-      "integrity": "sha512-ndBQYz3Eyy3rASjjQ9poMJGoAlsZ/aZnq6GBsGL4w/4sWIAwiUHVSsMuADbxa8WJw7pZ0oxLpGbtoDt4vRTdCg=="
+      "version": "1.4.404",
+      "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.404.tgz",
+      "integrity": "sha512-te57sWvQdpxmyd1GiswaodKdXdPgn9cN4ht8JlNa04QgtrfnUdWEo1261rY2vaC6TKaiHn0E7QerJWPKFCvMVw=="
     },
     "elliptic": {
       "version": "6.5.4",
       "version": "1.4.4",
       "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz",
       "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==",
-      "dev": true,
       "requires": {
         "once": "^1.4.0"
       }
     },
     "enhanced-resolve": {
-      "version": "5.12.0",
-      "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.12.0.tgz",
-      "integrity": "sha512-QHTXI/sZQmko1cbDoNAa3mJ5qhWUUNAq3vR0/YiD379fWQrcfuoX1+HW2S0MTt7XmoPLapdaDKUtelUSPic7hQ==",
+      "version": "5.14.0",
+      "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.14.0.tgz",
+      "integrity": "sha512-+DCows0XNwLDcUhbFJPdlQEVnT2zXlCv7hPxemTz86/O+B/hCQ+mb7ydkPKiflpVraqLPCAfu7lDy+hBXueojw==",
       "dev": true,
       "requires": {
         "graceful-fs": "^4.2.4",
       }
     },
     "es-abstract": {
-      "version": "1.21.1",
-      "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.21.1.tgz",
-      "integrity": "sha512-QudMsPOz86xYz/1dG1OuGBKOELjCh99IIWHLzy5znUB6j8xG2yMA7bfTV86VSqKF+Y/H08vQPR+9jyXpuC6hfg==",
+      "version": "1.21.2",
+      "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.21.2.tgz",
+      "integrity": "sha512-y/B5POM2iBnIxCiernH1G7rC9qQoM77lLIMQLuob0zhp8C56Po81+2Nj0WFKnd0pNReDTnkYryc+zhOzpEIROg==",
       "dev": true,
       "requires": {
+        "array-buffer-byte-length": "^1.0.0",
         "available-typed-arrays": "^1.0.5",
         "call-bind": "^1.0.2",
         "es-set-tostringtag": "^2.0.1",
         "es-to-primitive": "^1.2.1",
-        "function-bind": "^1.1.1",
         "function.prototype.name": "^1.1.5",
-        "get-intrinsic": "^1.1.3",
+        "get-intrinsic": "^1.2.0",
         "get-symbol-description": "^1.0.0",
         "globalthis": "^1.0.3",
         "gopd": "^1.0.1",
         "has-property-descriptors": "^1.0.0",
         "has-proto": "^1.0.1",
         "has-symbols": "^1.0.3",
-        "internal-slot": "^1.0.4",
-        "is-array-buffer": "^3.0.1",
+        "internal-slot": "^1.0.5",
+        "is-array-buffer": "^3.0.2",
         "is-callable": "^1.2.7",
         "is-negative-zero": "^2.0.2",
         "is-regex": "^1.1.4",
         "is-string": "^1.0.7",
         "is-typed-array": "^1.1.10",
         "is-weakref": "^1.0.2",
-        "object-inspect": "^1.12.2",
+        "object-inspect": "^1.12.3",
         "object-keys": "^1.1.1",
         "object.assign": "^4.1.4",
         "regexp.prototype.flags": "^1.4.3",
         "safe-regex-test": "^1.0.0",
+        "string.prototype.trim": "^1.2.7",
         "string.prototype.trimend": "^1.0.6",
         "string.prototype.trimstart": "^1.0.6",
         "typed-array-length": "^1.0.4",
         "ext": "^1.1.2"
       }
     },
-    "esbuild": {
-      "version": "0.14.22",
-      "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.14.22.tgz",
-      "integrity": "sha512-CjFCFGgYtbFOPrwZNJf7wsuzesx8kqwAffOlbYcFDLFuUtP8xloK1GH+Ai13Qr0RZQf9tE7LMTHJ2iVGJ1SKZA==",
-      "dev": true,
-      "optional": true,
-      "requires": {
-        "esbuild-android-arm64": "0.14.22",
-        "esbuild-darwin-64": "0.14.22",
-        "esbuild-darwin-arm64": "0.14.22",
-        "esbuild-freebsd-64": "0.14.22",
-        "esbuild-freebsd-arm64": "0.14.22",
-        "esbuild-linux-32": "0.14.22",
-        "esbuild-linux-64": "0.14.22",
-        "esbuild-linux-arm": "0.14.22",
-        "esbuild-linux-arm64": "0.14.22",
-        "esbuild-linux-mips64le": "0.14.22",
-        "esbuild-linux-ppc64le": "0.14.22",
-        "esbuild-linux-riscv64": "0.14.22",
-        "esbuild-linux-s390x": "0.14.22",
-        "esbuild-netbsd-64": "0.14.22",
-        "esbuild-openbsd-64": "0.14.22",
-        "esbuild-sunos-64": "0.14.22",
-        "esbuild-windows-32": "0.14.22",
-        "esbuild-windows-64": "0.14.22",
-        "esbuild-windows-arm64": "0.14.22"
-      }
-    },
     "esbuild-android-arm64": {
       "version": "0.14.22",
       "resolved": "https://registry.npmjs.org/esbuild-android-arm64/-/esbuild-android-arm64-0.14.22.tgz",
           "dev": true
         },
         "eslint-scope": {
-          "version": "7.1.1",
-          "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.1.1.tgz",
-          "integrity": "sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw==",
+          "version": "7.2.0",
+          "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.0.tgz",
+          "integrity": "sha512-DYj5deGlHBfMt15J7rdtyKNq/Nqlv5KfU4iodrQ019XESsRnwXH9KAE0y3cwtUHDo2ob7CypAnCqefh6vioWRw==",
           "dev": true,
           "requires": {
             "esrecurse": "^4.3.0",
           "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==",
           "dev": true
         },
+        "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==",
+          "dev": true
+        },
         "supports-color": {
           "version": "7.2.0",
           "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
       }
     },
     "eslint-visitor-keys": {
-      "version": "3.3.0",
-      "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz",
-      "integrity": "sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==",
+      "version": "3.4.1",
+      "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.1.tgz",
+      "integrity": "sha512-pZnmmLwYzf+kWaM/Qgrvpen51upAktaaiI01nsJD/Yr3lMOdNtq0cxkrrg16w64VtisN6okbs7Q8AfGqj4c9fA==",
       "dev": true
     },
     "espree": {
-      "version": "9.4.1",
-      "resolved": "https://registry.npmjs.org/espree/-/espree-9.4.1.tgz",
-      "integrity": "sha512-XwctdmTO6SIvCzd9810yyNzIrOrqNYV9Koizx4C/mRhf9uq0o4yHoCEU/670pOxOL/MSraektvSAji79kX90Vg==",
+      "version": "9.5.2",
+      "resolved": "https://registry.npmjs.org/espree/-/espree-9.5.2.tgz",
+      "integrity": "sha512-7OASN1Wma5fum5SrNhFMAMJxOUAbhyfQ8dQ//PJaJbNw0URTPWqIghHWt1MmAANKhHZIYOHruW4Kw4ruUWOdGw==",
       "dev": true,
       "requires": {
         "acorn": "^8.8.0",
         "acorn-jsx": "^5.3.2",
-        "eslint-visitor-keys": "^3.3.0"
+        "eslint-visitor-keys": "^3.4.1"
       }
     },
     "esprima": {
         }
       }
     },
+    "expand-template": {
+      "version": "2.0.3",
+      "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz",
+      "integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==",
+      "optional": true
+    },
     "expand-tilde": {
       "version": "2.0.2",
       "resolved": "https://registry.npmjs.org/expand-tilde/-/expand-tilde-2.0.2.tgz",
           "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
           "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
           "dev": true
+        },
+        "qs": {
+          "version": "6.11.0",
+          "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz",
+          "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==",
+          "dev": true,
+          "requires": {
+            "side-channel": "^1.0.4"
+          }
         }
       }
     },
     "follow-redirects": {
       "version": "1.15.2",
       "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz",
-      "integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==",
-      "dev": true
+      "integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA=="
     },
     "for-each": {
       "version": "0.3.3",
       "version": "4.0.0",
       "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
       "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==",
-      "dev": true,
       "requires": {
         "asynckit": "^0.4.0",
         "combined-stream": "^1.0.8",
       }
     },
     "form-data-encoder": {
-      "version": "1.7.2",
-      "resolved": "https://registry.npmjs.org/form-data-encoder/-/form-data-encoder-1.7.2.tgz",
-      "integrity": "sha512-qfqtYan3rxrnCk1VYaA4H+Ms9xdpPqvLZa6xmMgFvhO32x7/3J/ExcTd6qpxM0vH2GdMI+poehyBZvqfMTto8A=="
+      "version": "1.9.0",
+      "resolved": "https://registry.npmjs.org/form-data-encoder/-/form-data-encoder-1.9.0.tgz",
+      "integrity": "sha512-rahaRMkN8P8d/tgK/BLPX+WBVM27NbvdXBxqQujBtkDAIFspaRqN7Od7lfdGQA6KAD+f82fYCLBq1ipvcu8qLw=="
     },
     "format": {
       "version": "0.2.2",
     "fs-constants": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz",
-      "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==",
-      "dev": true
+      "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow=="
     },
     "fs-extra": {
       "version": "9.1.0",
       "dev": true
     },
     "gauge": {
-      "version": "4.0.4",
-      "resolved": "https://registry.npmjs.org/gauge/-/gauge-4.0.4.tgz",
-      "integrity": "sha512-f9m+BEN5jkg6a0fZjleidjN51VE1X+mPFQ2DJ0uv1V39oCLCbsGe6yjbBnp7eK7z/+GAon99a3nHuqbuuthyPg==",
-      "dev": true,
-      "requires": {
-        "aproba": "^1.0.3 || ^2.0.0",
-        "color-support": "^1.1.3",
-        "console-control-strings": "^1.1.0",
-        "has-unicode": "^2.0.1",
-        "signal-exit": "^3.0.7",
-        "string-width": "^4.2.3",
-        "strip-ansi": "^6.0.1",
-        "wide-align": "^1.1.5"
+      "version": "2.7.4",
+      "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz",
+      "integrity": "sha512-14x4kjc6lkD3ltw589k0NrPD6cCNTD6CWoVUNpB85+DrtONoZn+Rug6xZU5RvSC4+TZPxA5AnBibQYAvZn41Hg==",
+      "optional": true,
+      "requires": {
+        "aproba": "^1.0.3",
+        "console-control-strings": "^1.0.0",
+        "has-unicode": "^2.0.0",
+        "object-assign": "^4.1.0",
+        "signal-exit": "^3.0.0",
+        "string-width": "^1.0.1",
+        "strip-ansi": "^3.0.1",
+        "wide-align": "^1.1.0"
+      },
+      "dependencies": {
+        "ansi-regex": {
+          "version": "2.1.1",
+          "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz",
+          "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==",
+          "optional": true
+        },
+        "is-fullwidth-code-point": {
+          "version": "1.0.0",
+          "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz",
+          "integrity": "sha512-1pqUqRjkhPJ9miNq9SwMfdvi6lBJcd6eFxvfaivQhaH3SgisfiuudvFntdKOmxuee/77l+FPjKrQjWvmPjWrRw==",
+          "optional": true,
+          "requires": {
+            "number-is-nan": "^1.0.0"
+          }
+        },
+        "string-width": {
+          "version": "1.0.2",
+          "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz",
+          "integrity": "sha512-0XsVpQLnVCXHJfyEs8tC0zpTVIr5PKKsQtkT29IwupnPTjtPmQ3xT/4yCREF9hYkV/3M3kzcUTSAZT6a6h81tw==",
+          "optional": true,
+          "requires": {
+            "code-point-at": "^1.0.0",
+            "is-fullwidth-code-point": "^1.0.0",
+            "strip-ansi": "^3.0.0"
+          }
+        },
+        "strip-ansi": {
+          "version": "3.0.1",
+          "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
+          "integrity": "sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg==",
+          "optional": true,
+          "requires": {
+            "ansi-regex": "^2.0.0"
+          }
+        }
       }
     },
     "gensync": {
       "dev": true
     },
     "get-intrinsic": {
-      "version": "1.2.0",
-      "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.0.tgz",
-      "integrity": "sha512-L049y6nFOuom5wGyRc3/gdTLO94dySVKRACj1RmJZBQXlbTMhtNIgkWkUHq+jYmZvKf14EW1EoJnnjbmoHij0Q==",
+      "version": "1.2.1",
+      "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.1.tgz",
+      "integrity": "sha512-2DcsyfABl+gVHEfCOaTrWgyt+tb6MSEGmKq+kI5HwLbIYgjgmMcV8KQ41uaKz1xxUcn9tJtgFbQUEVcEbd0FYw==",
       "requires": {
         "function-bind": "^1.1.1",
         "has": "^1.0.3",
+        "has-proto": "^1.0.1",
         "has-symbols": "^1.0.3"
       }
     },
         }
       }
     },
+    "github-from-package": {
+      "version": "0.0.0",
+      "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz",
+      "integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==",
+      "optional": true
+    },
     "glob": {
       "version": "7.2.0",
       "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz",
       "dev": true,
       "requires": {
         "ini": "2.0.0"
+      },
+      "dependencies": {
+        "ini": {
+          "version": "2.0.0",
+          "resolved": "https://registry.npmjs.org/ini/-/ini-2.0.0.tgz",
+          "integrity": "sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA==",
+          "dev": true
+        }
       }
     },
     "global-modules": {
         "which": "^1.2.14"
       },
       "dependencies": {
-        "ini": {
-          "version": "1.3.8",
-          "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz",
-          "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==",
-          "dev": true
-        },
         "which": {
           "version": "1.3.1",
           "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz",
       }
     },
     "graceful-fs": {
-      "version": "4.2.10",
-      "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz",
-      "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==",
+      "version": "4.2.11",
+      "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
+      "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
       "dev": true
     },
     "growl": {
     "has-proto": {
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz",
-      "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==",
-      "dev": true
+      "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg=="
     },
     "has-symbols": {
       "version": "1.0.3",
     "has-unicode": {
       "version": "2.0.1",
       "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz",
-      "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==",
-      "dev": true
+      "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ=="
     },
     "has-value": {
       "version": "1.0.0",
         "inherits": "^2.0.4",
         "readable-stream": "^3.6.0",
         "safe-buffer": "^5.2.0"
+      },
+      "dependencies": {
+        "readable-stream": {
+          "version": "3.6.2",
+          "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
+          "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==",
+          "dev": true,
+          "requires": {
+            "inherits": "^2.0.3",
+            "string_decoder": "^1.1.1",
+            "util-deprecate": "^1.0.1"
+          }
+        }
       }
     },
     "hash.js": {
         "obuf": "^1.0.0",
         "readable-stream": "^2.0.1",
         "wbuf": "^1.1.0"
-      },
-      "dependencies": {
-        "readable-stream": {
-          "version": "2.3.8",
-          "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz",
-          "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==",
-          "dev": true,
-          "requires": {
-            "core-util-is": "~1.0.0",
-            "inherits": "~2.0.3",
-            "isarray": "~1.0.0",
-            "process-nextick-args": "~2.0.0",
-            "safe-buffer": "~5.1.1",
-            "string_decoder": "~1.1.1",
-            "util-deprecate": "~1.0.1"
-          }
-        },
-        "safe-buffer": {
-          "version": "5.1.2",
-          "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
-          "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
-          "dev": true
-        },
-        "string_decoder": {
-          "version": "1.1.1",
-          "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
-          "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
-          "dev": true,
-          "requires": {
-            "safe-buffer": "~5.1.0"
-          }
-        }
       }
     },
     "html-encoding-sniffer": {
       }
     },
     "html-tags": {
-      "version": "3.2.0",
-      "resolved": "https://registry.npmjs.org/html-tags/-/html-tags-3.2.0.tgz",
-      "integrity": "sha512-vy7ClnArOZwCnqZgvv+ddgHgJiAFXe3Ge9ML5/mBctVJoUoYPCdxVucOywjDARn6CVoh3dRSFdPHy2sX80L0Wg==",
+      "version": "3.3.1",
+      "resolved": "https://registry.npmjs.org/html-tags/-/html-tags-3.3.1.tgz",
+      "integrity": "sha512-ztqyC3kLto0e9WbNp0aeP+M3kTt+nbaIveGmUxAtZa+8iFgKLUOD4YKM5j+f3QD89bra7UeumolZHKuOXnTmeQ==",
       "dev": true
     },
     "htmlescape": {
           "requires": {
             "asap": "~2.0.6"
           }
+        },
+        "readable-stream": {
+          "version": "3.6.2",
+          "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
+          "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==",
+          "dev": true,
+          "requires": {
+            "inherits": "^2.0.3",
+            "string_decoder": "^1.1.1",
+            "util-deprecate": "^1.0.1"
+          }
         }
       }
     },
       }
     },
     "htmlparser2": {
-      "version": "8.0.1",
-      "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-8.0.1.tgz",
-      "integrity": "sha512-4lVbmc1diZC7GUJQtRQ5yBAeUCL1exyMwmForWkRLnwyzWBFxN633SALPMGYaWZvKe9j1pRZJpauvmxENSp/EA==",
+      "version": "8.0.2",
+      "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-8.0.2.tgz",
+      "integrity": "sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==",
       "dev": true,
       "requires": {
         "domelementtype": "^2.3.0",
-        "domhandler": "^5.0.2",
+        "domhandler": "^5.0.3",
         "domutils": "^3.0.1",
-        "entities": "^4.3.0"
+        "entities": "^4.4.0"
       },
       "dependencies": {
         "dom-serializer": {
           }
         },
         "domutils": {
-          "version": "3.0.1",
-          "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.0.1.tgz",
-          "integrity": "sha512-z08c1l761iKhDFtfXO04C7kTdPBLi41zwOZl00WS8b5eiaebNpY00HKbztwBq+e3vyqWNwWF3mP9YLUeqIrF+Q==",
+          "version": "3.1.0",
+          "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.1.0.tgz",
+          "integrity": "sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA==",
           "dev": true,
           "requires": {
             "dom-serializer": "^2.0.0",
             "domelementtype": "^2.3.0",
-            "domhandler": "^5.0.1"
+            "domhandler": "^5.0.3"
           }
         },
         "entities": {
-          "version": "4.4.0",
-          "resolved": "https://registry.npmjs.org/entities/-/entities-4.4.0.tgz",
-          "integrity": "sha512-oYp7156SP8LkeGD0GF85ad1X9Ai79WtRsZ2gxJqtBuzH+98YUV6jkHEKlZkMbcrjJjIVJNIDP/3WL9wQkoPbWA==",
+          "version": "4.5.0",
+          "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
+          "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
           "dev": true
         }
       }
       "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
     },
     "ini": {
-      "version": "2.0.0",
-      "resolved": "https://registry.npmjs.org/ini/-/ini-2.0.0.tgz",
-      "integrity": "sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA==",
-      "dev": true
+      "version": "1.3.8",
+      "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz",
+      "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew=="
     },
     "inline-source-map": {
       "version": "0.6.2",
           "dev": true
         },
         "rxjs": {
-          "version": "7.8.0",
-          "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.0.tgz",
-          "integrity": "sha512-F2+gxDshqmIub1KdvZkaEfGDwLNpPvk9Fs6LD/MyQxNgMds/WH9OdDDXOmxUZpME+iSK3rQCctkL0DYyytUqMg==",
+          "version": "7.8.1",
+          "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz",
+          "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==",
           "dev": true,
           "requires": {
             "tslib": "^2.1.0"
       }
     },
     "is-core-module": {
-      "version": "2.11.0",
-      "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.11.0.tgz",
-      "integrity": "sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw==",
+      "version": "2.12.1",
+      "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.12.1.tgz",
+      "integrity": "sha512-Q4ZuBAe2FUsKtyQJoQHlvP8OvBERxO3jEmy1I7hcRXcJBGGHFh/aJBswbXuS9sgrDH2QUO8ilkwNPHvHMd8clg==",
       "dev": true,
       "requires": {
         "has": "^1.0.3"
     "isarray": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
-      "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==",
-      "dev": true
+      "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ=="
     },
     "isexe": {
       "version": "2.0.0",
       }
     },
     "jake": {
-      "version": "10.8.5",
-      "resolved": "https://registry.npmjs.org/jake/-/jake-10.8.5.tgz",
-      "integrity": "sha512-sVpxYeuAhWt0OTWITwT98oyV0GsXyMlXCF+3L1SuafBVUIr/uILGRB+NqwkzhgXKvoJpDIpQvqkUALgdmQsQxw==",
+      "version": "10.8.6",
+      "resolved": "https://registry.npmjs.org/jake/-/jake-10.8.6.tgz",
+      "integrity": "sha512-G43Ub9IYEFfu72sua6rzooi8V8Gz2lkfk48rW20vEWCGizeaEPlKB1Kh8JIA84yQbiAEfqlPmSpGgCKKxH3rDA==",
       "dev": true,
       "requires": {
         "async": "^3.2.3",
         "chalk": "^4.0.2",
-        "filelist": "^1.0.1",
-        "minimatch": "^3.0.4"
+        "filelist": "^1.0.4",
+        "minimatch": "^3.1.2"
       },
       "dependencies": {
         "ansi-styles": {
           "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==",
           "dev": true
         },
+        "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==",
+          "dev": true
+        },
         "supports-color": {
           "version": "7.2.0",
           "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
       },
       "dependencies": {
         "esbuild": {
-          "version": "0.17.10",
-          "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.17.10.tgz",
-          "integrity": "sha512-n7V3v29IuZy5qgxx25TKJrEm0FHghAlS6QweUcyIgh/U0zYmQcvogWROitrTyZId1mHSkuhhuyEXtI9OXioq7A==",
+          "version": "0.17.19",
+          "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.17.19.tgz",
+          "integrity": "sha512-XQ0jAPFkK/u3LcVRcvVHQcTIqD6E2H1fvZMA5dQPSOWb3suUbWbfbRf94pjc0bNzRYLfIrDRQXr7X+LHIm5oHw==",
           "dev": true,
           "optional": true,
           "requires": {
-            "@esbuild/android-arm": "0.17.10",
-            "@esbuild/android-arm64": "0.17.10",
-            "@esbuild/android-x64": "0.17.10",
-            "@esbuild/darwin-arm64": "0.17.10",
-            "@esbuild/darwin-x64": "0.17.10",
-            "@esbuild/freebsd-arm64": "0.17.10",
-            "@esbuild/freebsd-x64": "0.17.10",
-            "@esbuild/linux-arm": "0.17.10",
-            "@esbuild/linux-arm64": "0.17.10",
-            "@esbuild/linux-ia32": "0.17.10",
-            "@esbuild/linux-loong64": "0.17.10",
-            "@esbuild/linux-mips64el": "0.17.10",
-            "@esbuild/linux-ppc64": "0.17.10",
-            "@esbuild/linux-riscv64": "0.17.10",
-            "@esbuild/linux-s390x": "0.17.10",
-            "@esbuild/linux-x64": "0.17.10",
-            "@esbuild/netbsd-x64": "0.17.10",
-            "@esbuild/openbsd-x64": "0.17.10",
-            "@esbuild/sunos-x64": "0.17.10",
-            "@esbuild/win32-arm64": "0.17.10",
-            "@esbuild/win32-ia32": "0.17.10",
-            "@esbuild/win32-x64": "0.17.10"
+            "@esbuild/android-arm": "0.17.19",
+            "@esbuild/android-arm64": "0.17.19",
+            "@esbuild/android-x64": "0.17.19",
+            "@esbuild/darwin-arm64": "0.17.19",
+            "@esbuild/darwin-x64": "0.17.19",
+            "@esbuild/freebsd-arm64": "0.17.19",
+            "@esbuild/freebsd-x64": "0.17.19",
+            "@esbuild/linux-arm": "0.17.19",
+            "@esbuild/linux-arm64": "0.17.19",
+            "@esbuild/linux-ia32": "0.17.19",
+            "@esbuild/linux-loong64": "0.17.19",
+            "@esbuild/linux-mips64el": "0.17.19",
+            "@esbuild/linux-ppc64": "0.17.19",
+            "@esbuild/linux-riscv64": "0.17.19",
+            "@esbuild/linux-s390x": "0.17.19",
+            "@esbuild/linux-x64": "0.17.19",
+            "@esbuild/netbsd-x64": "0.17.19",
+            "@esbuild/openbsd-x64": "0.17.19",
+            "@esbuild/sunos-x64": "0.17.19",
+            "@esbuild/win32-arm64": "0.17.19",
+            "@esbuild/win32-ia32": "0.17.19",
+            "@esbuild/win32-x64": "0.17.19"
           }
         }
       }
           }
         },
         "semver": {
-          "version": "7.3.8",
-          "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz",
-          "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==",
+          "version": "7.5.1",
+          "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.1.tgz",
+          "integrity": "sha512-Wvss5ivl8TMRZXXESstBA4uR5iXgEN/VC5/sOcuXdVLzcdkz4HWetIoRfG5gb5X+ij/G9rw9YoGn3QoQ8OCSpw==",
           "dev": true,
           "requires": {
             "lru-cache": "^6.0.0"
       "dev": true
     },
     "joi": {
-      "version": "17.8.3",
-      "resolved": "https://registry.npmjs.org/joi/-/joi-17.8.3.tgz",
-      "integrity": "sha512-q5Fn6Tj/jR8PfrLrx4fpGH4v9qM6o+vDUfD4/3vxxyg34OmKcNqYZ1qn2mpLza96S8tL0p0rIw2gOZX+/cTg9w==",
+      "version": "17.9.2",
+      "resolved": "https://registry.npmjs.org/joi/-/joi-17.9.2.tgz",
+      "integrity": "sha512-Itk/r+V4Dx0V3c7RLFdRh12IOjySm2/WGPMubBT92cQvRfYZhPM2W0hZlctjj72iES8jsRCwp7S/cRmWBnJ4nw==",
       "dev": true,
       "requires": {
         "@hapi/hoek": "^9.0.0",
       },
       "dependencies": {
         "rxjs": {
-          "version": "7.8.0",
-          "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.0.tgz",
-          "integrity": "sha512-F2+gxDshqmIub1KdvZkaEfGDwLNpPvk9Fs6LD/MyQxNgMds/WH9OdDDXOmxUZpME+iSK3rQCctkL0DYyytUqMg==",
+          "version": "7.8.1",
+          "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz",
+          "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==",
           "dev": true,
           "requires": {
             "tslib": "^2.1.0"
       }
     },
     "marked": {
-      "version": "4.2.12",
-      "resolved": "https://registry.npmjs.org/marked/-/marked-4.2.12.tgz",
-      "integrity": "sha512-yr8hSKa3Fv4D3jdZmtMMPghgVt6TWbk86WQaWhDloQjRSQhMMYCAro7jP7VDJrjjdV8pxVxMssXS8B8Y5DZ5aw==",
+      "version": "4.3.0",
+      "resolved": "https://registry.npmjs.org/marked/-/marked-4.3.0.tgz",
+      "integrity": "sha512-PRsaiG84bK+AMvxziE/lCFss8juXjNaWzVbN5tXAm4XjeaS9NAHhop+PjQxz2A9h8Q4M/xGmzP8vqNwy6JeK0A==",
       "dev": true
     },
     "mathml-tag-names": {
       }
     },
     "memfs": {
-      "version": "3.4.13",
-      "resolved": "https://registry.npmjs.org/memfs/-/memfs-3.4.13.tgz",
-      "integrity": "sha512-omTM41g3Skpvx5dSYeZIbXKcXoAVc/AoMNwn9TKx++L/gaen/+4TTttmu8ZSch5vfVJ8uJvGbroTsIlslRg6lg==",
+      "version": "3.5.1",
+      "resolved": "https://registry.npmjs.org/memfs/-/memfs-3.5.1.tgz",
+      "integrity": "sha512-UWbFJKvj5k+nETdteFndTpYxdeTMox/ULeqX5k/dpaQJCCFmj5EeKv3dBcyO2xmkRAx2vppRu5dVG7SOtsGOzA==",
       "dev": true,
       "requires": {
         "fs-monkey": "^1.0.3"
       "requires": {
         "errno": "^0.1.3",
         "readable-stream": "^2.0.1"
-      },
-      "dependencies": {
-        "readable-stream": {
-          "version": "2.3.8",
-          "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz",
-          "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==",
-          "dev": true,
-          "requires": {
-            "core-util-is": "~1.0.0",
-            "inherits": "~2.0.3",
-            "isarray": "~1.0.0",
-            "process-nextick-args": "~2.0.0",
-            "safe-buffer": "~5.1.1",
-            "string_decoder": "~1.1.1",
-            "util-deprecate": "~1.0.1"
-          }
-        },
-        "safe-buffer": {
-          "version": "5.1.2",
-          "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
-          "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
-          "dev": true
-        },
-        "string_decoder": {
-          "version": "1.1.1",
-          "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
-          "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
-          "dev": true,
-          "requires": {
-            "safe-buffer": "~5.1.0"
-          }
-        }
       }
     },
     "memorystream": {
           }
         },
         "semver": {
-          "version": "7.3.8",
-          "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz",
-          "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==",
+          "version": "7.5.1",
+          "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.1.tgz",
+          "integrity": "sha512-Wvss5ivl8TMRZXXESstBA4uR5iXgEN/VC5/sOcuXdVLzcdkz4HWetIoRfG5gb5X+ij/G9rw9YoGn3QoQ8OCSpw==",
           "dev": true,
           "requires": {
             "lru-cache": "^6.0.0"
     "mime-db": {
       "version": "1.52.0",
       "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
-      "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
-      "dev": true
+      "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="
     },
     "mime-types": {
       "version": "2.1.35",
       "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
       "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
-      "dev": true,
       "requires": {
         "mime-db": "1.52.0"
       }
       "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==",
       "dev": true
     },
+    "mimic-response": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-2.1.0.tgz",
+      "integrity": "sha512-wXqjST+SLt7R009ySCglWBCFpjUygmCIfD790/kVbiGmUgfYGuB14PiTd5DwVxSV4NcYHjzMkoj5LjQZwTQLEA==",
+      "optional": true
+    },
     "min-indent": {
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz",
           }
         },
         "schema-utils": {
-          "version": "4.0.0",
-          "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.0.0.tgz",
-          "integrity": "sha512-1edyXKgh6XnJsJSQ8mKWXnN/BVaIbFMLpouRUrXgVq7WYne5kw3MW7UPhO44uRXQSIpTSXoJbmrR2X0w9kUTyg==",
+          "version": "4.0.1",
+          "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.0.1.tgz",
+          "integrity": "sha512-lELhBAAly9NowEsX0yZBlw9ahZG+sK/1RJ21EpzdYHKEs13Vku3LJ+MIPhh4sMs0oCCeufZQEQbMekiA4vuVIQ==",
           "dev": true,
           "requires": {
             "@types/json-schema": "^7.0.9",
-            "ajv": "^8.8.0",
+            "ajv": "^8.9.0",
             "ajv-formats": "^2.1.1",
-            "ajv-keywords": "^5.0.0"
+            "ajv-keywords": "^5.1.0"
           }
         }
       }
     },
+    "minim": {
+      "version": "0.23.8",
+      "resolved": "https://registry.npmjs.org/minim/-/minim-0.23.8.tgz",
+      "integrity": "sha512-bjdr2xW1dBCMsMGGsUeqM4eFI60m94+szhxWys+B1ztIt6gWSfeGBdSVCIawezeHYLYn0j6zrsXdQS/JllBzww==",
+      "requires": {
+        "lodash": "^4.15.0"
+      }
+    },
     "minimalistic-assert": {
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz",
     "minimist": {
       "version": "1.2.8",
       "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
-      "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
-      "dev": true
+      "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA=="
     },
     "minimist-options": {
       "version": "4.1.0",
     "mkdirp-classic": {
       "version": "0.5.3",
       "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz",
-      "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==",
-      "dev": true
+      "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A=="
     },
     "mobx": {
       "version": "4.14.1",
             "randombytes": "^2.1.0"
           }
         },
+        "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==",
+          "dev": true
+        },
         "supports-color": {
           "version": "8.1.1",
           "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz",
           "requires": {
             "resolve": "^1.17.0"
           }
-        },
-        "readable-stream": {
-          "version": "2.3.8",
-          "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz",
-          "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==",
-          "dev": true,
-          "requires": {
-            "core-util-is": "~1.0.0",
-            "inherits": "~2.0.3",
-            "isarray": "~1.0.0",
-            "process-nextick-args": "~2.0.0",
-            "safe-buffer": "~5.1.1",
-            "string_decoder": "~1.1.1",
-            "util-deprecate": "~1.0.1"
-          }
-        },
-        "safe-buffer": {
-          "version": "5.1.2",
-          "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
-          "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
-          "dev": true
-        },
-        "string_decoder": {
-          "version": "1.1.1",
-          "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
-          "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
-          "dev": true,
-          "requires": {
-            "safe-buffer": "~5.1.0"
-          }
         }
       }
     },
         "thenify-all": "^1.0.0"
       }
     },
+    "nan": {
+      "version": "2.17.0",
+      "resolved": "https://registry.npmjs.org/nan/-/nan-2.17.0.tgz",
+      "integrity": "sha512-2ZTgtl0nJsO0KQCjEpxcIr5D+Yv90plTitZt9JBfQvVJDS5seMl3FOvsh3+9CoYWXf/1l5OaZzzF6nDm4cagaQ==",
+      "optional": true
+    },
     "nanoid": {
-      "version": "3.3.4",
-      "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz",
-      "integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==",
+      "version": "3.3.6",
+      "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz",
+      "integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==",
       "dev": true
     },
     "nanomatch": {
         "to-regex": "^3.0.1"
       }
     },
+    "napi-build-utils": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-1.0.2.tgz",
+      "integrity": "sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg==",
+      "optional": true
+    },
     "natural-compare": {
       "version": "1.4.0",
       "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
         "lower-case": "^1.1.1"
       }
     },
+    "node-abi": {
+      "version": "2.30.1",
+      "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-2.30.1.tgz",
+      "integrity": "sha512-/2D0wOQPgaUWzVSVgRMx+trKJRC2UG4SUc4oCJoXx9Uxjtp0Vy3/kt7zcbxHF8+Z/pK3UloLWzBISg72brfy1w==",
+      "optional": true,
+      "requires": {
+        "semver": "^5.4.1"
+      },
+      "dependencies": {
+        "semver": {
+          "version": "5.7.1",
+          "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
+          "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==",
+          "optional": true
+        }
+      }
+    },
     "node-addon-api": {
       "version": "3.2.1",
       "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-3.2.1.tgz",
       "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ=="
     },
     "node-fetch": {
-      "version": "2.6.7",
-      "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz",
-      "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==",
+      "version": "2.6.11",
+      "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.11.tgz",
+      "integrity": "sha512-4I6pdBY1EthSqDmJkiNk3JIT8cswwR9nfeW/cPdUagJYEQG7R95WRH74wpz7ma8Gh/9dI9FP+OU+0E4FvtA55w==",
       "requires": {
         "whatwg-url": "^5.0.0"
       }
         "which": "^2.0.2"
       },
       "dependencies": {
+        "are-we-there-yet": {
+          "version": "3.0.1",
+          "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-3.0.1.tgz",
+          "integrity": "sha512-QZW4EDmGwlYur0Yyf/b2uGucHQMa8aFUP7eu9ddR73vvhFyt4V0Vl3QHPcTNJ8l6qYOBdxgXdnBXQrHilfRQBg==",
+          "dev": true,
+          "requires": {
+            "delegates": "^1.0.0",
+            "readable-stream": "^3.6.0"
+          }
+        },
+        "gauge": {
+          "version": "4.0.4",
+          "resolved": "https://registry.npmjs.org/gauge/-/gauge-4.0.4.tgz",
+          "integrity": "sha512-f9m+BEN5jkg6a0fZjleidjN51VE1X+mPFQ2DJ0uv1V39oCLCbsGe6yjbBnp7eK7z/+GAon99a3nHuqbuuthyPg==",
+          "dev": true,
+          "requires": {
+            "aproba": "^1.0.3 || ^2.0.0",
+            "color-support": "^1.1.3",
+            "console-control-strings": "^1.1.0",
+            "has-unicode": "^2.0.1",
+            "signal-exit": "^3.0.7",
+            "string-width": "^4.2.3",
+            "strip-ansi": "^6.0.1",
+            "wide-align": "^1.1.5"
+          }
+        },
         "lru-cache": {
           "version": "6.0.0",
           "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
             "yallist": "^4.0.0"
           }
         },
+        "npmlog": {
+          "version": "6.0.2",
+          "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-6.0.2.tgz",
+          "integrity": "sha512-/vBvz5Jfr9dT/aFWd0FIRf+T/Q2WBsLENygUaFUqstqsycmZAP/t5BvFJTK0viFmSUxiUKTUplWy5vt+rvKIxg==",
+          "dev": true,
+          "requires": {
+            "are-we-there-yet": "^3.0.0",
+            "console-control-strings": "^1.1.0",
+            "gauge": "^4.0.3",
+            "set-blocking": "^2.0.0"
+          }
+        },
+        "readable-stream": {
+          "version": "3.6.2",
+          "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
+          "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==",
+          "dev": true,
+          "requires": {
+            "inherits": "^2.0.3",
+            "string_decoder": "^1.1.1",
+            "util-deprecate": "^1.0.1"
+          }
+        },
         "semver": {
-          "version": "7.3.8",
-          "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz",
-          "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==",
+          "version": "7.5.1",
+          "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.1.tgz",
+          "integrity": "sha512-Wvss5ivl8TMRZXXESstBA4uR5iXgEN/VC5/sOcuXdVLzcdkz4HWetIoRfG5gb5X+ij/G9rw9YoGn3QoQ8OCSpw==",
           "dev": true,
           "requires": {
             "lru-cache": "^6.0.0"
       "dev": true
     },
     "node-releases": {
-      "version": "2.0.10",
-      "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.10.tgz",
-      "integrity": "sha512-5GFldHPXVG/YZmFzJvKK2zDSzPKhEp0+ZR5SVaoSag9fsL5YgHbUHDfnG5494ISANDcK4KwPXAx2xqVEydmd7w=="
+      "version": "2.0.11",
+      "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.11.tgz",
+      "integrity": "sha512-+M0PwXeU80kRohZ3aT4J/OnR+l9/KD2nVLNNoRgFtnf+umQVFdGBAO2N8+nCnEi0xlh/Wk3zOGC+vNNx+uM79Q=="
     },
     "nopt": {
       "version": "5.0.0",
           }
         },
         "semver": {
-          "version": "7.3.8",
-          "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz",
-          "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==",
+          "version": "7.5.1",
+          "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.1.tgz",
+          "integrity": "sha512-Wvss5ivl8TMRZXXESstBA4uR5iXgEN/VC5/sOcuXdVLzcdkz4HWetIoRfG5gb5X+ij/G9rw9YoGn3QoQ8OCSpw==",
           "dev": true,
           "requires": {
             "lru-cache": "^6.0.0"
           }
         },
         "semver": {
-          "version": "7.3.8",
-          "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz",
-          "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==",
+          "version": "7.5.1",
+          "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.1.tgz",
+          "integrity": "sha512-Wvss5ivl8TMRZXXESstBA4uR5iXgEN/VC5/sOcuXdVLzcdkz4HWetIoRfG5gb5X+ij/G9rw9YoGn3QoQ8OCSpw==",
           "dev": true,
           "requires": {
             "lru-cache": "^6.0.0"
           }
         },
         "semver": {
-          "version": "7.3.8",
-          "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz",
-          "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==",
+          "version": "7.5.1",
+          "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.1.tgz",
+          "integrity": "sha512-Wvss5ivl8TMRZXXESstBA4uR5iXgEN/VC5/sOcuXdVLzcdkz4HWetIoRfG5gb5X+ij/G9rw9YoGn3QoQ8OCSpw==",
           "dev": true,
           "requires": {
             "lru-cache": "^6.0.0"
             "unique-filename": "^2.0.0"
           }
         },
+        "chownr": {
+          "version": "2.0.0",
+          "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz",
+          "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==",
+          "dev": true
+        },
         "glob": {
           "version": "8.1.0",
           "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz",
           }
         },
         "lru-cache": {
-          "version": "7.18.1",
-          "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.1.tgz",
-          "integrity": "sha512-8/HcIENyQnfUTCDizRu9rrDyG6XG/21M4X7/YEGZeD76ZJilFPAUVb/2zysFf7VVO1LEjCDFyHp8pMMvozIrvg==",
+          "version": "7.18.3",
+          "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz",
+          "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==",
           "dev": true
         },
         "make-fetch-happen": {
           }
         },
         "semver": {
-          "version": "7.3.8",
-          "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz",
-          "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==",
+          "version": "7.5.1",
+          "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.1.tgz",
+          "integrity": "sha512-Wvss5ivl8TMRZXXESstBA4uR5iXgEN/VC5/sOcuXdVLzcdkz4HWetIoRfG5gb5X+ij/G9rw9YoGn3QoQ8OCSpw==",
           "dev": true,
           "requires": {
             "lru-cache": "^6.0.0"
       }
     },
     "npmlog": {
-      "version": "6.0.2",
-      "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-6.0.2.tgz",
-      "integrity": "sha512-/vBvz5Jfr9dT/aFWd0FIRf+T/Q2WBsLENygUaFUqstqsycmZAP/t5BvFJTK0viFmSUxiUKTUplWy5vt+rvKIxg==",
-      "dev": true,
+      "version": "4.1.2",
+      "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz",
+      "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==",
+      "optional": true,
       "requires": {
-        "are-we-there-yet": "^3.0.0",
-        "console-control-strings": "^1.1.0",
-        "gauge": "^4.0.3",
-        "set-blocking": "^2.0.0"
+        "are-we-there-yet": "~1.1.2",
+        "console-control-strings": "~1.1.0",
+        "gauge": "~2.7.3",
+        "set-blocking": "~2.0.0"
       }
     },
     "nth-check": {
     "number-is-nan": {
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz",
-      "integrity": "sha512-4jbtZXNAsfZbAHiiqjLPBiCl16dES1zI4Hpzzxw61Tk+loF+sBDBKx1ICKKKwIqQ7M0mFn1TmkN7euSncWgHiQ==",
-      "dev": true
+      "integrity": "sha512-4jbtZXNAsfZbAHiiqjLPBiCl16dES1zI4Hpzzxw61Tk+loF+sBDBKx1ICKKKwIqQ7M0mFn1TmkN7euSncWgHiQ=="
     },
     "nwsapi": {
-      "version": "2.2.2",
-      "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.2.tgz",
-      "integrity": "sha512-90yv+6538zuvUMnN+zCr8LuV6bPFdq50304114vJYJ8RDyK8D5O9Phpbd6SZWgI7PwzmmfN1upeOJlvybDSgCw==",
+      "version": "2.2.4",
+      "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.4.tgz",
+      "integrity": "sha512-NHj4rzRo0tQdijE9ZqAx6kYDcoRwYwSYzCA8MY3JzfxlrvEU0jhnhJT9BhqhJs7I/dKcrDm6TyulaRqZPIhN5g==",
       "dev": true
     },
     "nx": {
         "rimraf": "^3.0.2",
         "ssri": "^8.0.1",
         "tar": "^6.1.0"
+      },
+      "dependencies": {
+        "chownr": {
+          "version": "2.0.0",
+          "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz",
+          "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==",
+          "dev": true
+        }
       }
     },
     "pad-right": {
             "inherits": "^2.0.1",
             "readable-stream": "^3.1.1"
           }
+        },
+        "readable-stream": {
+          "version": "3.6.2",
+          "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
+          "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==",
+          "dev": true,
+          "requires": {
+            "inherits": "^2.0.3",
+            "string_decoder": "^1.1.1",
+            "util-deprecate": "^1.0.1"
+          }
         }
       }
     },
           }
         },
         "semver": {
-          "version": "7.3.8",
-          "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz",
-          "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==",
+          "version": "7.5.1",
+          "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.1.tgz",
+          "integrity": "sha512-Wvss5ivl8TMRZXXESstBA4uR5iXgEN/VC5/sOcuXdVLzcdkz4HWetIoRfG5gb5X+ij/G9rw9YoGn3QoQ8OCSpw==",
           "dev": true,
           "requires": {
             "lru-cache": "^6.0.0"
       "dev": true
     },
     "postcss-modules-local-by-default": {
-      "version": "4.0.0",
-      "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.0.tgz",
-      "integrity": "sha512-sT7ihtmGSF9yhm6ggikHdV0hlziDTX7oFoXtuVWeDd3hHObNkcHRo9V3yg7vCAY7cONyxJC/XXCmmiHHcvX7bQ==",
+      "version": "4.0.2",
+      "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.2.tgz",
+      "integrity": "sha512-mR/pcIsQhU2UgKYOPjRCSgacmjn08pyrHk+Vrm8WEKjDWgqO43vdRkzmxyZOZWiKr6Ed9gpReQHhLUGVAcn9jw==",
       "dev": true,
       "requires": {
         "icss-utils": "^5.0.0",
       }
     },
     "postcss-selector-parser": {
-      "version": "6.0.11",
-      "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.11.tgz",
-      "integrity": "sha512-zbARubNdogI9j7WY4nQJBiNqQf3sLS3wCP4WfOidu+p28LofJqDH1tcXypGrcmMHhDk2t9wGhCsYe/+szLTy1g==",
+      "version": "6.0.13",
+      "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.13.tgz",
+      "integrity": "sha512-EaV1Gl4mUEV4ddhDnv/xtj7sxwrwxdetHdWUGnT4VJQf+4d05v6lHYZr8N573k5Z0BViss7BDhfWtKS3+sfAqQ==",
       "dev": true,
       "requires": {
         "cssesc": "^3.0.0",
       "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==",
       "dev": true
     },
+    "prebuild-install": {
+      "version": "6.1.4",
+      "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-6.1.4.tgz",
+      "integrity": "sha512-Z4vpywnK1lBg+zdPCVCsKq0xO66eEV9rWo2zrROGGiRS4JtueBOdlB1FnY8lcy7JsUud/Q3ijUxyWN26Ika0vQ==",
+      "optional": true,
+      "requires": {
+        "detect-libc": "^1.0.3",
+        "expand-template": "^2.0.3",
+        "github-from-package": "0.0.0",
+        "minimist": "^1.2.3",
+        "mkdirp-classic": "^0.5.3",
+        "napi-build-utils": "^1.0.1",
+        "node-abi": "^2.21.0",
+        "npmlog": "^4.0.1",
+        "pump": "^3.0.0",
+        "rc": "^1.2.7",
+        "simple-get": "^3.0.3",
+        "tar-fs": "^2.0.0",
+        "tunnel-agent": "^0.6.0"
+      }
+    },
     "preceptor-core": {
       "version": "0.10.1",
       "resolved": "https://registry.npmjs.org/preceptor-core/-/preceptor-core-0.10.1.tgz",
     "process": {
       "version": "0.11.10",
       "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz",
-      "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==",
-      "dev": true
+      "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A=="
     },
     "process-nextick-args": {
       "version": "2.0.1",
       "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
-      "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==",
-      "dev": true
+      "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag=="
     },
     "progress": {
       "version": "2.0.3",
       },
       "dependencies": {
         "@types/node": {
-          "version": "18.14.4",
-          "resolved": "https://registry.npmjs.org/@types/node/-/node-18.14.4.tgz",
-          "integrity": "sha512-VhCw7I7qO2X49+jaKcAUwi3rR+hbxT5VcYF493+Z5kMLI0DL568b7JI4IDJaxWFH0D/xwmGJNoXisyX+w7GH/g==",
+          "version": "20.2.3",
+          "resolved": "https://registry.npmjs.org/@types/node/-/node-20.2.3.tgz",
+          "integrity": "sha512-pg9d0yC4rVNWQzX8U7xb4olIOFuuVL9za3bzMT2pu2SU0SNEi66i2qrvhE2qt0HvkhuCaWJu7pLNOt/Pj8BIrw==",
           "dev": true
         }
       }
     "proxy-from-env": {
       "version": "1.1.0",
       "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
-      "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
-      "dev": true
+      "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="
     },
     "proxy-middleware": {
       "version": "0.15.0",
       "version": "3.0.0",
       "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz",
       "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==",
-      "dev": true,
       "requires": {
         "end-of-stream": "^1.1.0",
         "once": "^1.3.1"
       "integrity": "sha512-RofWgt/7fL5wP1Y7fxE7/EmTLzQVnB0ycyibJ0OOHIlJqTNzglYFxVwETOcIoJqJmpDXJ9xImDv+Fq34F/d4Dw=="
     },
     "qs": {
-      "version": "6.11.0",
-      "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz",
-      "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==",
+      "version": "6.11.2",
+      "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.2.tgz",
+      "integrity": "sha512-tDNIz22aBzCDxLtVH++VnTfzxlfeK5CbqohpSqpJgj1Wg/cQbStNAz3NuqCs5vV+pjBsK4x4pN9HlVh7rcYRiA==",
       "requires": {
         "side-channel": "^1.0.4"
       }
         "through2": "^2.0.0"
       }
     },
+    "ramda": {
+      "version": "0.29.0",
+      "resolved": "https://registry.npmjs.org/ramda/-/ramda-0.29.0.tgz",
+      "integrity": "sha512-BBea6L67bYLtdbOqfp8f58fPMqEwx0doL+pAi8TZyp2YWz8R9G8z9x75CZI8W+ftqhFHCpEX2cRnUUXK130iKA=="
+    },
+    "ramda-adjunct": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/ramda-adjunct/-/ramda-adjunct-4.0.0.tgz",
+      "integrity": "sha512-W/NiJAlZdwZ/iUkWEQQgRdH5Szqqet1WoVH9cdqDVjFbVaZHuJfJRvsxqHhvq6tZse+yVbFatLDLdVa30wBlGQ=="
+    },
     "randexp": {
       "version": "0.5.3",
       "resolved": "https://registry.npmjs.org/randexp/-/randexp-0.5.3.tgz",
         }
       }
     },
+    "rc": {
+      "version": "1.2.8",
+      "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz",
+      "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==",
+      "optional": true,
+      "requires": {
+        "deep-extend": "^0.6.0",
+        "ini": "~1.3.0",
+        "minimist": "^1.2.0",
+        "strip-json-comments": "~2.0.1"
+      }
+    },
     "react": {
       "version": "17.0.2",
       "resolved": "https://registry.npmjs.org/react/-/react-17.0.2.tgz",
     },
     "read-only-stream": {
       "version": "2.0.0",
-      "resolved": "https://registry.npmjs.org/read-only-stream/-/read-only-stream-2.0.0.tgz",
-      "integrity": "sha512-3ALe0bjBVZtkdWKIcThYpQCLbBMd/+Tbh2CDSrAIDO3UsZ4Xs+tnyjv2MjCOMMgBG+AsUOeuP1cgtY1INISc8w==",
-      "dev": true,
-      "requires": {
-        "readable-stream": "^2.0.2"
-      },
-      "dependencies": {
-        "readable-stream": {
-          "version": "2.3.8",
-          "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz",
-          "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==",
-          "dev": true,
-          "requires": {
-            "core-util-is": "~1.0.0",
-            "inherits": "~2.0.3",
-            "isarray": "~1.0.0",
-            "process-nextick-args": "~2.0.0",
-            "safe-buffer": "~5.1.1",
-            "string_decoder": "~1.1.1",
-            "util-deprecate": "~1.0.1"
-          }
-        },
-        "safe-buffer": {
-          "version": "5.1.2",
-          "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
-          "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
-          "dev": true
-        },
-        "string_decoder": {
-          "version": "1.1.1",
-          "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
-          "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
-          "dev": true,
-          "requires": {
-            "safe-buffer": "~5.1.0"
-          }
-        }
+      "resolved": "https://registry.npmjs.org/read-only-stream/-/read-only-stream-2.0.0.tgz",
+      "integrity": "sha512-3ALe0bjBVZtkdWKIcThYpQCLbBMd/+Tbh2CDSrAIDO3UsZ4Xs+tnyjv2MjCOMMgBG+AsUOeuP1cgtY1INISc8w==",
+      "dev": true,
+      "requires": {
+        "readable-stream": "^2.0.2"
       }
     },
     "read-package-json-fast": {
       }
     },
     "readable-stream": {
-      "version": "3.6.1",
-      "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.1.tgz",
-      "integrity": "sha512-+rQmrWMYGA90yenhTYsLWAsLsqVC8osOw6PKE1HDYiO0gdPeKe/xDHNzIAIn4C91YQ6oenEhfYqqc1883qHbjQ==",
-      "dev": true,
+      "version": "2.3.8",
+      "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz",
+      "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==",
       "requires": {
-        "inherits": "^2.0.3",
-        "string_decoder": "^1.1.1",
-        "util-deprecate": "^1.0.1"
+        "core-util-is": "~1.0.0",
+        "inherits": "~2.0.3",
+        "isarray": "~1.0.0",
+        "process-nextick-args": "~2.0.0",
+        "safe-buffer": "~5.1.1",
+        "string_decoder": "~1.1.1",
+        "util-deprecate": "~1.0.1"
+      },
+      "dependencies": {
+        "safe-buffer": {
+          "version": "5.1.2",
+          "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
+          "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
+        }
       }
     },
     "readdirp": {
       "dev": true
     },
     "regexp.prototype.flags": {
-      "version": "1.4.3",
-      "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.4.3.tgz",
-      "integrity": "sha512-fjggEOO3slI6Wvgjwflkc4NFRCTZAu5CnNfBd5qOMYhWdn67nJBBu34/TkD++eeFmd8C9r9jfXJ27+nSiRkSUA==",
+      "version": "1.5.0",
+      "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.0.tgz",
+      "integrity": "sha512-0SutC3pNudRKgquxGoRGIz946MZVHqbNfPjBdxeOhBrdgDKlRoXmYLQN9xRbrR09ZXWeGAdPuif7egofn6v5LA==",
       "dev": true,
       "requires": {
         "call-bind": "^1.0.2",
-        "define-properties": "^1.1.3",
-        "functions-have-names": "^1.2.2"
+        "define-properties": "^1.2.0",
+        "functions-have-names": "^1.2.3"
       }
     },
     "regexpp": {
       "dev": true
     },
     "regexpu-core": {
-      "version": "5.3.1",
-      "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-5.3.1.tgz",
-      "integrity": "sha512-nCOzW2V/X15XpLsK2rlgdwrysrBq+AauCn+omItIz4R1pIcmeot5zvjdmOBRLzEH/CkC6IxMJVmxDe3QcMuNVQ==",
+      "version": "5.3.2",
+      "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-5.3.2.tgz",
+      "integrity": "sha512-RAM5FlZz+Lhmo7db9L298p2vHP5ZywrVXmVXpmAD9GuL5MPH6t9ROw1iA/wfHkQ76Qe7AaPF0nGuim96/IrQMQ==",
       "dev": true,
       "requires": {
         "@babel/regjsgen": "^0.8.0",
       "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ=="
     },
     "reselect": {
-      "version": "4.1.7",
-      "resolved": "https://registry.npmjs.org/reselect/-/reselect-4.1.7.tgz",
-      "integrity": "sha512-Zu1xbUt3/OPwsXL46hvOOoQrap2azE7ZQbokq61BQfiXvhewsKDwhMeZjTX9sX0nvw1t/U5Audyn1I9P/m9z0A=="
+      "version": "4.1.8",
+      "resolved": "https://registry.npmjs.org/reselect/-/reselect-4.1.8.tgz",
+      "integrity": "sha512-ab9EmR80F/zQTMNeneUr4cv+jSwPJgIlvEmVwLerwrWVbpLlBuls9XHzIeTFy4cegU2NHBp3va0LKOzU5qFEYQ=="
     },
     "resolve": {
-      "version": "1.22.1",
-      "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz",
-      "integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==",
+      "version": "1.22.2",
+      "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.2.tgz",
+      "integrity": "sha512-Sb+mjNHOULsBv818T40qSPeRiuWLyaGMa5ewydRLFimneixmVy2zdivRl+AF6jaYPC8ERxGDmFSiqui6SfPd+g==",
       "dev": true,
       "requires": {
-        "is-core-module": "^2.9.0",
+        "is-core-module": "^2.11.0",
         "path-parse": "^1.0.7",
         "supports-preserve-symlinks-flag": "^1.0.0"
       }
     "set-blocking": {
       "version": "2.0.0",
       "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
-      "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==",
-      "dev": true
+      "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw=="
     },
     "set-value": {
       "version": "2.0.1",
       "dev": true
     },
     "shell-quote": {
-      "version": "1.8.0",
-      "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.0.tgz",
-      "integrity": "sha512-QHsz8GgQIGKlRi24yFc6a6lN69Idnx634w49ay6+jA5yFh7a1UY+4Rp6HPx/L/1zcEDPEij8cIsiqR6bQsE5VQ==",
+      "version": "1.8.1",
+      "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.1.tgz",
+      "integrity": "sha512-6j1W9l1iAs/4xYBI1SYOVZyFcCis9b4KCLQ8fgAGG07QvzaRLVVRQvAy85yNmmZSjYjg4MWh4gNvlPujU/5LpA==",
       "dev": true
     },
+    "short-unique-id": {
+      "version": "4.4.4",
+      "resolved": "https://registry.npmjs.org/short-unique-id/-/short-unique-id-4.4.4.tgz",
+      "integrity": "sha512-oLF1NCmtbiTWl2SqdXZQbo5KM1b7axdp0RgQLq8qCBBLoq+o3A5wmLrNM6bZIh54/a8BJ3l69kTXuxwZ+XCYuw=="
+    },
     "side-channel": {
       "version": "1.0.4",
       "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz",
     "signal-exit": {
       "version": "3.0.7",
       "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz",
-      "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==",
-      "dev": true
+      "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="
     },
     "simple-concat": {
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz",
-      "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==",
-      "dev": true
+      "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q=="
+    },
+    "simple-get": {
+      "version": "3.1.1",
+      "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-3.1.1.tgz",
+      "integrity": "sha512-CQ5LTKGfCpvE1K0n2us+kuMPbk/q0EKl82s4aheV9oXjFEz6W/Y7oQFVJuU6QG77hRT4Ghb5RURteF5vnWjupA==",
+      "optional": true,
+      "requires": {
+        "decompress-response": "^4.2.0",
+        "once": "^1.3.1",
+        "simple-concat": "^1.0.0"
+      }
     },
     "simplebar": {
       "version": "5.3.9",
       "integrity": "sha512-q/JSVd1Lptzhf5bkYm4ob4iWPjx0KiRe3sRFBNrVqbJkFaBm5vbbowy1mymoPNLRa52+oadOhJ+K49wsSeSjTA=="
     },
     "spdx-correct": {
-      "version": "3.1.1",
-      "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.1.tgz",
-      "integrity": "sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w==",
+      "version": "3.2.0",
+      "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.2.0.tgz",
+      "integrity": "sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==",
       "dev": true,
       "requires": {
         "spdx-expression-parse": "^3.0.0",
       }
     },
     "spdx-license-ids": {
-      "version": "3.0.12",
-      "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.12.tgz",
-      "integrity": "sha512-rr+VVSXtRhO4OHbXUiAF7xW3Bo9DuuF6C5jH+q/x15j2jniycgKbxU09Hr0WqlSLUs4i4ltHGXqTe7VHclYWyA==",
+      "version": "3.0.13",
+      "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.13.tgz",
+      "integrity": "sha512-XkD+zwiqXHikFZm4AX/7JSCXA98U5Db4AFd5XUg/+9UNtnH75+Z9KxtpYiJZx36mUDVOwH83pl7yvCer6ewM3w==",
       "dev": true
     },
     "spdy": {
         "obuf": "^1.1.2",
         "readable-stream": "^3.0.6",
         "wbuf": "^1.7.3"
+      },
+      "dependencies": {
+        "readable-stream": {
+          "version": "3.6.2",
+          "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
+          "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==",
+          "dev": true,
+          "requires": {
+            "inherits": "^2.0.3",
+            "string_decoder": "^1.1.1",
+            "util-deprecate": "^1.0.1"
+          }
+        }
       }
     },
     "specificity": {
         "stacktrace-gps": "^3.0.4"
       }
     },
+    "stampit": {
+      "version": "4.3.2",
+      "resolved": "https://registry.npmjs.org/stampit/-/stampit-4.3.2.tgz",
+      "integrity": "sha512-pE2org1+ZWQBnIxRPrBM2gVupkuDD0TTNIo1H6GdT/vO82NXli2z8lRE8cu/nBIHrcOCXFBAHpb9ZldrB2/qOA=="
+    },
     "start-server-and-test": {
       "version": "1.12.1",
       "resolved": "https://registry.npmjs.org/start-server-and-test/-/start-server-and-test-1.12.1.tgz",
           "requires": {
             "sourcemap-codec": "^1.4.1"
           }
-        },
-        "readable-stream": {
-          "version": "2.3.8",
-          "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz",
-          "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==",
-          "dev": true,
-          "requires": {
-            "core-util-is": "~1.0.0",
-            "inherits": "~2.0.3",
-            "isarray": "~1.0.0",
-            "process-nextick-args": "~2.0.0",
-            "safe-buffer": "~5.1.1",
-            "string_decoder": "~1.1.1",
-            "util-deprecate": "~1.0.1"
-          }
-        },
-        "safe-buffer": {
-          "version": "5.1.2",
-          "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
-          "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
-          "dev": true
-        },
-        "string_decoder": {
-          "version": "1.1.1",
-          "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
-          "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
-          "dev": true,
-          "requires": {
-            "safe-buffer": "~5.1.0"
-          }
         }
       }
     },
       "requires": {
         "inherits": "~2.0.1",
         "readable-stream": "^2.0.2"
-      },
-      "dependencies": {
-        "readable-stream": {
-          "version": "2.3.8",
-          "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz",
-          "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==",
-          "dev": true,
-          "requires": {
-            "core-util-is": "~1.0.0",
-            "inherits": "~2.0.3",
-            "isarray": "~1.0.0",
-            "process-nextick-args": "~2.0.0",
-            "safe-buffer": "~5.1.1",
-            "string_decoder": "~1.1.1",
-            "util-deprecate": "~1.0.1"
-          }
-        },
-        "safe-buffer": {
-          "version": "5.1.2",
-          "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
-          "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
-          "dev": true
-        },
-        "string_decoder": {
-          "version": "1.1.1",
-          "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
-          "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
-          "dev": true,
-          "requires": {
-            "safe-buffer": "~5.1.0"
-          }
-        }
       }
     },
     "stream-buffers": {
       "requires": {
         "duplexer2": "~0.1.0",
         "readable-stream": "^2.0.2"
-      },
-      "dependencies": {
-        "readable-stream": {
-          "version": "2.3.8",
-          "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz",
-          "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==",
-          "dev": true,
-          "requires": {
-            "core-util-is": "~1.0.0",
-            "inherits": "~2.0.3",
-            "isarray": "~1.0.0",
-            "process-nextick-args": "~2.0.0",
-            "safe-buffer": "~5.1.1",
-            "string_decoder": "~1.1.1",
-            "util-deprecate": "~1.0.1"
-          }
-        },
-        "safe-buffer": {
-          "version": "5.1.2",
-          "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
-          "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
-          "dev": true
-        },
-        "string_decoder": {
-          "version": "1.1.1",
-          "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
-          "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
-          "dev": true,
-          "requires": {
-            "safe-buffer": "~5.1.0"
-          }
-        }
       }
     },
     "stream-http": {
         "inherits": "^2.0.4",
         "readable-stream": "^3.6.0",
         "xtend": "^4.0.2"
+      },
+      "dependencies": {
+        "readable-stream": {
+          "version": "3.6.2",
+          "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
+          "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==",
+          "dev": true,
+          "requires": {
+            "inherits": "^2.0.3",
+            "string_decoder": "^1.1.1",
+            "util-deprecate": "^1.0.1"
+          }
+        }
       }
     },
     "stream-splicer": {
       "requires": {
         "inherits": "^2.0.1",
         "readable-stream": "^2.0.2"
-      },
-      "dependencies": {
-        "readable-stream": {
-          "version": "2.3.8",
-          "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz",
-          "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==",
-          "dev": true,
-          "requires": {
-            "core-util-is": "~1.0.0",
-            "inherits": "~2.0.3",
-            "isarray": "~1.0.0",
-            "process-nextick-args": "~2.0.0",
-            "safe-buffer": "~5.1.1",
-            "string_decoder": "~1.1.1",
-            "util-deprecate": "~1.0.1"
-          }
-        },
-        "safe-buffer": {
-          "version": "5.1.2",
-          "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
-          "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
-          "dev": true
-        },
-        "string_decoder": {
-          "version": "1.1.1",
-          "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
-          "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
-          "dev": true,
-          "requires": {
-            "safe-buffer": "~5.1.0"
-          }
-        }
       }
     },
     "streamroller": {
         "es-abstract": "^1.20.4"
       }
     },
+    "string.prototype.trim": {
+      "version": "1.2.7",
+      "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.7.tgz",
+      "integrity": "sha512-p6TmeT1T3411M8Cgg9wBTMRtY2q9+PNy9EV1i2lIXUN/btt763oIfxwN3RR8VU6wHX8j/1CFy0L+YuThm6bgOg==",
+      "dev": true,
+      "requires": {
+        "call-bind": "^1.0.2",
+        "define-properties": "^1.1.4",
+        "es-abstract": "^1.20.4"
+      }
+    },
     "string.prototype.trimend": {
       "version": "1.0.6",
       "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.6.tgz",
       }
     },
     "string_decoder": {
-      "version": "1.3.0",
-      "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
-      "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
-      "dev": true,
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
+      "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
       "requires": {
-        "safe-buffer": "~5.2.0"
+        "safe-buffer": "~5.1.0"
+      },
+      "dependencies": {
+        "safe-buffer": {
+          "version": "5.1.2",
+          "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
+          "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
+        }
       }
     },
     "strip-ansi": {
       }
     },
     "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==",
-      "dev": true
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz",
+      "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==",
+      "optional": true
     },
     "strong-log-transformer": {
       "version": "2.1.0",
           "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
           "dev": true
         },
-        "ini": {
-          "version": "1.3.8",
-          "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz",
-          "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==",
-          "dev": true
-        },
         "picocolors": {
           "version": "0.2.1",
           "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-0.2.1.tgz",
       "dev": true
     },
     "swagger-client": {
-      "version": "3.18.5",
-      "resolved": "https://registry.npmjs.org/swagger-client/-/swagger-client-3.18.5.tgz",
-      "integrity": "sha512-c0txGDtfQTJnaIBaEKCwtRNcUaaAfj+RXI4QVV9p3WW+AUCQqp4naCjaDNNsOfMkE4ySyhnblbL+jGqAVC7snw==",
-      "requires": {
-        "@babel/runtime-corejs3": "^7.11.2",
+      "version": "3.19.7",
+      "resolved": "https://registry.npmjs.org/swagger-client/-/swagger-client-3.19.7.tgz",
+      "integrity": "sha512-5U4+tksrzVODZaLTtivzS9be6u7rX5ZSWFKDIYWsy8HCwt9FH1ANrrGpY1wDHydpOeaySbxMjMaqEM9cGWxOuQ==",
+      "requires": {
+        "@babel/runtime-corejs3": "^7.20.13",
+        "@swagger-api/apidom-core": ">=0.69.3 <1.0.0",
+        "@swagger-api/apidom-json-pointer": ">=0.69.3 <1.0.0",
+        "@swagger-api/apidom-ns-openapi-3-1": ">=0.69.3 <1.0.0",
+        "@swagger-api/apidom-reference": ">=0.69.3 <1.0.0",
         "cookie": "~0.5.0",
         "cross-fetch": "^3.1.5",
-        "deepmerge": "~4.2.2",
+        "deepmerge": "~4.3.0",
         "fast-json-patch": "^3.0.0-1",
         "form-data-encoder": "^1.4.3",
         "formdata-node": "^4.0.0",
       "dev": true
     },
     "tar": {
-      "version": "6.1.13",
-      "resolved": "https://registry.npmjs.org/tar/-/tar-6.1.13.tgz",
-      "integrity": "sha512-jdIBIN6LTIe2jqzay/2vtYLlBHa3JF42ot3h1dW8Q0PaAG4v8rm0cvpVePtau5C6OKXGGcgO9q2AMNSWxiLqKw==",
+      "version": "6.1.15",
+      "resolved": "https://registry.npmjs.org/tar/-/tar-6.1.15.tgz",
+      "integrity": "sha512-/zKt9UyngnxIT/EAGYuxaMYgOIJiP81ab9ZfkILq4oNLPFX50qyYmu7jRj9qeXoxmJHjGlbH0+cm2uy1WCs10A==",
       "dev": true,
       "requires": {
         "chownr": "^2.0.0",
         "fs-minipass": "^2.0.0",
-        "minipass": "^4.0.0",
+        "minipass": "^5.0.0",
         "minizlib": "^2.1.1",
         "mkdirp": "^1.0.3",
         "yallist": "^4.0.0"
       },
       "dependencies": {
+        "chownr": {
+          "version": "2.0.0",
+          "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz",
+          "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==",
+          "dev": true
+        },
         "minipass": {
-          "version": "4.2.4",
-          "resolved": "https://registry.npmjs.org/minipass/-/minipass-4.2.4.tgz",
-          "integrity": "sha512-lwycX3cBMTvcejsHITUgYj6Gy6A7Nh4Q6h9NP4sTHY1ccJlC7yKzDmiShEHsJ16Jf1nKGDEaiHxiltsJEvk0nQ==",
+          "version": "5.0.0",
+          "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz",
+          "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==",
           "dev": true
         },
         "yallist": {
         }
       }
     },
+    "tar-fs": {
+      "version": "2.1.1",
+      "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz",
+      "integrity": "sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==",
+      "optional": true,
+      "requires": {
+        "chownr": "^1.1.1",
+        "mkdirp-classic": "^0.5.2",
+        "pump": "^3.0.0",
+        "tar-stream": "^2.1.4"
+      }
+    },
     "tar-stream": {
       "version": "2.2.0",
       "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz",
       "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==",
-      "dev": true,
       "requires": {
         "bl": "^4.0.3",
         "end-of-stream": "^1.4.1",
         "fs-constants": "^1.0.0",
         "inherits": "^2.0.3",
         "readable-stream": "^3.1.1"
+      },
+      "dependencies": {
+        "readable-stream": {
+          "version": "3.6.2",
+          "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
+          "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==",
+          "requires": {
+            "inherits": "^2.0.3",
+            "string_decoder": "^1.1.1",
+            "util-deprecate": "^1.0.1"
+          }
+        }
       }
     },
     "terminal-link": {
       }
     },
     "terser-webpack-plugin": {
-      "version": "5.3.7",
-      "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.7.tgz",
-      "integrity": "sha512-AfKwIktyP7Cu50xNjXF/6Qb5lBNzYaWpU6YfoX3uZicTx0zTy0stDDCsvjDapKsSDvOeWo5MEq4TmdBy2cNoHw==",
+      "version": "5.3.9",
+      "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.9.tgz",
+      "integrity": "sha512-ZuXsqE07EcggTWQjXUj+Aot/OMcD0bMKGgF63f7UxYcu5/AJF53aIpK1YoP5xR9l6s/Hy2b+t1AM0bLNPRuhwA==",
       "dev": true,
       "requires": {
         "@jridgewell/trace-mapping": "^0.3.17",
         "jest-worker": "^27.4.5",
         "schema-utils": "^3.1.1",
         "serialize-javascript": "^6.0.1",
-        "terser": "^5.16.5"
+        "terser": "^5.16.8"
       },
       "dependencies": {
         "ajv": {
           "dev": true
         },
         "schema-utils": {
-          "version": "3.1.1",
-          "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.1.tgz",
-          "integrity": "sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw==",
+          "version": "3.1.2",
+          "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.2.tgz",
+          "integrity": "sha512-pvjEHOgWc9OWA/f/DE3ohBWTD6EleVLf7iFUkoSwAxttdBhB9QUebQgxER2kWueOvRJXPHNnyrvvh9eZINB8Eg==",
           "dev": true,
           "requires": {
             "@types/json-schema": "^7.0.8",
           }
         },
         "terser": {
-          "version": "5.16.6",
-          "resolved": "https://registry.npmjs.org/terser/-/terser-5.16.6.tgz",
-          "integrity": "sha512-IBZ+ZQIA9sMaXmRZCUMDjNH0D5AQQfdn4WUjHL0+1lF4TP1IHRJbrhb6fNaXWikrYQTSkb7SLxkeXAiy1p7mbg==",
+          "version": "5.17.5",
+          "resolved": "https://registry.npmjs.org/terser/-/terser-5.17.5.tgz",
+          "integrity": "sha512-NqFkzBX34WExkCbk3K5urmNCpEWqMPZnwGI1pMHwqvJ/zDlXC75u3NI7BrzoR8/pryy8Abx2e1i8ChrWkhH1Hg==",
           "dev": true,
           "requires": {
             "@jridgewell/source-map": "^0.3.2",
       "requires": {
         "readable-stream": "~2.3.6",
         "xtend": "~4.0.1"
-      },
-      "dependencies": {
-        "readable-stream": {
-          "version": "2.3.8",
-          "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz",
-          "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==",
-          "dev": true,
-          "requires": {
-            "core-util-is": "~1.0.0",
-            "inherits": "~2.0.3",
-            "isarray": "~1.0.0",
-            "process-nextick-args": "~2.0.0",
-            "safe-buffer": "~5.1.1",
-            "string_decoder": "~1.1.1",
-            "util-deprecate": "~1.0.1"
-          }
-        },
-        "safe-buffer": {
-          "version": "5.1.2",
-          "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
-          "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
-          "dev": true
-        },
-        "string_decoder": {
-          "version": "1.1.1",
-          "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
-          "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
-          "dev": true,
-          "requires": {
-            "safe-buffer": "~5.1.0"
-          }
-        }
       }
     },
     "thunky": {
       "integrity": "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==",
       "dev": true
     },
-    "time-stamp": {
-      "version": "1.1.0",
-      "resolved": "https://registry.npmjs.org/time-stamp/-/time-stamp-1.1.0.tgz",
-      "integrity": "sha512-gLCeArryy2yNTRzTGKbZbloctj64jkZ57hj5zdraXue6aFgd6PmvVtEyiUU+hvU0v7q08oVv8r8ev0tRo6bvgw==",
-      "dev": true
-    },
     "timers-browserify": {
       "version": "1.4.2",
       "resolved": "https://registry.npmjs.org/timers-browserify/-/timers-browserify-1.4.2.tgz",
       "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==",
       "dev": true
     },
+    "tree-sitter": {
+      "version": "0.20.1",
+      "resolved": "https://registry.npmjs.org/tree-sitter/-/tree-sitter-0.20.1.tgz",
+      "integrity": "sha512-Cmb8V0ocamHbgWMVhZIa+78k/7r8VCQ6+ePG8eYEAO7AccwWi06Ct4ATNiI94KwhIkRl0+OwZ42/5nk3GnEMpQ==",
+      "optional": true,
+      "requires": {
+        "nan": "^2.14.0",
+        "prebuild-install": "^6.0.1"
+      }
+    },
+    "tree-sitter-json": {
+      "version": "0.20.0",
+      "resolved": "https://registry.npmjs.org/tree-sitter-json/-/tree-sitter-json-0.20.0.tgz",
+      "integrity": "sha512-PteOLH+Tx6Bz4ZA/d40/DbkiSXXRM/gKahhHI8hQ1lWNfFvdknnz9k3Mz84ol5srRyLboJ8wp8GSkhZ6ht9EGQ==",
+      "optional": true,
+      "requires": {
+        "nan": "^2.14.1"
+      }
+    },
+    "tree-sitter-yaml": {
+      "version": "0.5.0",
+      "resolved": "https://registry.npmjs.org/tree-sitter-yaml/-/tree-sitter-yaml-0.5.0.tgz",
+      "integrity": "sha512-POJ4ZNXXSWIG/W4Rjuyg36MkUD4d769YRUGKRqN+sVaj/VCo6Dh6Pkssn1Rtewd5kybx+jT1BWMyWN0CijXnMA==",
+      "optional": true,
+      "requires": {
+        "nan": "^2.14.0"
+      }
+    },
     "trim-newlines": {
       "version": "3.0.1",
       "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-3.0.1.tgz",
           }
         },
         "semver": {
-          "version": "7.3.8",
-          "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz",
-          "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==",
+          "version": "7.5.1",
+          "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.1.tgz",
+          "integrity": "sha512-Wvss5ivl8TMRZXXESstBA4uR5iXgEN/VC5/sOcuXdVLzcdkz4HWetIoRfG5gb5X+ij/G9rw9YoGn3QoQ8OCSpw==",
           "dev": true,
           "requires": {
             "lru-cache": "^6.0.0"
         }
       }
     },
+    "ts-toolbelt": {
+      "version": "9.6.0",
+      "resolved": "https://registry.npmjs.org/ts-toolbelt/-/ts-toolbelt-9.6.0.tgz",
+      "integrity": "sha512-nsZd8ZeNUzukXPlJmTBwUAuABDe/9qtVDelJeT/qW0ow3ZS3BsQJtNkan1802aM9Uf68/Y8ljw86Hu0h5IUW3w=="
+    },
     "tsconfig-paths": {
-      "version": "4.1.2",
-      "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-4.1.2.tgz",
-      "integrity": "sha512-uhxiMgnXQp1IR622dUXI+9Ehnws7i/y6xvpZB9IbUVOPy0muvdvgXeZOn88UcGPiT98Vp3rJPTa8bFoalZ3Qhw==",
+      "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,
       "requires": {
         "json5": "^2.2.2",
       "version": "0.6.0",
       "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz",
       "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==",
-      "dev": true,
       "requires": {
         "safe-buffer": "^5.0.1"
       }
         "is-typedarray": "^1.0.0"
       }
     },
+    "types-ramda": {
+      "version": "0.29.2",
+      "resolved": "https://registry.npmjs.org/types-ramda/-/types-ramda-0.29.2.tgz",
+      "integrity": "sha512-HpLcR0ly2EfXQwG8VSI5ov6ml7PvtT+u+cp+7lZLu7q4nhnPDVW+rUTC1uy/SNs4aAyTUXri5M/LyhgvjEXJDg==",
+      "requires": {
+        "ts-toolbelt": "^9.6.0"
+      }
+    },
     "typescript": {
       "version": "4.6.4",
       "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.6.4.tgz",
       "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==",
       "dev": true
     },
+    "unraw": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/unraw/-/unraw-2.0.1.tgz",
+      "integrity": "sha512-tdOvLfRzHolwYcHS6HIX860MkK9LQ4+oLuNwFYL7bpgTEO64PZrcQxkisgwJYCfF8sKiWLwwu1c83DvMkbefIQ=="
+    },
     "unset-value": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz",
       "dev": true
     },
     "update-browserslist-db": {
-      "version": "1.0.10",
-      "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.10.tgz",
-      "integrity": "sha512-OztqDenkfFkbSG+tRxBeAnCVPckDBcvibKd35yDONx6OU8N7sqgwc7rCbkJ/WcYtVRZ4ba68d6byhC21GFh7sQ==",
+      "version": "1.0.11",
+      "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.11.tgz",
+      "integrity": "sha512-dCwEFf0/oT85M1fHBg4F0jtLwJrutGoHSQXCh7u4o2t1drG+c0a9Flnqww6XUKSfQMPpJBRjU8d4RXB09qtvaA==",
       "requires": {
         "escalade": "^3.1.1",
         "picocolors": "^1.0.0"
     "util-deprecate": {
       "version": "1.0.2",
       "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
-      "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
-      "dev": true
+      "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="
     },
     "utils-merge": {
       "version": "1.0.1",
             "ieee754": "^1.1.4"
           }
         },
-        "readable-stream": {
-          "version": "2.3.8",
-          "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz",
-          "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==",
-          "dev": true,
-          "requires": {
-            "core-util-is": "~1.0.0",
-            "inherits": "~2.0.3",
-            "isarray": "~1.0.0",
-            "process-nextick-args": "~2.0.0",
-            "safe-buffer": "~5.1.1",
-            "string_decoder": "~1.1.1",
-            "util-deprecate": "~1.0.1"
-          },
-          "dependencies": {
-            "string_decoder": {
-              "version": "1.1.1",
-              "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
-              "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
-              "dev": true,
-              "requires": {
-                "safe-buffer": "~5.1.0"
-              }
-            }
-          }
-        },
-        "safe-buffer": {
-          "version": "5.1.2",
-          "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
-          "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
-          "dev": true
-        },
         "stream-browserify": {
           "version": "3.0.0",
           "resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-3.0.0.tgz",
           },
           "dependencies": {
             "readable-stream": {
-              "version": "3.6.1",
-              "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.1.tgz",
-              "integrity": "sha512-+rQmrWMYGA90yenhTYsLWAsLsqVC8osOw6PKE1HDYiO0gdPeKe/xDHNzIAIn4C91YQ6oenEhfYqqc1883qHbjQ==",
+              "version": "3.6.2",
+              "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
+              "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==",
               "dev": true,
               "requires": {
                 "inherits": "^2.0.3",
           },
           "dependencies": {
             "readable-stream": {
-              "version": "3.6.1",
-              "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.1.tgz",
-              "integrity": "sha512-+rQmrWMYGA90yenhTYsLWAsLsqVC8osOw6PKE1HDYiO0gdPeKe/xDHNzIAIn4C91YQ6oenEhfYqqc1883qHbjQ==",
+              "version": "3.6.2",
+              "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
+              "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==",
               "dev": true,
               "requires": {
                 "inherits": "^2.0.3",
       "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-4.0.0-beta.3.tgz",
       "integrity": "sha512-QW95TCTaHmsYfHDybGMwO5IJIM93I/6vTRk+daHTWFPhwh+C8Cg7j7XyKrwrj8Ib6vYXe0ocYNrmzY4xAAN6ug=="
     },
+    "web-tree-sitter": {
+      "version": "0.20.7",
+      "resolved": "https://registry.npmjs.org/web-tree-sitter/-/web-tree-sitter-0.20.7.tgz",
+      "integrity": "sha512-flC9JJmTII9uAeeYpWF8hxDJ7bfY+leldQryetll8Nv4WgI+MXc6h7TiyAZASWl9uC9TvmfdgOjZn1DAQecb3A==",
+      "optional": true
+    },
     "webidl-conversions": {
       "version": "3.0.1",
       "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
           "dev": true
         },
         "schema-utils": {
-          "version": "3.1.1",
-          "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.1.tgz",
-          "integrity": "sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw==",
+          "version": "3.1.2",
+          "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.2.tgz",
+          "integrity": "sha512-pvjEHOgWc9OWA/f/DE3ohBWTD6EleVLf7iFUkoSwAxttdBhB9QUebQgxER2kWueOvRJXPHNnyrvvh9eZINB8Eg==",
           "dev": true,
           "requires": {
             "@types/json-schema": "^7.0.8",
           }
         },
         "schema-utils": {
-          "version": "4.0.0",
-          "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.0.0.tgz",
-          "integrity": "sha512-1edyXKgh6XnJsJSQ8mKWXnN/BVaIbFMLpouRUrXgVq7WYne5kw3MW7UPhO44uRXQSIpTSXoJbmrR2X0w9kUTyg==",
+          "version": "4.0.1",
+          "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.0.1.tgz",
+          "integrity": "sha512-lELhBAAly9NowEsX0yZBlw9ahZG+sK/1RJ21EpzdYHKEs13Vku3LJ+MIPhh4sMs0oCCeufZQEQbMekiA4vuVIQ==",
           "dev": true,
           "requires": {
             "@types/json-schema": "^7.0.9",
-            "ajv": "^8.8.0",
+            "ajv": "^8.9.0",
             "ajv-formats": "^2.1.1",
-            "ajv-keywords": "^5.0.0"
+            "ajv-keywords": "^5.1.0"
           }
         }
       }
           "dev": true
         },
         "schema-utils": {
-          "version": "4.0.0",
-          "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.0.0.tgz",
-          "integrity": "sha512-1edyXKgh6XnJsJSQ8mKWXnN/BVaIbFMLpouRUrXgVq7WYne5kw3MW7UPhO44uRXQSIpTSXoJbmrR2X0w9kUTyg==",
+          "version": "4.0.1",
+          "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.0.1.tgz",
+          "integrity": "sha512-lELhBAAly9NowEsX0yZBlw9ahZG+sK/1RJ21EpzdYHKEs13Vku3LJ+MIPhh4sMs0oCCeufZQEQbMekiA4vuVIQ==",
           "dev": true,
           "requires": {
             "@types/json-schema": "^7.0.9",
-            "ajv": "^8.8.0",
+            "ajv": "^8.9.0",
             "ajv-formats": "^2.1.1",
-            "ajv-keywords": "^5.0.0"
+            "ajv-keywords": "^5.1.0"
           }
         },
         "strip-ansi": {
       }
     },
     "which-module": {
-      "version": "2.0.0",
-      "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz",
-      "integrity": "sha512-B+enWhmw6cjfVC7kS8Pj9pCrKSc5txArRyaYGe088shv/FGWH+0Rjx/xPgtsWfsUtS27FkP697E4DDhgrgoc0Q==",
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.1.tgz",
+      "integrity": "sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==",
       "dev": true
     },
     "which-typed-array": {
       "version": "1.1.5",
       "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz",
       "integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==",
-      "dev": true,
       "requires": {
         "string-width": "^1.0.2 || 2 || 3 || 4"
       }
     },
     "wildcard": {
-      "version": "2.0.0",
-      "resolved": "https://registry.npmjs.org/wildcard/-/wildcard-2.0.0.tgz",
-      "integrity": "sha512-JcKqAHLPxcdb9KM49dufGXn2x3ssnfjbcaQdLlfZsL9rH9wgDQjUtDxbo8NE0F6SFvydeu1VhZe7hZuHsB2/pw==",
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/wildcard/-/wildcard-2.0.1.tgz",
+      "integrity": "sha512-CC1bOL87PIWSBhDcTrdeLo6eGT7mCFtrg0uIJtqJUFyK+eJnzl8A1niH56uu7KMa5XFrtiV+AQuHO3n7DsHnLQ==",
       "dev": true
     },
     "windows-release": {
       }
     },
     "ws": {
-      "version": "8.12.1",
-      "resolved": "https://registry.npmjs.org/ws/-/ws-8.12.1.tgz",
-      "integrity": "sha512-1qo+M9Ba+xNhPB+YTWUlK6M17brTut5EXbcBaMRN5pH5dFrXz7lzz1ChFSUq3bOUl8yEvSenhHmYUNJxFzdJew==",
+      "version": "8.13.0",
+      "resolved": "https://registry.npmjs.org/ws/-/ws-8.13.0.tgz",
+      "integrity": "sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==",
       "dev": true
     },
     "xliff": {
       "dev": true
     },
     "xmldoc": {
-      "version": "1.2.0",
-      "resolved": "https://registry.npmjs.org/xmldoc/-/xmldoc-1.2.0.tgz",
-      "integrity": "sha512-2eN8QhjBsMW2uVj7JHLHkMytpvGHLHxKXBy4J3fAT/HujsEtM6yU84iGjpESYGHg6XwK0Vu4l+KgqQ2dv2cCqg==",
+      "version": "1.3.0",
+      "resolved": "https://registry.npmjs.org/xmldoc/-/xmldoc-1.3.0.tgz",
+      "integrity": "sha512-y7IRWW6PvEnYQZNZFMRLNJw+p3pezM4nKYPfr15g4OOW9i8VpeydycFuipE2297OvZnh3jSb2pxOt9QpkZUVng==",
       "dev": true,
       "requires": {
         "sax": "^1.2.4"
       "dev": true
     },
     "yargs": {
-      "version": "17.7.1",
-      "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.1.tgz",
-      "integrity": "sha512-cwiTb08Xuv5fqF4AovYacTFNxk62th7LKJ6BL9IGUpTJrWoU7/7WdQGTP2SjKf1dUNBGzDd28p/Yfs/GI6JrLw==",
+      "version": "17.7.2",
+      "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz",
+      "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==",
       "requires": {
         "cliui": "^8.0.1",
         "escalade": "^3.1.1",
index de08d364a1220f33cbcbe132d19e78009306a7f8..e55cb39bc0434f41ec2a428b8055589ba705dca8 100644 (file)
@@ -25,7 +25,7 @@
     "pree2e:ci": "npm run pree2e",
     "e2e:ci": "start-test 4200 'cypress run -b chrome --headless'",
     "lint:eslint": "ng lint",
-    "lint:gherkin": "gherkin-lint -c .gherkin-lintrc cypress/integration",
+    "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",
@@ -93,7 +93,6 @@
     "@apteco/ngth": "1.5.0",
     "@compodoc/compodoc": "1.1.18",
     "@cypress/browserify-preprocessor": "3.0.2",
-    "@grafana/e2e": "8.4.5",
     "@types/brace-expansion": "1.1.0",
     "@types/cypress-cucumber-preprocessor": "4.0.1",
     "@types/jest": "28.1.3",
     "@typescript-eslint/eslint-plugin": "5.27.1",
     "@typescript-eslint/parser": "5.27.1",
     "axe-core": "4.4.3",
-    "cypress": "9.7.0",
+    "cypress": "10.11.0",
     "cypress-axe": "0.14.0",
     "cypress-cucumber-preprocessor": "4.3.1",
     "cypress-iframe": "1.0.1",
     "typescript": "4.6.4"
   },
   "cypress-cucumber-preprocessor": {
-    "nonGlobalStepDefinitions": true
+    "stepDefinitions": "cypress/e2e/common"
   }
 }
index e01d3480ee079f4f1e500563dd4eb65f6459fc59..dab14fd5842d6a473a1d5fb29767dce69102889f 100644 (file)
@@ -30,7 +30,9 @@
           <div class="col-sm-6 col-lg-6 metadata"
                *ngIf="metadata">
             <legend>{{ metadataTitle }}</legend>
-            <cd-table-key-value [data]="metadata"></cd-table-key-value>
+            <div>
+              <cd-table-key-value [data]="metadata"></cd-table-key-value>
+            </div>
           </div>
         </div>
       </div>