diff --git a/cypress/e2e/po/pages/explorer/cluster-dashboard.po.ts b/cypress/e2e/po/pages/explorer/cluster-dashboard.po.ts index bb8770e6564..92ba3571c74 100644 --- a/cypress/e2e/po/pages/explorer/cluster-dashboard.po.ts +++ b/cypress/e2e/po/pages/explorer/cluster-dashboard.po.ts @@ -63,4 +63,20 @@ export default class ClusterDashboardPagePo extends PagePo { clusterActionsHeader() { return new HeaderPo(); } + + fleetStatus() { + return cy.get('[data-testid="k8s-service-fleet"]'); + } + + etcdStatus() { + return cy.get('[data-testid="k8s-service-etcd"]'); + } + + schedulerStatus() { + return cy.get('[data-testid="k8s-service-scheduler"]'); + } + + controllerManagerStatus() { + return cy.get('[data-testid="k8s-service-controller-manager"]'); + } } diff --git a/cypress/e2e/tests/pages/explorer/dashboard/cluster-dashboard.spec.ts b/cypress/e2e/tests/pages/explorer/dashboard/cluster-dashboard.spec.ts index ea24cb0888a..97cf7cf1b5b 100644 --- a/cypress/e2e/tests/pages/explorer/dashboard/cluster-dashboard.spec.ts +++ b/cypress/e2e/tests/pages/explorer/dashboard/cluster-dashboard.spec.ts @@ -62,6 +62,12 @@ describe('Cluster Dashboard', { testIsolation: 'off', tags: ['@explorer', '@admi cy.title().should('eq', 'Rancher - local - Cluster Dashboard'); }); + it('shows fleet controller status', () => { + ClusterDashboardPagePo.navTo(); + clusterDashboard.waitForPage(); + clusterDashboard.fleetStatus().should('exist'); + }); + it('can import a YAML successfully, using the header action "Import YAML"', () => { ClusterDashboardPagePo.navTo(); @@ -284,6 +290,73 @@ describe('Cluster Dashboard', { testIsolation: 'off', tags: ['@explorer', '@admi }); }); + describe('Cluster dashboard with limited permissions', () => { + let stdProjectName; + let stdNsName; + let stdUsername; + + beforeEach(() => { + stdProjectName = `standard-user-project${ +new Date() }`; + stdNsName = `standard-user-ns${ +new Date() }`; + stdUsername = `standard-user-${ +new Date() }`; + // log in as admin + cy.login(); + cy.getRancherResource('v3', 'users?me=true').then((resp: Cypress.Response) => { + const adminUserId = resp.body.data[0].id.trim(); + + // create project + cy.createProject(stdProjectName, 'local', adminUserId).then((resp: Cypress.Response) => { + cy.wrap(resp.body.id.trim()).as('standardUserProject'); + + // create ns in project + cy.get('@standardUserProject').then((projId) => { + cy.createNamespaceInProject(stdNsName, projId); + + // create std user and assign to project + cy.createUser({ + username: stdUsername, + globalRole: { role: 'user' }, + projectRole: { + clusterId: 'local', projectName: stdProjectName, role: 'project-owner' + } + }).as('createUserRequest'); + }); + }); + }); + // log in as new standard user + cy.login(stdUsername, Cypress.env('password'), false); + + // go to cluster dashboard + ClusterDashboardPagePo.navTo(); + clusterDashboard.waitForPage(); + }); + + // note - this would be 'fleet agent' on downstream clusters + it('does not show fleet controller status if the user does not have permission to view the fleet controller deployment', () => { + clusterDashboard.fleetStatus().should('not.exist'); + + clusterDashboard.etcdStatus().should('exist'); + clusterDashboard.schedulerStatus().should('exist'); + clusterDashboard.controllerManagerStatus().should('exist'); + }); + + // log back in as admin and delete the project, ns, and user from previous test + afterEach(() => { + cy.login(); + cy.deleteRancherResource('v1', 'namespaces', stdNsName); + + cy.get('@standardUserProject').then((projectId) => { + cy.deleteRancherResource('v3', 'projects', projectId); + }); + + cy.get('@createUserRequest').then((req) => { + const userId = req.body.userPrincipalId.split('//').pop(); + + cy.deleteRancherResource('v3', 'users', userId); + }); + }); + }); + after(function() { if (removePod) { cy.deleteRancherResource('v1', `pods/${ nsName }`, `pod-${ podName }`); diff --git a/shell/pages/c/_cluster/explorer/__tests__/index.test.ts b/shell/pages/c/_cluster/explorer/__tests__/index.test.ts index 733527246ce..86849df86d8 100644 --- a/shell/pages/c/_cluster/explorer/__tests__/index.test.ts +++ b/shell/pages/c/_cluster/explorer/__tests__/index.test.ts @@ -25,9 +25,12 @@ describe('page: cluster dashboard', () => { 'cluster/inError': () => false, 'cluster/schemaFor': jest.fn(), 'cluster/canList': jest.fn(), - 'cluster/all': jest.fn(), - 'i18n/exists': jest.fn(), - 'i18n/t': (label: string) => label === 'generic.provisioning' ? '—' : jest.fn()(), + 'cluster/byId': () => { + return {}; + }, + 'cluster/all': jest.fn(), + 'i18n/exists': jest.fn(), + 'i18n/t': (label: string) => label === 'generic.provisioning' ? '—' : jest.fn()(), } } }, diff --git a/shell/pages/c/_cluster/explorer/index.vue b/shell/pages/c/_cluster/explorer/index.vue index 37af4248bf4..6aca6c700d2 100644 --- a/shell/pages/c/_cluster/explorer/index.vue +++ b/shell/pages/c/_cluster/explorer/index.vue @@ -115,12 +115,6 @@ export default { if (this.currentCluster.isLocal && this.$store.getters['management/schemaFor'](MANAGEMENT.NODE)) { this.$store.dispatch('management/findAll', { type: MANAGEMENT.NODE }); } - - this.canViewAgents = this.$store.getters['cluster/canList'](WORKLOAD_TYPES.DEPLOYMENT) && this.$store.getters['cluster/canList'](WORKLOAD_TYPES.STATEFUL_SET); - - if (this.canViewAgents) { - this.loadAgents(); - } } }, @@ -138,7 +132,6 @@ export default { cattleDeployment: 'loading', fleetDeployment: 'loading', fleetStatefulSet: 'loading', - canViewAgents: false, disconnected: false, events: [], nodeMetrics: [], @@ -172,6 +165,17 @@ export default { clearInterval(this.interval); }, + watch: { + canViewAgents: { + handler(neu, old) { + if (neu && !old) { + this.loadAgents(); + } + }, + immediate: true + } + }, + computed: { ...mapGetters(['currentCluster']), ...monitoringStatus(), @@ -184,6 +188,22 @@ export default { return this.$store.getters['management/all'](MANAGEMENT.CLUSTER); }, + fleetAgentNamespace() { + return this.$store.getters['cluster/byId'](NAMESPACE, 'cattle-fleet-system'); + }, + + cattleAgentNamespace() { + if (this.currentCluster.isLocal) { + return; + } + + return this.$store.getters['cluster/byId'](NAMESPACE, 'cattle-system'); + }, + + canViewAgents() { + return !!this.fleetAgentNamespace || (!this.currentCluster.isLocal && this.cattleAgentNamespace); + }, + showClusterTools() { return this.$store.getters['cluster/canList'](CATALOG.CLUSTER_REPO) && this.$store.getters['cluster/canList'](CATALOG.APP); @@ -268,15 +288,15 @@ export default { }); }); - if (this.canViewAgents) { - if (!this.currentCluster.isLocal) { - services.push({ - name: 'cattle', - status: this.cattleStatus, - labelKey: 'clusterIndexPage.sections.componentStatus.cattle', - }); - } + if (this.cattleAgentNamespace) { + services.push({ + name: 'cattle', + status: this.cattleStatus, + labelKey: 'clusterIndexPage.sections.componentStatus.cattle', + }); + } + if (this.fleetAgentNamespace) { services.push({ name: 'fleet', status: this.fleetStatus, @@ -485,14 +505,16 @@ export default { methods: { loadAgents() { - if (this.currentCluster.isLocal) { - this.setAgentResource('fleetDeployment', WORKLOAD_TYPES.DEPLOYMENT, 'cattle-fleet-system/fleet-controller'); - this.setAgentResource('fleetStatefulSet', WORKLOAD_TYPES.STATEFUL_SET, 'cattle-fleet-local-system/fleet-agent'); - } else { - this.setAgentResource('fleetStatefulSet', WORKLOAD_TYPES.STATEFUL_SET, 'cattle-fleet-system/fleet-agent'); + if (this.fleetAgentNamespace) { + if (this.currentCluster.isLocal) { + this.setAgentResource('fleetDeployment', WORKLOAD_TYPES.DEPLOYMENT, 'cattle-fleet-system/fleet-controller'); + this.setAgentResource('fleetStatefulSet', WORKLOAD_TYPES.STATEFUL_SET, 'cattle-fleet-local-system/fleet-agent'); + } else { + this.setAgentResource('fleetStatefulSet', WORKLOAD_TYPES.STATEFUL_SET, 'cattle-fleet-system/fleet-agent'); + } + } + if (this.cattleAgentNamespace) { this.setAgentResource('cattleDeployment', WORKLOAD_TYPES.DEPLOYMENT, 'cattle-system/cattle-cluster-agent'); - - // Scaling Up/Down cattle deployment causes web sockets disconnection; this.interval = setInterval(() => { this.disconnected = !!this.$store.getters['cluster/inError']({ type: NODE }); }, 1000);