From: Nizamudeen A Date: Wed, 8 Feb 2023 15:49:31 +0000 (+0530) Subject: mgr/dashboard: update to cypress 10 X-Git-Tag: v18.1.1~57^2~5 X-Git-Url: http://git-server-git.apps.pok.os.sepia.ceph.com/?a=commitdiff_plain;h=f2144686a29875d7f9dfd68975d2f26bebf297e3;p=ceph.git mgr/dashboard: update to cypress 10 Fixes: https://tracker.ceph.com/issues/61354 Signed-off-by: Nizamudeen A (cherry picked from commit 222dec3750304385dff51a2a4bfb91e12eb4e9aa) --- 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 4b6ad4dbe88b..dbb2cb61bbf0 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 @@ -53,5 +53,5 @@ kcli ssh -u root ceph-node-00 'cephadm shell "ceph dashboard set-prometheus-api- kcli ssh -u root ceph-node-00 'cephadm shell "ceph dashboard set-grafana-api-url https://192.168.100.100:3000"' kcli ssh -u root ceph-node-00 'cephadm shell "ceph orch apply node-exporter --placement 'count:2'"' -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 000000000000..3948ea8dbb53 --- /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 35cf87241d33..000000000000 --- 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 000000000000..4feea0da71d0 --- /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 000000000000..2a0c5c5a533f --- /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 000000000000..5c89359db790 --- /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 000000000000..bf6cbc05263b --- /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 000000000000..cef4874bed50 --- /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 000000000000..08efa6408bd7 --- /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 000000000000..266176949c59 --- /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 000000000000..c4adca8b72fe --- /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 000000000000..d022d59cfa9a --- /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 000000000000..0133dc31f903 --- /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 000000000000..300eddbcc3de --- /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 000000000000..0a454739fd40 --- /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 000000000000..a5d2d591ce04 --- /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 000000000000..e4f9936c3e36 --- /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 000000000000..9511142ed43d --- /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 000000000000..5a9abdc036c9 --- /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 000000000000..ecc3cc1cd912 --- /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 000000000000..7efd8a6528a3 --- /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 000000000000..0a2aa8184cee --- /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 000000000000..04d2eee46142 --- /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 000000000000..a23d071e6d72 --- /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 000000000000..4113b99288d1 --- /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 000000000000..2fc148a1788c --- /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 000000000000..cd812f474fb8 --- /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 000000000000..c464a3f6cf81 --- /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 000000000000..87acda97fd83 --- /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 000000000000..bce659ff005c --- /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 000000000000..d5b4645b8e7c --- /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 000000000000..d18c34855469 --- /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 000000000000..4b2ee4d00437 --- /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 000000000000..286355085715 --- /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 000000000000..e623475fd784 --- /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 000000000000..bd6e5b8b7b44 --- /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 000000000000..aca36ade1921 --- /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 000000000000..a64e3bc8c020 --- /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 000000000000..41f0933b7a0b --- /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 000000000000..fb5e6ac8923a --- /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 000000000000..62476ad25a4b --- /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 000000000000..6ba2fc4fc54c --- /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 000000000000..be49fcba0993 --- /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 000000000000..745a2ec5d18f --- /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 000000000000..24262435da2a --- /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 000000000000..f93ad7a975bb --- /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 000000000000..94cb36c1929e --- /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 000000000000..a0a1dd03214b --- /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 000000000000..6e8c63279c2c --- /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 000000000000..91f2de58e6b5 --- /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 000000000000..f97509db3b03 --- /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 000000000000..c700ef0581dd --- /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 000000000000..e4bbd3f34211 --- /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 000000000000..b4c3c75ac5b8 --- /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 000000000000..7cca96aa8f46 --- /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 000000000000..6c50b48ec0b8 --- /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 000000000000..a27be3c6ba48 --- /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 000000000000..f3129a7ffd9e --- /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 000000000000..82a179463bc3 --- /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 000000000000..b5f366a09093 --- /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 000000000000..980cced88072 --- /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 000000000000..52994859e249 --- /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 000000000000..c7a8d222d2c3 --- /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 000000000000..8fa0013c36a0 --- /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 000000000000..597d2db9b508 --- /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 000000000000..6e95c596d075 --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/cypress/e2e/ui/dashboard.e2e-spec.ts @@ -0,0 +1,124 @@ +import { IscsiPageHelper } from '../block/iscsi.po'; +import { HostsPageHelper } from '../cluster/hosts.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(); + + 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}` + ); + }); + }); + } + }); +}); 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 000000000000..42d63ef44117 --- /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 000000000000..ccf16c2b55c7 --- /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 000000000000..80e21ba1e3d2 --- /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 000000000000..2b337e634162 --- /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 000000000000..d4d2c692116a --- /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 000000000000..fee2d2db967a --- /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 000000000000..f797bbc26a90 --- /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 000000000000..2ee73a70632b --- /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 000000000000..12c424e350d7 --- /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 000000000000..c3f325dbbe13 --- /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 000000000000..1cc3630a4631 --- /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 000000000000..92dc772121b1 --- /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 000000000000..fb2b79129443 --- /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 000000000000..b83d16d3d86c --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/cypress/e2e/visualTests/dashboard.vrt-spec.ts @@ -0,0 +1,22 @@ +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('.card-text').should('be.visible'); + cy.eyesCheckWindow({ tag: 'Dashboard landing page', ignore: { selector: '.card-text' } }); + }); +}); 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 000000000000..ea74f1d0f748 --- /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 4feea0da71d0..000000000000 --- 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 2a0c5c5a533f..000000000000 --- 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 5c89359db790..000000000000 --- 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 bf6cbc05263b..000000000000 --- 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 cef4874bed50..000000000000 --- 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 08efa6408bd7..000000000000 --- 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 4e17840df016..000000000000 --- 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 c4adca8b72fe..000000000000 --- 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 d022d59cfa9a..000000000000 --- 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 0133dc31f903..000000000000 --- 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 300eddbcc3de..000000000000 --- 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 0a454739fd40..000000000000 --- 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 a5d2d591ce04..000000000000 --- 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 e4f9936c3e36..000000000000 --- 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 9511142ed43d..000000000000 --- 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 5a9abdc036c9..000000000000 --- 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 ecc3cc1cd912..000000000000 --- 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 7efd8a6528a3..000000000000 --- 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 0a2aa8184cee..000000000000 --- 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 04d2eee46142..000000000000 --- 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 a23d071e6d72..000000000000 --- 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 4113b99288d1..000000000000 --- 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 2fc148a1788c..000000000000 --- 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 cd812f474fb8..000000000000 --- 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 c464a3f6cf81..000000000000 --- 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 87acda97fd83..000000000000 --- 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 8778384f46f8..000000000000 --- 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 d5b4645b8e7c..000000000000 --- 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 d18c34855469..000000000000 --- 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 7366f8babb3f..000000000000 --- 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 286355085715..000000000000 --- 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 e623475fd784..000000000000 --- 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 bd6e5b8b7b44..000000000000 --- 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 aca36ade1921..000000000000 --- 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 a64e3bc8c020..000000000000 --- 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 41f0933b7a0b..000000000000 --- 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 fb5e6ac8923a..000000000000 --- 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 62476ad25a4b..000000000000 --- 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 6ba2fc4fc54c..000000000000 --- 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 be49fcba0993..000000000000 --- 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 745a2ec5d18f..000000000000 --- 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 24262435da2a..000000000000 --- 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 f93ad7a975bb..000000000000 --- 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 94cb36c1929e..000000000000 --- 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 a0a1dd03214b..000000000000 --- 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 6e8c63279c2c..000000000000 --- 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 91f2de58e6b5..000000000000 --- 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 f97509db3b03..000000000000 --- 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 c700ef0581dd..000000000000 --- 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 e4bbd3f34211..000000000000 --- 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 b4c3c75ac5b8..000000000000 --- 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 7cca96aa8f46..000000000000 --- 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 6c50b48ec0b8..000000000000 --- 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 a27be3c6ba48..000000000000 --- 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 f3129a7ffd9e..000000000000 --- 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 82a179463bc3..000000000000 --- 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 b5f366a09093..000000000000 --- 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 a4266f989e08..000000000000 --- 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 52994859e249..000000000000 --- 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 c7a8d222d2c3..000000000000 --- 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.e2e-spec.ts b/src/pybind/mgr/dashboard/frontend/cypress/integration/ui/dashboard.e2e-spec.ts deleted file mode 100644 index 9cb84480b643..000000000000 --- a/src/pybind/mgr/dashboard/frontend/cypress/integration/ui/dashboard.e2e-spec.ts +++ /dev/null @@ -1,124 +0,0 @@ -import { IscsiPageHelper } from '../block/iscsi.po'; -import { HostsPageHelper } from '../cluster/hosts.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(); - - 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}` - ); - }); - }); - } - }); -}); 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 42d63ef44117..000000000000 --- 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 ccf16c2b55c7..000000000000 --- 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 80e21ba1e3d2..000000000000 --- 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 2b337e634162..000000000000 --- 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 d4d2c692116a..000000000000 --- 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 fee2d2db967a..000000000000 --- 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 3bad05559907..000000000000 --- 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 2ee73a70632b..000000000000 --- 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 12c424e350d7..000000000000 --- 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 c3f325dbbe13..000000000000 --- 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 1cc3630a4631..000000000000 --- 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 92dc772121b1..000000000000 --- 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 fb2b79129443..000000000000 --- 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 b83d16d3d86c..000000000000 --- a/src/pybind/mgr/dashboard/frontend/cypress/integration/visualTests/dashboard.vrt-spec.ts +++ /dev/null @@ -1,22 +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('.card-text').should('be.visible'); - cy.eyesCheckWindow({ tag: 'Dashboard landing page', ignore: { selector: '.card-text' } }); - }); -}); 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 ea74f1d0f748..000000000000 --- 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 04eabb8abbd8..2ab1b50025b8 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 000000000000..4db2c6a4926a --- /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 4db2c6a4926a..000000000000 --- 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 7b689aa5f22c..bf30cb5a272b 100644 --- a/src/pybind/mgr/dashboard/frontend/package-lock.json +++ b/src/pybind/mgr/dashboard/frontend/package-lock.json @@ -5,9 +5,9 @@ "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": { @@ -20,12 +20,12 @@ } }, "@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": { @@ -129,30 +129,6 @@ "@jridgewell/trace-mapping": "^0.3.9" } }, - "@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" - } - }, "@babel/core": { "version": "7.16.12", "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.16.12.tgz", @@ -181,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 } } }, @@ -199,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": { @@ -323,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", @@ -341,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", @@ -383,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", @@ -426,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", @@ -571,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", @@ -606,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", @@ -615,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", @@ -676,9 +673,9 @@ } }, "semver": { - "version": "7.4.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.4.0.tgz", - "integrity": "sha512-RgOxM8Mw+7Zus0+zcLEUn8+JfoLpj/huFTItQy2hsM4khuC1HYRDp0cU482Ewn/Fcy6bCjufD8vAj7voC66KQw==", + "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" @@ -1552,9 +1549,9 @@ } }, "@babel/compat-data": { - "version": "7.21.4", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.21.4.tgz", - "integrity": "sha512-/DYyDpeCfaVinT40FPGdkkb+lYSKvsVuMjDAG7jPOWWiM1ibOaB9CXJAlc4d1QpP/U2q2P9jbrSlClKSErd55g==" + "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", @@ -1579,11 +1576,11 @@ } }, "@babel/generator": { - "version": "7.21.4", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.21.4.tgz", - "integrity": "sha512-NieM3pVIYW2SwGzKoqfPrQsf4xGs9M9AIG3ThppsSRmO+m7eQhmI6amajKMUeIO37wFfsvnvcxQFx6x6iqxDnA==", + "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.4", + "@babel/types": "^7.21.5", "@jridgewell/gen-mapping": "^0.3.2", "@jridgewell/trace-mapping": "^0.3.17", "jsesc": "^2.5.1" @@ -1599,21 +1596,20 @@ } }, "@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.21.4", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.21.4.tgz", - "integrity": "sha512-Fa0tTuOXZ1iL8IeDFUWCzjZcn+sJGd9RZdH9esYVjEejGmzf+FFYQpMi/kZUk2kPy/q1H3/GPw7np8qar/stfg==", + "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.21.4", + "@babel/compat-data": "^7.21.5", "@babel/helper-validator-option": "^7.21.0", "browserslist": "^4.21.3", "lru-cache": "^5.1.1", @@ -1621,19 +1617,20 @@ } }, "@babel/helper-create-class-features-plugin": { - "version": "7.21.4", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.21.4.tgz", - "integrity": "sha512-46QrX2CQlaFRF4TkwfTt6nJD7IHq8539cCL7SDpqWSDeJKY1xylKKY5F/33mJhLZ3mFvKv2gGrVS6NkyF6qs+Q==", + "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": { @@ -1648,13 +1645,14 @@ } }, "@babel/helper-create-regexp-features-plugin": { - "version": "7.21.4", - "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.21.4.tgz", - "integrity": "sha512-M00OuhU+0GyZ5iBBN9czjugzWrEq2vDpf/zCYHxxf93ul/Q5rv+a5h+/+0WnI1AebHNVtl5bFV0qsJoH23DbfA==", + "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": { @@ -1683,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", @@ -1714,12 +1703,12 @@ } }, "@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": { @@ -1731,18 +1720,18 @@ } }, "@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": { @@ -1755,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": { @@ -1784,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": { @@ -1823,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", @@ -1850,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": { @@ -1870,9 +1859,9 @@ } }, "@babel/parser": { - "version": "7.21.4", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.21.4.tgz", - "integrity": "sha512-alVJj7k7zIxqBZ7BTRhz0IqJFxW1VJbm6N8JbcYhQ186df9ZBPbZBmWSqAMXwHGsCJdYks7z/voa3ibiS5bCIw==" + "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", @@ -2226,12 +2215,12 @@ } }, "@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": { @@ -2292,12 +2281,12 @@ } }, "@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" } }, @@ -2340,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": { @@ -2388,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": { @@ -2477,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": { @@ -2531,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" } }, @@ -2610,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": { @@ -2744,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.4", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.21.4.tgz", - "integrity": "sha512-eyKrRHKdyZxqDm+fV1iqL9UAHMoIg0nDaGqfIOd8rKH17m5snv7Gn4qgjBoFfLz9APvjFU/ICT00NVCv1Epp8Q==", + "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.21.4", - "@babel/generator": "^7.21.4", - "@babel/helper-environment-visitor": "^7.18.9", + "@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.4", - "@babel/types": "^7.21.4", + "@babel/parser": "^7.21.5", + "@babel/types": "^7.21.5", "debug": "^4.1.0", "globals": "^11.1.0" } }, "@babel/types": { - "version": "7.21.4", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.21.4.tgz", - "integrity": "sha512-rU2oY501qDxE8Pyo7i/Orqma4ziCOrby0/9mvbDUGEfvZjb279Nk9k19e2fiCxHbRRpY2ZyrgW1eq22mvmOIzA==", + "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" } @@ -2935,9 +2924,9 @@ } }, "semver": { - "version": "7.4.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.4.0.tgz", - "integrity": "sha512-RgOxM8Mw+7Zus0+zcLEUn8+JfoLpj/huFTItQy2hsM4khuC1HYRDp0cU482Ewn/Fcy6bCjufD8vAj7voC66KQw==", + "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" @@ -3021,12 +3010,6 @@ "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", "dev": true }, - "proxy-middleware": { - "version": "0.15.0", - "resolved": "https://registry.npmjs.org/proxy-middleware/-/proxy-middleware-0.15.0.tgz", - "integrity": "sha512-EGCG8SeoIRVMhsqHQUdDigB2i7qU7fCsWASwn54+nPutYO8n4q6EiwMzyfWlC+dzRFExP+kvcnDFdBDHoZBU7Q==", - "dev": true - }, "send": { "version": "0.18.0", "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", @@ -3051,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": { @@ -3215,25 +3211,6 @@ } } }, - "@cypress/webpack-preprocessor": { - "version": "5.11.0", - "resolved": "https://registry.npmjs.org/@cypress/webpack-preprocessor/-/webpack-preprocessor-5.11.0.tgz", - "integrity": "sha512-0VMEodVAOkYYhCGKQ2wilI28RtISc3rCre9wlFhishwtnT0B1onJJ8fwhWmcT3Y2/K88WP+cyVO2ZaQPcsEFQg==", - "dev": true, - "requires": { - "bluebird": "3.7.1", - "debug": "^4.3.2", - "lodash": "^4.17.20" - }, - "dependencies": { - "bluebird": { - "version": "3.7.1", - "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.1.tgz", - "integrity": "sha512-DdmyoGCleJnkbp3nkbxTLJ18rjDsE4yCggEwKNXkeV123sPNfOCYeDoeuOY+F2FrSjO1YXcTU+dsy96KMy+gcg==", - "dev": true - } - } - }, "@cypress/xvfb": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/@cypress/xvfb/-/xvfb-1.2.4.tgz", @@ -3262,156 +3239,156 @@ "dev": true }, "@esbuild/android-arm": { - "version": "0.17.16", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.17.16.tgz", - "integrity": "sha512-baLqRpLe4JnKrUXLJChoTN0iXZH7El/mu58GE3WIA6/H834k0XWvLRmGLG8y8arTRS9hJJibPnF0tiGhmWeZgw==", + "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.16", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.17.16.tgz", - "integrity": "sha512-QX48qmsEZW+gcHgTmAj+x21mwTz8MlYQBnzF6861cNdQGvj2jzzFjqH0EBabrIa/WVZ2CHolwMoqxVryqKt8+Q==", + "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.16", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.17.16.tgz", - "integrity": "sha512-G4wfHhrrz99XJgHnzFvB4UwwPxAWZaZBOFXh+JH1Duf1I4vIVfuYY9uVLpx4eiV2D/Jix8LJY+TAdZ3i40tDow==", + "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.16", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.17.16.tgz", - "integrity": "sha512-/Ofw8UXZxuzTLsNFmz1+lmarQI6ztMZ9XktvXedTbt3SNWDn0+ODTwxExLYQ/Hod91EZB4vZPQJLoqLF0jvEzA==", + "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.16", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.17.16.tgz", - "integrity": "sha512-SzBQtCV3Pdc9kyizh36Ol+dNVhkDyIrGb/JXZqFq8WL37LIyrXU0gUpADcNV311sCOhvY+f2ivMhb5Tuv8nMOQ==", + "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.16", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.17.16.tgz", - "integrity": "sha512-ZqftdfS1UlLiH1DnS2u3It7l4Bc3AskKeu+paJSfk7RNOMrOxmeFDhLTMQqMxycP1C3oj8vgkAT6xfAuq7ZPRA==", + "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.16", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.17.16.tgz", - "integrity": "sha512-rHV6zNWW1tjgsu0dKQTX9L0ByiJHHLvQKrWtnz8r0YYJI27FU3Xu48gpK2IBj1uCSYhJ+pEk6Y0Um7U3rIvV8g==", + "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.16", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.17.16.tgz", - "integrity": "sha512-n4O8oVxbn7nl4+m+ISb0a68/lcJClIbaGAoXwqeubj/D1/oMMuaAXmJVfFlRjJLu/ZvHkxoiFJnmbfp4n8cdSw==", + "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.16", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.17.16.tgz", - "integrity": "sha512-8yoZhGkU6aHu38WpaM4HrRLTFc7/VVD9Q2SvPcmIQIipQt2I/GMTZNdEHXoypbbGao5kggLcxg0iBKjo0SQYKA==", + "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.16", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.17.16.tgz", - "integrity": "sha512-9ZBjlkdaVYxPNO8a7OmzDbOH9FMQ1a58j7Xb21UfRU29KcEEU3VTHk+Cvrft/BNv0gpWJMiiZ/f4w0TqSP0gLA==", + "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.16", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.17.16.tgz", - "integrity": "sha512-TIZTRojVBBzdgChY3UOG7BlPhqJz08AL7jdgeeu+kiObWMFzGnQD7BgBBkWRwOtKR1i2TNlO7YK6m4zxVjjPRQ==", + "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.16", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.17.16.tgz", - "integrity": "sha512-UPeRuFKCCJYpBbIdczKyHLAIU31GEm0dZl1eMrdYeXDH+SJZh/i+2cAmD3A1Wip9pIc5Sc6Kc5cFUrPXtR0XHA==", + "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.16", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.17.16.tgz", - "integrity": "sha512-io6yShgIEgVUhExJejJ21xvO5QtrbiSeI7vYUnr7l+v/O9t6IowyhdiYnyivX2X5ysOVHAuyHW+Wyi7DNhdw6Q==", + "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.16", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.17.16.tgz", - "integrity": "sha512-WhlGeAHNbSdG/I2gqX2RK2gfgSNwyJuCiFHMc8s3GNEMMHUI109+VMBfhVqRb0ZGzEeRiibi8dItR3ws3Lk+cA==", + "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.16", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.17.16.tgz", - "integrity": "sha512-gHRReYsJtViir63bXKoFaQ4pgTyah4ruiMRQ6im9YZuv+gp3UFJkNTY4sFA73YDynmXZA6hi45en4BGhNOJUsw==", + "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.16", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.17.16.tgz", - "integrity": "sha512-mfiiBkxEbUHvi+v0P+TS7UnA9TeGXR48aK4XHkTj0ZwOijxexgMF01UDFaBX7Q6CQsB0d+MFNv9IiXbIHTNd4g==", + "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.16", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.17.16.tgz", - "integrity": "sha512-n8zK1YRDGLRZfVcswcDMDM0j2xKYLNXqei217a4GyBxHIuPMGrrVuJ+Ijfpr0Kufcm7C1k/qaIrGy6eG7wvgmA==", + "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.16", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.17.16.tgz", - "integrity": "sha512-lEEfkfsUbo0xC47eSTBqsItXDSzwzwhKUSsVaVjVji07t8+6KA5INp2rN890dHZeueXJAI8q0tEIfbwVRYf6Ew==", + "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.16", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.17.16.tgz", - "integrity": "sha512-jlRjsuvG1fgGwnE8Afs7xYDnGz0dBgTNZfgCK6TlvPH3Z13/P5pi6I57vyLE8qZYLrGVtwcm9UbUx1/mZ8Ukag==", + "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.16", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.17.16.tgz", - "integrity": "sha512-TzoU2qwVe2boOHl/3KNBUv2PNUc38U0TNnzqOAcgPiD/EZxT2s736xfC2dYQbszAwo4MKzzwBV0iHjhfjxMimg==", + "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.16", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.17.16.tgz", - "integrity": "sha512-B8b7W+oo2yb/3xmwk9Vc99hC9bNolvqjaTZYEfMQhzdpBsjTvZBlXQ/teUE55Ww6sg//wlcDjOaqldOKyigWdA==", + "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.16", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.17.16.tgz", - "integrity": "sha512-xJ7OH/nanouJO9pf03YsL9NAFQBHd8AqfrQd7Pf5laGyyTt/gToul6QYOA/i5i/q8y9iaM5DQFNTgpi995VkOg==", + "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 }, @@ -3528,403 +3505,6 @@ "integrity": "sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw==", "dev": true }, - "@grafana/e2e": { - "version": "8.4.5", - "resolved": "https://registry.npmjs.org/@grafana/e2e/-/e2e-8.4.5.tgz", - "integrity": "sha512-3UTxH0CdXqQkuuW4UvNG60R7yQGgD/27VWqVks5+nEuo+DxWrAgzHbxdvks8C1Hm/EUUhmwj5GkaU/9AN74pWQ==", - "dev": true, - "requires": { - "@babel/core": "7.16.7", - "@babel/preset-env": "7.16.7", - "@cypress/webpack-preprocessor": "5.11.0", - "@grafana/e2e-selectors": "8.4.5", - "@grafana/tsconfig": "^1.0.0-rc1", - "@mochajs/json-file-reporter": "^1.2.0", - "babel-loader": "8.2.3", - "blink-diff": "1.0.13", - "chrome-remote-interface": "0.31.1", - "commander": "8.3.0", - "cypress": "9.3.1", - "cypress-file-upload": "5.0.8", - "devtools-protocol": "0.0.927104", - "execa": "5.1.1", - "lodash": "4.17.21", - "mocha": "9.2.0", - "resolve-as-bin": "2.1.0", - "rimraf": "3.0.2", - "tracelib": "1.0.1", - "ts-loader": "6.2.1", - "tslib": "2.3.1", - "typescript": "4.4.4", - "uuid": "8.3.2", - "yaml": "^1.8.3" - }, - "dependencies": { - "@babel/core": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.16.7.tgz", - "integrity": "sha512-aeLaqcqThRNZYmbMqtulsetOQZ/5gbR/dWruUCJcpas4Qoyy+QeagfDsPdMrqwsPRDNxJvBlRiZxxX7THO7qtA==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.16.7", - "@babel/generator": "^7.16.7", - "@babel/helper-compilation-targets": "^7.16.7", - "@babel/helper-module-transforms": "^7.16.7", - "@babel/helpers": "^7.16.7", - "@babel/parser": "^7.16.7", - "@babel/template": "^7.16.7", - "@babel/traverse": "^7.16.7", - "@babel/types": "^7.16.7", - "convert-source-map": "^1.7.0", - "debug": "^4.1.0", - "gensync": "^1.0.0-beta.2", - "json5": "^2.1.2", - "semver": "^6.3.0", - "source-map": "^0.5.0" - } - }, - "@babel/preset-env": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.16.7.tgz", - "integrity": "sha512-urX3Cee4aOZbRWOSa3mKPk0aqDikfILuo+C7qq7HY0InylGNZ1fekq9jmlr3pLWwZHF4yD7heQooc2Pow2KMyQ==", - "dev": true, - "requires": { - "@babel/compat-data": "^7.16.4", - "@babel/helper-compilation-targets": "^7.16.7", - "@babel/helper-plugin-utils": "^7.16.7", - "@babel/helper-validator-option": "^7.16.7", - "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.16.7", - "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.16.7", - "@babel/plugin-proposal-async-generator-functions": "^7.16.7", - "@babel/plugin-proposal-class-properties": "^7.16.7", - "@babel/plugin-proposal-class-static-block": "^7.16.7", - "@babel/plugin-proposal-dynamic-import": "^7.16.7", - "@babel/plugin-proposal-export-namespace-from": "^7.16.7", - "@babel/plugin-proposal-json-strings": "^7.16.7", - "@babel/plugin-proposal-logical-assignment-operators": "^7.16.7", - "@babel/plugin-proposal-nullish-coalescing-operator": "^7.16.7", - "@babel/plugin-proposal-numeric-separator": "^7.16.7", - "@babel/plugin-proposal-object-rest-spread": "^7.16.7", - "@babel/plugin-proposal-optional-catch-binding": "^7.16.7", - "@babel/plugin-proposal-optional-chaining": "^7.16.7", - "@babel/plugin-proposal-private-methods": "^7.16.7", - "@babel/plugin-proposal-private-property-in-object": "^7.16.7", - "@babel/plugin-proposal-unicode-property-regex": "^7.16.7", - "@babel/plugin-syntax-async-generators": "^7.8.4", - "@babel/plugin-syntax-class-properties": "^7.12.13", - "@babel/plugin-syntax-class-static-block": "^7.14.5", - "@babel/plugin-syntax-dynamic-import": "^7.8.3", - "@babel/plugin-syntax-export-namespace-from": "^7.8.3", - "@babel/plugin-syntax-json-strings": "^7.8.3", - "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", - "@babel/plugin-syntax-numeric-separator": "^7.10.4", - "@babel/plugin-syntax-object-rest-spread": "^7.8.3", - "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", - "@babel/plugin-syntax-optional-chaining": "^7.8.3", - "@babel/plugin-syntax-private-property-in-object": "^7.14.5", - "@babel/plugin-syntax-top-level-await": "^7.14.5", - "@babel/plugin-transform-arrow-functions": "^7.16.7", - "@babel/plugin-transform-async-to-generator": "^7.16.7", - "@babel/plugin-transform-block-scoped-functions": "^7.16.7", - "@babel/plugin-transform-block-scoping": "^7.16.7", - "@babel/plugin-transform-classes": "^7.16.7", - "@babel/plugin-transform-computed-properties": "^7.16.7", - "@babel/plugin-transform-destructuring": "^7.16.7", - "@babel/plugin-transform-dotall-regex": "^7.16.7", - "@babel/plugin-transform-duplicate-keys": "^7.16.7", - "@babel/plugin-transform-exponentiation-operator": "^7.16.7", - "@babel/plugin-transform-for-of": "^7.16.7", - "@babel/plugin-transform-function-name": "^7.16.7", - "@babel/plugin-transform-literals": "^7.16.7", - "@babel/plugin-transform-member-expression-literals": "^7.16.7", - "@babel/plugin-transform-modules-amd": "^7.16.7", - "@babel/plugin-transform-modules-commonjs": "^7.16.7", - "@babel/plugin-transform-modules-systemjs": "^7.16.7", - "@babel/plugin-transform-modules-umd": "^7.16.7", - "@babel/plugin-transform-named-capturing-groups-regex": "^7.16.7", - "@babel/plugin-transform-new-target": "^7.16.7", - "@babel/plugin-transform-object-super": "^7.16.7", - "@babel/plugin-transform-parameters": "^7.16.7", - "@babel/plugin-transform-property-literals": "^7.16.7", - "@babel/plugin-transform-regenerator": "^7.16.7", - "@babel/plugin-transform-reserved-words": "^7.16.7", - "@babel/plugin-transform-shorthand-properties": "^7.16.7", - "@babel/plugin-transform-spread": "^7.16.7", - "@babel/plugin-transform-sticky-regex": "^7.16.7", - "@babel/plugin-transform-template-literals": "^7.16.7", - "@babel/plugin-transform-typeof-symbol": "^7.16.7", - "@babel/plugin-transform-unicode-escapes": "^7.16.7", - "@babel/plugin-transform-unicode-regex": "^7.16.7", - "@babel/preset-modules": "^0.1.5", - "@babel/types": "^7.16.7", - "babel-plugin-polyfill-corejs2": "^0.3.0", - "babel-plugin-polyfill-corejs3": "^0.4.0", - "babel-plugin-polyfill-regenerator": "^0.3.0", - "core-js-compat": "^3.19.1", - "semver": "^6.3.0" - } - }, - "@types/node": { - "version": "14.18.42", - "resolved": "https://registry.npmjs.org/@types/node/-/node-14.18.42.tgz", - "integrity": "sha512-xefu+RBie4xWlK8hwAzGh3npDz/4VhF6icY/shU+zv/1fNn+ZVG7T7CRwe9LId9sAYRPxI+59QBPuKL3WpyGRg==", - "dev": true - }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "babel-loader": { - "version": "8.2.3", - "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-8.2.3.tgz", - "integrity": "sha512-n4Zeta8NC3QAsuyiizu0GkmRcQ6clkV9WFUnUf1iXP//IeSKbWjofW3UHyZVwlOB4y039YQKefawyTn64Zwbuw==", - "dev": true, - "requires": { - "find-cache-dir": "^3.3.1", - "loader-utils": "^1.4.0", - "make-dir": "^3.1.0", - "schema-utils": "^2.6.5" - } - }, - "babel-plugin-polyfill-corejs3": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.4.0.tgz", - "integrity": "sha512-YxFreYwUfglYKdLUGvIF2nJEsGwj+RhWSX/ije3D2vQPOXuyMLMtg/cCGMDpOA7Nd+MwlNdnGODbd2EwUZPlsw==", - "dev": true, - "requires": { - "@babel/helper-define-polyfill-provider": "^0.3.0", - "core-js-compat": "^3.18.0" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "dependencies": { - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "commander": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", - "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==", - "dev": true - }, - "cypress": { - "version": "9.3.1", - "resolved": "https://registry.npmjs.org/cypress/-/cypress-9.3.1.tgz", - "integrity": "sha512-BODdPesxX6bkVUnH8BVsV8I/jn57zQtO1FEOUTiuG2us3kslW7g0tcuwiny7CKCmJUZz8S/D587ppC+s58a+5Q==", - "dev": true, - "requires": { - "@cypress/request": "^2.88.10", - "@cypress/xvfb": "^1.2.4", - "@types/node": "^14.14.31", - "@types/sinonjs__fake-timers": "8.1.1", - "@types/sizzle": "^2.3.2", - "arch": "^2.2.0", - "blob-util": "^2.0.2", - "bluebird": "^3.7.2", - "buffer": "^5.6.0", - "cachedir": "^2.3.0", - "chalk": "^4.1.0", - "check-more-types": "^2.24.0", - "cli-cursor": "^3.1.0", - "cli-table3": "~0.6.1", - "commander": "^5.1.0", - "common-tags": "^1.8.0", - "dayjs": "^1.10.4", - "debug": "^4.3.2", - "enquirer": "^2.3.6", - "eventemitter2": "^6.4.3", - "execa": "4.1.0", - "executable": "^4.1.1", - "extract-zip": "2.0.1", - "figures": "^3.2.0", - "fs-extra": "^9.1.0", - "getos": "^3.2.1", - "is-ci": "^3.0.0", - "is-installed-globally": "~0.4.0", - "lazy-ass": "^1.6.0", - "listr2": "^3.8.3", - "lodash": "^4.17.21", - "log-symbols": "^4.0.0", - "minimist": "^1.2.5", - "ospath": "^1.2.2", - "pretty-bytes": "^5.6.0", - "proxy-from-env": "1.0.0", - "request-progress": "^3.0.0", - "supports-color": "^8.1.1", - "tmp": "~0.2.1", - "untildify": "^4.0.0", - "url": "^0.11.0", - "yauzl": "^2.10.0" - }, - "dependencies": { - "commander": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-5.1.0.tgz", - "integrity": "sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==", - "dev": true - }, - "execa": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-4.1.0.tgz", - "integrity": "sha512-j5W0//W7f8UxAn8hXVnwG8tLwdiUy4FJLcSupCg6maBYZDpyBvTApK7KyuI4bKj8KOh1r2YH+6ucuYtJv1bTZA==", - "dev": true, - "requires": { - "cross-spawn": "^7.0.0", - "get-stream": "^5.0.0", - "human-signals": "^1.1.1", - "is-stream": "^2.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.0", - "onetime": "^5.1.0", - "signal-exit": "^3.0.2", - "strip-final-newline": "^2.0.0" - } - } - } - }, - "get-stream": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", - "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", - "dev": true, - "requires": { - "pump": "^3.0.0" - } - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "human-signals": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-1.1.1.tgz", - "integrity": "sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw==", - "dev": true - }, - "loader-utils": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.2.tgz", - "integrity": "sha512-I5d00Pd/jwMD2QCduo657+YM/6L3KZu++pmX9VFncxaxvHcru9jx1lBaFft+r4Mt2jK0Yhp41XlRAihzPxHNCg==", - "dev": true, - "requires": { - "big.js": "^5.2.2", - "emojis-list": "^3.0.0", - "json5": "^1.0.1" - }, - "dependencies": { - "json5": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", - "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", - "dev": true, - "requires": { - "minimist": "^1.2.0" - } - } - } - }, - "proxy-from-env": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.0.0.tgz", - "integrity": "sha512-F2JHgJQ1iqwnHDcQjVBsq3n/uoaFL+iPW/eAeL7kVxy/2RrWaN4WroKjjvbsoRtv0ftelNyC01bjRhn/bhcf4A==", - "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 - }, - "supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - }, - "tmp": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz", - "integrity": "sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==", - "dev": true, - "requires": { - "rimraf": "^3.0.0" - } - }, - "typescript": { - "version": "4.4.4", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.4.4.tgz", - "integrity": "sha512-DqGhF5IKoBl8WNf8C1gu8q0xZSInh9j1kJJMqT3a94w1JzVaBU4EXOSMrz9yDqMT0xt3selp83fuFMQ0uzv6qA==", - "dev": true - } - } - }, - "@grafana/e2e-selectors": { - "version": "8.4.5", - "resolved": "https://registry.npmjs.org/@grafana/e2e-selectors/-/e2e-selectors-8.4.5.tgz", - "integrity": "sha512-sfdafy76s5nCtrkyCMv8v2QMJTsypWFad5BHF4zL4nHbYEhhpZPbky13RufiqBImFeIWLM9Zmz7LDFFJVBhFtA==", - "dev": true, - "requires": { - "@grafana/tsconfig": "^1.0.0-rc1", - "tslib": "2.3.1", - "typescript": "4.4.4" - }, - "dependencies": { - "typescript": { - "version": "4.4.4", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.4.4.tgz", - "integrity": "sha512-DqGhF5IKoBl8WNf8C1gu8q0xZSInh9j1kJJMqT3a94w1JzVaBU4EXOSMrz9yDqMT0xt3selp83fuFMQ0uzv6qA==", - "dev": true - } - } - }, - "@grafana/tsconfig": { - "version": "1.0.0-rc1", - "resolved": "https://registry.npmjs.org/@grafana/tsconfig/-/tsconfig-1.0.0-rc1.tgz", - "integrity": "sha512-nucKPGyzlSKYSiJk5RA8GzMdVWhdYNdF+Hh65AXxjD9PlY69JKr5wANj8bVdQboag6dgg0BFKqgKPyY+YtV4Iw==", - "dev": true - }, "@hapi/hoek": { "version": "9.3.0", "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz", @@ -4583,12 +4163,6 @@ "resolved": "https://registry.npmjs.org/@juggle/resize-observer/-/resize-observer-3.4.0.tgz", "integrity": "sha512-dfLbk+PwWvFzSxwk3n5ySL0hfBog779o8h68wK/7/APo/7cgyWp5jcXockbxdk5kFRkbeXWm4Fbi9FrdN381sA==" }, - "@mochajs/json-file-reporter": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@mochajs/json-file-reporter/-/json-file-reporter-1.3.0.tgz", - "integrity": "sha512-evIxpeP8EOixo/T2xh5xYEIzwbEHk8YNJfRUm1KeTs8F3bMjgNn2580Ogze9yisXNlTxu88JiJJYzXjjg5NdLA==", - "dev": true - }, "@ng-bootstrap/ng-bootstrap": { "version": "12.1.2", "resolved": "https://registry.npmjs.org/@ng-bootstrap/ng-bootstrap/-/ng-bootstrap-12.1.2.tgz", @@ -4665,9 +4239,9 @@ } }, "semver": { - "version": "7.4.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.4.0.tgz", - "integrity": "sha512-RgOxM8Mw+7Zus0+zcLEUn8+JfoLpj/huFTItQy2hsM4khuC1HYRDp0cU482Ewn/Fcy6bCjufD8vAj7voC66KQw==", + "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" @@ -4707,9 +4281,9 @@ } }, "semver": { - "version": "7.4.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.4.0.tgz", - "integrity": "sha512-RgOxM8Mw+7Zus0+zcLEUn8+JfoLpj/huFTItQy2hsM4khuC1HYRDp0cU482Ewn/Fcy6bCjufD8vAj7voC66KQw==", + "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" @@ -4771,21 +4345,21 @@ } }, "@nrwl/cli": { - "version": "15.9.2", - "resolved": "https://registry.npmjs.org/@nrwl/cli/-/cli-15.9.2.tgz", - "integrity": "sha512-QoCmyrcGakHAYTJaNBbOerRQAmqJHMYGCdqtQidV+aP9p1Dy33XxDELfhd+IYmGqngutXuEWChNpWNhPloLnoA==", + "version": "15.9.3", + "resolved": "https://registry.npmjs.org/@nrwl/cli/-/cli-15.9.3.tgz", + "integrity": "sha512-qiAKHkov3iBx6hroPTitUrkRSUZFQqVgNJiF9gXRFC6pNJe9RS4rlmcIaoUFOboi9CnH5jwblNJVcz8YSVYOvA==", "dev": true, "requires": { - "nx": "15.9.2" + "nx": "15.9.3" }, "dependencies": { "@nrwl/tao": { - "version": "15.9.2", - "resolved": "https://registry.npmjs.org/@nrwl/tao/-/tao-15.9.2.tgz", - "integrity": "sha512-+LqNC37w9c6q6Ukdpf0z0tt1PQFNi4gwhHpJvkYQiKRETHjyrrlyqTNEPEyA7PI62RuYC6VrpVw2gzI7ufqZEA==", + "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.9.2" + "nx": "15.9.3" } }, "@zkochan/js-yaml": { @@ -4932,22 +4506,22 @@ } }, "nx": { - "version": "15.9.2", - "resolved": "https://registry.npmjs.org/nx/-/nx-15.9.2.tgz", - "integrity": "sha512-wtcs+wsuplSckvgk+bV+/XuGlo+sVWzSG0RpgWBjQYeqA3QsVFEAPVY66Z5cSoukDbTV77ddcAjEw+Rz8oOR1A==", - "dev": true, - "requires": { - "@nrwl/cli": "15.9.2", - "@nrwl/nx-darwin-arm64": "15.9.2", - "@nrwl/nx-darwin-x64": "15.9.2", - "@nrwl/nx-linux-arm-gnueabihf": "15.9.2", - "@nrwl/nx-linux-arm64-gnu": "15.9.2", - "@nrwl/nx-linux-arm64-musl": "15.9.2", - "@nrwl/nx-linux-x64-gnu": "15.9.2", - "@nrwl/nx-linux-x64-musl": "15.9.2", - "@nrwl/nx-win32-arm64-msvc": "15.9.2", - "@nrwl/nx-win32-x64-msvc": "15.9.2", - "@nrwl/tao": "15.9.2", + "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", @@ -5059,65 +4633,65 @@ } }, "@nrwl/nx-darwin-arm64": { - "version": "15.9.2", - "resolved": "https://registry.npmjs.org/@nrwl/nx-darwin-arm64/-/nx-darwin-arm64-15.9.2.tgz", - "integrity": "sha512-Yv+OVsQt3C/hmWOC+YhJZQlsyph5w1BHfbp4jyCvV1ZXBbb8NdvwxgDHPWXxKPTc1EXuB7aEX3qzxM3/OWEUJg==", + "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.9.2", - "resolved": "https://registry.npmjs.org/@nrwl/nx-darwin-x64/-/nx-darwin-x64-15.9.2.tgz", - "integrity": "sha512-qHfdluHlPzV0UHOwj1ZJ+qNEhzfLGiBuy1cOth4BSzDlvMnkuqBWoprfaXoztzYcus2NSILY1/7b3Jw4DAWmMw==", + "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.9.2", - "resolved": "https://registry.npmjs.org/@nrwl/nx-linux-arm-gnueabihf/-/nx-linux-arm-gnueabihf-15.9.2.tgz", - "integrity": "sha512-0GzwbablosnYnnJDCJvAeZv8LlelSrNwUnGhe43saeoZdAew35Ay1E34zBrg/GCGTASuz+knEEYFM+gDD9Mc6A==", + "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.9.2", - "resolved": "https://registry.npmjs.org/@nrwl/nx-linux-arm64-gnu/-/nx-linux-arm64-gnu-15.9.2.tgz", - "integrity": "sha512-3mFIY7iUTPG45hSIRaM2DmraCy8W6hNoArAGRrTgYw40BIJHtLrW+Rt7DLyvVXaYCvrKugWOKtxC+jG7kpIZVA==", + "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.9.2", - "resolved": "https://registry.npmjs.org/@nrwl/nx-linux-arm64-musl/-/nx-linux-arm64-musl-15.9.2.tgz", - "integrity": "sha512-FNBnXEtockwxZa4I3NqggrJp0YIbNokJvt/clrICP+ijOacdUDkv8mJedavobkFsRsNq9gzCbRbUScKymrOLrg==", + "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.9.2", - "resolved": "https://registry.npmjs.org/@nrwl/nx-linux-x64-gnu/-/nx-linux-x64-gnu-15.9.2.tgz", - "integrity": "sha512-gHWsP5lbe4FNQCa1Q/VLxIuik+BqAOcSzyPjdUa4gCDcbxPa8xiE57PgXB5E1XUzOWNnDTlXa/Ll07/TIuKuog==", + "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.9.2", - "resolved": "https://registry.npmjs.org/@nrwl/nx-linux-x64-musl/-/nx-linux-x64-musl-15.9.2.tgz", - "integrity": "sha512-EaFUukCbmoHsYECX2AS4pxXH933yesBFVvBgD38DkoFDxDoJMVt6JqYwm+d5R7S4R2P9U3l++aurljQTRq567Q==", + "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.9.2", - "resolved": "https://registry.npmjs.org/@nrwl/nx-win32-arm64-msvc/-/nx-win32-arm64-msvc-15.9.2.tgz", - "integrity": "sha512-PGAe7QMr51ivx1X3avvs8daNlvv1wGo3OFrobjlu5rSyjC1Y3qHwT9+wdlwzNZ93FIqWOq09s+rE5gfZRfpdAg==", + "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.9.2", - "resolved": "https://registry.npmjs.org/@nrwl/nx-win32-x64-msvc/-/nx-win32-x64-msvc-15.9.2.tgz", - "integrity": "sha512-Q8onNzhuAZ0l9DNkm8D4Z1AEIzJr8JiT4L2fVBLYrV/R75C2HS3q7lzvfo6oqMY6mXge1cFPcrTtg3YXBQaSWA==", + "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 }, @@ -5318,6 +4892,37 @@ "@angular-devkit/core": "13.3.9", "@angular-devkit/schematics": "13.3.9", "jsonc-parser": "3.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": { @@ -5375,21 +4980,21 @@ }, "dependencies": { "@babel/core": { - "version": "7.21.4", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.21.4.tgz", - "integrity": "sha512-qt/YV149Jman/6AfmlxJ04LMIu8bMoyl3RB91yTFrxQmgbrSvQMy7cI8Q62FHx1t8wJ8B5fu0UDoLwHAhUo1QA==", + "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.21.4", - "@babel/generator": "^7.21.4", - "@babel/helper-compilation-targets": "^7.21.4", - "@babel/helper-module-transforms": "^7.21.2", - "@babel/helpers": "^7.21.0", - "@babel/parser": "^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.4", - "@babel/types": "^7.21.4", + "@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", @@ -5410,330 +5015,330 @@ } }, "@swagger-api/apidom-ast": { - "version": "0.69.0", - "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ast/-/apidom-ast-0.69.0.tgz", - "integrity": "sha512-JsRyi1Ir3VeNSSWmIFqgaFOQCIUvCoKcfmOcU/h4Jz1IOkQij1vj3qEFln4J9sByOWHrhA8zD1Cf+LnXkbGVZg==", + "version": "0.70.0", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ast/-/apidom-ast-0.70.0.tgz", + "integrity": "sha512-zQ1RUkXjx5NPYv1bmkoXwlQi7oJC7DJqYi0syTQKswJZDbOkHCwz8cDP/YystOEOL+yyIN7i5EQBIHfy5yAMmA==", "requires": { "@babel/runtime-corejs3": "^7.20.7", - "@types/ramda": "=0.28.23", - "ramda": "=0.28.0", - "ramda-adjunct": "=3.4.0", - "stampit": "=4.3.2", - "unraw": "=2.0.1" + "@types/ramda": "~0.29.1", + "ramda": "~0.29.0", + "ramda-adjunct": "^4.0.0", + "stampit": "^4.3.2", + "unraw": "^2.0.1" } }, "@swagger-api/apidom-core": { - "version": "0.69.2", - "resolved": "https://registry.npmjs.org/@swagger-api/apidom-core/-/apidom-core-0.69.2.tgz", - "integrity": "sha512-av9vS1SbXxGJvCt4QggrIvS8dr3ZfL6jxrNQGr4cq1wFY/n5ruj0RsXix208c3Zp1Kua3QVOUaJvA+7RdT1VJA==", + "version": "0.70.0", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-core/-/apidom-core-0.70.0.tgz", + "integrity": "sha512-nUw0aehnhm2BUd17pbCLUuyd4E5bsn+K3teYLGs8Z/LGo9gpjZ/BMTL3H/3+F42ZRux79/3b6QksDj4f9yeSpg==", "requires": { "@babel/runtime-corejs3": "^7.20.7", - "@swagger-api/apidom-ast": "^0.69.0", - "@types/ramda": "=0.28.23", - "minim": "=0.23.8", - "ramda": "=0.28.0", - "ramda-adjunct": "=3.4.0", - "short-unique-id": "=4.4.4", - "stampit": "=4.3.2" + "@swagger-api/apidom-ast": "^0.70.0", + "@types/ramda": "~0.29.1", + "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.2", - "resolved": "https://registry.npmjs.org/@swagger-api/apidom-json-pointer/-/apidom-json-pointer-0.69.2.tgz", - "integrity": "sha512-ipu94QNw8ZKWC+pfie5IyIzVImR5N0PANXkUSfFon5L4aMAtggKpZn7aUv/2Cxn51JsCvjZwkXT7PaJ8RddpxA==", + "version": "0.70.0", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-json-pointer/-/apidom-json-pointer-0.70.0.tgz", + "integrity": "sha512-6MSgWgl1juBMiK4lFp/IBuWO21FB6dm+T9PnRIl2D8tSESndhNHfk3EPkfrfXDOtCK2gqij52w9JorRKEfxfWw==", "requires": { "@babel/runtime-corejs3": "^7.20.7", - "@swagger-api/apidom-core": "^0.69.2", - "@types/ramda": "=0.28.23", - "ramda": "=0.28.0", - "ramda-adjunct": "=3.4.0" + "@swagger-api/apidom-core": "^0.70.0", + "@types/ramda": "~0.29.1", + "ramda": "~0.29.0", + "ramda-adjunct": "^4.0.0" } }, "@swagger-api/apidom-ns-api-design-systems": { - "version": "0.69.2", - "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ns-api-design-systems/-/apidom-ns-api-design-systems-0.69.2.tgz", - "integrity": "sha512-JnOPiDvPfNH/6WWVBqBwK0oIHscHECtL1iZDUE9nB4NSbyoV10oulkuhHNAO9BImqtU4rxtWbXyTL1MEuUdHHQ==", + "version": "0.70.0", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ns-api-design-systems/-/apidom-ns-api-design-systems-0.70.0.tgz", + "integrity": "sha512-CA/5cyLWr1lC4oqoEm3BNbQ7ZLXQWhCK6ddTGckgrFNj4j6TxJycC83JGFvt8K2M9uSNRTh7t6b1o1kRzQ+Scg==", "optional": true, "requires": { "@babel/runtime-corejs3": "^7.20.7", - "@swagger-api/apidom-core": "^0.69.2", - "@swagger-api/apidom-ns-openapi-3-1": "^0.69.2", - "@types/ramda": "=0.28.23", - "ramda": "=0.28.0", - "ramda-adjunct": "=3.4.0", - "stampit": "=4.3.2" + "@swagger-api/apidom-core": "^0.70.0", + "@swagger-api/apidom-ns-openapi-3-1": "^0.70.0", + "@types/ramda": "~0.29.1", + "ramda": "~0.29.0", + "ramda-adjunct": "^4.0.0", + "stampit": "^4.3.2" } }, "@swagger-api/apidom-ns-asyncapi-2": { - "version": "0.69.2", - "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ns-asyncapi-2/-/apidom-ns-asyncapi-2-0.69.2.tgz", - "integrity": "sha512-SOg9P4rM5Aj2jabt4njmFbZTRMX8/vRHgiDL9vE+uz7s6j64B9QBSFF42j17cEVq6ToqQ7jtu40f1D7K62puQA==", + "version": "0.70.0", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ns-asyncapi-2/-/apidom-ns-asyncapi-2-0.70.0.tgz", + "integrity": "sha512-P7r6LZR1o8BVGcY+j4HWWlIk1XhP8H2tL7XCHJ3fmYCgqrbdyo3pJxZujfddbvI/TT4/8YMfq2fat6MMu0DhfQ==", "optional": true, "requires": { "@babel/runtime-corejs3": "^7.20.7", - "@swagger-api/apidom-core": "^0.69.2", - "@swagger-api/apidom-ns-json-schema-draft-7": "^0.69.2", - "@types/ramda": "=0.28.23", - "ramda": "=0.28.0", - "ramda-adjunct": "=3.4.0", - "stampit": "=4.3.2" + "@swagger-api/apidom-core": "^0.70.0", + "@swagger-api/apidom-ns-json-schema-draft-7": "^0.70.0", + "@types/ramda": "~0.29.1", + "ramda": "~0.29.0", + "ramda-adjunct": "^4.0.0", + "stampit": "^4.3.2" } }, "@swagger-api/apidom-ns-json-schema-draft-4": { - "version": "0.69.2", - "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ns-json-schema-draft-4/-/apidom-ns-json-schema-draft-4-0.69.2.tgz", - "integrity": "sha512-8yB4afGBAX+vN5oNRxZMWWS/2G0Q9VUzlL2AOu0Q70FQkscbjQcsb6QX9LbHFMrwi3MgOgE0ewMncpcwskuoXA==", + "version": "0.70.0", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ns-json-schema-draft-4/-/apidom-ns-json-schema-draft-4-0.70.0.tgz", + "integrity": "sha512-Y8O8Xl4P2cKDIunsvDV5Gvq+BVN/uIN5rJ5axHsZt0PL1D0c2Ypu6uCBDNb6iPdUZ3REpzemYNi2eZanL5gKtQ==", "requires": { "@babel/runtime-corejs3": "^7.20.7", - "@swagger-api/apidom-core": "^0.69.2", - "@types/ramda": "=0.28.23", - "ramda": "=0.28.0", - "ramda-adjunct": "=3.4.0", - "stampit": "=4.3.2" + "@swagger-api/apidom-core": "^0.70.0", + "@types/ramda": "~0.29.1", + "ramda": "~0.29.0", + "ramda-adjunct": "^4.0.0", + "stampit": "^4.3.2" } }, "@swagger-api/apidom-ns-json-schema-draft-6": { - "version": "0.69.2", - "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ns-json-schema-draft-6/-/apidom-ns-json-schema-draft-6-0.69.2.tgz", - "integrity": "sha512-RPazXv7L37vrdqHeFy/ZrKVz+vnVOqEFrHFefaq2L5avnzInVTRsTJm+61q0jqnckozz426Gbg5wJgq+Yvpbqg==", + "version": "0.70.0", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ns-json-schema-draft-6/-/apidom-ns-json-schema-draft-6-0.70.0.tgz", + "integrity": "sha512-xaYZ7NeivNZreYzaufNLpxAqusjAXji8Ls+HArzgZWdpjddnN2wvHDEkF8gPBdYMRWiXWLKzXFey/ZhOSwLi9Q==", "optional": true, "requires": { "@babel/runtime-corejs3": "^7.20.7", - "@swagger-api/apidom-core": "^0.69.2", - "@swagger-api/apidom-ns-json-schema-draft-4": "^0.69.2", - "@types/ramda": "=0.28.23", - "ramda": "=0.28.0", - "ramda-adjunct": "=3.4.0", - "stampit": "=4.3.2" + "@swagger-api/apidom-core": "^0.70.0", + "@swagger-api/apidom-ns-json-schema-draft-4": "^0.70.0", + "@types/ramda": "~0.29.1", + "ramda": "~0.29.0", + "ramda-adjunct": "^4.0.0", + "stampit": "^4.3.2" } }, "@swagger-api/apidom-ns-json-schema-draft-7": { - "version": "0.69.2", - "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ns-json-schema-draft-7/-/apidom-ns-json-schema-draft-7-0.69.2.tgz", - "integrity": "sha512-pQQE85xjv+UaObQzkhpOPochQ8GhWETAUuzDxHrgKmw20Ca03QAC7RV3tbSnkIbI5cy9wpb4gRM0T5/PzZnBYA==", + "version": "0.70.0", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ns-json-schema-draft-7/-/apidom-ns-json-schema-draft-7-0.70.0.tgz", + "integrity": "sha512-yy1cha8GiIHBfhK31ycTSd3UwWzQGjWcSLAl7dfT/UpemcHn4wGzJtoFx+tqpGHwpUie3qpEm7ReLRrYzeg0Ew==", "optional": true, "requires": { "@babel/runtime-corejs3": "^7.20.7", - "@swagger-api/apidom-core": "^0.69.2", - "@swagger-api/apidom-ns-json-schema-draft-6": "^0.69.2", - "@types/ramda": "=0.28.23", - "ramda": "=0.28.0", - "ramda-adjunct": "=3.4.0", - "stampit": "=4.3.2" + "@swagger-api/apidom-core": "^0.70.0", + "@swagger-api/apidom-ns-json-schema-draft-6": "^0.70.0", + "@types/ramda": "~0.29.1", + "ramda": "~0.29.0", + "ramda-adjunct": "^4.0.0", + "stampit": "^4.3.2" } }, "@swagger-api/apidom-ns-openapi-3-0": { - "version": "0.69.2", - "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ns-openapi-3-0/-/apidom-ns-openapi-3-0-0.69.2.tgz", - "integrity": "sha512-OroXRC+Q1btrpuQ3+ZbMi9XGYiab3YQMg/Rx1wpszbW5C5IPtaa2/FtMcBGYWR5IIdxi+bAT8itbMBGSCcz/Ew==", + "version": "0.70.0", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ns-openapi-3-0/-/apidom-ns-openapi-3-0-0.70.0.tgz", + "integrity": "sha512-wEw5uHd1/XqgkwvGM1DrhwPsDWcMNedxHybq0b7Bp2ZE7QWI4V/OS1gOnYpBvB6OUpHkEc5c2z++hr6fd0FhHQ==", "requires": { "@babel/runtime-corejs3": "^7.20.7", - "@swagger-api/apidom-core": "^0.69.2", - "@swagger-api/apidom-ns-json-schema-draft-4": "^0.69.2", - "@types/ramda": "=0.28.23", - "ramda": "=0.28.0", - "ramda-adjunct": "=3.4.0", - "stampit": "=4.3.2" + "@swagger-api/apidom-core": "^0.70.0", + "@swagger-api/apidom-ns-json-schema-draft-4": "^0.70.0", + "@types/ramda": "~0.29.1", + "ramda": "~0.29.0", + "ramda-adjunct": "^4.0.0", + "stampit": "^4.3.2" } }, "@swagger-api/apidom-ns-openapi-3-1": { - "version": "0.69.2", - "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ns-openapi-3-1/-/apidom-ns-openapi-3-1-0.69.2.tgz", - "integrity": "sha512-2yyUmdbvkDnZuOGDduvlp4dtLL/8a1PCR+Ajk9+PR4ZTdbMFtZWcr/knGc33Rtr8eXQwd4NPypFHhTLCEHiGwg==", + "version": "0.70.0", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ns-openapi-3-1/-/apidom-ns-openapi-3-1-0.70.0.tgz", + "integrity": "sha512-0d8IPy7A9lCjscXsw260j07xTQu8ExG3OpFxSJPOwQsrZ6X2Oooaq/0e0hnK15ngaVQzv9Iq8pBXSpJ/oJD8Ww==", "requires": { "@babel/runtime-corejs3": "^7.20.7", - "@swagger-api/apidom-core": "^0.69.2", - "@swagger-api/apidom-ns-openapi-3-0": "^0.69.2", - "@types/ramda": "=0.28.23", - "ramda": "=0.28.0", - "ramda-adjunct": "=3.4.0", - "stampit": "=4.3.2" + "@swagger-api/apidom-core": "^0.70.0", + "@swagger-api/apidom-ns-openapi-3-0": "^0.70.0", + "@types/ramda": "~0.29.1", + "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.2", - "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-api-design-systems-json/-/apidom-parser-adapter-api-design-systems-json-0.69.2.tgz", - "integrity": "sha512-qVL8JrnsjuD3uS1PTNoBTKd5YXPs2SfxRGyKwcmrUXFS+jJmbyYF04xmCgXkwI6TZYjY6KlNb8vmOt4Xr8FqnA==", + "version": "0.70.0", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-api-design-systems-json/-/apidom-parser-adapter-api-design-systems-json-0.70.0.tgz", + "integrity": "sha512-mXR+0EjOa+1trve5xGKmhDSXdIs8zMMJAN9KlnPvAVVezpoDLgg8s687qfPyNmfRmYcnO/18X8pn3/WhQCVFHA==", "optional": true, "requires": { "@babel/runtime-corejs3": "^7.20.7", - "@swagger-api/apidom-core": "^0.69.2", - "@swagger-api/apidom-ns-api-design-systems": "^0.69.2", - "@swagger-api/apidom-parser-adapter-json": "^0.69.2", - "@types/ramda": "=0.28.23", - "ramda": "=0.28.0", - "ramda-adjunct": "=3.4.0" + "@swagger-api/apidom-core": "^0.70.0", + "@swagger-api/apidom-ns-api-design-systems": "^0.70.0", + "@swagger-api/apidom-parser-adapter-json": "^0.70.0", + "@types/ramda": "~0.29.1", + "ramda": "~0.29.0", + "ramda-adjunct": "^4.0.0" } }, "@swagger-api/apidom-parser-adapter-api-design-systems-yaml": { - "version": "0.69.2", - "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-api-design-systems-yaml/-/apidom-parser-adapter-api-design-systems-yaml-0.69.2.tgz", - "integrity": "sha512-vMWfQiXcSeo9XSJJ1Yny4BAUw/3RBbBTsPBbNSsnMtpcDhBbodxdko1CeYkGc8sKM0QyeTFPJuiVhb3kGidbcQ==", + "version": "0.70.0", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-api-design-systems-yaml/-/apidom-parser-adapter-api-design-systems-yaml-0.70.0.tgz", + "integrity": "sha512-sfQq2cetMAH9CWS9UINTDdZ+Hs735C6CojtG6dkeA+BJe0gJg+NmQI2qwePQ1jY9p9D+lHMMl56ispYQQnaCjg==", "optional": true, "requires": { "@babel/runtime-corejs3": "^7.20.7", - "@swagger-api/apidom-core": "^0.69.2", - "@swagger-api/apidom-ns-api-design-systems": "^0.69.2", - "@swagger-api/apidom-parser-adapter-yaml-1-2": "^0.69.2", - "@types/ramda": "=0.28.23", - "ramda": "=0.28.0", - "ramda-adjunct": "=3.4.0" + "@swagger-api/apidom-core": "^0.70.0", + "@swagger-api/apidom-ns-api-design-systems": "^0.70.0", + "@swagger-api/apidom-parser-adapter-yaml-1-2": "^0.70.0", + "@types/ramda": "~0.29.1", + "ramda": "~0.29.0", + "ramda-adjunct": "^4.0.0" } }, "@swagger-api/apidom-parser-adapter-asyncapi-json-2": { - "version": "0.69.2", - "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-asyncapi-json-2/-/apidom-parser-adapter-asyncapi-json-2-0.69.2.tgz", - "integrity": "sha512-/WDVzOcGFxSgBVCiXHVnfiOeYIWtn2RG/Bnn8leUmWRN9oRBiCS3Lh60qWVpvs4BOVntL7SYJnC+buicKD1iJA==", + "version": "0.70.0", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-asyncapi-json-2/-/apidom-parser-adapter-asyncapi-json-2-0.70.0.tgz", + "integrity": "sha512-u35roIPmq9zvXKfuGyMzY9gZMj1kTLmFSdCvOA5JvysEQLSx59s/Qs4um+BLvH2Z9/D5DYVb2cnyujTIVmoMYg==", "optional": true, "requires": { "@babel/runtime-corejs3": "^7.20.7", - "@swagger-api/apidom-core": "^0.69.2", - "@swagger-api/apidom-ns-asyncapi-2": "^0.69.2", - "@swagger-api/apidom-parser-adapter-json": "^0.69.2", - "@types/ramda": "=0.28.23", - "ramda": "=0.28.0", - "ramda-adjunct": "=3.4.0" + "@swagger-api/apidom-core": "^0.70.0", + "@swagger-api/apidom-ns-asyncapi-2": "^0.70.0", + "@swagger-api/apidom-parser-adapter-json": "^0.70.0", + "@types/ramda": "~0.29.1", + "ramda": "~0.29.0", + "ramda-adjunct": "^4.0.0" } }, "@swagger-api/apidom-parser-adapter-asyncapi-yaml-2": { - "version": "0.69.2", - "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-asyncapi-yaml-2/-/apidom-parser-adapter-asyncapi-yaml-2-0.69.2.tgz", - "integrity": "sha512-qr5epdayokdYyPIJITyqWmUxdknhgPbXhWpSxdEvM8UHgtyutvvvFenFM5pXD1ft8I/uv9oj2WNZxCH2mpsR1g==", + "version": "0.70.0", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-asyncapi-yaml-2/-/apidom-parser-adapter-asyncapi-yaml-2-0.70.0.tgz", + "integrity": "sha512-NYxbr+BFDf6y92+o8sFaQ0UWxh72jNo8TkCl1/JYSrc5QuLSIu5UIMbjtLPGY+82okMRxCwjG7dCd8h62/KpiQ==", "optional": true, "requires": { "@babel/runtime-corejs3": "^7.20.7", - "@swagger-api/apidom-core": "^0.69.2", - "@swagger-api/apidom-ns-asyncapi-2": "^0.69.2", - "@swagger-api/apidom-parser-adapter-yaml-1-2": "^0.69.2", - "@types/ramda": "=0.28.23", - "ramda": "=0.28.0", - "ramda-adjunct": "=3.4.0" + "@swagger-api/apidom-core": "^0.70.0", + "@swagger-api/apidom-ns-asyncapi-2": "^0.70.0", + "@swagger-api/apidom-parser-adapter-yaml-1-2": "^0.70.0", + "@types/ramda": "~0.29.1", + "ramda": "~0.29.0", + "ramda-adjunct": "^4.0.0" } }, "@swagger-api/apidom-parser-adapter-json": { - "version": "0.69.2", - "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-json/-/apidom-parser-adapter-json-0.69.2.tgz", - "integrity": "sha512-mRXrf9bz2lxf9DY2n2WkB3GlQ8MXRqKBwpXLwjDqqve25Wf6X8QNzfz5ykYORQMuyDIgPq/8aMet7yjHRNtcUQ==", + "version": "0.70.0", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-json/-/apidom-parser-adapter-json-0.70.0.tgz", + "integrity": "sha512-j0Pb4Xg1VdFaCwYInF9qQxCDpaznzgse4oSUy1uJkLK0W/eC6bLNHyDGJmxR/TAqxY4yzb5zNCJnlJkjZqKxkQ==", "optional": true, "requires": { "@babel/runtime-corejs3": "^7.20.7", - "@swagger-api/apidom-ast": "^0.69.0", - "@swagger-api/apidom-core": "^0.69.2", - "@types/ramda": "=0.28.23", - "ramda": "=0.28.0", - "ramda-adjunct": "=3.4.0", - "stampit": "=4.3.2", + "@swagger-api/apidom-ast": "^0.70.0", + "@swagger-api/apidom-core": "^0.70.0", + "@types/ramda": "~0.29.1", + "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.2", - "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-openapi-json-3-0/-/apidom-parser-adapter-openapi-json-3-0-0.69.2.tgz", - "integrity": "sha512-NGOX9NcrAy1RX1f+uA2rLZbgVWte6O4HRVk0eVjuR3NKjJzuXFdfaYJUpT8IGJx2cW6HsQtJU+BpR+LMfZnM9A==", + "version": "0.70.0", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-openapi-json-3-0/-/apidom-parser-adapter-openapi-json-3-0-0.70.0.tgz", + "integrity": "sha512-GsQH3GaOWVnGYtd+jajrsXjn1N0JqH1cQz/RoyxaD2zeI0LDW3efXGfa2zr3tFWqxTPrdOIMu6irH8aWtiC5sA==", "optional": true, "requires": { "@babel/runtime-corejs3": "^7.20.7", - "@swagger-api/apidom-core": "^0.69.2", - "@swagger-api/apidom-ns-openapi-3-0": "^0.69.2", - "@swagger-api/apidom-parser-adapter-json": "^0.69.2", - "@types/ramda": "=0.28.23", - "ramda": "=0.28.0", - "ramda-adjunct": "=3.4.0" + "@swagger-api/apidom-core": "^0.70.0", + "@swagger-api/apidom-ns-openapi-3-0": "^0.70.0", + "@swagger-api/apidom-parser-adapter-json": "^0.70.0", + "@types/ramda": "~0.29.1", + "ramda": "~0.29.0", + "ramda-adjunct": "^4.0.0" } }, "@swagger-api/apidom-parser-adapter-openapi-json-3-1": { - "version": "0.69.2", - "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-openapi-json-3-1/-/apidom-parser-adapter-openapi-json-3-1-0.69.2.tgz", - "integrity": "sha512-agafo94Uru42/nnydZ2wEt3ENAB6LWAd9l4d8wZL0ifAkjx8fv8rfH601LpFBQq3iD2DlGm0+UpFZXLfBuuQbQ==", + "version": "0.70.0", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-openapi-json-3-1/-/apidom-parser-adapter-openapi-json-3-1-0.70.0.tgz", + "integrity": "sha512-e8bUiTa+q4xTS77hZC9pyg6U91rWlbfXBhzUGAn6fB+JF4PXxyep6sH+8JbJvGjI6DFvvuiwzRvVFt6DyKFhZg==", "optional": true, "requires": { "@babel/runtime-corejs3": "^7.20.7", - "@swagger-api/apidom-core": "^0.69.2", - "@swagger-api/apidom-ns-openapi-3-1": "^0.69.2", - "@swagger-api/apidom-parser-adapter-json": "^0.69.2", - "@types/ramda": "=0.28.23", - "ramda": "=0.28.0", - "ramda-adjunct": "=3.4.0" + "@swagger-api/apidom-core": "^0.70.0", + "@swagger-api/apidom-ns-openapi-3-1": "^0.70.0", + "@swagger-api/apidom-parser-adapter-json": "^0.70.0", + "@types/ramda": "~0.29.1", + "ramda": "~0.29.0", + "ramda-adjunct": "^4.0.0" } }, "@swagger-api/apidom-parser-adapter-openapi-yaml-3-0": { - "version": "0.69.2", - "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-openapi-yaml-3-0/-/apidom-parser-adapter-openapi-yaml-3-0-0.69.2.tgz", - "integrity": "sha512-p7Wqk7vCgh9mkXQmk9I/uXri2+1MLCQ14NHfrVowey4ntH4LzBf0NtvxgfCryzzLb2RpdUwIxoh+uf4ibTzyyA==", + "version": "0.70.0", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-openapi-yaml-3-0/-/apidom-parser-adapter-openapi-yaml-3-0-0.70.0.tgz", + "integrity": "sha512-4dwRV9uhjuIziV5Z9742RpVCo+QtrQDrGxh5pQaTbU8mNvXdrZ7ebFOxYQIPmxGcPAbxm1x6dvicUvhs97Lsrg==", "optional": true, "requires": { "@babel/runtime-corejs3": "^7.20.7", - "@swagger-api/apidom-core": "^0.69.2", - "@swagger-api/apidom-ns-openapi-3-0": "^0.69.2", - "@swagger-api/apidom-parser-adapter-yaml-1-2": "^0.69.2", - "@types/ramda": "=0.28.23", - "ramda": "=0.28.0", - "ramda-adjunct": "=3.4.0" + "@swagger-api/apidom-core": "^0.70.0", + "@swagger-api/apidom-ns-openapi-3-0": "^0.70.0", + "@swagger-api/apidom-parser-adapter-yaml-1-2": "^0.70.0", + "@types/ramda": "~0.29.1", + "ramda": "~0.29.0", + "ramda-adjunct": "^4.0.0" } }, "@swagger-api/apidom-parser-adapter-openapi-yaml-3-1": { - "version": "0.69.2", - "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-openapi-yaml-3-1/-/apidom-parser-adapter-openapi-yaml-3-1-0.69.2.tgz", - "integrity": "sha512-Dq914JCnOqmRl6DyxeaP91MlZvIn62hax4RsANeiHIm2ICwwCQLNM9RNUkWq3iimHZpvTz+etM3QkKMakXUnqQ==", + "version": "0.70.0", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-openapi-yaml-3-1/-/apidom-parser-adapter-openapi-yaml-3-1-0.70.0.tgz", + "integrity": "sha512-u/JE8maz+PSm3prCTE9jx0XaK1/+kxoFT1v7l9AXqAyczsyQ/FMPNUvQ8Qyr/Jad5wHW6IcIIhqG79n4e819JQ==", "optional": true, "requires": { "@babel/runtime-corejs3": "^7.20.7", - "@swagger-api/apidom-core": "^0.69.2", - "@swagger-api/apidom-ns-openapi-3-1": "^0.69.2", - "@swagger-api/apidom-parser-adapter-yaml-1-2": "^0.69.2", - "@types/ramda": "=0.28.23", - "ramda": "=0.28.0", - "ramda-adjunct": "=3.4.0" + "@swagger-api/apidom-core": "^0.70.0", + "@swagger-api/apidom-ns-openapi-3-1": "^0.70.0", + "@swagger-api/apidom-parser-adapter-yaml-1-2": "^0.70.0", + "@types/ramda": "~0.29.1", + "ramda": "~0.29.0", + "ramda-adjunct": "^4.0.0" } }, "@swagger-api/apidom-parser-adapter-yaml-1-2": { - "version": "0.69.2", - "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-yaml-1-2/-/apidom-parser-adapter-yaml-1-2-0.69.2.tgz", - "integrity": "sha512-5sWKGF/phSd+kvOD8xfB2W26QeN2U6LOoLy6eYvf8DP5q4doiLoEcVIunpcVjl04IWVet+VFH9XFGEMlKk7qKQ==", + "version": "0.70.0", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-yaml-1-2/-/apidom-parser-adapter-yaml-1-2-0.70.0.tgz", + "integrity": "sha512-DfJ9f7Qm6QdmGJMMHicksEZKRJYR0yY9KTF+UKPBGChakXxr9Gt6ZkxWwRz0ataPFJZbW+Q8/P56Mrp/ETH+WQ==", "optional": true, "requires": { "@babel/runtime-corejs3": "^7.20.7", - "@swagger-api/apidom-ast": "^0.69.0", - "@swagger-api/apidom-core": "^0.69.2", - "@types/ramda": "=0.28.23", - "ramda": "=0.28.0", - "ramda-adjunct": "=3.4.0", - "stampit": "=4.3.2", + "@swagger-api/apidom-ast": "^0.70.0", + "@swagger-api/apidom-core": "^0.70.0", + "@types/ramda": "~0.29.1", + "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.2", - "resolved": "https://registry.npmjs.org/@swagger-api/apidom-reference/-/apidom-reference-0.69.2.tgz", - "integrity": "sha512-aJsgtCP71t8a+frS+qn1FW9MqjjK60c3AnB2G3cUXGVwzmEPEkvBFF0LGlPmHftVvzzBvI7AsMC7+HZPM/t7rQ==", + "version": "0.70.0", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-reference/-/apidom-reference-0.70.0.tgz", + "integrity": "sha512-HKfDmfQQc/RmdSdOpN/WtGF+97WjgRmvIdt3FmuPTqFRM4CeGpDM/MXvcxa71sN1yUx/o6LkRRwYSqDlYfutIw==", "requires": { "@babel/runtime-corejs3": "^7.20.7", - "@swagger-api/apidom-core": "^0.69.2", - "@swagger-api/apidom-json-pointer": "^0.69.2", - "@swagger-api/apidom-ns-asyncapi-2": "^0.69.2", - "@swagger-api/apidom-ns-openapi-3-0": "^0.69.2", - "@swagger-api/apidom-ns-openapi-3-1": "^0.69.2", - "@swagger-api/apidom-parser-adapter-api-design-systems-json": "^0.69.2", - "@swagger-api/apidom-parser-adapter-api-design-systems-yaml": "^0.69.2", - "@swagger-api/apidom-parser-adapter-asyncapi-json-2": "^0.69.2", - "@swagger-api/apidom-parser-adapter-asyncapi-yaml-2": "^0.69.2", - "@swagger-api/apidom-parser-adapter-json": "^0.69.2", - "@swagger-api/apidom-parser-adapter-openapi-json-3-0": "^0.69.2", - "@swagger-api/apidom-parser-adapter-openapi-json-3-1": "^0.69.2", - "@swagger-api/apidom-parser-adapter-openapi-yaml-3-0": "^0.69.2", - "@swagger-api/apidom-parser-adapter-openapi-yaml-3-1": "^0.69.2", - "@swagger-api/apidom-parser-adapter-yaml-1-2": "^0.69.2", - "@types/ramda": "=0.28.23", - "axios": "=1.3.4", - "minimatch": "=7.4.3", - "process": "=0.11.10", - "ramda": "=0.28.0", - "ramda-adjunct": "=3.4.0", - "stampit": "=4.3.2" + "@swagger-api/apidom-core": "^0.70.0", + "@swagger-api/apidom-json-pointer": "^0.70.0", + "@swagger-api/apidom-ns-asyncapi-2": "^0.70.0", + "@swagger-api/apidom-ns-openapi-3-0": "^0.70.0", + "@swagger-api/apidom-ns-openapi-3-1": "^0.70.0", + "@swagger-api/apidom-parser-adapter-api-design-systems-json": "^0.70.0", + "@swagger-api/apidom-parser-adapter-api-design-systems-yaml": "^0.70.0", + "@swagger-api/apidom-parser-adapter-asyncapi-json-2": "^0.70.0", + "@swagger-api/apidom-parser-adapter-asyncapi-yaml-2": "^0.70.0", + "@swagger-api/apidom-parser-adapter-json": "^0.70.0", + "@swagger-api/apidom-parser-adapter-openapi-json-3-0": "^0.70.0", + "@swagger-api/apidom-parser-adapter-openapi-json-3-1": "^0.70.0", + "@swagger-api/apidom-parser-adapter-openapi-yaml-3-0": "^0.70.0", + "@swagger-api/apidom-parser-adapter-openapi-yaml-3-1": "^0.70.0", + "@swagger-api/apidom-parser-adapter-yaml-1-2": "^0.70.0", + "@types/ramda": "~0.29.1", + "axios": "^1.4.0", + "minimatch": "^7.4.3", + "process": "^0.11.10", + "ramda": "~0.29.0", + "ramda-adjunct": "^4.0.0", + "stampit": "^4.3.2" }, "dependencies": { "brace-expansion": { @@ -5745,9 +5350,9 @@ } }, "minimatch": { - "version": "7.4.3", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-7.4.3.tgz", - "integrity": "sha512-5UB4yYusDtkRPbRiy1cqZ1IpGNcJCGlEMG17RKzPddpyiPKoCdwohbED8g4QXT0ewCt8LTkQXuljsUfQ3FKM4A==", + "version": "7.4.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-7.4.6.tgz", + "integrity": "sha512-sBz8G/YjVniEz6lKPNpKxXwazJe4c19fEfV2GDMX6AjFz+MX9uDWIZW8XreVhkFW3fkIdTv/gxWr/Kks5FFAVw==", "requires": { "brace-expansion": "^2.0.1" } @@ -5781,9 +5386,9 @@ } }, "@types/babel__core": { - "version": "7.20.0", - "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.0.tgz", - "integrity": "sha512-+n8dL/9GWblDO0iU6eZAwEIJVr5DWigtle+Q6HLOrh/pdbXOhOtqzq8VPPE2zvNJzSKY4vH/z3iT3tn0A3ypiQ==", + "version": "7.20.1", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.1.tgz", + "integrity": "sha512-aACu/U/omhdk15O4Nfb+fHgH/z3QsfQzpnvRZhYhThms83ZnAOZz7zZAWO7mn2yyNQaA4xTO8GLK3uqFU4bYYw==", "dev": true, "requires": { "@babel/parser": "^7.20.7", @@ -5813,12 +5418,12 @@ } }, "@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.20.0", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.0.tgz", + "integrity": "sha512-TBOjqAGf0hmaqRwpii5LLkJLg7c6OMm4nHLmpsUxwk9bBHtoTC6dAHdVWdGv4TBxj2CZOZY8Xfq8WmfoVi7n4Q==", "dev": true, "requires": { - "@babel/types": "^7.3.0" + "@babel/types": "^7.20.7" } }, "@types/body-parser": { @@ -6015,9 +5620,9 @@ } }, "@types/json-schema": { - "version": "7.0.11", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz", - "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==", + "version": "7.0.12", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.12.tgz", + "integrity": "sha512-Hr5Jfhc9eYOQNPYO5WLDq/n4jqijdHNlDXjuAQkkt+mWdQR+XJToOHrsD4cPaMXpn6KO7y2+wM8AZEs8VpBLVA==", "dev": true }, "@types/lodash": { @@ -6101,11 +5706,11 @@ "dev": true }, "@types/ramda": { - "version": "0.28.23", - "resolved": "https://registry.npmjs.org/@types/ramda/-/ramda-0.28.23.tgz", - "integrity": "sha512-9TYWiwkew+mCMsL7jZ+kkzy6QXn8PL5/SKmBPmjgUlTpkokZWTBr+OhiIUDztpAEbslWyt24NNfEmZUBFmnXig==", + "version": "0.29.2", + "resolved": "https://registry.npmjs.org/@types/ramda/-/ramda-0.29.2.tgz", + "integrity": "sha512-JxvBGR3G4gV3RTOBugVHAAOD6iiv2WjlJ8BHr0s7KALdPpx2l+didoTuoJMmrA0eqpUaCm/slKP4TmxMRihd8g==", "requires": { - "ts-toolbelt": "^6.15.1" + "types-ramda": "^0.29.3" } }, "@types/range-parser": { @@ -6115,9 +5720,9 @@ "dev": true }, "@types/react": { - "version": "18.0.34", - "resolved": "https://registry.npmjs.org/@types/react/-/react-18.0.34.tgz", - "integrity": "sha512-NO1UO8941541CJl1BeOXi8a9dNKFK09Gnru5ZJqkm4Q3/WoQJtHvmwt0VX0SB9YCEwe7TfSSxDuaNmx6H2BAIQ==", + "version": "18.2.7", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.7.tgz", + "integrity": "sha512-ojrXpSH2XFCmHm7Jy3q44nXDyN54+EYKP2lBhJ2bqfyPj6cIUW/FZW/Csdia34NQgq7KYcAlHi5184m4X88+yw==", "requires": { "@types/prop-types": "*", "@types/scheduler": "*", @@ -6286,9 +5891,9 @@ } }, "semver": { - "version": "7.4.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.4.0.tgz", - "integrity": "sha512-RgOxM8Mw+7Zus0+zcLEUn8+JfoLpj/huFTItQy2hsM4khuC1HYRDp0cU482Ewn/Fcy6bCjufD8vAj7voC66KQw==", + "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" @@ -6395,9 +6000,9 @@ } }, "semver": { - "version": "7.4.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.4.0.tgz", - "integrity": "sha512-RgOxM8Mw+7Zus0+zcLEUn8+JfoLpj/huFTItQy2hsM4khuC1HYRDp0cU482Ewn/Fcy6bCjufD8vAj7voC66KQw==", + "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" @@ -6441,12 +6046,6 @@ "eslint-visitor-keys": "^3.3.0" } }, - "@ungap/promise-all-settled": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@ungap/promise-all-settled/-/promise-all-settled-1.1.2.tgz", - "integrity": "sha512-sL/cEvJWAnClXw0wHk85/2L0G6Sj8UB0Ctc1TEMbKSsmpRosqhwj9gWgFRZSrBr2f9tiXISwNhCPmlfqUqyb9Q==", - "dev": true - }, "@webassemblyjs/ast": { "version": "1.11.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.1.tgz", @@ -6612,9 +6211,9 @@ "dev": true }, "@yarnpkg/parsers": { - "version": "3.0.0-rc.42", - "resolved": "https://registry.npmjs.org/@yarnpkg/parsers/-/parsers-3.0.0-rc.42.tgz", - "integrity": "sha512-eW9Mbegmb5bJjwawJM9ghjUjUqciNMhC6L7XrQPF/clXS5bbP66MstsgCT5hy9VlfUh/CfBT+0Wucf531dMjHA==", + "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", @@ -6641,9 +6240,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 } } @@ -6866,15 +6465,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", @@ -6894,12 +6484,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", @@ -7056,9 +6640,9 @@ "dev": true }, "asap": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/asap/-/asap-1.0.0.tgz", - "integrity": "sha512-Ej9qjcXY+8Tuy1cNqiwNMwFRXOy9UwgTeMA8LxreodygIPV48lx8PU1ecFxb5ZeU1DpMKxiq6vGLTxcitWZPbA==", + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", + "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==", "dev": true }, "asn1": { @@ -7138,14 +6722,6 @@ "diff": "^3.0.0", "pad-right": "^0.2.2", "repeat-string": "^1.6.1" - }, - "dependencies": { - "diff": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", - "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", - "dev": true - } } }, "assign-symbols": { @@ -7300,9 +6876,9 @@ "dev": true }, "axios": { - "version": "1.3.4", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.3.4.tgz", - "integrity": "sha512-toYm+Bsyl6VC5wSkfkbbNB6ROv7KY93PEBBL6xyDczaIHasAiv4wPqQ/c4RjoQzipxRD2W5g21cOqQulZ7rHwQ==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.4.0.tgz", + "integrity": "sha512-S4XCWMEmzvo64T9GfvQDOXgYRDJ/wsSZc7Jvdgx5u1sd0JwsuPLqb3SYmusag+edF6ziyMensPVqLTSc1PiSEA==", "requires": { "follow-redirects": "^1.15.0", "form-data": "^4.0.0", @@ -7682,17 +7258,6 @@ } } }, - "blink-diff": { - "version": "1.0.13", - "resolved": "https://registry.npmjs.org/blink-diff/-/blink-diff-1.0.13.tgz", - "integrity": "sha512-2hIEnGq8wruXfje9GvDV41VXo+4YdjrjL5ZMlVJT3Wi5k1jjz20fCTlVejSXoERirhEVsFYz9NmgdUYgQ41Giw==", - "dev": true, - "requires": { - "pngjs-image": "~0.11.5", - "preceptor-core": "~0.10.0", - "promise": "6.0.0" - } - }, "blob-util": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/blob-util/-/blob-util-2.0.2.tgz", @@ -7870,12 +7435,6 @@ } } }, - "browser-stdout": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", - "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", - "dev": true - }, "browserify": { "version": "16.5.2", "resolved": "https://registry.npmjs.org/browserify/-/browserify-16.5.2.tgz", @@ -8279,9 +7838,9 @@ "integrity": "sha512-ceOhN1DL7Y4O6M0j9ICgmTYziV89WMd96SvSl0REd8PMgrY0B/WBOPoed5S1KUmJqXgUXh8gzSe6E3ae27upsQ==" }, "caniuse-lite": { - "version": "1.0.30001477", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001477.tgz", - "integrity": "sha512-lZim4iUHhGcy5p+Ri/G7m84hJwncj+Kz7S5aD4hoQfslKZJgt0tHc/hafVbqHC5bbhHb+mrW2JOUHkI5KH7toQ==" + "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", @@ -8421,20 +7980,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": { @@ -8506,20 +8065,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 } } @@ -8557,30 +8116,6 @@ "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", "optional": true }, - "chrome-remote-interface": { - "version": "0.31.1", - "resolved": "https://registry.npmjs.org/chrome-remote-interface/-/chrome-remote-interface-0.31.1.tgz", - "integrity": "sha512-cvNTnXfx4kYCaeh2sEKrdlqZsYRleACPL47O8LrrjihVfBQbfPmf03vVqSSm7SIeqyo2P77ZXovrBAs4D/nopQ==", - "dev": true, - "requires": { - "commander": "2.11.x", - "ws": "^7.2.0" - }, - "dependencies": { - "commander": { - "version": "2.11.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.11.0.tgz", - "integrity": "sha512-b0553uYA5YAEGgyYIGYROzKQ7X5RAqedkfjiZxwi0kL1g3bOaBNNZfYkzt/CL0umgD5wc9Jec2FbB98CjkMRvQ==", - "dev": true - }, - "ws": { - "version": "7.5.9", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz", - "integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==", - "dev": true - } - } - }, "chrome-trace-event": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz", @@ -8668,9 +8203,9 @@ } }, "cli-spinners": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.8.0.tgz", - "integrity": "sha512-/eG5sJcvEIwxcdYM86k5tPwn0MUzkX5YY3eImTGpJOZgVe4SdTMY14vQpcxgBzJ0wXwAYrS8E+c3uHeK4JNyzQ==", + "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": { @@ -8832,9 +8367,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": { @@ -9150,23 +8685,23 @@ } }, "core-js": { - "version": "3.30.0", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.30.0.tgz", - "integrity": "sha512-hQotSSARoNh1mYPi9O2YaWeiq/cEB95kOrFb4NCrO4RIFt1qqNpKsaE+vy/L3oiqvND5cThqXzUU3r9F7Efztg==" + "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.30.0", - "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.30.0.tgz", - "integrity": "sha512-P5A2h/9mRYZFIAP+5Ab8ns6083IyVpSclU74UNvbGVQ8VM7n3n3/g2yF3AkKQ9NXz2O+ioxLbEWKnDtgsFamhg==", + "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.30.0", - "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.30.0.tgz", - "integrity": "sha512-+2KbMFGeBU0ln/csoPqTe0i/yfHbrd2EUhNMObsGtXMKS/RTtlkYyi+/3twLcevbgNR0yM/r0Psa3TEoQRpFMQ==" + "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", @@ -9307,11 +8842,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": { @@ -9638,9 +9173,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", @@ -9662,7 +9197,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", @@ -9688,9 +9223,9 @@ }, "dependencies": { "@types/node": { - "version": "14.18.42", - "resolved": "https://registry.npmjs.org/@types/node/-/node-14.18.42.tgz", - "integrity": "sha512-xefu+RBie4xWlK8hwAzGh3npDz/4VhF6icY/shU+zv/1fNn+ZVG7T7CRwe9LId9sAYRPxI+59QBPuKL3WpyGRg==", + "version": "14.18.48", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.18.48.tgz", + "integrity": "sha512-iL0PIMwejpmuVHgfibHpfDwOdsbmB50wr21X71VnF5d7SsBF7WK+ZvP/SCcFm7Iwb9iiYSap9rlrdhToNAWdxg==", "dev": true }, "ansi-styles": { @@ -9798,9 +9333,9 @@ "dev": true }, "semver": { - "version": "7.4.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.4.0.tgz", - "integrity": "sha512-RgOxM8Mw+7Zus0+zcLEUn8+JfoLpj/huFTItQy2hsM4khuC1HYRDp0cU482Ewn/Fcy6bCjufD8vAj7voC66KQw==", + "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" @@ -9937,12 +9472,6 @@ } } }, - "cypress-file-upload": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/cypress-file-upload/-/cypress-file-upload-5.0.8.tgz", - "integrity": "sha512-+8VzNabRk3zG6x8f8BWArF/xA/W0VK4IZNx3MV0jFWrJS/qKn8eHfa5nU73P9fOQAgwHFJx7zjg4lwOnljMO8g==", - "dev": true - }, "cypress-iframe": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/cypress-iframe/-/cypress-iframe-1.0.1.tgz", @@ -10035,12 +9564,6 @@ } } }, - "date-format": { - "version": "0.0.0", - "resolved": "https://registry.npmjs.org/date-format/-/date-format-0.0.0.tgz", - "integrity": "sha512-kAmAdtsjW5nQ02FERwI1bP4xe6HQBPwy5kpAF4CRSLOMUs/vgMIEEwpy6JqUs7NitTyhZiImxwAjgPpnteycHg==", - "dev": true - }, "dateformat": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-3.0.3.tgz", @@ -10071,9 +9594,9 @@ } }, "decamelize": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz", - "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", "dev": true }, "decamelize-keys": { @@ -10086,12 +9609,6 @@ "map-obj": "^1.0.0" }, "dependencies": { - "decamelize": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", - "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", - "dev": true - }, "map-obj": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz", @@ -10381,12 +9898,6 @@ "minimist": "^1.2.6" } }, - "devtools-protocol": { - "version": "0.0.927104", - "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.927104.tgz", - "integrity": "sha512-5jfffjSuTOv0Lz53wTNNTcCUV8rv7d82AhYcapj28bC2B5tDxEZzVb7k51cNxZP2KHw24QE+sW7ZuSeD9NfMpA==", - "dev": true - }, "dfa": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/dfa/-/dfa-1.2.0.tgz", @@ -10394,9 +9905,9 @@ "dev": true }, "diff": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz", - "integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==", + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", + "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", "dev": true }, "diff-sequences": { @@ -10539,9 +10050,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": { @@ -10606,9 +10117,9 @@ } }, "electron-to-chromium": { - "version": "1.4.357", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.357.tgz", - "integrity": "sha512-UTkCbNTAcGXABmEnQrGcW4m3cG6fcyBfD4KDF0iyEAlbrGZiY9dmslyDAGOD1Kr5biN2F743Y30aRCOtau35Vw==" + "version": "1.4.408", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.408.tgz", + "integrity": "sha512-vjeaj0u/UYnzA/CIdGXzzcxRLCqRwREYc9YfaWInjIEr7/XPttZ6ShpyqapchEy0S2r6LpLjDBTnNj7ZxnxJKg==" }, "elliptic": { "version": "6.5.4", @@ -10728,6 +10239,7 @@ "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.8.tgz", "integrity": "sha512-dJ6oBr5SQ1VSd9qkk7ByRgb/1SH4JZjCHSW/mr63/QcXO9zLVxvJ6Oy13nio03rxpSnVDDjFor75SjVeZWPW/A==", "dev": true, + "optional": true, "requires": { "prr": "~1.0.1" } @@ -11161,9 +10673,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", @@ -11281,20 +10793,20 @@ } }, "eslint-visitor-keys": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.0.tgz", - "integrity": "sha512-HPpKPUBQcAsZOsHAFwTtIKcYlCje62XB7SEAcxjtmW6TD1WVpkS6i6/hOVtTZIl4zGj/mBqpFVGvaDneik+VoQ==", + "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.5.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-9.5.1.tgz", - "integrity": "sha512-5yxtHSZXRSW5pvv3hAlXM5+/Oswi1AUFqBmbibKb5s6bp3rGIDkyXU6xCoyuuLhijr4SFwPrXRoZjz0AZDN9tg==", + "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.4.0" + "eslint-visitor-keys": "^3.4.1" } }, "esprima": { @@ -11399,9 +10911,9 @@ "dev": true }, "eventemitter2": { - "version": "6.4.9", - "resolved": "https://registry.npmjs.org/eventemitter2/-/eventemitter2-6.4.9.tgz", - "integrity": "sha512-JEPTiaOt9f04oa6NOkc4aH+nVp5I3wEjpHbIPqfgCdD5v5bUzy7xQqwcVO2aDQgOWhI28da57HksMrzK9HlRxg==", + "version": "6.4.7", + "resolved": "https://registry.npmjs.org/eventemitter2/-/eventemitter2-6.4.7.tgz", + "integrity": "sha512-tYUSVOGeQPKt/eC1ABfhHy5Xd96N3oIijJvN3O9+TsC28T5V9yX9oEfEK5faP0EFSNVOG97qtAS68GBrQB2hDg==", "dev": true }, "eventemitter3": { @@ -12443,12 +11955,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" } }, @@ -12730,12 +12243,6 @@ "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", "dev": true }, - "growl": { - "version": "1.10.5", - "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", - "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", - "dev": true - }, "handle-thing": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/handle-thing/-/handle-thing-2.0.1.tgz", @@ -12842,8 +12349,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", @@ -13117,12 +12623,6 @@ "promise": "^8.0.2" }, "dependencies": { - "asap": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", - "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==", - "dev": true - }, "dom-serializer": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.2.2.tgz", @@ -13192,15 +12692,6 @@ "readable-stream": "^3.1.1" } }, - "promise": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/promise/-/promise-8.3.0.tgz", - "integrity": "sha512-rZPNPKTOYVNEEKFaq1HqTgOwZD+4/YHS5ukLzQCypkj+OkYx7iv0mA91lJlpPPZ8vMau3IIGj5Qlwrx+8iiSmg==", - "dev": true, - "requires": { - "asap": "~2.0.6" - } - }, "readable-stream": { "version": "3.6.2", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", @@ -13253,12 +12744,6 @@ "wrap-ansi": "^2.0.0" } }, - "decamelize": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", - "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", - "dev": true - }, "find-up": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", @@ -13462,20 +12947,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 } } @@ -13822,9 +13307,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" @@ -14029,9 +13514,9 @@ } }, "is-core-module": { - "version": "2.12.0", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.12.0.tgz", - "integrity": "sha512-RECHCBCd/viahWmwj6enj19sKbHfJrddi/6cBDsNTKbNq0f7VeaUkBo60BqzvPqo/W54ChS62Z5qyun7cfOMqQ==", + "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" @@ -14489,15 +13974,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": { @@ -15340,34 +14825,34 @@ }, "dependencies": { "esbuild": { - "version": "0.17.16", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.17.16.tgz", - "integrity": "sha512-aeSuUKr9aFVY9Dc8ETVELGgkj4urg5isYx8pLf4wlGgB0vTFjxJQdHnNH6Shmx4vYYrOTLCHtRI5i1XZ9l2Zcg==", + "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.16", - "@esbuild/android-arm64": "0.17.16", - "@esbuild/android-x64": "0.17.16", - "@esbuild/darwin-arm64": "0.17.16", - "@esbuild/darwin-x64": "0.17.16", - "@esbuild/freebsd-arm64": "0.17.16", - "@esbuild/freebsd-x64": "0.17.16", - "@esbuild/linux-arm": "0.17.16", - "@esbuild/linux-arm64": "0.17.16", - "@esbuild/linux-ia32": "0.17.16", - "@esbuild/linux-loong64": "0.17.16", - "@esbuild/linux-mips64el": "0.17.16", - "@esbuild/linux-ppc64": "0.17.16", - "@esbuild/linux-riscv64": "0.17.16", - "@esbuild/linux-s390x": "0.17.16", - "@esbuild/linux-x64": "0.17.16", - "@esbuild/netbsd-x64": "0.17.16", - "@esbuild/openbsd-x64": "0.17.16", - "@esbuild/sunos-x64": "0.17.16", - "@esbuild/win32-arm64": "0.17.16", - "@esbuild/win32-ia32": "0.17.16", - "@esbuild/win32-x64": "0.17.16" + "@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" } } } @@ -15875,9 +15360,9 @@ } }, "semver": { - "version": "7.4.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.4.0.tgz", - "integrity": "sha512-RgOxM8Mw+7Zus0+zcLEUn8+JfoLpj/huFTItQy2hsM4khuC1HYRDp0cU482Ewn/Fcy6bCjufD8vAj7voC66KQw==", + "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" @@ -16138,9 +15623,9 @@ "dev": true }, "joi": { - "version": "17.9.1", - "resolved": "https://registry.npmjs.org/joi/-/joi-17.9.1.tgz", - "integrity": "sha512-FariIi9j6QODKATGBrEX7HZcja8Bsh3rfdGYy/Sb65sGlZWK/QWesU1ghk7aJWDj95knjXlQfSmzFSPPkLVsfw==", + "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", @@ -16728,9 +16213,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" @@ -16977,40 +16462,6 @@ } } }, - "log4js": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/log4js/-/log4js-1.1.1.tgz", - "integrity": "sha512-lYb14ZSs1M/CUFuvy7Zk3VZLDtqrqOaVql9CE0tv8g6/qE1Gfq97XKdltBsjSxxvcJ+t8fAXOnvFxSsms7gGVg==", - "dev": true, - "requires": { - "debug": "^2.2.0", - "semver": "^5.3.0", - "streamroller": "^0.4.0" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "dev": true - }, - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "dev": true - } - } - }, "loglevel": { "version": "1.8.1", "resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.8.1.tgz", @@ -17305,16 +16756,6 @@ "fs-monkey": "^1.0.3" } }, - "memory-fs": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.5.0.tgz", - "integrity": "sha512-jA0rdU5KoQMC0e6ppoNRtpp6vjFq6+NY7r8hywnC7V+1Xj/MtHwGIbB1QaK/dunyjWteJzmkpd7ooeWg10T7GA==", - "dev": true, - "requires": { - "errno": "^0.1.3", - "readable-stream": "^2.0.1" - } - }, "memorystream": { "version": "0.3.1", "resolved": "https://registry.npmjs.org/memorystream/-/memorystream-0.3.1.tgz", @@ -17341,12 +16782,6 @@ "yargs-parser": "^20.2.3" }, "dependencies": { - "decamelize": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", - "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", - "dev": true - }, "lru-cache": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", @@ -17369,9 +16804,9 @@ } }, "semver": { - "version": "7.4.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.4.0.tgz", - "integrity": "sha512-RgOxM8Mw+7Zus0+zcLEUn8+JfoLpj/huFTItQy2hsM4khuC1HYRDp0cU482Ewn/Fcy6bCjufD8vAj7voC66KQw==", + "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" @@ -17694,231 +17129,54 @@ "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", "dev": true } - } - }, - "mixin-deep": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.2.tgz", - "integrity": "sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==", - "dev": true, - "requires": { - "for-in": "^1.0.2", - "is-extendable": "^1.0.1" - }, - "dependencies": { - "is-extendable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", - "dev": true, - "requires": { - "is-plain-object": "^2.0.4" - } - }, - "is-plain-object": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", - "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", - "dev": true, - "requires": { - "isobject": "^3.0.1" - } - } - } - }, - "mkdirp": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", - "dev": true - }, - "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==" - }, - "mobx": { - "version": "4.14.1", - "resolved": "https://registry.npmjs.org/mobx/-/mobx-4.14.1.tgz", - "integrity": "sha512-Oyg7Sr7r78b+QPYLufJyUmxTWcqeQ96S1nmtyur3QL8SeI6e0TqcKKcxbG+sVJLWANhHQkBW/mDmgG5DDC4fdw==" - }, - "mocha": { - "version": "9.2.0", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-9.2.0.tgz", - "integrity": "sha512-kNn7E8g2SzVcq0a77dkphPsDSN7P+iYkqE0ZsGCYWRsoiKjOt+NvXfaagik8vuDa6W5Zw3qxe8Jfpt5qKf+6/Q==", - "dev": true, - "requires": { - "@ungap/promise-all-settled": "1.1.2", - "ansi-colors": "4.1.1", - "browser-stdout": "1.3.1", - "chokidar": "3.5.3", - "debug": "4.3.3", - "diff": "5.0.0", - "escape-string-regexp": "4.0.0", - "find-up": "5.0.0", - "glob": "7.2.0", - "growl": "1.10.5", - "he": "1.2.0", - "js-yaml": "4.1.0", - "log-symbols": "4.1.0", - "minimatch": "3.0.4", - "ms": "2.1.3", - "nanoid": "3.2.0", - "serialize-javascript": "6.0.0", - "strip-json-comments": "3.1.1", - "supports-color": "8.1.1", - "which": "2.0.2", - "workerpool": "6.2.0", - "yargs": "16.2.0", - "yargs-parser": "20.2.4", - "yargs-unparser": "2.0.0" - }, - "dependencies": { - "cliui": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", - "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", - "dev": true, - "requires": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^7.0.0" - } - }, - "debug": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", - "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", - "dev": true, - "requires": { - "ms": "2.1.2" - }, - "dependencies": { - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - } - } - }, - "escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true - }, - "find-up": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", - "dev": true, - "requires": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" - } - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "locate-path": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", - "dev": true, - "requires": { - "p-locate": "^5.0.0" - } - }, - "minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "dev": true, - "requires": { - "brace-expansion": "^1.1.7" - } - }, - "ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true - }, - "nanoid": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.2.0.tgz", - "integrity": "sha512-fmsZYa9lpn69Ad5eDn7FMcnnSR+8R34W9qJEijxYhTbfOWzr22n1QxCMzXLK+ODyW2973V3Fux959iQoUxzUIA==", - "dev": true - }, - "p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dev": true, - "requires": { - "yocto-queue": "^0.1.0" - } - }, - "p-locate": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", - "dev": true, - "requires": { - "p-limit": "^3.0.2" - } - }, - "serialize-javascript": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz", - "integrity": "sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==", - "dev": true, - "requires": { - "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", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + } + }, + "mixin-deep": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.2.tgz", + "integrity": "sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==", + "dev": true, + "requires": { + "for-in": "^1.0.2", + "is-extendable": "^1.0.1" + }, + "dependencies": { + "is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", "dev": true, "requires": { - "has-flag": "^4.0.0" + "is-plain-object": "^2.0.4" } }, - "yargs": { - "version": "16.2.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", - "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", "dev": true, "requires": { - "cliui": "^7.0.2", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.0", - "y18n": "^5.0.5", - "yargs-parser": "^20.2.2" + "isobject": "^3.0.1" } - }, - "yargs-parser": { - "version": "20.2.4", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz", - "integrity": "sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==", - "dev": true } } }, + "mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "dev": true + }, + "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==" + }, + "mobx": { + "version": "4.14.1", + "resolved": "https://registry.npmjs.org/mobx/-/mobx-4.14.1.tgz", + "integrity": "sha512-Oyg7Sr7r78b+QPYLufJyUmxTWcqeQ96S1nmtyur3QL8SeI6e0TqcKKcxbG+sVJLWANhHQkBW/mDmgG5DDC4fdw==" + }, "mocha-junit-reporter": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/mocha-junit-reporter/-/mocha-junit-reporter-2.1.0.tgz", @@ -18317,9 +17575,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" } @@ -18407,9 +17665,9 @@ } }, "semver": { - "version": "7.4.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.4.0.tgz", - "integrity": "sha512-RgOxM8Mw+7Zus0+zcLEUn8+JfoLpj/huFTItQy2hsM4khuC1HYRDp0cU482Ewn/Fcy6bCjufD8vAj7voC66KQw==", + "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" @@ -18436,9 +17694,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.12", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.12.tgz", + "integrity": "sha512-QzsYKWhXTWx8h1kIvqfnC++o0pEmpRQA/aenALsL2F4pqNVr7YzcdMlDij5WBnwftRbJCNJL/O7zdKaxKPHqgQ==" }, "nopt": { "version": "5.0.0", @@ -18521,9 +17779,9 @@ } }, "semver": { - "version": "7.4.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.4.0.tgz", - "integrity": "sha512-RgOxM8Mw+7Zus0+zcLEUn8+JfoLpj/huFTItQy2hsM4khuC1HYRDp0cU482Ewn/Fcy6bCjufD8vAj7voC66KQw==", + "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" @@ -18564,9 +17822,9 @@ } }, "semver": { - "version": "7.4.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.4.0.tgz", - "integrity": "sha512-RgOxM8Mw+7Zus0+zcLEUn8+JfoLpj/huFTItQy2hsM4khuC1HYRDp0cU482Ewn/Fcy6bCjufD8vAj7voC66KQw==", + "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" @@ -18614,9 +17872,9 @@ } }, "semver": { - "version": "7.4.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.4.0.tgz", - "integrity": "sha512-RgOxM8Mw+7Zus0+zcLEUn8+JfoLpj/huFTItQy2hsM4khuC1HYRDp0cU482Ewn/Fcy6bCjufD8vAj7voC66KQw==", + "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" @@ -18789,9 +18047,9 @@ } }, "semver": { - "version": "7.4.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.4.0.tgz", - "integrity": "sha512-RgOxM8Mw+7Zus0+zcLEUn8+JfoLpj/huFTItQy2hsM4khuC1HYRDp0cU482Ewn/Fcy6bCjufD8vAj7voC66KQw==", + "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" @@ -18964,9 +18222,9 @@ "integrity": "sha512-4jbtZXNAsfZbAHiiqjLPBiCl16dES1zI4Hpzzxw61Tk+loF+sBDBKx1ICKKKwIqQ7M0mFn1TmkN7euSncWgHiQ==" }, "nwsapi": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.3.tgz", - "integrity": "sha512-jscxIO4/VKScHlbmFBdV1Z6LXnLO+ZR4VMtypudUdfwtKxUN3TQcNFIHLwKtrUbDyHN4/GycY9+oRGZ2XMXYPw==", + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.4.tgz", + "integrity": "sha512-NHj4rzRo0tQdijE9ZqAx6kYDcoRwYwSYzCA8MY3JzfxlrvEU0jhnhJT9BhqhJs7I/dKcrDm6TyulaRqZPIhN5g==", "dev": true }, "nx": { @@ -19816,34 +19074,6 @@ "integrity": "sha512-k+YsbhpA9e+EFfKjTCH3VW6aoKlyNYI6NYdTfDL4CIvFnvsuO84ttonmZE7rc+v23SLTH8XX+5w/Ak9v0xGY4g==", "dev": true }, - "pngjs": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-2.3.1.tgz", - "integrity": "sha512-ITNPqvx+SSssNFOgHQzGG87HrqQ0g2nMSHc1jjU5Piq9xJEJ40fiFEPz0S5HSSXxBHrTnhaBHIayTO5aRfk2vw==", - "dev": true - }, - "pngjs-image": { - "version": "0.11.7", - "resolved": "https://registry.npmjs.org/pngjs-image/-/pngjs-image-0.11.7.tgz", - "integrity": "sha512-JRyrmT+HXa1/gvdHpebus8TGqKa8WRgcsHz/DDalxRsMhvu6AOA99/enBFjZIPvmXVAzwKR051s80TuE1IiCpg==", - "dev": true, - "requires": { - "iconv-lite": "^0.4.8", - "pako": "^0.2.6", - "pngjs": "2.3.1", - "request": "^2.55.0", - "stream-buffers": "1.0.1", - "underscore": "1.7.0" - }, - "dependencies": { - "pako": { - "version": "0.2.9", - "resolved": "https://registry.npmjs.org/pako/-/pako-0.2.9.tgz", - "integrity": "sha512-NUcwaKxUxWrZLpDG+z/xZaCgQITkA/Dv4V/T6bw7VON6l1Xz/VnrBqrYjZQ12TamKHzITTfOEIYUj48y2KXImA==", - "dev": true - } - } - }, "portfinder": { "version": "1.0.32", "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.32.tgz", @@ -20469,9 +19699,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", @@ -20545,16 +19775,6 @@ "tunnel-agent": "^0.6.0" } }, - "preceptor-core": { - "version": "0.10.1", - "resolved": "https://registry.npmjs.org/preceptor-core/-/preceptor-core-0.10.1.tgz", - "integrity": "sha512-WLDk+UowEESixvlhiamGOj/iqWrp8IWeCCHvBZrLh0g4/A1Fa77fDQWqQUd5S5rScT+9u49aDfa45xYRkxqmiA==", - "dev": true, - "requires": { - "log4js": "1.1.1", - "underscore": "1.7.0" - } - }, "prelude-ls": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", @@ -20718,12 +19938,12 @@ "dev": true }, "promise": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/promise/-/promise-6.0.0.tgz", - "integrity": "sha512-PjIqIEWR8EWwP5ml3Wf5KWIP3sIdXAew9vQ6vLOLV+z4LMa/8ZQyLd7sTWe2r8OuA8A9jsIYptDfbEn/L36ogw==", + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/promise/-/promise-8.3.0.tgz", + "integrity": "sha512-rZPNPKTOYVNEEKFaq1HqTgOwZD+4/YHS5ukLzQCypkj+OkYx7iv0mA91lJlpPPZ8vMau3IIGj5Qlwrx+8iiSmg==", "dev": true, "requires": { - "asap": "~1.0.0" + "asap": "~2.0.6" } }, "promise-inflight": { @@ -20800,9 +20020,9 @@ }, "dependencies": { "@types/node": { - "version": "18.15.11", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.15.11.tgz", - "integrity": "sha512-E5Kwq2n4SbMzQOn6wnmBjuK9ouqlURrcZDVfbo9ftDDTFt3nk7ZKK4GMOzoYgnpQJKcxwQw+lGaBvvlMo0qN/Q==", + "version": "20.2.4", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.2.4.tgz", + "integrity": "sha512-ni5f8Xlf4PwnT/Z3f0HURc3ZSw8UyrqMqmM3L5ysa7VjHu8c3FOmIo1nKCcLrV/OAmtf3N4kFna/aJqxsfEtnA==", "dev": true } } @@ -20830,11 +20050,18 @@ "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" }, + "proxy-middleware": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/proxy-middleware/-/proxy-middleware-0.15.0.tgz", + "integrity": "sha512-EGCG8SeoIRVMhsqHQUdDigB2i7qU7fCsWASwn54+nPutYO8n4q6EiwMzyfWlC+dzRFExP+kvcnDFdBDHoZBU7Q==", + "dev": true + }, "prr": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz", "integrity": "sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw==", - "dev": true + "dev": true, + "optional": true }, "ps-tree": { "version": "1.2.0", @@ -20929,9 +20156,9 @@ "integrity": "sha512-RofWgt/7fL5wP1Y7fxE7/EmTLzQVnB0ycyibJ0OOHIlJqTNzglYFxVwETOcIoJqJmpDXJ9xImDv+Fq34F/d4Dw==" }, "qs": { - "version": "6.11.1", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.1.tgz", - "integrity": "sha512-0wsrzgTz/kAVIeuxSjnpGC56rzYtr6JT/2BwEvMaPhFIoYa1aGO8LbzuU1R0uUYQkLpWBTOj0l/CLAJB64J6nQ==", + "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" } @@ -20976,14 +20203,14 @@ } }, "ramda": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/ramda/-/ramda-0.28.0.tgz", - "integrity": "sha512-9QnLuG/kPVgWvMQ4aODhsBUFKOUmnbUnsSXACv+NCQZcHbeb+v8Lodp8OVxtRULN1/xOyYLLaL6npE6dMq5QTA==" + "version": "0.29.0", + "resolved": "https://registry.npmjs.org/ramda/-/ramda-0.29.0.tgz", + "integrity": "sha512-BBea6L67bYLtdbOqfp8f58fPMqEwx0doL+pAi8TZyp2YWz8R9G8z9x75CZI8W+ftqhFHCpEX2cRnUUXK130iKA==" }, "ramda-adjunct": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/ramda-adjunct/-/ramda-adjunct-3.4.0.tgz", - "integrity": "sha512-qKRgqwZzJUZmPJfGK8/uLVxQXkiftKhW6FW9NUCUlQrzsBUZBvFAZUxwH7nTRwDMg+ChRU69rVVuS/4EUgtuIg==" + "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", @@ -21381,14 +20608,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": { @@ -21626,9 +20853,9 @@ "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.2", @@ -21641,66 +20868,6 @@ "supports-preserve-symlinks-flag": "^1.0.0" } }, - "resolve-as-bin": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/resolve-as-bin/-/resolve-as-bin-2.1.0.tgz", - "integrity": "sha512-ileUuPIOP+xj+GS/d/EbB2XqRA8T2IeZTFkMggNIW2Mo72VyBMbq+HvIAxdW0ED9D44aEzJwHvUtbMm2PJT5Kw==", - "dev": true, - "requires": { - "cross-spawn": "^6.0.5" - }, - "dependencies": { - "cross-spawn": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", - "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", - "dev": true, - "requires": { - "nice-try": "^1.0.4", - "path-key": "^2.0.1", - "semver": "^5.5.0", - "shebang-command": "^1.2.0", - "which": "^1.2.9" - } - }, - "path-key": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", - "integrity": "sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==", - "dev": true - }, - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "dev": true - }, - "shebang-command": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", - "integrity": "sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==", - "dev": true, - "requires": { - "shebang-regex": "^1.0.0" - } - }, - "shebang-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", - "integrity": "sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==", - "dev": true - }, - "which": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", - "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", - "dev": true, - "requires": { - "isexe": "^2.0.0" - } - } - } - }, "resolve-cwd": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", @@ -23007,12 +22174,6 @@ "readable-stream": "^2.0.2" } }, - "stream-buffers": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/stream-buffers/-/stream-buffers-1.0.1.tgz", - "integrity": "sha512-t+8bSU8qPq7NnWHWAvikjcZf+biErLZzD15RroYft1IKQwYbkRyiwppT7kNqwdtYLS59YPxc4sTSvwbLSMaodw==", - "dev": true - }, "stream-combiner": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/stream-combiner/-/stream-combiner-0.2.2.tgz", @@ -23068,59 +22229,6 @@ "readable-stream": "^2.0.2" } }, - "streamroller": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/streamroller/-/streamroller-0.4.1.tgz", - "integrity": "sha512-w0GGkMlWOiIBIYTmOWHTWKy9Y5hKxGKpQ5WpiHqwhvoSoMHXNTITrk6ZsR3fdgz3Bi/c+CXVHwmfPUQFkEPL+A==", - "dev": true, - "requires": { - "date-format": "^0.0.0", - "debug": "^0.7.2", - "mkdirp": "^0.5.1", - "readable-stream": "^1.1.7" - }, - "dependencies": { - "debug": { - "version": "0.7.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-0.7.4.tgz", - "integrity": "sha512-EohAb3+DSHSGx8carOSKJe8G0ayV5/i609OD0J2orCkuyae7SyZSz2aoLmQF2s0Pj5gITDebwPH7GFBlqOUQ1Q==", - "dev": true - }, - "isarray": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", - "integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==", - "dev": true - }, - "mkdirp": { - "version": "0.5.6", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", - "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", - "dev": true, - "requires": { - "minimist": "^1.2.6" - } - }, - "readable-stream": { - "version": "1.1.14", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", - "integrity": "sha512-+MeVjFf4L44XUkhM1eYbD8fyEsxcV81pqMSR5gblfcLCHfZvbrqy4/qYHE+/R5HoBUT11WV5O08Cr1n3YXkWVQ==", - "dev": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.1", - "isarray": "0.0.1", - "string_decoder": "~0.10.x" - } - }, - "string_decoder": { - "version": "0.10.31", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", - "integrity": "sha512-ev2QzSzWPYmy9GuqfIVildA4OdcGLeFZQrq5ys6RtiuF+RQQiZWr8TZNyAcuVXyQRYfEO+MsoB/1BuQVhOJuoQ==", - "dev": true - } - } - }, "string-argv": { "version": "0.0.2", "resolved": "https://registry.npmjs.org/string-argv/-/string-argv-0.0.2.tgz", @@ -23660,15 +22768,15 @@ "dev": true }, "swagger-client": { - "version": "3.19.6", - "resolved": "https://registry.npmjs.org/swagger-client/-/swagger-client-3.19.6.tgz", - "integrity": "sha512-fd0XaoKz3lgs6viKkqK+o8QyrOOZULD4tLcUd8wEfsVBjJIAks2Qa1AhGUr87mfCWZw0Z9OXItWF9T477rRXzw==", + "version": "3.19.8", + "resolved": "https://registry.npmjs.org/swagger-client/-/swagger-client-3.19.8.tgz", + "integrity": "sha512-+zfLp+1U6kOK+o9QyYDWMXxMYGKiOK20LFjNDtZsVzscbg1S3REKW5BaH9zioWKtK1x2zZWeiqnhNKDL8WgLTQ==", "requires": { "@babel/runtime-corejs3": "^7.20.13", - "@swagger-api/apidom-core": ">=0.69.2 <1.0.0", - "@swagger-api/apidom-json-pointer": ">=0.69.2 <1.0.0", - "@swagger-api/apidom-ns-openapi-3-1": ">=0.69.2 <1.0.0", - "@swagger-api/apidom-reference": ">=0.69.2 <1.0.0", + "@swagger-api/apidom-core": ">=0.70.0 <1.0.0", + "@swagger-api/apidom-json-pointer": ">=0.70.0 <1.0.0", + "@swagger-api/apidom-ns-openapi-3-1": ">=0.70.0 <1.0.0", + "@swagger-api/apidom-reference": ">=0.70.0 <1.0.0", "cookie": "~0.5.0", "cross-fetch": "^3.1.5", "deepmerge": "~4.3.0", @@ -23803,14 +22911,14 @@ "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" @@ -23823,9 +22931,9 @@ "dev": true }, "minipass": { - "version": "4.2.7", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-4.2.7.tgz", - "integrity": "sha512-ScVIgqHcXRMyfflqHmEW0bm8z8rb5McHyOY3ewX9JBgZaR77G7nxq9L/mtV96/QbAAwtbCAHVVLzD1kkyfFQEw==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", + "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", "dev": true }, "yallist": { @@ -24019,12 +23127,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", @@ -24146,12 +23248,6 @@ "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" }, - "tracelib": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/tracelib/-/tracelib-1.0.1.tgz", - "integrity": "sha512-T2Vkpa/7Vdm3sV8nXRn8vZ0tnq6wlnO4Zx7Pux+JA1W6DMlg5EtbNcPZu/L7XRTPc9S0eAKhEFR4p/u0GcsDpQ==", - "dev": true - }, "transifex-i18ntool": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/transifex-i18ntool/-/transifex-i18ntool-1.1.0.tgz", @@ -24243,9 +23339,9 @@ } }, "semver": { - "version": "7.4.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.4.0.tgz", - "integrity": "sha512-RgOxM8Mw+7Zus0+zcLEUn8+JfoLpj/huFTItQy2hsM4khuC1HYRDp0cU482Ewn/Fcy6bCjufD8vAj7voC66KQw==", + "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" @@ -24259,58 +23355,6 @@ } } }, - "ts-loader": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-6.2.1.tgz", - "integrity": "sha512-Dd9FekWuABGgjE1g0TlQJ+4dFUfYGbYcs52/HQObE0ZmUNjQlmLAS7xXsSzy23AMaMwipsx5sNHvoEpT2CZq1g==", - "dev": true, - "requires": { - "chalk": "^2.3.0", - "enhanced-resolve": "^4.0.0", - "loader-utils": "^1.0.2", - "micromatch": "^4.0.0", - "semver": "^6.0.0" - }, - "dependencies": { - "enhanced-resolve": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-4.5.0.tgz", - "integrity": "sha512-Nv9m36S/vxpsI+Hc4/ZGRs0n9mXqSWGGq49zxb/cJfPAQMbUtttJAlNPS4AQzaBdw/pKskw5bMbekT/Y7W/Wlg==", - "dev": true, - "requires": { - "graceful-fs": "^4.1.2", - "memory-fs": "^0.5.0", - "tapable": "^1.0.0" - } - }, - "json5": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", - "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", - "dev": true, - "requires": { - "minimist": "^1.2.0" - } - }, - "loader-utils": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.2.tgz", - "integrity": "sha512-I5d00Pd/jwMD2QCduo657+YM/6L3KZu++pmX9VFncxaxvHcru9jx1lBaFft+r4Mt2jK0Yhp41XlRAihzPxHNCg==", - "dev": true, - "requires": { - "big.js": "^5.2.2", - "emojis-list": "^3.0.0", - "json5": "^1.0.1" - } - }, - "tapable": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-1.1.3.tgz", - "integrity": "sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA==", - "dev": true - } - } - }, "ts-morph": { "version": "13.0.3", "resolved": "https://registry.npmjs.org/ts-morph/-/ts-morph-13.0.3.tgz", @@ -24343,9 +23387,9 @@ } }, "ts-toolbelt": { - "version": "6.15.5", - "resolved": "https://registry.npmjs.org/ts-toolbelt/-/ts-toolbelt-6.15.5.tgz", - "integrity": "sha512-FZIXf1ksVyLcfr7M317jbB67XFJhOO1YqdTcuGaq9q5jLUoTikukZ+98TPjKiP2jC5CgmYdWWYs0s2nLSU0/1A==" + "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.2.0", @@ -24474,6 +23518,14 @@ "is-typedarray": "^1.0.0" } }, + "types-ramda": { + "version": "0.29.3", + "resolved": "https://registry.npmjs.org/types-ramda/-/types-ramda-0.29.3.tgz", + "integrity": "sha512-6z8/UCI5/kRorQ91Mo+TUXImHpGAhmhg8ZIdT/tNrG+xSQvDwJXYyT5Nlw2U5VpoKetUZVqQXYYLQyq5Bzccsg==", + "requires": { + "ts-toolbelt": "^9.6.0" + } + }, "typescript": { "version": "4.6.4", "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.6.4.tgz", @@ -24532,12 +23584,6 @@ } } }, - "underscore": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.7.0.tgz", - "integrity": "sha512-cp0oQQyZhUM1kpJDLdGO1jPZHgS/MpzoWYfe9+CM2h/QGDZlqwT2T3YGukuBdaNJ/CAPoeyAZRRHz8JFo176vA==", - "dev": true - }, "unicode-canonical-property-names-ecmascript": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz", @@ -24746,9 +23792,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" @@ -25480,9 +24526,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": { @@ -25568,12 +24614,6 @@ "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==", "dev": true }, - "workerpool": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.2.0.tgz", - "integrity": "sha512-Rsk5qQHJ9eowMH28Jwhe8HEbmdYDX4lwoMWshiCXugjtHqMD9ZbiqSDLxcsfdqsETPzVUtX5s1Z5kStiIM6l4A==", - "dev": true - }, "wrap-ansi": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", @@ -25702,9 +24742,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", @@ -25720,32 +24760,6 @@ "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==" }, - "yargs-unparser": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz", - "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==", - "dev": true, - "requires": { - "camelcase": "^6.0.0", - "decamelize": "^4.0.0", - "flat": "^5.0.2", - "is-plain-obj": "^2.1.0" - }, - "dependencies": { - "camelcase": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", - "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", - "dev": true - }, - "is-plain-obj": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", - "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", - "dev": true - } - } - }, "yauzl": { "version": "2.10.0", "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", diff --git a/src/pybind/mgr/dashboard/frontend/package.json b/src/pybind/mgr/dashboard/frontend/package.json index de08d364a122..e55cb39bc043 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 e01d3480ee07..dab14fd5842d 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 @@