Merge pull request #70544 from rhcs-dashboard/switcher

mgr/dashboard: rgw gateway switcher read-only for resource page

Reviewed-by: Afreen Misbah <afreen@ibm.com>
This commit is contained in:
Afreen Misbah 2026-07-28 19:03:46 +05:30 committed by GitHub
commit ff681c5884
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 119 additions and 46 deletions

View File

@ -1,32 +1,70 @@
<ng-container
*ngIf="{
@if (
{
ftMap: featureToggleMap$ | async,
daemons: rgwDaemonService.daemons$ | async,
selectedDaemon: rgwDaemonService.selectedDaemon$ | async
} as data"
>
<ng-container
*ngIf="
data.ftMap && data.ftMap.rgw && permissions.rgw.read && isRgwRoute && data.daemons.length > 1
"
>
<div class="cd-context-bar pt-3 pb-3">
};
as data
) {
@if (
data.ftMap && data.ftMap.rgw && permissions.rgw.read && isRgwRoute && data.daemons.length > 1
) {
<div
class="cd-context-bar cds-pt-5 cds-pb-5"
[class.cds-pl-5]="isRgwResourcePage"
>
<span
class="me-1"
class="cds-mr-2"
i18n
>Object gateway:</span
>
<div
ngbDropdown
placement="bottom-left"
class="d-inline-block ms-2"
>
@if (!isRgwResourcePage) {
<div
ngbDropdown
placement="bottom-left"
class="cds-ml-3"
style="display: inline-block"
>
<button
ngbDropdownToggle
class="btn btn-outline-info ctx-bar-selected-rgw-daemon"
i18n-title
title="Object gateway"
type="button"
>
{{ data.selectedDaemon.id }}
<cds-tag
type="blue"
size="sm"
>{{ data.selectedDaemon.zonegroup_name }}</cds-tag
>
</button>
<div ngbDropdownMenu>
@for (daemon of data.daemons; track daemon.id) {
<button
ngbDropdownItem
class="ctx-bar-available-rgw-daemon"
(click)="onDaemonSelection(daemon)"
type="button"
>
{{ daemon.id }}
<cds-tag
type="blue"
size="sm"
>{{ daemon.zonegroup_name }}</cds-tag
>
</button>
}
</div>
</div>
} @else {
<button
ngbDropdownToggle
class="btn btn-outline-info ctx-bar-selected-rgw-daemon"
i18n-title
title="Object gateway"
type="button"
disabled
>
{{ data.selectedDaemon.id }}
<cds-tag
@ -35,24 +73,7 @@
>{{ data.selectedDaemon.zonegroup_name }}</cds-tag
>
</button>
<div ngbDropdownMenu>
<ng-container *ngFor="let daemon of data.daemons">
<button
ngbDropdownItem
class="ctx-bar-available-rgw-daemon"
(click)="onDaemonSelection(daemon)"
type="button"
>
{{ daemon.id }}
<cds-tag
type="blue"
size="sm"
>{{ daemon.zonegroup_name }}</cds-tag
>
</button>
</ng-container>
</div>
</div>
}
</div>
</ng-container>
</ng-container>
}
}

View File

@ -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);
});
});

View File

@ -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);
}
}

View File

@ -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;
}