]> git-server-git.apps.pok.os.sepia.ceph.com Git - ceph.git/commitdiff
mgr/dashboard: Remove tabs under subsystem 67535/head
authorpujaoshahu <pshahu@redhat.com>
Mon, 23 Feb 2026 18:31:30 +0000 (00:01 +0530)
committerSagar Gopale <sagar.gopale@ibm.com>
Thu, 26 Feb 2026 12:24:18 +0000 (17:54 +0530)
Fixes: https://tracker.ceph.com/issues/74904
Signed-off-by: pujaoshahu <pshahu@redhat.com>
19 files changed:
src/pybind/mgr/dashboard/frontend/src/app/ceph/block/block.module.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvme-subsystem-view/nvme-subsystem-view.component.spec.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-gateway-node/nvmeof-gateway-node.component.html
src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-gateway-subsystem/nvmeof-gateway-subsystem.component.html
src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-gateway/nvmeof-gateway.component.spec.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-gateway/nvmeof-gateway.component.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-initiators-list/nvmeof-initiators-list.component.html
src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-initiators-list/nvmeof-initiators-list.component.spec.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-listeners-form/nvmeof-listeners-form.component.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-listeners-list/nvmeof-listeners-list.component.html
src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-namespaces-form/nvmeof-namespaces-form.component.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-namespaces-list/nvmeof-namespaces-list.component.html
src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-namespaces-list/nvmeof-namespaces-list.component.spec.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-namespaces-list/nvmeof-namespaces-list.component.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-subsystem-namespaces-list/nvmeof-subsystem-namespaces-list.component.html
src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-subsystem-overview/nvmeof-subsystem-overview.component.spec.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-subsystems-form/nvmeof-subsystem-step-4/nvmeof-subsystem-step-4.component.html
src/pybind/mgr/dashboard/frontend/src/app/core/navigation/breadcrumbs/breadcrumbs.component.ts
src/pybind/mgr/dashboard/frontend/src/app/shared/api/nvmeof.service.spec.ts

index 2c1db86f7caba8cb4249c2ddbf94592f03ca3de0..07a67ff8884c7c1c3f876a9402e1dbdc5461fe45 100644 (file)
@@ -184,9 +184,9 @@ import { NvmeofSubsystemPerformanceComponent } from './nvmeof-subsystem-performa
     NvmeofNamespaceExpandModalComponent,
     NvmeSubsystemViewComponent,
     NvmeofEditHostKeyModalComponent,
+    NvmeofSubsystemsStepFourComponent,
     NvmeofSubsystemOverviewComponent,
-    NvmeofSubsystemPerformanceComponent,
-    NvmeofSubsystemsStepFourComponent
+    NvmeofSubsystemPerformanceComponent
   ],
 
   exports: [RbdConfigurationListComponent, RbdConfigurationFormComponent]
