From: Devika Babrekar Date: Mon, 22 Jun 2026 14:53:17 +0000 (+0530) Subject: mgr/dashboard: Disable multisite Action dropdown for read-only user X-Git-Url: http://git-server-git.apps.pok.os.sepia.ceph.com/?a=commitdiff_plain;h=bed41f0cb17ad8d824a2dbc0402892c3fe93e482;p=ceph.git mgr/dashboard: Disable multisite Action dropdown for read-only user Fixes: https://tracker.ceph.com/issues/77570 Signed-off-by: Devika Babrekar Conflicts: src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-multisite-details/rgw-multisite-details.component.html - added disabled property on table component to disable the table src/pybind/mgr/dashboard/frontend/src/app/shared/datatable/table-actions/table-actions.component.html - added disabled property on table component to disable the table --- diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-multisite-details/rgw-multisite-details.component.html b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-multisite-details/rgw-multisite-details.component.html index 448acead01e..d95986556de 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-multisite-details/rgw-multisite-details.component.html +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-multisite-details/rgw-multisite-details.component.html @@ -53,6 +53,7 @@ dropDownOnly="Actions" [dropDownOnlyBtnColor]="'tertiary'" [dropDownOnlyOffset]="{ x: 45, y: 0 }" + [disabled]="disableActions" > diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-multisite-details/rgw-multisite-details.component.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-multisite-details/rgw-multisite-details.component.spec.ts index ed2b838eefb..527b14c1e10 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-multisite-details/rgw-multisite-details.component.spec.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-multisite-details/rgw-multisite-details.component.spec.ts @@ -1,6 +1,7 @@ import { HttpClientTestingModule } from '@angular/common/http/testing'; import { DebugElement } from '@angular/core'; import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { By } from '@angular/platform-browser'; import { SharedModule } from '~/app/shared/shared.module'; @@ -10,6 +11,11 @@ import { configureTestBed } from '~/testing/unit-test-helper'; import { NgbNavModule, NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'; import { ModalCdsService } from '~/app/shared/services/modal-cds.service'; import { RgwMultisiteRealmFormComponent } from '../rgw-multisite-realm-form/rgw-multisite-realm-form.component'; +import { TableActionsComponent } from '~/app/shared/datatable/table-actions/table-actions.component'; +import { Icons } from '~/app/shared/enum/icons.enum'; +import { CdTableSelection } from '~/app/shared/models/cd-table-selection'; +import { Permission, Permissions } from '~/app/shared/models/permissions'; +import { AuthStorageService } from '~/app/shared/services/auth-storage.service'; describe('RgwMultisiteDetailsComponent', () => { let component: RgwMultisiteDetailsComponent; @@ -56,4 +62,94 @@ describe('RgwMultisiteDetailsComponent', () => { multisiteInfo: component.multisiteInfo }); }); + + describe('disableActions', () => { + it('should return true when rgw.create is false', () => { + component.permissions = new Permissions({ rgw: ['read'] }); + expect(component.disableActions).toBe(true); + }); + + it('should return false when rgw.create is true', () => { + component.permissions = new Permissions({ rgw: ['read', 'create'] }); + expect(component.disableActions).toBe(false); + }); + }); + + describe('multisite create table actions', () => { + const createFixture = (rgwPermissions: string[]) => { + spyOn(TestBed.inject(AuthStorageService), 'getPermissions').and.returnValue( + new Permissions({ rgw: rgwPermissions, 'config-opt': ['read'] }) + ); + const testFixture = TestBed.createComponent(RgwMultisiteDetailsComponent); + testFixture.detectChanges(); + return testFixture; + }; + + const getMultisiteTableActions = ( + testFixture: ComponentFixture + ): TableActionsComponent => + testFixture.debugElement.query(By.css('cd-table-actions.multisite-actions')) + .componentInstance; + + it('should pass disabled=true to TableActionsComponent when rgw.create is false', () => { + const testFixture = createFixture(['read']); + const tableActions = getMultisiteTableActions(testFixture); + const button = testFixture.debugElement.query( + By.css('cd-table-actions.multisite-actions button.cds--btn--tertiary') + ); + + expect(testFixture.componentInstance.disableActions).toBe(true); + expect(tableActions.disabled).toBe(true); + expect(button.nativeElement.disabled).toBe(true); + }); + + it('should pass disabled=false to TableActionsComponent when rgw.create is true', () => { + const testFixture = createFixture(['read', 'create']); + const tableActions = getMultisiteTableActions(testFixture); + const button = testFixture.debugElement.query( + By.css('cd-table-actions.multisite-actions button.cds--btn--tertiary') + ); + + expect(testFixture.componentInstance.disableActions).toBe(false); + expect(tableActions.disabled).toBe(false); + expect(button.nativeElement.disabled).toBe(false); + }); + }); + + describe('TableActionsComponent disabled input', () => { + const createTableActionsFixture = (disabled: boolean) => { + const tableActionsFixture = TestBed.createComponent(TableActionsComponent); + const tableActionsComponent = tableActionsFixture.componentInstance; + tableActionsComponent.permission = new Permission(['create']); + tableActionsComponent.dropDownOnly = 'Actions'; + tableActionsComponent.dropDownOnlyBtnColor = 'tertiary'; + tableActionsComponent.tableActions = [ + { permission: 'create', icon: Icons.add, name: 'Create Realm', click: () => undefined } + ]; + tableActionsComponent.selection = new CdTableSelection(); + tableActionsComponent.disabled = disabled; + tableActionsFixture.detectChanges(); + return tableActionsFixture; + }; + + const getDropdownTriggerButton = ( + tableActionsFixture: ComponentFixture + ) => tableActionsFixture.debugElement.query(By.css('button.cds--btn--tertiary')); + + it('should disable the trigger button when disabled is true', () => { + const tableActionsFixture = createTableActionsFixture(true); + const button = getDropdownTriggerButton(tableActionsFixture); + + expect(tableActionsFixture.componentInstance.disabled).toBe(true); + expect(button.nativeElement.disabled).toBe(true); + }); + + it('should enable the trigger button when disabled is false', () => { + const tableActionsFixture = createTableActionsFixture(false); + const button = getDropdownTriggerButton(tableActionsFixture); + + expect(tableActionsFixture.componentInstance.disabled).toBe(false); + expect(button.nativeElement.disabled).toBe(false); + }); + }); }); diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-multisite-details/rgw-multisite-details.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-multisite-details/rgw-multisite-details.component.ts index 0d0b56ef2d4..2b448298aa6 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-multisite-details/rgw-multisite-details.component.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-multisite-details/rgw-multisite-details.component.ts @@ -125,6 +125,10 @@ export class RgwMultisiteDetailsComponent extends CdForm implements OnDestroy, O MODULE_NAME = 'rgw'; NAVIGATE_TO = '/rgw/multisite'; + get disableActions(): boolean { + return !this.permissions?.rgw?.create; + } + constructor( private modalService: ModalService, private timerService: TimerService, diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/datatable/table-actions/table-actions.component.html b/src/pybind/mgr/dashboard/frontend/src/app/shared/datatable/table-actions/table-actions.component.html index 6021cfa1f5d..bfd6a5753c4 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/shared/datatable/table-actions/table-actions.component.html +++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/datatable/table-actions/table-actions.component.html @@ -94,6 +94,7 @@