]> git-server-git.apps.pok.os.sepia.ceph.com Git - ceph.git/commitdiff
mgr/dashboard: rgw gateway switcher read-only for resource page 70544/head
authorSyed Ali Ul Hasan <syedaliulhasan19@gmail.com>
Sat, 25 Jul 2026 22:08:19 +0000 (03:38 +0530)
committerSyed Ali Ul Hasan <syedaliulhasan19@gmail.com>
Mon, 27 Jul 2026 20:35:43 +0000 (02:05 +0530)
- Fixes: https://tracker.ceph.com/issues/78513

Signed-off-by: Syed Ali Ul Hasan <syedaliulhasan19@gmail.com>
src/pybind/mgr/dashboard/frontend/src/app/core/context/context.component.html
src/pybind/mgr/dashboard/frontend/src/app/core/context/context.component.spec.ts
src/pybind/mgr/dashboard/frontend/src/app/core/context/context.component.ts
src/pybind/mgr/dashboard/frontend/src/styles/ceph-custom/_spacings.scss

index 9d7376310ed591e537059c467a38440765637e41..4b13df82b1a9b009ec7b9e1f3bd0b3fb7eb2914c 100644 (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
             >{{ 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>
+  }
+}
index 865e9b26f7090fb24349c17e98c747bd3f90b08d..a8a9f8c87634dbda91bb1bc1319a38fda02f41a4 100644 (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);
+  });
 });
index 46e293d8d852123d341113f831fb613e2ef78004..e9d3e27715899347e2dfd41d523903a7e710f0c4 100644 (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);
+  }
 }
index c34f3a94f6526ee7c122335b16d3295245d6136c..24e8bb4edc8b243667274cdd627ee8836d1aebcc 100644 (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;
 }
   padding-top: layout.$spacing-05;
 }
 
+.cds-pb-5 {
+  padding-bottom: layout.$spacing-05;
+}
+
 .cds-pr-4 {
   padding-right: layout.$spacing-04;
 }