@@ -346,6 +346,7 @@ const routes: Routes = [
       {
         path: 'gateways',
         component: NvmeofGatewayComponent,
+        data: { breadcrumbs: 'Gateways' },
         children: [
           {
             path: `${URLVerbs.EDIT}/:subsystem_nqn/namespace/:nsid`,
index e7c3c196157c3ce672487575814d9b39fbf723a3..910041f7a8e512594bffc0a496c79e6ace433743 100644 (file)
@@ -43,28 +43,37 @@ describe('NvmeSubsystemViewComponent', () => {
   });
 
   it('should build sidebar items correctly', () => {
-    expect(component.sidebarItems.length).toBe(3);
+    expect(component.sidebarItems.length).toBe(5);
 
-    // Verify first item (Initiators)
     expect(component.sidebarItems[0].route).toEqual([
       '/block/nvmeof/subsystems',
       'nqn.test',
-      'hosts'
+      'overview'
     ]);
     expect(component.sidebarItems[0].routeExtras).toEqual({ queryParams: { group: 'my-group' } });
 
-    // Verify second item (Namespaces)
     expect(component.sidebarItems[1].route).toEqual([
       '/block/nvmeof/subsystems',
       'nqn.test',
-      'namespaces'
+      'hosts'
     ]);
 
-    // Verify third item (Listeners)
     expect(component.sidebarItems[2].route).toEqual([
+      '/block/nvmeof/subsystems',
+      'nqn.test',
+      'namespaces'
+    ]);
+
+    expect(component.sidebarItems[3].route).toEqual([
       '/block/nvmeof/subsystems',
       'nqn.test',
       'listeners'
     ]);
+
+    expect(component.sidebarItems[4].route).toEqual([
+      '/block/nvmeof/subsystems',
+      'nqn.test',
+      'performance'
+    ]);
   });
 });
index d586dcc67304109b08232353494c81564c8922e8..678e0cd5f63466cc9f0d9d4a8a8d404c7eb24fb2 100644 (file)
@@ -11,7 +11,6 @@
     [maxLimit]="25"
     identifier="hostname"
     forceIdentifier="true"
-    [autoReload]="true"
     (updateSelection)="updateSelection($event)"
     emptyStateTitle="No nodes available"
     i18n-emptyStateTitle
index 4e2ebf2315e7c28825f000f7ab7f2a284d17e81f..84db49150d8cda151c3ec567650eaa6c14f47933 100644 (file)
@@ -2,7 +2,6 @@
   <cd-table
     [data]="subsystems"
     [columns]="columns"
-    [autoReload]="true"
     columnMode="flex"
     selectionType="none"
     identifier="nqn"
index 683768cf1a9a2e58b4c2303dc341c7c3a85e92c9..5156185775670cf30f1fa460c9bd98015c75576c 100644 (file)
@@ -1,5 +1,5 @@
 import { ComponentFixture, TestBed } from '@angular/core/testing';
-import { ActivatedRoute, Router } from '@angular/router';
+import { ActivatedRoute } from '@angular/router';
 
 import { NvmeofGatewayComponent } from './nvmeof-gateway.component';
 
@@ -14,7 +14,6 @@ describe('NvmeofGatewayComponent', () => {
   let component: NvmeofGatewayComponent;
   let fixture: ComponentFixture<NvmeofGatewayComponent>;
   let breadcrumbService: BreadcrumbService;
-  let router: Router;
 
   beforeEach(async () => {
     await TestBed.configureTestingModule({
@@ -41,7 +40,6 @@ describe('NvmeofGatewayComponent', () => {
     fixture = TestBed.createComponent(NvmeofGatewayComponent);
     component = fixture.componentInstance;
     breadcrumbService = TestBed.inject(BreadcrumbService);
-    router = TestBed.inject(Router);
     fixture.detectChanges();
   });
 
@@ -56,14 +54,8 @@ describe('NvmeofGatewayComponent', () => {
   });
 
   it('should update tab crumb on tab switch', () => {
-    spyOn(router, 'navigate');
     spyOn(breadcrumbService, 'setTabCrumb');
     component.onSelected(component.Tabs.subsystem);
-    expect(router.navigate).toHaveBeenCalledWith([], {
-      relativeTo: TestBed.inject(ActivatedRoute),
-      queryParams: { tab: component.Tabs.subsystem },
-      queryParamsHandling: 'merge'
-    });
     expect(breadcrumbService.setTabCrumb).toHaveBeenCalledWith('Subsystem');
   });
 
index 2927fb1615cbea6d15489dc17b02beb33c5d92c8..db4f5189e26065afe39d0ff8ca863ffc2d95e55c 100644 (file)
@@ -1,7 +1,5 @@
 import { Component, OnDestroy, OnInit, TemplateRef, ViewChild } from '@angular/core';
-import { ActivatedRoute, NavigationEnd, Router } from '@angular/router';
-import { Subject } from 'rxjs';
-import { filter, takeUntil } from 'rxjs/operators';
+import { ActivatedRoute, Router } from '@angular/router';
 
 import _ from 'lodash';
 
@@ -30,7 +28,6 @@ const TAB_LABELS: Record<TABS, string> = {
 export class NvmeofGatewayComponent implements OnInit, OnDestroy {
   selectedTab: TABS;
   activeTab: TABS = TABS.gateways;
-  private readonly destroy$ = new Subject<void>();
 
   @ViewChild('statusTpl', { static: true })
   statusTpl: TemplateRef<any>;
@@ -44,39 +41,28 @@ export class NvmeofGatewayComponent implements OnInit, OnDestroy {
   ) {}
 
   ngOnInit() {
-    this.route.queryParams.pipe(takeUntil(this.destroy$)).subscribe((params) => {
+    this.route.queryParams.subscribe((params) => {
       if (params['tab'] && Object.values(TABS).includes(params['tab'])) {
         this.activeTab = params['tab'] as TABS;
       }
       this.breadcrumbService.setTabCrumb(TAB_LABELS[this.activeTab]);
     });
-
-    this.router.events
-      .pipe(
-        filter((event) => event instanceof NavigationEnd),
-        takeUntil(this.destroy$)
-      )
-      .subscribe(() => {
-        // Run after NavigationEnd handlers so tab crumb is not cleared by global breadcrumb reset.
-        setTimeout(() => this.breadcrumbService.setTabCrumb(TAB_LABELS[this.activeTab]));
-      });
   }
 
   ngOnDestroy() {
-    this.destroy$.next();
-    this.destroy$.complete();
     this.breadcrumbService.clearTabCrumb();
   }
 
   onSelected(tab: TABS) {
     this.selectedTab = tab;
     this.activeTab = tab;
+    this.breadcrumbService.setTabCrumb(TAB_LABELS[tab]);
     this.router.navigate([], {
       relativeTo: this.route,
       queryParams: { tab },
-      queryParamsHandling: 'merge'
+      queryParamsHandling: 'merge',
+      replaceUrl: true
     });
-    this.breadcrumbService.setTabCrumb(TAB_LABELS[tab]);
   }
 
   public get Tabs(): typeof TABS {
index c535005fe2b89fddc477ab3fe1e869ee13cc3cf6..97a520db0c6a6b36f52cf0c3508002a97d2776c7 100644 (file)
@@ -26,7 +26,6 @@
           (fetchData)="listInitiators()"
           [columns]="initiatorColumns"
           selectionType="multiClick"
-          [autoReload]="false"
           (updateSelection)="updateSelection($event)">
   <div class="table-actions">
     <cd-table-actions [permission]="permission"
index 8cebc17928c26e5a8d421fd468189388a27acff6..7152385931cfdc4b830ce4f288123500d9b998a8 100644 (file)
@@ -65,6 +65,7 @@ describe('NvmeofInitiatorsListComponent', () => {
     component.subsystemNQN = 'nqn.2016-06.io.spdk:cnode1';
     component.group = 'group1';
     component.ngOnInit();
+    fixture.detectChanges();
   });
 
   it('should create', () => {
index cb1f0293ee57c5710fb71b3670f035408f73d920..d39a42b5a4760ace50f44bc75da298de1cef782c 100644 (file)
@@ -63,6 +63,10 @@ export class NvmeofListenersFormComponent implements OnInit {
       .subscribe({
         error: () => {
           this.isSubmitLoading = false;
+          this.router.navigate([{ outlets: { modal: null } }], {
+            relativeTo: this.route.parent,
+            queryParamsHandling: 'preserve'
+          });
         },
         complete: () => {
           this.isSubmitLoading = false;
index 368d658506554218a2cbddaef5d9b48cc48d3893..dee0ed502db91e8edb967abc3a511dd8fa2aa12f 100644 (file)
@@ -11,7 +11,6 @@
           (fetchData)="listListeners()"
           [columns]="listenerColumns"
           identifier="id"
-          [autoReload]="true"
           forceIdentifier="true"
           selectionType="single"
           emptyStateTitle="No listener found."
index e730bb081e4bd2ef5764a30e8cf4675b72ff5fef..1f07a4a0fc9bf8fe1f1bfde7c4814d6659cf572b 100644 (file)
@@ -1,4 +1,4 @@
-import { Component, OnInit } from '@angular/core';
+import { ChangeDetectorRef, Component, OnInit } from '@angular/core';
 import { UntypedFormControl, Validators } from '@angular/forms';
 import { ActivatedRoute, Params, Router } from '@angular/router';
 import {
@@ -67,6 +67,7 @@ export class NvmeofNamespacesFormComponent implements OnInit {
     private rbdService: RbdService,
     private router: Router,
     private route: ActivatedRoute,
+    private cdr: ChangeDetectorRef,
     public formatterService: FormatterService,
     public dimlessBinaryPipe: DimlessBinaryPipe
   ) {
@@ -83,7 +84,9 @@ export class NvmeofNamespacesFormComponent implements OnInit {
     this.description = $localize`Namespaces define the storage volumes that subsystems present to hosts.`;
 
     this.route.params.subscribe((params: Params) => {
-      this.subsystemNQN = params['subsystem_nqn'];
+      if (params['subsystem_nqn']) {
+        this.subsystemNQN = params['subsystem_nqn'];
+      }
       this.nsid = params['nsid'];
       if (params['group']) {
         this.group = params['group'];
@@ -117,15 +120,18 @@ export class NvmeofNamespacesFormComponent implements OnInit {
     });
     if (this.group) {
       this.fetchUsedImages();
-      this.nvmeofService.listSubsystems(this.group).subscribe((subsystems: NvmeofSubsystem[]) => {
-        this.subsystems = subsystems;
-        if (this.subsystemNQN) {
-          const selectedSubsystem = this.subsystems.find((s) => s.nqn === this.subsystemNQN);
-          if (selectedSubsystem) {
-            this.nsForm.get('subsystem').setValue(selectedSubsystem.nqn);
+      this.nvmeofService
+        .listSubsystems(this.group)
+        .subscribe((res: NvmeofSubsystem[] | NvmeofSubsystem) => {
+          this.subsystems = Array.isArray(res) ? res : [res];
+          this.cdr.detectChanges();
+          if (this.subsystemNQN) {
+            const selectedSubsystem = this.subsystems.find((s) => s.nqn === this.subsystemNQN);
+            if (selectedSubsystem) {
+              this.nsForm.get('subsystem').setValue(selectedSubsystem.nqn);
+            }
           }
-        }
-      });
+        });
     }
   }
 
@@ -302,11 +308,14 @@ export class NvmeofNamespacesFormComponent implements OnInit {
     return Math.random().toString(36).substring(2);
   }
 
-  private normalizeImageSizeInput(value: string): string {
-    const input = (value || '').trim();
+  private normalizeImageSizeInput(value: string | number): string {
+    const input = String(value ?? '').trim();
     if (!input) {
       return input;
     }
+    if (typeof value === 'number') {
+      return input;
+    }
     // Accept plain numeric values as GiB (e.g. "45" => "45GiB").
     return /^\d+(\.\d+)?$/.test(input) ? `${input}GiB` : input;
   }
@@ -337,15 +346,20 @@ export class NvmeofNamespacesFormComponent implements OnInit {
       }
 
       if (isGatewayProvisioned) {
-        request.rbd_image_name = `nvme_${pool}_${this.group}_${this.randomString()}`;
+        const rbdImageName = this.nsForm.getValue('rbd_image_name');
+        if (rbdImageName) {
+          request.rbd_image_name = loopCount > 1 ? `${rbdImageName}-${i}` : rbdImageName;
+        } else {
+          request.rbd_image_name = `nvme_${pool}_${this.group}_${this.randomString()}`;
+        }
         if (rbdImageSize) {
           request['rbd_image_size'] = rbdImageSize;
         }
-      }
-
-      const rbdImageName = this.nsForm.getValue('rbd_image_name');
-      if (rbdImageName) {
-        request['rbd_image_name'] = rbdImageName;
+      } else {
+        const rbdImageName = this.nsForm.getValue('rbd_image_name');
+        if (rbdImageName) {
+          request['rbd_image_name'] = rbdImageName;
+        }
       }
 
       const subsystemNQN = this.nsForm.getValue('subsystem') || this.subsystemNQN;
@@ -429,7 +443,7 @@ export class NvmeofNamespacesFormComponent implements OnInit {
       },
       complete: () => {
         this.router.navigate([this.pageURL], {
-          queryParams: { group: this.group }
+          queryParams: { group: this.group, tab: 'namespace' }
         });
       }
     });
index 59bd7b3dade677eb30138f418a088886d25ba267..dde00bf70112282e9c63f941c145f2a1c3c8f129 100644 (file)
@@ -26,6 +26,8 @@
             columnMode="flex"
             (fetchData)="fetchData()"
             [columns]="namespacesColumns"
+            identifier="unique_id"
+            [forceIdentifier]="true"
             selectionType="single"
             (updateSelection)="updateSelection($event)"
             emptyStateTitle="No namespaces created."
index 1f9c412f21531d6404e4b9d28770ec2880621961..6ac05c63b699f3d1459a18950096987b08733375 100644 (file)
@@ -90,7 +90,12 @@ describe('NvmeofNamespacesListComponent', () => {
   it('should retrieve namespaces', (done) => {
     component.group = 'g1';
     component.namespaces$.pipe(take(1)).subscribe((namespaces) => {
-      expect(namespaces).toEqual(mockNamespaces);
+      expect(namespaces).toEqual(
+        mockNamespaces.map((ns) => ({
+          ...ns,
+          unique_id: `${ns.nsid}_${ns['ns_subsystem_nqn']}`
+        }))
+      );
       done();
     });
     component.listNamespaces();
index 804bcbf192bdc601844a4d79e6d6ed6ea6cfa56b..37b1aa4d3a6803d55802df433697d6d7918c6df5 100644 (file)
@@ -1,4 +1,4 @@
-import { Component, Input, OnDestroy, OnInit, TemplateRef, ViewChild } from '@angular/core';
+import { Component, Input, NgZone, OnDestroy, OnInit, TemplateRef, ViewChild } from '@angular/core';
 import { ActivatedRoute, Router } from '@angular/router';
 import { NvmeofService, GroupsComboboxItem } from '~/app/shared/api/nvmeof.service';
 import { DeleteConfirmationModalComponent } from '~/app/shared/components/delete-confirmation-modal/delete-confirmation-modal.component';
@@ -53,6 +53,7 @@ export class NvmeofNamespacesListComponent implements OnInit, OnDestroy {
     public actionLabels: ActionLabelsI18n,
     private router: Router,
     private route: ActivatedRoute,
+    private ngZone: NgZone,
     private modalService: ModalCdsService,
     private authStorageService: AuthStorageService,
     private taskWrapper: TaskWrapperService,
@@ -112,20 +113,22 @@ export class NvmeofNamespacesListComponent implements OnInit, OnDestroy {
         icon: Icons.edit,
         click: (row: NvmeofSubsystemNamespace) => {
           const namespace = row || this.selection.first();
-          this.router.navigate(
-            [
-              {
-                outlets: {
-                  modal: [URLVerbs.EDIT, namespace.ns_subsystem_nqn, 'namespace', namespace.nsid]
+          this.ngZone.run(() => {
+            this.router.navigate(
+              [
+                {
+                  outlets: {
+                    modal: [URLVerbs.EDIT, namespace.ns_subsystem_nqn, 'namespace', namespace.nsid]
+                  }
                 }
+              ],
+              {
+                relativeTo: this.route,
+                queryParams: { group: this.group },
+                queryParamsHandling: 'merge'
               }
-            ],
-            {
-              relativeTo: this.route,
-              queryParams: { group: this.group },
-              queryParamsHandling: 'merge'
-            }
-          );
+            );
+          });
         }
       },
       {
@@ -146,12 +149,17 @@ export class NvmeofNamespacesListComponent implements OnInit, OnDestroy {
             const namespaces = Array.isArray(res) ? res : res.namespaces || [];
             // Deduplicate by nsid + subsystem NQN (API with wildcard can return duplicates per gateway)
             const seen = new Set<string>();
-            return namespaces.filter((ns) => {
-              const key = `${ns.nsid}_${ns['ns_subsystem_nqn']}`;
-              if (seen.has(key)) return false;
-              seen.add(key);
-              return true;
-            });
+            return namespaces
+              .filter((ns) => {
+                const key = `${ns.nsid}_${ns['ns_subsystem_nqn']}`;
+                if (seen.has(key)) return false;
+                seen.add(key);
+                return true;
+              })
+              .map((ns) => ({
+                ...ns,
+                unique_id: `${ns.nsid}_${ns['ns_subsystem_nqn']}`
+              }));
           }),
           catchError(() => of([]))
         );
index 5a8a035ed7f5581ec64bb6d7414ded96c737c876..8913b850cf0af113a3b86b4bed09d75176e2a7c2 100644 (file)
@@ -1,7 +1,6 @@
   <cd-table [data]="namespaces"
             columnMode="flex"
             (fetchData)="listNamespaces()"
-            [autoReload]="true"
             [columns]="namespacesColumns"
             selectionType="single"
             (updateSelection)="updateSelection($event)"
index ad7f77d4bb1e1a00a5bc3d043e2a19cbd7de7eda..d76456422fdfac10ec829eb7a05249ec997fcaa4 100644 (file)
@@ -149,14 +149,14 @@ describe('NvmeofSubsystemOverviewComponent', () => {
     expect(labelTexts).toContain('Maximum allowed namespaces');
   }));
 
-  it('should display subsystem type from subsystem data', fakeAsync(() => {
+  it('should not display MTLS label in overview details', fakeAsync(() => {
     component.ngOnInit();
     tick();
     fixture.detectChanges();
 
     const values = fixture.nativeElement.querySelectorAll('.cds--type-body-compact-01');
     const valueTexts = Array.from(values).map((el: HTMLElement) => el.textContent.trim());
-    expect(valueTexts).toContain('NVMe');
+    expect(valueTexts).not.toContain('MTLS');
   }));
 
   it('should display hosts allowed from subsystem data', fakeAsync(() => {
@@ -169,13 +169,12 @@ describe('NvmeofSubsystemOverviewComponent', () => {
     expect(valueTexts).toContain('Any host');
   }));
 
-  it('should display HA status from subsystem data', fakeAsync(() => {
+  it('should not render Edit link for Hosts allowed', fakeAsync(() => {
     component.ngOnInit();
     tick();
     fixture.detectChanges();
 
-    const values = fixture.nativeElement.querySelectorAll('.cds--type-body-compact-01');
-    const valueTexts = Array.from(values).map((el: HTMLElement) => el.textContent.trim());
-    expect(valueTexts).toContain('Yes');
+    const editLink = fixture.nativeElement.querySelector('a[cdsLink]');
+    expect(editLink).toBeFalsy();
   }));
 });
index 3842781f4a93320016444ffbd270fb9218691897..9d25200f568a88b458a99db9d292d23faad38272 100644 (file)
@@ -4,11 +4,8 @@
      [fullWidth]="true">
   <div cdsCol
        [columnNumbers]="{sm: 4, md: 8, lg: 12}">
-    <div cdsRow
-         class="form-heading">
-      <h3 class="cds--type-heading-03"
-          i18n>Review summary</h3>
-    </div>
+    <h3 class="cds--type-heading-03 cds-mb-5"
+        i18n>Review summary</h3>
   </div>
 
   <!-- Subsystem details -->
index 0b4cd23a09b22831660ade4fae312b0d7208b3c4..706a671ddc5db9e13e21e41e43f062626d82a0f0 100644 (file)
@@ -27,7 +27,7 @@ import { Title } from '@angular/platform-browser';
 import { ActivatedRouteSnapshot, NavigationEnd, NavigationStart, Router } from '@angular/router';
 
 import { concat, from, Observable, of, Subscription } from 'rxjs';
-import { distinct, filter, first, mergeMap, toArray } from 'rxjs/operators';
+import { distinct, filter, first, mergeMap, toArray, take } from 'rxjs/operators';
 import { AppConstants } from '~/app/shared/constants/app.constants';
 
 import { BreadcrumbsResolver, IBreadcrumb } from '~/app/shared/models/breadcrumbs';
@@ -53,7 +53,6 @@ export class BreadcrumbsComponent implements OnDestroy {
   private tabCrumbSubscription: Subscription;
   private defaultResolver = new BreadcrumbsResolver();
   private baseCrumbs: IBreadcrumb[] = [];
-  private currentTabCrumb: IBreadcrumb = null;
 
   constructor(
     private router: Router,
@@ -65,12 +64,12 @@ export class BreadcrumbsComponent implements OnDestroy {
       .pipe(filter((x) => x instanceof NavigationStart))
       .subscribe(() => {
         this.finished = false;
+        this.breadcrumbService.clearTabCrumb();
       });
 
     this.subscription = this.router.events
       .pipe(filter((x) => x instanceof NavigationEnd))
       .subscribe(() => {
-        this.breadcrumbService.clearTabCrumb();
         const currentRoot = router.routerState.snapshot.root;
 
         this._resolveCrumbs(currentRoot)
@@ -86,16 +85,17 @@ export class BreadcrumbsComponent implements OnDestroy {
           .subscribe((x) => {
             this.finished = true;
             this.baseCrumbs = x;
-            this.crumbs = this.currentTabCrumb ? [...x, this.currentTabCrumb] : [...x];
-            const title = this.getTitleFromCrumbs(this.crumbs);
-            this.titleService.setTitle(title);
+            this.breadcrumbService.tabCrumb$.pipe(take(1)).subscribe((tabCrumb) => {
+              this.crumbs = tabCrumb && x.length > 0 ? [...x.slice(0, -1), tabCrumb] : [...x];
+              const title = this.getTitleFromCrumbs(this.crumbs);
+              this.titleService.setTitle(title);
+            });
           });
       });
 
     this.tabCrumbSubscription = this.breadcrumbService.tabCrumb$.subscribe((tabCrumb) => {
-      this.currentTabCrumb = tabCrumb;
-      if (tabCrumb) {
-        this.crumbs = [...this.baseCrumbs, tabCrumb];
+      if (tabCrumb && this.baseCrumbs.length > 0) {
+        this.crumbs = [...this.baseCrumbs.slice(0, -1), tabCrumb];
       } else {
         this.crumbs = [...this.baseCrumbs];
       }
index 37e8932c365e67bc4fad443a19ded2b6beaa4650..aefdf0739d51f7186d69a601eb8dc6894168fd7f 100755 (executable)
@@ -247,7 +247,9 @@ describe('NvmeofService', () => {
     const mockNsid = '1';
     it('should call listNamespaces', () => {
       service.listNamespaces(mockGroupName).subscribe();
-      const req = httpTesting.expectOne(`${API_PATH}/subsystem/*/namespace?gw_group=${mockGroupName}`);
+      const req = httpTesting.expectOne(
+        `${API_PATH}/subsystem/*/namespace?gw_group=${mockGroupName}`
+      );
       expect(req.request.method).toBe('GET');
     });
     it('should call getNamespace', () => {