From: Nizamudeen A Date: Wed, 8 Feb 2023 15:49:31 +0000 (+0530) Subject: mgr/dashboard: update to cypress 10 X-Git-Tag: v19.0.0~1140^2~2 X-Git-Url: http://git-server-git.apps.pok.os.sepia.ceph.com/?a=commitdiff_plain;h=222dec3750304385dff51a2a4bfb91e12eb4e9aa;p=ceph.git mgr/dashboard: update to cypress 10 Fixes: https://tracker.ceph.com/issues/61354 Signed-off-by: Nizamudeen A --- diff --git a/src/pybind/mgr/dashboard/ci/cephadm/run-cephadm-e2e-tests.sh b/src/pybind/mgr/dashboard/ci/cephadm/run-cephadm-e2e-tests.sh index 45bb3f0257ebc..e416de1b1337d 100755 --- a/src/pybind/mgr/dashboard/ci/cephadm/run-cephadm-e2e-tests.sh +++ b/src/pybind/mgr/dashboard/ci/cephadm/run-cephadm-e2e-tests.sh @@ -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 index 0000000000000..3948ea8dbb53d --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/cypress.config.ts @@ -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 index 35cf87241d338..0000000000000 --- a/src/pybind/mgr/dashboard/frontend/cypress.json +++ /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 index 0000000000000..4feea0da71d07 --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/cypress/e2e/a11y/dashboard.e2e-spec.ts @@ -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 index 0000000000000..2a0c5c5a533f5 --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/cypress/e2e/a11y/navigation.e2e-spec.ts @@ -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 index 0000000000000..5c89359db790e --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/cypress/e2e/block/images.e2e-spec.ts @@ -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 index 0000000000000..bf6cbc05263b0 --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/cypress/e2e/block/images.po.ts @@ -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 index 0000000000000..cef4874bed50f --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/cypress/e2e/block/iscsi.e2e-spec.ts @@ -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 index 0000000000000..08efa6408bd7a --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/cypress/e2e/block/iscsi.po.ts @@ -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 index 0000000000000..266176949c596 --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/cypress/e2e/block/mirroring.e2e-spec.ts @@ -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 index 0000000000000..c4adca8b72fe9 --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/cypress/e2e/block/mirroring.po.ts @@ -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 index 0000000000000..d022d59cfa9a2 --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/cypress/e2e/cluster/configuration.e2e-spec.ts @@ -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 index 0000000000000..0133dc31f9030 --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/cypress/e2e/cluster/configuration.po.ts @@ -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 index 0000000000000..300eddbcc3de8 --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/cypress/e2e/cluster/create-cluster.po.ts @@ -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 index 0000000000000..0a454739fd406 --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/cypress/e2e/cluster/crush-map.e2e-spec.ts @@ -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 index 0000000000000..a5d2d591ce048 --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/cypress/e2e/cluster/crush-map.po.ts @@ -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 index 0000000000000..e4f9936c3e36f --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/cypress/e2e/cluster/hosts.e2e-spec.ts @@ -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 index 0000000000000..9511142ed43d9 --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/cypress/e2e/cluster/hosts.po.ts @@ -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 index 0000000000000..5a9abdc036c91 --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/cypress/e2e/cluster/inventory.po.ts @@ -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 index 0000000000000..ecc3cc1cd9126 --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/cypress/e2e/cluster/logs.e2e-spec.ts @@ -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 index 0000000000000..7efd8a6528a3e --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/cypress/e2e/cluster/logs.po.ts @@ -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 index 0000000000000..0a2aa8184ceee --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/cypress/e2e/cluster/mgr-modules.e2e-spec.ts @@ -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 index 0000000000000..04d2eee46142f --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/cypress/e2e/cluster/mgr-modules.po.ts @@ -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 index 0000000000000..a23d071e6d721 --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/cypress/e2e/cluster/monitors.e2e-spec.ts @@ -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 index 0000000000000..4113b99288d1f --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/cypress/e2e/cluster/monitors.po.ts @@ -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 index 0000000000000..2fc148a1788c3 --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/cypress/e2e/cluster/osds.e2e-spec.ts @@ -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 index 0000000000000..cd812f474fb89 --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/cypress/e2e/cluster/osds.po.ts @@ -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 index 0000000000000..c464a3f6cf817 --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/cypress/e2e/cluster/services.po.ts @@ -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 index 0000000000000..87acda97fd83c --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/cypress/e2e/cluster/users.e2e-spec.ts @@ -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 index 0000000000000..bce659ff005c6 --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/cypress/e2e/cluster/users.po.ts @@ -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 index 0000000000000..d5b4645b8e7c6 --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/cypress/e2e/common/01-global.feature.po.ts @@ -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 index 0000000000000..d18c348554691 --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/cypress/e2e/common/create-cluster/create-cluster.feature.po.ts @@ -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 index 0000000000000..4b2ee4d00437e --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/cypress/e2e/common/grafana.feature.po.ts @@ -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 index 0000000000000..2863550857150 --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/cypress/e2e/common/urls.po.ts @@ -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 index 0000000000000..e623475fd784d --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/cypress/e2e/filesystems/filesystems.e2e-spec.ts @@ -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 index 0000000000000..bd6e5b8b7b44c --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/cypress/e2e/filesystems/filesystems.po.ts @@ -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 index 0000000000000..aca36ade19219 --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/cypress/e2e/orchestrator/01-hosts.e2e-spec.ts @@ -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 index 0000000000000..a64e3bc8c020f --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/cypress/e2e/orchestrator/03-inventory.e2e-spec.ts @@ -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 index 0000000000000..41f0933b7a0b8 --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/cypress/e2e/orchestrator/04-osds.e2e-spec.ts @@ -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 index 0000000000000..fb5e6ac8923aa --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/cypress/e2e/orchestrator/05-services.e2e-spec.ts @@ -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 index 0000000000000..62476ad25a4bf --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/cypress/e2e/orchestrator/grafana/grafana.feature @@ -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 "" + When I view the grafana panel "" + Then I should not see "No Data" in the 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 "" + When I view the grafana panel "" + Then I should not see No Data in the graph "" + And I should see the legends "" in the graph "" + + 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 "" + And I go to the "Performance Details" tab + Then I should see the grafana panel "" + When I view the grafana panel "" + Then I should not see No Data in the graph "" + And I should see the legends "" in the graph "" + + 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 index 0000000000000..6ba2fc4fc54c0 --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/cypress/e2e/orchestrator/workflow/01-create-cluster-welcome.feature @@ -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 index 0000000000000..be49fcba09935 --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/cypress/e2e/orchestrator/workflow/02-create-cluster-add-host.feature @@ -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" "" + And select options "" + And I click on "Add Host" button + Then I should not see the modal + And I should see a row with "" + And I should see row "" have "" + + 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 "" + When I select a row "" + 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 "" + + 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 "" + And I click on "Edit" button from the table actions + And "add" option "" + And I click on "Edit Host" button + Then I should see row "" have "" + When I select a row "" + And I click on "Edit" button from the table actions + And "remove" option "" + And I click on "Edit Host" button + Then I should see row "" does not have "" + + 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 index 0000000000000..745a2ec5d18fb --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/cypress/e2e/orchestrator/workflow/03-create-cluster-create-services.e2e-spec.ts @@ -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 index 0000000000000..24262435da2aa --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/cypress/e2e/orchestrator/workflow/04-create-cluster-create-osds.e2e-spec.ts @@ -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 index 0000000000000..f93ad7a975bb9 --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/cypress/e2e/orchestrator/workflow/05-create-cluster-review.e2e-spec.ts @@ -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 index 0000000000000..94cb36c1929e3 --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/cypress/e2e/orchestrator/workflow/06-cluster-check.e2e-spec.ts @@ -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 index 0000000000000..a0a1dd03214b4 --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/cypress/e2e/orchestrator/workflow/07-osds.e2e-spec.ts @@ -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 index 0000000000000..6e8c63279c2cd --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/cypress/e2e/orchestrator/workflow/08-hosts.e2e-spec.ts @@ -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 index 0000000000000..91f2de58e6b58 --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/cypress/e2e/orchestrator/workflow/09-services.e2e-spec.ts @@ -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 index 0000000000000..f97509db3b033 --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/cypress/e2e/orchestrator/workflow/10-nfs-exports.e2e-spec.ts @@ -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 index 0000000000000..c700ef0581dd7 --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/cypress/e2e/orchestrator/workflow/nfs/nfs-export.po.ts @@ -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 index 0000000000000..e4bbd3f342119 --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/cypress/e2e/page-helper.po.ts @@ -0,0 +1,309 @@ +interface Page { + url: string; + id: string; +} + +export abstract class PageHelper { + pages: Record; + + /** + * 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 index 0000000000000..b4c3c75ac5b85 --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/cypress/e2e/pools/pools.e2e-spec.ts @@ -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 index 0000000000000..7cca96aa8f466 --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/cypress/e2e/pools/pools.po.ts @@ -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 index 0000000000000..6c50b48ec0b8a --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/cypress/e2e/rgw/buckets.e2e-spec.ts @@ -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 index 0000000000000..a27be3c6ba487 --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/cypress/e2e/rgw/buckets.po.ts @@ -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 index 0000000000000..f3129a7ffd9e8 --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/cypress/e2e/rgw/daemons.e2e-spec.ts @@ -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 index 0000000000000..82a179463bc32 --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/cypress/e2e/rgw/daemons.po.ts @@ -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 index 0000000000000..b5f366a090935 --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/cypress/e2e/rgw/users.e2e-spec.ts @@ -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 index 0000000000000..980cced88072d --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/cypress/e2e/rgw/users.po.ts @@ -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 index 0000000000000..52994859e2495 --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/cypress/e2e/ui/api-docs.e2e-spec.ts @@ -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 index 0000000000000..c7a8d222d2c35 --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/cypress/e2e/ui/api-docs.po.ts @@ -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 index 0000000000000..8fa0013c36a0a --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/cypress/e2e/ui/dashboard-v3.e2e-spec.ts @@ -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 index 0000000000000..597d2db9b5088 --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/cypress/e2e/ui/dashboard-v3.po.ts @@ -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 index 0000000000000..43def20e1fdc2 --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/cypress/e2e/ui/dashboard.e2e-spec.ts @@ -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 index 0000000000000..42d63ef44117c --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/cypress/e2e/ui/dashboard.po.ts @@ -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 index 0000000000000..ccf16c2b55c78 --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/cypress/e2e/ui/language.e2e-spec.ts @@ -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 index 0000000000000..80e21ba1e3d25 --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/cypress/e2e/ui/language.po.ts @@ -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 index 0000000000000..2b337e6341628 --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/cypress/e2e/ui/login.e2e-spec.ts @@ -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 index 0000000000000..d4d2c692116a8 --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/cypress/e2e/ui/login.po.ts @@ -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 index 0000000000000..fee2d2db967ad --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/cypress/e2e/ui/navigation.e2e-spec.ts @@ -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 index 0000000000000..f797bbc26a90c --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/cypress/e2e/ui/navigation.po.ts @@ -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 index 0000000000000..2ee73a70632ba --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/cypress/e2e/ui/notification.e2e-spec.ts @@ -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 index 0000000000000..12c424e350d74 --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/cypress/e2e/ui/notification.po.ts @@ -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 index 0000000000000..c3f325dbbe13d --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/cypress/e2e/ui/role-mgmt.e2e-spec.ts @@ -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 index 0000000000000..1cc3630a46318 --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/cypress/e2e/ui/role-mgmt.po.ts @@ -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 index 0000000000000..92dc772121b11 --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/cypress/e2e/ui/user-mgmt.e2e-spec.ts @@ -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 index 0000000000000..fb2b791294432 --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/cypress/e2e/ui/user-mgmt.po.ts @@ -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 index 0000000000000..450cff871b53d --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/cypress/e2e/visualTests/dashboard.vrt-spec.ts @@ -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 index 0000000000000..ea74f1d0f748a --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/cypress/e2e/visualTests/login.vrt-spec.ts @@ -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 index 4feea0da71d07..0000000000000 --- a/src/pybind/mgr/dashboard/frontend/cypress/integration/a11y/dashboard.e2e-spec.ts +++ /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 index 2a0c5c5a533f5..0000000000000 --- a/src/pybind/mgr/dashboard/frontend/cypress/integration/a11y/navigation.e2e-spec.ts +++ /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 index 5c89359db790e..0000000000000 --- a/src/pybind/mgr/dashboard/frontend/cypress/integration/block/images.e2e-spec.ts +++ /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 index bf6cbc05263b0..0000000000000 --- a/src/pybind/mgr/dashboard/frontend/cypress/integration/block/images.po.ts +++ /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 index cef4874bed50f..0000000000000 --- a/src/pybind/mgr/dashboard/frontend/cypress/integration/block/iscsi.e2e-spec.ts +++ /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 index 08efa6408bd7a..0000000000000 --- a/src/pybind/mgr/dashboard/frontend/cypress/integration/block/iscsi.po.ts +++ /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 index 4e17840df016e..0000000000000 --- a/src/pybind/mgr/dashboard/frontend/cypress/integration/block/mirroring.e2e-spec.ts +++ /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 index c4adca8b72fe9..0000000000000 --- a/src/pybind/mgr/dashboard/frontend/cypress/integration/block/mirroring.po.ts +++ /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 index d022d59cfa9a2..0000000000000 --- a/src/pybind/mgr/dashboard/frontend/cypress/integration/cluster/configuration.e2e-spec.ts +++ /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 index 0133dc31f9030..0000000000000 --- a/src/pybind/mgr/dashboard/frontend/cypress/integration/cluster/configuration.po.ts +++ /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 index 300eddbcc3de8..0000000000000 --- a/src/pybind/mgr/dashboard/frontend/cypress/integration/cluster/create-cluster.po.ts +++ /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 index 0a454739fd406..0000000000000 --- a/src/pybind/mgr/dashboard/frontend/cypress/integration/cluster/crush-map.e2e-spec.ts +++ /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 index a5d2d591ce048..0000000000000 --- a/src/pybind/mgr/dashboard/frontend/cypress/integration/cluster/crush-map.po.ts +++ /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 index e4f9936c3e36f..0000000000000 --- a/src/pybind/mgr/dashboard/frontend/cypress/integration/cluster/hosts.e2e-spec.ts +++ /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 index 9511142ed43d9..0000000000000 --- a/src/pybind/mgr/dashboard/frontend/cypress/integration/cluster/hosts.po.ts +++ /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 index 5a9abdc036c91..0000000000000 --- a/src/pybind/mgr/dashboard/frontend/cypress/integration/cluster/inventory.po.ts +++ /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 index ecc3cc1cd9126..0000000000000 --- a/src/pybind/mgr/dashboard/frontend/cypress/integration/cluster/logs.e2e-spec.ts +++ /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 index 7efd8a6528a3e..0000000000000 --- a/src/pybind/mgr/dashboard/frontend/cypress/integration/cluster/logs.po.ts +++ /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 index 0a2aa8184ceee..0000000000000 --- a/src/pybind/mgr/dashboard/frontend/cypress/integration/cluster/mgr-modules.e2e-spec.ts +++ /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 index 04d2eee46142f..0000000000000 --- a/src/pybind/mgr/dashboard/frontend/cypress/integration/cluster/mgr-modules.po.ts +++ /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 index a23d071e6d721..0000000000000 --- a/src/pybind/mgr/dashboard/frontend/cypress/integration/cluster/monitors.e2e-spec.ts +++ /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 index 4113b99288d1f..0000000000000 --- a/src/pybind/mgr/dashboard/frontend/cypress/integration/cluster/monitors.po.ts +++ /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 index 2fc148a1788c3..0000000000000 --- a/src/pybind/mgr/dashboard/frontend/cypress/integration/cluster/osds.e2e-spec.ts +++ /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 index cd812f474fb89..0000000000000 --- a/src/pybind/mgr/dashboard/frontend/cypress/integration/cluster/osds.po.ts +++ /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 index c464a3f6cf817..0000000000000 --- a/src/pybind/mgr/dashboard/frontend/cypress/integration/cluster/services.po.ts +++ /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 index 87acda97fd83c..0000000000000 --- a/src/pybind/mgr/dashboard/frontend/cypress/integration/cluster/users.e2e-spec.ts +++ /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 index 8778384f46f80..0000000000000 --- a/src/pybind/mgr/dashboard/frontend/cypress/integration/cluster/users.po.ts +++ /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 index d5b4645b8e7c6..0000000000000 --- a/src/pybind/mgr/dashboard/frontend/cypress/integration/common/01-global.feature.po.ts +++ /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 index d18c348554691..0000000000000 --- a/src/pybind/mgr/dashboard/frontend/cypress/integration/common/create-cluster/create-cluster.feature.po.ts +++ /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 index 7366f8babb3f6..0000000000000 --- a/src/pybind/mgr/dashboard/frontend/cypress/integration/common/grafana.feature.po.ts +++ /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 index 2863550857150..0000000000000 --- a/src/pybind/mgr/dashboard/frontend/cypress/integration/common/urls.po.ts +++ /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 index e623475fd784d..0000000000000 --- a/src/pybind/mgr/dashboard/frontend/cypress/integration/filesystems/filesystems.e2e-spec.ts +++ /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 index bd6e5b8b7b44c..0000000000000 --- a/src/pybind/mgr/dashboard/frontend/cypress/integration/filesystems/filesystems.po.ts +++ /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 index aca36ade19219..0000000000000 --- a/src/pybind/mgr/dashboard/frontend/cypress/integration/orchestrator/01-hosts.e2e-spec.ts +++ /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 index a64e3bc8c020f..0000000000000 --- a/src/pybind/mgr/dashboard/frontend/cypress/integration/orchestrator/03-inventory.e2e-spec.ts +++ /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 index 41f0933b7a0b8..0000000000000 --- a/src/pybind/mgr/dashboard/frontend/cypress/integration/orchestrator/04-osds.e2e-spec.ts +++ /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 index fb5e6ac8923aa..0000000000000 --- a/src/pybind/mgr/dashboard/frontend/cypress/integration/orchestrator/05-services.e2e-spec.ts +++ /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 index 62476ad25a4bf..0000000000000 --- a/src/pybind/mgr/dashboard/frontend/cypress/integration/orchestrator/grafana/grafana.feature +++ /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 "" - When I view the grafana panel "" - Then I should not see "No Data" in the 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 "" - When I view the grafana panel "" - Then I should not see No Data in the graph "" - And I should see the legends "" in the graph "" - - 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 "" - And I go to the "Performance Details" tab - Then I should see the grafana panel "" - When I view the grafana panel "" - Then I should not see No Data in the graph "" - And I should see the legends "" in the graph "" - - 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 index 6ba2fc4fc54c0..0000000000000 --- a/src/pybind/mgr/dashboard/frontend/cypress/integration/orchestrator/workflow/01-create-cluster-welcome.feature +++ /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 index be49fcba09935..0000000000000 --- a/src/pybind/mgr/dashboard/frontend/cypress/integration/orchestrator/workflow/02-create-cluster-add-host.feature +++ /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" "" - And select options "" - And I click on "Add Host" button - Then I should not see the modal - And I should see a row with "" - And I should see row "" have "" - - 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 "" - When I select a row "" - 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 "" - - 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 "" - And I click on "Edit" button from the table actions - And "add" option "" - And I click on "Edit Host" button - Then I should see row "" have "" - When I select a row "" - And I click on "Edit" button from the table actions - And "remove" option "" - And I click on "Edit Host" button - Then I should see row "" does not have "" - - 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 index 745a2ec5d18fb..0000000000000 --- a/src/pybind/mgr/dashboard/frontend/cypress/integration/orchestrator/workflow/03-create-cluster-create-services.e2e-spec.ts +++ /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 index 24262435da2aa..0000000000000 --- a/src/pybind/mgr/dashboard/frontend/cypress/integration/orchestrator/workflow/04-create-cluster-create-osds.e2e-spec.ts +++ /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 index f93ad7a975bb9..0000000000000 --- a/src/pybind/mgr/dashboard/frontend/cypress/integration/orchestrator/workflow/05-create-cluster-review.e2e-spec.ts +++ /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 index 94cb36c1929e3..0000000000000 --- a/src/pybind/mgr/dashboard/frontend/cypress/integration/orchestrator/workflow/06-cluster-check.e2e-spec.ts +++ /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 index a0a1dd03214b4..0000000000000 --- a/src/pybind/mgr/dashboard/frontend/cypress/integration/orchestrator/workflow/07-osds.e2e-spec.ts +++ /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 index 6e8c63279c2cd..0000000000000 --- a/src/pybind/mgr/dashboard/frontend/cypress/integration/orchestrator/workflow/08-hosts.e2e-spec.ts +++ /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 index 91f2de58e6b58..0000000000000 --- a/src/pybind/mgr/dashboard/frontend/cypress/integration/orchestrator/workflow/09-services.e2e-spec.ts +++ /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 index f97509db3b033..0000000000000 --- a/src/pybind/mgr/dashboard/frontend/cypress/integration/orchestrator/workflow/10-nfs-exports.e2e-spec.ts +++ /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 index c700ef0581dd7..0000000000000 --- a/src/pybind/mgr/dashboard/frontend/cypress/integration/orchestrator/workflow/nfs/nfs-export.po.ts +++ /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 index e4bbd3f342119..0000000000000 --- a/src/pybind/mgr/dashboard/frontend/cypress/integration/page-helper.po.ts +++ /dev/null @@ -1,309 +0,0 @@ -interface Page { - url: string; - id: string; -} - -export abstract class PageHelper { - pages: Record; - - /** - * 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 index b4c3c75ac5b85..0000000000000 --- a/src/pybind/mgr/dashboard/frontend/cypress/integration/pools/pools.e2e-spec.ts +++ /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 index 7cca96aa8f466..0000000000000 --- a/src/pybind/mgr/dashboard/frontend/cypress/integration/pools/pools.po.ts +++ /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 index 6c50b48ec0b8a..0000000000000 --- a/src/pybind/mgr/dashboard/frontend/cypress/integration/rgw/buckets.e2e-spec.ts +++ /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 index a27be3c6ba487..0000000000000 --- a/src/pybind/mgr/dashboard/frontend/cypress/integration/rgw/buckets.po.ts +++ /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 index f3129a7ffd9e8..0000000000000 --- a/src/pybind/mgr/dashboard/frontend/cypress/integration/rgw/daemons.e2e-spec.ts +++ /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 index 82a179463bc32..0000000000000 --- a/src/pybind/mgr/dashboard/frontend/cypress/integration/rgw/daemons.po.ts +++ /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 index b5f366a090935..0000000000000 --- a/src/pybind/mgr/dashboard/frontend/cypress/integration/rgw/users.e2e-spec.ts +++ /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 index a4266f989e083..0000000000000 --- a/src/pybind/mgr/dashboard/frontend/cypress/integration/rgw/users.po.ts +++ /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 index 52994859e2495..0000000000000 --- a/src/pybind/mgr/dashboard/frontend/cypress/integration/ui/api-docs.e2e-spec.ts +++ /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 index c7a8d222d2c35..0000000000000 --- a/src/pybind/mgr/dashboard/frontend/cypress/integration/ui/api-docs.po.ts +++ /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 index cb72824e97182..0000000000000 --- a/src/pybind/mgr/dashboard/frontend/cypress/integration/ui/dashboard-v3.e2e-spec.ts +++ /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 index e720724103e29..0000000000000 --- a/src/pybind/mgr/dashboard/frontend/cypress/integration/ui/dashboard-v3.po.ts +++ /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 index 110b496037f8d..0000000000000 --- a/src/pybind/mgr/dashboard/frontend/cypress/integration/ui/dashboard.e2e-spec.ts +++ /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 index 42d63ef44117c..0000000000000 --- a/src/pybind/mgr/dashboard/frontend/cypress/integration/ui/dashboard.po.ts +++ /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 index ccf16c2b55c78..0000000000000 --- a/src/pybind/mgr/dashboard/frontend/cypress/integration/ui/language.e2e-spec.ts +++ /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 index 80e21ba1e3d25..0000000000000 --- a/src/pybind/mgr/dashboard/frontend/cypress/integration/ui/language.po.ts +++ /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 index 2b337e6341628..0000000000000 --- a/src/pybind/mgr/dashboard/frontend/cypress/integration/ui/login.e2e-spec.ts +++ /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 index d4d2c692116a8..0000000000000 --- a/src/pybind/mgr/dashboard/frontend/cypress/integration/ui/login.po.ts +++ /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 index fee2d2db967ad..0000000000000 --- a/src/pybind/mgr/dashboard/frontend/cypress/integration/ui/navigation.e2e-spec.ts +++ /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 index 3bad05559907f..0000000000000 --- a/src/pybind/mgr/dashboard/frontend/cypress/integration/ui/navigation.po.ts +++ /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 index 2ee73a70632ba..0000000000000 --- a/src/pybind/mgr/dashboard/frontend/cypress/integration/ui/notification.e2e-spec.ts +++ /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 index 12c424e350d74..0000000000000 --- a/src/pybind/mgr/dashboard/frontend/cypress/integration/ui/notification.po.ts +++ /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 index c3f325dbbe13d..0000000000000 --- a/src/pybind/mgr/dashboard/frontend/cypress/integration/ui/role-mgmt.e2e-spec.ts +++ /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 index 1cc3630a46318..0000000000000 --- a/src/pybind/mgr/dashboard/frontend/cypress/integration/ui/role-mgmt.po.ts +++ /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 index 92dc772121b11..0000000000000 --- a/src/pybind/mgr/dashboard/frontend/cypress/integration/ui/user-mgmt.e2e-spec.ts +++ /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 index fb2b791294432..0000000000000 --- a/src/pybind/mgr/dashboard/frontend/cypress/integration/ui/user-mgmt.po.ts +++ /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 index 450cff871b53d..0000000000000 --- a/src/pybind/mgr/dashboard/frontend/cypress/integration/visualTests/dashboard.vrt-spec.ts +++ /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 index ea74f1d0f748a..0000000000000 --- a/src/pybind/mgr/dashboard/frontend/cypress/integration/visualTests/login.vrt-spec.ts +++ /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' }); - }); -}); diff --git a/src/pybind/mgr/dashboard/frontend/cypress/support/commands.ts b/src/pybind/mgr/dashboard/frontend/cypress/support/commands.ts index 04eabb8abbd88..2ab1b50025b84 100644 --- a/src/pybind/mgr/dashboard/frontend/cypress/support/commands.ts +++ b/src/pybind/mgr/dashboard/frontend/cypress/support/commands.ts @@ -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 index 0000000000000..4db2c6a4926a9 --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/cypress/support/e2e.ts @@ -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 index 4db2c6a4926a9..0000000000000 --- a/src/pybind/mgr/dashboard/frontend/cypress/support/index.ts +++ /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; -}); diff --git a/src/pybind/mgr/dashboard/frontend/package-lock.json b/src/pybind/mgr/dashboard/frontend/package-lock.json index 2668137b6c058..0b48ce40bd9dd 100644 --- a/src/pybind/mgr/dashboard/frontend/package-lock.json +++ b/src/pybind/mgr/dashboard/frontend/package-lock.json @@ -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": { @@ -119,28 +119,14 @@ "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": { @@ -171,12 +157,6 @@ "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 } } }, @@ -189,14 +169,6 @@ "@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": { @@ -219,12 +191,50 @@ "@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", @@ -275,6 +285,12 @@ "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", @@ -293,30 +309,6 @@ "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", @@ -335,9 +327,9 @@ } }, "@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", @@ -378,6 +370,20 @@ "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", @@ -451,6 +457,12 @@ "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", @@ -517,6 +529,30 @@ "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", @@ -526,6 +562,12 @@ "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", @@ -546,6 +588,15 @@ "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", @@ -555,6 +606,12 @@ "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", @@ -616,9 +673,9 @@ } }, "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" @@ -1128,6 +1185,12 @@ "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", @@ -1478,17 +1541,17 @@ "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", @@ -1513,26 +1576,14 @@ } }, "@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": { @@ -1545,41 +1596,41 @@ } }, "@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": { @@ -1594,13 +1645,14 @@ } }, "@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": { @@ -1629,18 +1681,9 @@ } }, "@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", @@ -1660,35 +1703,35 @@ } }, "@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": { @@ -1701,9 +1744,9 @@ } }, "@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": { @@ -1730,25 +1773,25 @@ } }, "@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": { @@ -1769,9 +1812,9 @@ } }, "@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", @@ -1796,13 +1839,13 @@ } }, "@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": { @@ -1816,9 +1859,9 @@ } }, "@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", @@ -2082,12 +2125,12 @@ } }, "@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": { @@ -2163,21 +2206,21 @@ } }, "@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": { @@ -2238,19 +2281,19 @@ } }, "@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" @@ -2286,12 +2329,12 @@ } }, "@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": { @@ -2334,14 +2377,14 @@ } }, "@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": { @@ -2396,9 +2439,9 @@ } }, "@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" @@ -2423,16 +2466,16 @@ } }, "@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": { @@ -2477,12 +2520,12 @@ } }, "@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" } }, @@ -2556,12 +2599,12 @@ } }, "@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": { @@ -2690,55 +2733,55 @@ "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" } @@ -2881,9 +2924,9 @@ } }, "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" @@ -2991,40 +3034,53 @@ } }, "@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": { @@ -3037,9 +3093,9 @@ } }, "@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": { @@ -3202,156 +3258,156 @@ "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 }, @@ -3398,6 +3454,12 @@ "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 } } }, @@ -3600,9 +3662,9 @@ } }, "@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": { @@ -4462,12 +4524,13 @@ } }, "@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": { @@ -4481,40 +4544,34 @@ "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": { @@ -4604,9 +4661,9 @@ } }, "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" @@ -4646,9 +4703,9 @@ } }, "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" @@ -4710,21 +4767,21 @@ } }, "@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": { @@ -4801,9 +4858,9 @@ } }, "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", @@ -4871,22 +4928,22 @@ } }, "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", @@ -4998,65 +5055,65 @@ } }, "@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 }, @@ -5257,25 +5314,56 @@ "@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 @@ -5314,21 +5402,21 @@ }, "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", @@ -5348,6 +5436,351 @@ "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", @@ -5407,9 +5840,9 @@ } }, "@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" @@ -5458,9 +5891,9 @@ } }, "@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": "*", @@ -5474,9 +5907,9 @@ "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": "*", @@ -5512,14 +5945,15 @@ } }, "@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": { @@ -5554,9 +5988,9 @@ } }, "@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": "*" @@ -5626,18 +6060,18 @@ "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": { @@ -5693,6 +6127,14 @@ "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", @@ -5700,9 +6142,9 @@ "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": "*", @@ -5727,9 +6169,19 @@ "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", @@ -5810,9 +6262,9 @@ } }, "@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": "*" @@ -5861,9 +6313,9 @@ } }, "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" @@ -5970,9 +6422,9 @@ } }, "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" @@ -6187,9 +6639,9 @@ "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", @@ -6216,9 +6668,9 @@ } }, "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 } } @@ -6289,9 +6741,9 @@ } }, "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": { @@ -6364,22 +6816,14 @@ } }, "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": { @@ -6449,15 +6893,6 @@ } } }, - "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", @@ -6477,12 +6912,6 @@ "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", @@ -6515,10 +6944,9 @@ "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", @@ -6527,13 +6955,13 @@ "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": { @@ -6575,6 +7003,16 @@ "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", @@ -6813,8 +7251,7 @@ "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", @@ -6848,14 +7285,6 @@ "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": { @@ -6883,10 +7312,9 @@ "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", @@ -7248,11 +7676,22 @@ "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": { @@ -7324,6 +7763,15 @@ "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" + } } } }, @@ -7526,38 +7974,6 @@ "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 } } }, @@ -7634,6 +8050,19 @@ "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": { @@ -7678,7 +8107,6 @@ "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" @@ -7767,6 +8195,12 @@ "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", @@ -7857,9 +8291,9 @@ "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", @@ -7999,20 +8433,20 @@ } }, "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": { @@ -8084,20 +8518,20 @@ } }, "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 } } @@ -8130,10 +8564,10 @@ } }, "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", @@ -8246,9 +8680,9 @@ } }, "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": { @@ -8356,8 +8790,7 @@ "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", @@ -8411,9 +8844,9 @@ "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": { @@ -8458,7 +8891,6 @@ "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" } @@ -8554,38 +8986,6 @@ "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": { @@ -8662,8 +9062,7 @@ "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", @@ -8749,43 +9148,42 @@ } }, "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", @@ -8921,11 +9319,11 @@ } }, "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": { @@ -9033,9 +9431,9 @@ } }, "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" @@ -9139,9 +9537,9 @@ } }, "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", @@ -9252,9 +9650,9 @@ "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", @@ -9276,7 +9674,7 @@ "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", @@ -9302,9 +9700,9 @@ }, "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": { @@ -9358,6 +9756,12 @@ "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", @@ -9412,9 +9816,9 @@ "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" @@ -9726,6 +10130,15 @@ "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", @@ -9767,9 +10180,9 @@ "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", @@ -9899,14 +10312,12 @@ "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", @@ -9959,6 +10370,12 @@ "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", @@ -10140,9 +10557,9 @@ } }, "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": { @@ -10169,38 +10586,6 @@ "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": { @@ -10230,18 +10615,18 @@ "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", @@ -10315,15 +10700,14 @@ "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", @@ -10385,18 +10769,18 @@ } }, "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", @@ -10404,8 +10788,8 @@ "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", @@ -10413,11 +10797,12 @@ "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", @@ -10521,34 +10906,6 @@ "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", @@ -10822,9 +11179,9 @@ "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", @@ -10888,6 +11245,12 @@ "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", @@ -10936,20 +11299,20 @@ } }, "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": { @@ -11180,6 +11543,12 @@ } } }, + "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", @@ -11261,6 +11630,15 @@ "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" + } } } }, @@ -11835,8 +12213,7 @@ "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", @@ -11877,7 +12254,6 @@ "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", @@ -11885,9 +12261,9 @@ } }, "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", @@ -11939,8 +12315,7 @@ "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", @@ -12011,19 +12386,56 @@ "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": { @@ -12049,12 +12461,13 @@ "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" } }, @@ -12192,6 +12605,12 @@ } } }, + "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", @@ -12227,6 +12646,14 @@ "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": { @@ -12253,12 +12680,6 @@ "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", @@ -12323,9 +12744,9 @@ } }, "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": { @@ -12440,8 +12861,7 @@ "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", @@ -12460,8 +12880,7 @@ "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", @@ -12524,6 +12943,19 @@ "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": { @@ -12645,38 +13077,6 @@ "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": { @@ -12712,9 +13112,9 @@ } }, "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": { @@ -12818,6 +13218,17 @@ "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" + } } } }, @@ -13037,15 +13448,15 @@ } }, "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": { @@ -13069,20 +13480,20 @@ } }, "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 } } @@ -13345,10 +13756,9 @@ "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", @@ -13430,9 +13840,9 @@ "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" @@ -13637,9 +14047,9 @@ } }, "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" @@ -13978,8 +14388,7 @@ "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", @@ -14098,15 +14507,15 @@ } }, "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": { @@ -14449,6 +14858,12 @@ "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", @@ -14943,34 +15358,34 @@ }, "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" } } } @@ -15478,9 +15893,9 @@ } }, "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" @@ -15741,9 +16156,9 @@ "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", @@ -16331,9 +16746,9 @@ }, "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" @@ -16810,9 +17225,9 @@ } }, "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": { @@ -16900,9 +17315,9 @@ } }, "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" @@ -16916,38 +17331,6 @@ "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": { @@ -17004,9 +17387,9 @@ } }, "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" @@ -17120,14 +17503,12 @@ "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" } @@ -17138,6 +17519,12 @@ "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", @@ -17163,19 +17550,27 @@ } }, "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", @@ -17199,8 +17594,7 @@ "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", @@ -17359,8 +17753,7 @@ "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", @@ -17506,6 +17899,12 @@ "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", @@ -17608,36 +18007,6 @@ "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" - } } } }, @@ -17767,10 +18136,16 @@ "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": { @@ -17792,6 +18167,12 @@ "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", @@ -17925,6 +18306,23 @@ "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", @@ -17937,9 +18335,9 @@ "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" } @@ -17968,6 +18366,32 @@ "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", @@ -17977,10 +18401,33 @@ "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" @@ -18007,9 +18454,9 @@ "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", @@ -18092,9 +18539,9 @@ } }, "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" @@ -18135,9 +18582,9 @@ } }, "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" @@ -18185,9 +18632,9 @@ } }, "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" @@ -18276,6 +18723,12 @@ "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", @@ -18301,9 +18754,9 @@ } }, "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": { @@ -18354,9 +18807,9 @@ } }, "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" @@ -18497,15 +18950,15 @@ } }, "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": { @@ -18526,13 +18979,12 @@ "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": { @@ -19020,6 +19472,14 @@ "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": { @@ -19648,6 +20108,17 @@ "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" + } } } }, @@ -19741,9 +20212,9 @@ } }, "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" @@ -19782,9 +20253,9 @@ "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", @@ -20016,9 +20487,9 @@ } }, "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", @@ -20071,6 +20542,27 @@ "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", @@ -20230,14 +20722,12 @@ "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", @@ -20328,9 +20818,9 @@ }, "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 } } @@ -20356,8 +20846,7 @@ "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", @@ -20453,7 +20942,6 @@ "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" @@ -20465,9 +20953,9 @@ "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" } @@ -20511,6 +20999,16 @@ "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", @@ -20564,6 +21062,18 @@ } } }, + "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", @@ -20680,43 +21190,11 @@ }, "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": { @@ -20797,14 +21275,24 @@ } }, "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": { @@ -20917,14 +21405,14 @@ "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": { @@ -20934,9 +21422,9 @@ "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", @@ -21162,17 +21650,17 @@ "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" } @@ -21704,8 +22192,7 @@ "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", @@ -21804,11 +22291,16 @@ "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", @@ -21822,14 +22314,23 @@ "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", @@ -22176,9 +22677,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", @@ -22202,9 +22703,9 @@ } }, "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": { @@ -22232,6 +22733,19 @@ "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": { @@ -22362,6 +22876,11 @@ "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", @@ -22487,36 +23006,6 @@ "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" - } } } }, @@ -22540,38 +23029,6 @@ "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": { @@ -22598,38 +23055,6 @@ "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": { @@ -22642,6 +23067,19 @@ "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": { @@ -22652,38 +23090,6 @@ "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": { @@ -22776,6 +23182,17 @@ "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", @@ -22799,12 +23216,18 @@ } }, "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": { @@ -22843,10 +23266,10 @@ } }, "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", @@ -23022,12 +23445,6 @@ "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", @@ -23267,14 +23684,18 @@ "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", @@ -23406,23 +23827,29 @@ "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": { @@ -23433,17 +23860,40 @@ } } }, + "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": { @@ -23469,16 +23919,16 @@ } }, "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": { @@ -23500,9 +23950,9 @@ "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", @@ -23511,9 +23961,9 @@ } }, "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", @@ -23585,38 +24035,6 @@ "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": { @@ -23625,12 +24043,6 @@ "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", @@ -23783,6 +24195,34 @@ "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", @@ -23821,9 +24261,9 @@ } }, "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" @@ -23920,10 +24360,15 @@ } } }, + "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", @@ -23969,7 +24414,6 @@ "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" } @@ -24048,6 +24492,14 @@ "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", @@ -24268,6 +24720,11 @@ "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", @@ -24315,9 +24772,9 @@ "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" @@ -24402,8 +24859,7 @@ "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", @@ -24677,38 +25133,6 @@ "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", @@ -24720,9 +25144,9 @@ }, "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", @@ -24742,9 +25166,9 @@ }, "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", @@ -24802,6 +25226,12 @@ "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", @@ -24858,9 +25288,9 @@ "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", @@ -24893,15 +25323,15 @@ } }, "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" } } } @@ -24959,15 +25389,15 @@ "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": { @@ -25076,9 +25506,9 @@ } }, "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": { @@ -25099,15 +25529,14 @@ "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": { @@ -25220,9 +25649,9 @@ } }, "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": { @@ -25269,9 +25698,9 @@ "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" @@ -25299,9 +25728,9 @@ "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", diff --git a/src/pybind/mgr/dashboard/frontend/package.json b/src/pybind/mgr/dashboard/frontend/package.json index de08d364a1220..e55cb39bc0434 100644 --- a/src/pybind/mgr/dashboard/frontend/package.json +++ b/src/pybind/mgr/dashboard/frontend/package.json @@ -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", @@ -103,7 +102,7 @@ "@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", @@ -134,6 +133,6 @@ "typescript": "4.6.4" }, "cypress-cucumber-preprocessor": { - "nonGlobalStepDefinitions": true + "stepDefinitions": "cypress/e2e/common" } } diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/crushmap/crushmap.component.html b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/crushmap/crushmap.component.html index e01d3480ee079..dab14fd5842d6 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/crushmap/crushmap.component.html +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/crushmap/crushmap.component.html @@ -30,7 +30,9 @@