diff --git a/README.md b/README.md index c60d437e35..c82108501f 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,7 @@ Developer documentation and documentation for our UI components is available her Rancher Dashboard supports an extension mechanism that allows developers to independently provide additional functionality to Rancher. You can learn more from our [Rancher Extensions Docs](https://rancher.github.io/dashboard/extensions/introduction). + # What is it? Rancher Dashboard provides a sophisticated UI for managing Kubernetes clusters and Workloads. diff --git a/cypress/e2e/po/pages/explorer/config-map.po.ts b/cypress/e2e/po/pages/explorer/config-map.po.ts index 93833d0a36..ae0bda5f06 100644 --- a/cypress/e2e/po/pages/explorer/config-map.po.ts +++ b/cypress/e2e/po/pages/explorer/config-map.po.ts @@ -1,5 +1,7 @@ import PagePo from '@/cypress/e2e/po/pages/page.po'; import BaseResourceList from '@/cypress/e2e/po/lists/base-resource-list.po'; +import BurgerMenuPo from '@/cypress/e2e/po/side-bars/burger-side-menu.po'; +import ProductNavPo from '@/cypress/e2e/po/side-bars/product-side-nav.po'; export class ConfigMapPagePo extends PagePo { private static createPath(clusterId: string) { @@ -10,19 +12,33 @@ export class ConfigMapPagePo extends PagePo { return super.goTo(ConfigMapPagePo.createPath(clusterId)); } + static navTo(clusterId = 'local') { + const burgerMenu = new BurgerMenuPo(); + const sideNav = new ProductNavPo(); + + BurgerMenuPo.toggle(); + burgerMenu.clusters().contains(clusterId).click(); + sideNav.navToSideMenuGroupByLabel('Storage'); + sideNav.navToSideMenuEntryByLabel('ConfigMaps'); + } + constructor(clusterId = 'local') { super(ConfigMapPagePo.createPath(clusterId)); } - clickCreate() { - const baseResourceList = new BaseResourceList(this.self()); + list() { + return new BaseResourceList(this.self()); + } - return baseResourceList.masthead().actions().eq(0).click(); + clickCreate() { + return this.list().masthead().create(); } listElementWithName(name:string) { - const baseResourceList = new BaseResourceList(this.self()); + return this.list().resourceTable().sortableTable().rowElementWithName(name); + } - return baseResourceList.resourceTable().sortableTable().rowElementWithName(name); + searchForConfigMap(name: string) { + return this.list().resourceTable().sortableTable().filter(name); } } diff --git a/cypress/e2e/tests/pages/explorer/api/api-services.spec.ts b/cypress/e2e/tests/pages/explorer/api/api-services.spec.ts index a975316c4a..4bff4f2107 100644 --- a/cypress/e2e/tests/pages/explorer/api/api-services.spec.ts +++ b/cypress/e2e/tests/pages/explorer/api/api-services.spec.ts @@ -1,20 +1,17 @@ import { APIServicesPagePo } from '@/cypress/e2e/po/pages/explorer/api-services.po'; +const apiServicesPage = new APIServicesPagePo('local'); + describe('Cluster Explorer', { tags: ['@explorer', '@adminUser'] }, () => { beforeEach(() => { cy.login(); }); describe('API: APIServices', () => { - let apiServicesPage: APIServicesPagePo; - - beforeEach(() => { - apiServicesPage = new APIServicesPagePo('local'); + it('Should be able to use shift+j to select rows and the count of selected is correct', () => { apiServicesPage.goTo(); apiServicesPage.waitForRequests(); - }); - it('Should be able to use shift+j to select rows and the count of selected is correct', () => { apiServicesPage.title().should('contain', 'APIServices'); const sortableTable = apiServicesPage.sortableTable(); diff --git a/cypress/e2e/tests/pages/explorer/node-list.spec.ts b/cypress/e2e/tests/pages/explorer/node-list.spec.ts index 624fd400e3..02c4e92446 100644 --- a/cypress/e2e/tests/pages/explorer/node-list.spec.ts +++ b/cypress/e2e/tests/pages/explorer/node-list.spec.ts @@ -15,7 +15,7 @@ describe('Nodes list', { tags: ['@explorer', '@adminUser'], testIsolation: 'off' after(() => { // Ensure we delete the dummy node - cy.deleteRancherResource('v1', 'nodes', 'bigip1'); + cy.deleteRancherResource('v1', 'nodes', dummyNode.metadata.name); }); it('should show the nodes list page', () => { @@ -48,7 +48,7 @@ describe('Nodes list', { tags: ['@explorer', '@adminUser'], testIsolation: 'off' // Check the node names nodeList.sortableTable().rowNames().should((names: any) => { expect(names).to.have.length(count); - expect(names).to.contain('bigip1'); + expect(names).to.contain(dummyNode.metadata.name); }); }); diff --git a/cypress/e2e/tests/pages/explorer/storage/configmap.spec.ts b/cypress/e2e/tests/pages/explorer/storage/configmap.spec.ts index 525e90dac7..9b0bbc5524 100644 --- a/cypress/e2e/tests/pages/explorer/storage/configmap.spec.ts +++ b/cypress/e2e/tests/pages/explorer/storage/configmap.spec.ts @@ -1,14 +1,14 @@ import { ConfigMapPagePo } from '@/cypress/e2e/po/pages/explorer/config-map.po'; import ConfigMapPo from '@/cypress/e2e/po/components/storage/config-map.po'; -describe('ConfigMap', { tags: ['@explorer', '@adminUser'] }, () => { +const configMapPage = new ConfigMapPagePo('local'); + +describe('ConfigMap', { testIsolation: 'off', tags: ['@explorer', '@adminUser'] }, () => { beforeEach(() => { cy.login(); }); it('has the correct title', () => { - const configMapPage = new ConfigMapPagePo('local'); - configMapPage.goTo(); cy.title().should('eq', 'Rancher - local - ConfigMaps'); @@ -27,9 +27,7 @@ skipGeometric=true`; // Visit the main menu and select the 'local' cluster // Navigate to Service Discovery => ConfigMaps - const configMapPage = new ConfigMapPagePo('local'); - - configMapPage.goTo(); + ConfigMapPagePo.navTo(); // Click on Create configMapPage.clickCreate(); @@ -40,7 +38,9 @@ skipGeometric=true`; // Enter ConfigMap description const configMapPo = new ConfigMapPo(); - configMapPo.nameInput().set('custom-config-map'); + const configMapName = 'custom-config-map'; + + configMapPo.nameInput().set(configMapName); configMapPo.keyInput().set('managerApiConfiguration.properties'); configMapPo.valueInput().set(expectedValue); configMapPo.descriptionInput().set('Custom Config Map Description'); @@ -49,14 +49,12 @@ skipGeometric=true`; configMapPo.saveCreateForm().click(); // Check if the ConfigMap is created successfully - configMapPage.listElementWithName('custom-config-map').should('exist'); + configMapPage.waitForPage(); + configMapPage.searchForConfigMap(configMapName); + configMapPage.listElementWithName(configMapName).should('exist'); // Navigate back to the edit page - configMapPage.listElementWithName('custom-config-map') - .find(`button[data-testid="sortable-table-0-action-button"]`) - .click() - .get(`li[data-testid="action-menu-0-item"]`) - .click(); + configMapPage.list().actionMenu(configMapName).getMenuItem('Edit Config').click(); // Assert the current value yaml dumps will append a newline at the end configMapPo.valueInput().value().should('eq', `${ expectedValue }\n`); diff --git a/cypress/e2e/tests/pages/generic/home.spec.ts b/cypress/e2e/tests/pages/generic/home.spec.ts index d5e60d0a7b..6483175222 100644 --- a/cypress/e2e/tests/pages/generic/home.spec.ts +++ b/cypress/e2e/tests/pages/generic/home.spec.ts @@ -8,7 +8,7 @@ import { RANCHER_PAGE_EXCEPTIONS, catchTargetPageException } from '~/cypress/sup const homePage = new HomePagePo(); const homeClusterList = homePage.list(); const provClusterList = new ClusterManagerListPagePo('local'); -const longClusterDescription = 'this-is-some-really-really-really-really-really-really-long-decription'; +const longClusterDescription = 'this-is-some-really-really-really-really-really-really-long-description'; const rowDetails = (text) => text.split('\n').map((r) => r.trim()).filter((f) => f); diff --git a/shell/components/ResourceList/index.vue b/shell/components/ResourceList/index.vue index b3078a962e..c248037a13 100644 --- a/shell/components/ResourceList/index.vue +++ b/shell/components/ResourceList/index.vue @@ -172,7 +172,7 @@ export default { * This covers case 1 */ pagination(neu, old) { - if (neu && !this.componentWillFetch && this.paginationEqual(neu, old)) { + if (neu && !this.componentWillFetch && !this.paginationEqual(neu, old)) { this.$fetchType(this.resource); } }, diff --git a/shell/components/SortableTable/index.vue b/shell/components/SortableTable/index.vue index 423999381c..034777ed10 100644 --- a/shell/components/SortableTable/index.vue +++ b/shell/components/SortableTable/index.vue @@ -29,21 +29,6 @@ import { getParent } from '@shell/utils/dom'; // NOTE: This is populated by a plugin (formatters.js) to avoid issues with plugins export const FORMATTERS = {}; -export const COLUMN_BREAKPOINTS = { - /** - * Only show column if at tablet width or wider - */ - TABLET: 'tablet', - /** - * Only show column if at laptop width or wider - */ - LAPTOP: 'laptop', - /** - * Only show column if at desktop width or wider - */ - DESKTOP: 'desktop' -}; - // @TODO: // Fixed header/scrolling diff --git a/shell/config/product/explorer.js b/shell/config/product/explorer.js index e0fb117825..cdd9c4ecf7 100644 --- a/shell/config/product/explorer.js +++ b/shell/config/product/explorer.js @@ -19,7 +19,7 @@ import { STORAGE_CLASS_PROVISIONER, PERSISTENT_VOLUME_SOURCE, HPA_REFERENCE, MIN_REPLICA, MAX_REPLICA, CURRENT_REPLICA, ACCESS_KEY, DESCRIPTION, EXPIRES, EXPIRY_STATE, SUB_TYPE, AGE_NORMAN, SCOPE_NORMAN, PERSISTENT_VOLUME_CLAIM, RECLAIM_POLICY, PV_REASON, WORKLOAD_HEALTH_SCALE, POD_RESTARTS, - DURATION, MESSAGE, REASON, LAST_SEEN_TIME, EVENT_TYPE, OBJECT, ROLE, SECRET_DATA + DURATION, MESSAGE, REASON, LAST_SEEN_TIME, EVENT_TYPE, OBJECT, ROLE, ROLES, VERSION, INTERNAL_EXTERNAL_IP, KUBE_NODE_OS, CPU, RAM, SECRET_DATA } from '@shell/config/table-headers'; import { DSL } from '@shell/store/type-map'; @@ -27,6 +27,8 @@ import { STEVE_AGE_COL, STEVE_LIST_GROUPS, STEVE_NAMESPACE_COL, STEVE_NAME_COL, STEVE_STATE_COL } from '@shell/config/pagination-table-headers'; +import { COLUMN_BREAKPOINTS } from '@shell/types/store/type-map'; + export const NAME = 'explorer'; export function init(store) { @@ -233,8 +235,11 @@ export function init(store) { value: 'metadata.fields.1', sort: 'metadata.fields.1', search: 'metadata.fields.1', + }, { + ...SECRET_DATA, + sort: false, + search: false, }, - SECRET_DATA, STEVE_AGE_COL ]); @@ -267,6 +272,66 @@ export function init(store) { STEVE_AGE_COL ]); + headers(NODE, + [ + STATE, + NAME_COL, + ROLES, + VERSION, + INTERNAL_EXTERNAL_IP, + { + ...KUBE_NODE_OS, + breakpoint: COLUMN_BREAKPOINTS.LAPTOP, + getValue: (row) => row.status?.nodeInfo?.operatingSystem + }, + { + ...CPU, + breakpoint: COLUMN_BREAKPOINTS.LAPTOP, + getValue: (row) => row.cpuUsagePercentage + }, { + ...RAM, + breakpoint: COLUMN_BREAKPOINTS.LAPTOP, + getValue: (row) => row.ramUsagePercentage + }, + AGE + ], + [ + STEVE_STATE_COL, + STEVE_NAME_COL, + { + ...ROLES, + sort: false, + search: false + }, + { + ...VERSION, + value: 'status.nodeInfo.kubeletVersion', + getValue: undefined, + sort: ['status.nodeInfo.kubeletVersion'], + search: 'status.nodeInfo.kubeletVersion' + }, { + ...INTERNAL_EXTERNAL_IP, + sort: false, + search: false, + }, { + ...KUBE_NODE_OS, + breakpoint: COLUMN_BREAKPOINTS.LAPTOP, + getValue: undefined + }, { + ...CPU, + breakpoint: COLUMN_BREAKPOINTS.LAPTOP, + getValue: (row) => row.cpuUsagePercentage, + sort: false, + search: false, + }, { + ...RAM, + breakpoint: COLUMN_BREAKPOINTS.LAPTOP, + sort: false, + search: false, + }, + STEVE_AGE_COL + ]); + headers(MANAGEMENT.PSA, [STATE, NAME_COL, { ...DESCRIPTION, width: undefined diff --git a/shell/config/table-headers.js b/shell/config/table-headers.js index 7aa3531abc..98df8540b1 100644 --- a/shell/config/table-headers.js +++ b/shell/config/table-headers.js @@ -1,6 +1,6 @@ import { CATTLE_PUBLIC_ENDPOINTS } from '@shell/config/labels-annotations'; import { NODE as NODE_TYPE } from '@shell/config/types'; -import { COLUMN_BREAKPOINTS } from '@shell/components/SortableTable/index.vue'; +import { COLUMN_BREAKPOINTS } from '@shell/types/store/type-map'; // Note: 'id' is always the last sort, so you don't have to specify it here. diff --git a/shell/list/node.vue b/shell/list/node.vue index 84c25fd388..4b4d96e0fb 100644 --- a/shell/list/node.vue +++ b/shell/list/node.vue @@ -1,21 +1,28 @@ -