From: pujaoshahu Date: Mon, 23 Feb 2026 18:31:30 +0000 (+0530) Subject: mgr/dashboard: Remove tabs under subsystem X-Git-Url: http://git-server-git.apps.pok.os.sepia.ceph.com/?a=commitdiff_plain;h=refs%2Fpull%2F67535%2Fhead;p=ceph.git mgr/dashboard: Remove tabs under subsystem Fixes: https://tracker.ceph.com/issues/74904 Signed-off-by: pujaoshahu --- diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/block.module.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/block.module.ts index 2c1db86f7ca..07a67ff8884 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/block.module.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/block.module.ts @@ -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`, diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvme-subsystem-view/nvme-subsystem-view.component.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvme-subsystem-view/nvme-subsystem-view.component.spec.ts index e7c3c196157..910041f7a8e 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvme-subsystem-view/nvme-subsystem-view.component.spec.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvme-subsystem-view/nvme-subsystem-view.component.spec.ts @@ -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' + ]); }); }); diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-gateway-node/nvmeof-gateway-node.component.html b/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-gateway-node/nvmeof-gateway-node.component.html index d586dcc6730..678e0cd5f63 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-gateway-node/nvmeof-gateway-node.component.html +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-gateway-node/nvmeof-gateway-node.component.html @@ -11,7 +11,6 @@ [maxLimit]="25" identifier="hostname" forceIdentifier="true" - [autoReload]="true" (updateSelection)="updateSelection($event)" emptyStateTitle="No nodes available" i18n-emptyStateTitle diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-gateway-subsystem/nvmeof-gateway-subsystem.component.html b/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-gateway-subsystem/nvmeof-gateway-subsystem.component.html index 4e2ebf2315e..84db49150d8 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-gateway-subsystem/nvmeof-gateway-subsystem.component.html +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-gateway-subsystem/nvmeof-gateway-subsystem.component.html @@ -2,7 +2,6 @@ { let component: NvmeofGatewayComponent; let fixture: ComponentFixture; 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'); }); diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-gateway/nvmeof-gateway.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-gateway/nvmeof-gateway.component.ts index 2927fb1615c..db4f5189e26 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-gateway/nvmeof-gateway.component.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-gateway/nvmeof-gateway.component.ts @@ -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 = { export class NvmeofGatewayComponent implements OnInit, OnDestroy { selectedTab: TABS; activeTab: TABS = TABS.gateways; - private readonly destroy$ = new Subject(); @ViewChild('statusTpl', { static: true }) statusTpl: TemplateRef; @@ -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 { diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-initiators-list/nvmeof-initiators-list.component.html b/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-initiators-list/nvmeof-initiators-list.component.html index c535005fe2b..97a520db0c6 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-initiators-list/nvmeof-initiators-list.component.html +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-initiators-list/nvmeof-initiators-list.component.html @@ -26,7 +26,6 @@ (fetchData)="listInitiators()" [columns]="initiatorColumns" selectionType="multiClick" - [autoReload]="false" (updateSelection)="updateSelection($event)">
{ component.subsystemNQN = 'nqn.2016-06.io.spdk:cnode1'; component.group = 'group1'; component.ngOnInit(); + fixture.detectChanges(); }); it('should create', () => { diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-listeners-form/nvmeof-listeners-form.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-listeners-form/nvmeof-listeners-form.component.ts index cb1f0293ee5..d39a42b5a47 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-listeners-form/nvmeof-listeners-form.component.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-listeners-form/nvmeof-listeners-form.component.ts @@ -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; diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-listeners-list/nvmeof-listeners-list.component.html b/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-listeners-list/nvmeof-listeners-list.component.html index 368d6585065..dee0ed502db 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-listeners-list/nvmeof-listeners-list.component.html +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-listeners-list/nvmeof-listeners-list.component.html @@ -11,7 +11,6 @@ (fetchData)="listListeners()" [columns]="listenerColumns" identifier="id" - [autoReload]="true" forceIdentifier="true" selectionType="single" emptyStateTitle="No listener found." diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-namespaces-form/nvmeof-namespaces-form.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-namespaces-form/nvmeof-namespaces-form.component.ts index e730bb081e4..1f07a4a0fc9 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-namespaces-form/nvmeof-namespaces-form.component.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-namespaces-form/nvmeof-namespaces-form.component.ts @@ -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' } }); } }); diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-namespaces-list/nvmeof-namespaces-list.component.html b/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-namespaces-list/nvmeof-namespaces-list.component.html index 59bd7b3dade..dde00bf7011 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-namespaces-list/nvmeof-namespaces-list.component.html +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-namespaces-list/nvmeof-namespaces-list.component.html @@ -26,6 +26,8 @@ columnMode="flex" (fetchData)="fetchData()" [columns]="namespacesColumns" + identifier="unique_id" + [forceIdentifier]="true" selectionType="single" (updateSelection)="updateSelection($event)" emptyStateTitle="No namespaces created." diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-namespaces-list/nvmeof-namespaces-list.component.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-namespaces-list/nvmeof-namespaces-list.component.spec.ts index 1f9c412f215..6ac05c63b69 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-namespaces-list/nvmeof-namespaces-list.component.spec.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-namespaces-list/nvmeof-namespaces-list.component.spec.ts @@ -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(); diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-namespaces-list/nvmeof-namespaces-list.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-namespaces-list/nvmeof-namespaces-list.component.ts index 804bcbf192b..37b1aa4d3a6 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-namespaces-list/nvmeof-namespaces-list.component.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-namespaces-list/nvmeof-namespaces-list.component.ts @@ -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(); - 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([])) ); diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-subsystem-namespaces-list/nvmeof-subsystem-namespaces-list.component.html b/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-subsystem-namespaces-list/nvmeof-subsystem-namespaces-list.component.html index 5a8a035ed7f..8913b850cf0 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-subsystem-namespaces-list/nvmeof-subsystem-namespaces-list.component.html +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-subsystem-namespaces-list/nvmeof-subsystem-namespaces-list.component.html @@ -1,7 +1,6 @@ { 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(); })); }); diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-subsystems-form/nvmeof-subsystem-step-4/nvmeof-subsystem-step-4.component.html b/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-subsystems-form/nvmeof-subsystem-step-4/nvmeof-subsystem-step-4.component.html index 3842781f4a9..9d25200f568 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-subsystems-form/nvmeof-subsystem-step-4/nvmeof-subsystem-step-4.component.html +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-subsystems-form/nvmeof-subsystem-step-4/nvmeof-subsystem-step-4.component.html @@ -4,11 +4,8 @@ [fullWidth]="true">
-
-

Review summary

-
+

Review summary

diff --git a/src/pybind/mgr/dashboard/frontend/src/app/core/navigation/breadcrumbs/breadcrumbs.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/core/navigation/breadcrumbs/breadcrumbs.component.ts index 0b4cd23a09b..706a671ddc5 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/core/navigation/breadcrumbs/breadcrumbs.component.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/core/navigation/breadcrumbs/breadcrumbs.component.ts @@ -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]; } diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/api/nvmeof.service.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/api/nvmeof.service.spec.ts index 37e8932c365..aefdf0739d5 100755 --- a/src/pybind/mgr/dashboard/frontend/src/app/shared/api/nvmeof.service.spec.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/api/nvmeof.service.spec.ts @@ -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', () => {