diff --git a/src/pybind/mgr/dashboard/frontend/src/app/core/context/context.component.html b/src/pybind/mgr/dashboard/frontend/src/app/core/context/context.component.html index 9d7376310ed..4b13df82b1a 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/core/context/context.component.html +++ b/src/pybind/mgr/dashboard/frontend/src/app/core/context/context.component.html @@ -1,32 +1,70 @@ - - -
+ }; + as data +) { + @if ( + data.ftMap && data.ftMap.rgw && permissions.rgw.read && isRgwRoute && data.daemons.length > 1 + ) { +
Object gateway: -
+ + @if (!isRgwResourcePage) { +
+ +
+ @for (daemon of data.daemons; track daemon.id) { + + } +
+
+ } @else { -
- - - -
-
+ }
- - + } +} diff --git a/src/pybind/mgr/dashboard/frontend/src/app/core/context/context.component.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/core/context/context.component.spec.ts index 865e9b26f70..a8a9f8c8763 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/core/context/context.component.spec.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/core/context/context.component.spec.ts @@ -100,4 +100,36 @@ describe('ContextComponent', () => { expect(selectedDaemon.textContent).toEqual(' daemon3 zonegroup3'); component.ngOnDestroy(); })); + + it('should show a read-only selected daemon on RGW resource page', fakeAsync(() => { + component.isRgwRoute = true; + component.isRgwResourcePage = true; + fixture.detectChanges(); + tick(); + const req = httpTesting.expectOne('api/rgw/daemon'); + req.flush(daemonList); + fixture.detectChanges(); + + const selectedDaemon = fixture.debugElement.nativeElement.querySelector( + '.ctx-bar-selected-rgw-daemon' + ); + const availableDaemons = fixture.debugElement.nativeElement.querySelectorAll( + '.ctx-bar-available-rgw-daemon' + ); + + expect(selectedDaemon.disabled).toBe(true); + expect(availableDaemons.length).toEqual(0); + component.ngOnDestroy(); + })); + + it('should detect a RGW resource page from a hash route on reload', () => { + expect(component['getRoutePath']('http://localhost/#/rgw/accounts/test-account/overview')).toBe( + '/rgw/accounts/test-account/overview' + ); + expect( + component['isRgwAccountsResourcePage']( + component['getRoutePath']('http://localhost/#/rgw/accounts/test-account/overview') + ) + ).toBe(true); + }); }); diff --git a/src/pybind/mgr/dashboard/frontend/src/app/core/context/context.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/core/context/context.component.ts index 46e293d8d85..e9d3e277158 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/core/context/context.component.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/core/context/context.component.ts @@ -27,9 +27,11 @@ export class ContextComponent implements OnInit, OnDestroy { private rgwUserUrlPrefix = '/rgw/user'; private rgwBuckerUrlPrefix = '/rgw/bucket'; private rgwAccountsUrlPrefix = '/rgw/accounts'; + private rgwAccountsResourcePagePattern = /^\/rgw\/accounts\/[^/]+\/(overview|roles)(?:$|[?#])/; private rgwMultisiteSyncPolicyPrefix = '/rgw/multisite/sync-policy'; permissions: Permissions; featureToggleMap$: FeatureTogglesMap$; + isRgwResourcePage = this.isRgwAccountsResourcePage(this.getRoutePath(document.location.href)); isRgwRoute = document.location.href.includes(this.rgwUserUrlPrefix) || document.location.href.includes(this.rgwBuckerUrlPrefix) || @@ -51,15 +53,16 @@ export class ContextComponent implements OnInit, OnDestroy { this.subs.add( this.router.events .pipe(filter((event: Event) => event instanceof NavigationEnd)) - .subscribe( - () => - (this.isRgwRoute = [ - this.rgwBuckerUrlPrefix, - this.rgwUserUrlPrefix, - this.rgwAccountsUrlPrefix, - this.rgwMultisiteSyncPolicyPrefix - ].some((urlPrefix) => this.router.url.startsWith(urlPrefix))) - ) + .subscribe(() => { + const currentRoute = this.getRoutePath(this.router.url); + this.isRgwRoute = [ + this.rgwBuckerUrlPrefix, + this.rgwUserUrlPrefix, + this.rgwAccountsUrlPrefix, + this.rgwMultisiteSyncPolicyPrefix + ].some((urlPrefix) => this.router.url.startsWith(urlPrefix)); + this.isRgwResourcePage = this.isRgwAccountsResourcePage(currentRoute); + }) ); // Set daemon list polling only when in RGW route: this.subs.add( @@ -84,4 +87,13 @@ export class ContextComponent implements OnInit, OnDestroy { this.router.navigate([currentUrl]); }); } + + private isRgwAccountsResourcePage(url: string): boolean { + return this.rgwAccountsResourcePagePattern.test(url); + } + + private getRoutePath(url: string): string { + const hashIndex = url.indexOf('#'); + return hashIndex === -1 ? url : url.slice(hashIndex + 1); + } } diff --git a/src/pybind/mgr/dashboard/frontend/src/styles/ceph-custom/_spacings.scss b/src/pybind/mgr/dashboard/frontend/src/styles/ceph-custom/_spacings.scss index c34f3a94f65..24e8bb4edc8 100644 --- a/src/pybind/mgr/dashboard/frontend/src/styles/ceph-custom/_spacings.scss +++ b/src/pybind/mgr/dashboard/frontend/src/styles/ceph-custom/_spacings.scss @@ -9,6 +9,10 @@ padding-left: layout.$spacing-06; } +.cds-pl-5 { + padding-left: layout.$spacing-05; +} + .cds-pl-4 { padding-left: layout.$spacing-04; } @@ -33,6 +37,10 @@ padding-top: layout.$spacing-05; } +.cds-pb-5 { + padding-bottom: layout.$spacing-05; +} + .cds-pr-4 { padding-right: layout.$spacing-04; }