From: pujashahu Date: Fri, 12 Jun 2026 22:05:13 +0000 (+0530) Subject: mgr/dashboard: Implement NVMe-oF Gateway Group Resource Overview Page X-Git-Tag: testing/wip-vshankar-testing-20260708.090602~39^2~2 X-Git-Url: http://git-server-git.apps.pok.os.sepia.ceph.com/?a=commitdiff_plain;h=eb36d949e945e6f24f4e1b4cf39b87a58d863826;p=ceph-ci.git mgr/dashboard: Implement NVMe-oF Gateway Group Resource Overview Page Fixes: https://tracker.ceph.com/issues/77383 Signed-off-by: pujaoshahu (cherry picked from commit 254ab5383505962460d06b483dd931bf91c1f0b8) --- diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-gateway-group/nvmeof-gateway-group.component.html b/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-gateway-group/nvmeof-gateway-group.component.html index 8d4007806bc..5084e98a14b 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-gateway-group/nvmeof-gateway-group.component.html +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-gateway-group/nvmeof-gateway-group.component.html @@ -1,4 +1,17 @@ + + + + + + = of([]); subject = new BehaviorSubject([]); - context!: CdTableFetchDataContext; + context?: CdTableFetchDataContext; gatewayGroupName = ''; subsystemCount = 0; gatewayCount = 0; + selectedGatewayDetails: DetailItem[] = []; private lastGroupCount = 0; viewUrl = `/${BASE_URL}/view`; @@ -127,6 +129,14 @@ export class NvmeofGatewayGroupComponent implements OnInit, OnDestroy { canBePrimary: (selection: CdTableSelection) => !selection.hasSelection }; + const editAction: CdTableAction = { + permission: 'update', + icon: Icons.edit, + routerLink: () => this.urlBuilder.getEdit(this.selection.first()?.name), + name: this.actionLabels.EDIT, + canBePrimary: (selection: CdTableSelection) => selection.hasSingleSelection + }; + const viewAction: CdTableAction = { permission: 'read', icon: Icons.eye, @@ -143,7 +153,7 @@ export class NvmeofGatewayGroupComponent implements OnInit, OnDestroy { canBePrimary: (selection: CdTableSelection) => selection.hasMultiSelection }; - this.tableActions = [createAction, viewAction, deleteAction]; + this.tableActions = [createAction, editAction, viewAction, deleteAction]; this.gatewayGroup$ = this.subject.pipe( switchMap(() => @@ -206,6 +216,7 @@ export class NvmeofGatewayGroupComponent implements OnInit, OnDestroy { updateSelection(selection: CdTableSelection): void { this.selection = selection; + this.selectedGatewayDetails = this.buildGatewayDetails(selection.first()); } deleteGatewayGroupModal() { @@ -324,6 +335,45 @@ export class NvmeofGatewayGroupComponent implements OnInit, OnDestroy { this.router.navigate([this.viewUrl, groupName]); } + editSelectedGatewayGroup(): void { + const selectedGroup = this.selection.first(); + if (!selectedGroup) { + return; + } + this.router.navigate([this.urlBuilder.getEdit(selectedGroup.name)]); + } + + private buildGatewayDetails(selectedGroup: any): DetailItem[] { + if (!selectedGroup) { + return []; + } + + const runningGateways = selectedGroup.statusCount?.running ?? 0; + const errorGateways = selectedGroup.statusCount?.error ?? 0; + const totalGateways = runningGateways + errorGateways; + + return [ + { + label: $localize`Gateway name`, + value: selectedGroup.name + }, + { + label: $localize`Gateway nodes`, + value: totalGateways + }, + { + label: $localize`Encryption`, + value: selectedGroup.spec?.enable_auth ? $localize`Enabled` : $localize`Disabled`, + type: 'status' + }, + { + label: $localize`mTLS`, + value: selectedGroup.spec?.enable_mtls ? $localize`Enabled` : $localize`Disabled`, + type: 'status' + } + ]; + } + ngOnDestroy(): void { this.destroy$.next(); this.destroy$.complete(); diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-gateway-node-add-modal/nvmeof-gateway-node-add-modal.component.html b/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-gateway-node-add-modal/nvmeof-gateway-node-add-modal.component.html new file mode 100644 index 00000000000..e69de29bb2d diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-gateway-node-add-modal/nvmeof-gateway-node-add-modal.component.scss b/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-gateway-node-add-modal/nvmeof-gateway-node-add-modal.component.scss new file mode 100644 index 00000000000..e69de29bb2d diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-gateway-node-add-modal/nvmeof-gateway-node-add-modal.component.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-gateway-node-add-modal/nvmeof-gateway-node-add-modal.component.spec.ts new file mode 100644 index 00000000000..e69de29bb2d diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-gateway-node-add-modal/nvmeof-gateway-node-add-modal.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-gateway-node-add-modal/nvmeof-gateway-node-add-modal.component.ts new file mode 100644 index 00000000000..e69de29bb2d diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-gateway-node/nvmeof-gateway-node-add-modal/nvmeof-gateway-node-add-modal.component.scss b/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-gateway-node/nvmeof-gateway-node-add-modal/nvmeof-gateway-node-add-modal.component.scss index f93358bb00d..83757259841 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-gateway-node/nvmeof-gateway-node-add-modal/nvmeof-gateway-node-add-modal.component.scss +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-gateway-node/nvmeof-gateway-node-add-modal/nvmeof-gateway-node-add-modal.component.scss @@ -1,3 +1,4 @@ +// Scoped to this component (ViewEncapsulation.None) cd-nvmeof-gateway-node-add-modal { .cds--modal-container { height: 80vh; diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/components/components.module.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/components/components.module.ts index 15452fcf264..294dd0ad5f2 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/shared/components/components.module.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/components/components.module.ts @@ -45,6 +45,7 @@ import { LayerModule, ThemeModule } from 'carbon-components-angular'; +import { ProductiveCardComponent } from './productive-card/productive-card.component'; import EditIcon from '@carbon/icons/es/edit/16'; import CodeIcon from '@carbon/icons/es/code/16'; import { MotdComponent } from '~/app/shared/components/motd/motd.component'; @@ -186,7 +187,8 @@ import { ProductiveCardComponent } from './productive-card/productive-card.compo TagModule, LinkModule, LayerModule, - ThemeModule + ThemeModule, + ProductiveCardComponent ], declarations: [ SparklineComponent, diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/components/details-card/details-card.component.html b/src/pybind/mgr/dashboard/frontend/src/app/shared/components/details-card/details-card.component.html index dd302459d61..d60b1f3b345 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/shared/components/details-card/details-card.component.html +++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/components/details-card/details-card.component.html @@ -2,23 +2,31 @@ @if (cardTitle) {

{{ cardTitle }}

+ @if (showEditButton) { + + } }
-
+
@if (details && details.length > 0) { -
+
@for (detail of getVisibleDetails(); track detail.label) { -
+
@switch (detail.type) { @case ('status') { -
+
{{ getDisplayValue(detail.value) }}
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/components/details-card/details-card.component.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/components/details-card/details-card.component.spec.ts index 8e1912a5da0..a8eb9996aa8 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/shared/components/details-card/details-card.component.spec.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/components/details-card/details-card.component.spec.ts @@ -36,6 +36,11 @@ describe('DetailsCardComponent', () => { expect(component.isStatusDisabled('Enabled')).toBe(false); }); + it('should emit editClicked event', () => { + spyOn(component.editClicked, 'emit'); + component.onEditClick(); + expect(component.editClicked.emit).toHaveBeenCalled(); + }); it('should handle empty details array', () => { component.details = []; const visible = component.getVisibleDetails(); diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/components/details-card/details-card.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/components/details-card/details-card.component.ts index 41d69190d5b..7cfed7235e0 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/shared/components/details-card/details-card.component.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/components/details-card/details-card.component.ts @@ -1,4 +1,4 @@ -import { Component, Input, TemplateRef } from '@angular/core'; +import { Component, EventEmitter, Input, Output, TemplateRef } from '@angular/core'; type DetailValue = any; type DetailTemplateContext = { $implicit: DetailValue; detail: DetailItem }; @@ -25,6 +25,25 @@ export class DetailsCardComponent { @Input() details?: DetailItem[] = []; + @Input() + showEditButton = false; + + @Input() + editButtonLabel = $localize`Edit`; + + @Input() + editButtonDisabled = false; + + @Input() + columns = 4; + + @Output() + editClicked = new EventEmitter(); + + onEditClick(): void { + this.editClicked.emit(); + } + @Input() columns = 4;