From c3efbebd047f83f9fa7cfa968d212eca1b6930d6 Mon Sep 17 00:00:00 2001 From: pujashahu Date: Fri, 26 Jun 2026 00:42:45 +0530 Subject: [PATCH] mgr/dashboard: Implement support for editing existing nvme-oF gateway groups through UI Fixes: https://tracker.ceph.com/issues/77386 Signed-off-by: pujaoshahu --- .../src/app/ceph/block/block.module.ts | 16 +- .../nvmeof-gateway-group.component.html | 15 +- .../nvmeof-gateway-group.component.ts | 45 +-- ...meof-gateway-node-add-modal.component.html | 0 ...meof-gateway-node-add-modal.component.scss | 0 ...f-gateway-node-add-modal.component.spec.ts | 0 ...nvmeof-gateway-node-add-modal.component.ts | 0 .../nvmeof-gateway-node.component.ts | 84 +++++- .../nvmeof-group-form.component.html | 71 ++--- .../nvmeof-group-form.component.ts | 267 +++++------------- .../nvmeof-subsystem-step-1.component.html | 4 +- .../service-form/service-form.component.html | 12 +- .../service-form.component.spec.ts | 3 +- .../service-form/service-form.component.ts | 29 ++ .../src/app/shared/constants/app.constants.ts | 2 + .../app/shared/models/service.interface.ts | 1 + 16 files changed, 261 insertions(+), 288 deletions(-) delete mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-gateway-node-add-modal/nvmeof-gateway-node-add-modal.component.html delete mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-gateway-node-add-modal/nvmeof-gateway-node-add-modal.component.scss delete mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-gateway-node-add-modal/nvmeof-gateway-node-add-modal.component.spec.ts delete mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-gateway-node-add-modal/nvmeof-gateway-node-add-modal.component.ts 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 7dbdfbbd518..368a7de95b7 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 @@ -10,7 +10,6 @@ import { ActionLabels, URLVerbs } from '~/app/shared/constants/app.constants'; import { FeatureTogglesGuardService } from '~/app/shared/services/feature-toggles-guard.service'; import { ModuleStatusGuardService } from '~/app/shared/services/module-status-guard.service'; import { SharedModule } from '~/app/shared/shared.module'; -import { TextLabelListComponent } from '~/app/shared/components/text-label-list/text-label-list.component'; import { IscsiSettingComponent } from './iscsi-setting/iscsi-setting.component'; import { IscsiTabsComponent } from './iscsi-tabs/iscsi-tabs.component'; import { IscsiTargetDetailsComponent } from './iscsi-target-details/iscsi-target-details.component'; @@ -148,9 +147,7 @@ import { NvmeofGatewayGroupFilterComponent } from './nvmeof-gateway-group-filter LayoutModule, ThemeModule, NvmeofSetupCardsComponent, - NvmeofGatewayGroupFilterComponent, - TextLabelListComponent - // ProductiveCardComponent + NvmeofGatewayGroupFilterComponent ], declarations: [ RbdListComponent, @@ -381,17 +378,6 @@ const routes: Routes = [ } } }, - { - path: `${URLVerbs.EDIT}/:name`, - component: NvmeofGroupFormComponent, - data: { - breadcrumbs: ActionLabels.EDIT, - pageHeader: { - title: $localize`Edit Gateway Group`, - description: $localize`Modify gateway group configuration.` - } - } - }, { path: `${URLVerbs.VIEW}/:group`, component: NvmeGatewayViewComponent, 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 435410a6aca..926e3267883 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,15 +1,4 @@ - - - - - - + [routerLink]="[viewUrl, value]" + (click)="$event.stopPropagation()"> {{ value }} diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-gateway-group/nvmeof-gateway-group.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-gateway-group/nvmeof-gateway-group.component.ts index 9b00fff7533..4ca09129451 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-gateway-group/nvmeof-gateway-group.component.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-gateway-group/nvmeof-gateway-group.component.ts @@ -76,7 +76,6 @@ export class NvmeofGatewayGroupComponent implements OnInit, OnDestroy { gatewayCount = 0; selectedGatewayDetails: DetailItem[] = []; private lastGroupCount = 0; - showDetailsCard = false; viewUrl = `/${BASE_URL}/view`; icons = Icons; @@ -130,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, @@ -146,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(() => @@ -210,7 +217,6 @@ export class NvmeofGatewayGroupComponent implements OnInit, OnDestroy { updateSelection(selection: CdTableSelection): void { this.selection = selection; this.selectedGatewayDetails = this.buildGatewayDetails(selection.first()); - this.showDetailsCard = false; } deleteGatewayGroupModal() { @@ -269,11 +275,6 @@ export class NvmeofGatewayGroupComponent implements OnInit, OnDestroy { }) .pipe( tap({ - next: () => { - this.table.data = this.table.data.filter( - (row: CephServiceSpec) => row.spec?.group !== selectedGroup.spec.group - ); - }, complete: () => { this.nvmeofStateService.requestRefresh(); } @@ -291,14 +292,6 @@ export class NvmeofGatewayGroupComponent implements OnInit, OnDestroy { }); } - onNameClick(row: any, event?: Event): void { - event?.preventDefault(); - event?.stopPropagation(); - this.selection = new CdTableSelection([row]); - this.selectedGatewayDetails = this.buildGatewayDetails(row); - this.showDetailsCard = true; - } - private checkNodesAvailability(): void { forkJoin([this.nvmeofService.listGatewayGroups(), this.hostService.getAllHosts()]).subscribe( ([groups, hosts]: [GatewayGroup[][], any[]]) => { @@ -342,6 +335,19 @@ export class NvmeofGatewayGroupComponent implements OnInit, OnDestroy { this.router.navigate([this.viewUrl, groupName]); } + ngOnDestroy(): void { + this.destroy$.next(); + this.destroy$.complete(); + } + + 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 []; @@ -367,14 +373,9 @@ export class NvmeofGatewayGroupComponent implements OnInit, OnDestroy { }, { label: $localize`mTLS`, - value: selectedGroup.spec?.enable_auth ? $localize`Enabled` : $localize`Disabled`, + 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 deleted file mode 100644 index e69de29bb2d..00000000000 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 deleted file mode 100644 index e69de29bb2d..00000000000 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 deleted file mode 100644 index e69de29bb2d..00000000000 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 deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-gateway-node/nvmeof-gateway-node.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-gateway-node/nvmeof-gateway-node.component.ts index 98e7190faf2..89fe4865356 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-gateway-node/nvmeof-gateway-node.component.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-gateway-node/nvmeof-gateway-node.component.ts @@ -2,9 +2,11 @@ import { Component, EventEmitter, Input, + OnChanges, OnDestroy, OnInit, Output, + SimpleChanges, TemplateRef, ViewChild } from '@angular/core'; @@ -46,7 +48,7 @@ import { DetailItem } from '~/app/shared/components/details-card/details-card.co styleUrls: ['./nvmeof-gateway-node.component.scss'], standalone: false }) -export class NvmeofGatewayNodeComponent implements OnInit, OnDestroy { +export class NvmeofGatewayNodeComponent implements OnInit, OnDestroy, OnChanges { @ViewChild(TableComponent, { static: true }) table!: TableComponent; @@ -67,6 +69,7 @@ export class NvmeofGatewayNodeComponent implements OnInit, OnDestroy { @Input() groupName: string | undefined; @Input() mode: 'selector' | 'details' = NvmeofGatewayNodeMode.SELECTOR; + @Input() preSelectedHostnames: string[] = []; usedHostnames: Set = new Set(); serviceSpec: CephServiceSpec | undefined; @@ -144,6 +147,43 @@ export class NvmeofGatewayNodeComponent implements OnInit, OnDestroy { ]; } + ngOnChanges(changes: SimpleChanges): void { + if ( + changes['preSelectedHostnames'] && + !changes['preSelectedHostnames'].firstChange && + changes['preSelectedHostnames'].currentValue?.length > 0 + ) { + this.table.refreshBtn(); + } + } + + private applyPreSelection(): void { + if (!this.preSelectedHostnames?.length || !this.table) { + return; + } + + const hostsToSelect = this.hosts.filter((host) => + this.preSelectedHostnames.includes(host.hostname) + ); + + if (hostsToSelect.length > 0) { + this.selection.selected = hostsToSelect; + this.selectionChange.emit(this.selection); + + setTimeout(() => { + hostsToSelect.forEach((host) => { + const rowIndex = this.table.model?.data?.findIndex( + (row: any) => _.get(row, [0, 'selected', 'hostname']) === host.hostname + ); + if (rowIndex > -1) { + this.table.model.selectRow(rowIndex, true); + } + }); + this.table.updateSelection.emit(this.selection); + }); + } + } + private setTableActions() { this.tableActions = [ { @@ -273,8 +313,11 @@ export class NvmeofGatewayNodeComponent implements OnInit, OnDestroy { this.sub.unsubscribe(); } + const useEditMode = + this.mode === NvmeofGatewayNodeMode.SELECTOR && this.preSelectedHostnames?.length > 0; + const fetchData$: Observable = - this.mode === NvmeofGatewayNodeMode.DETAILS + this.mode === NvmeofGatewayNodeMode.DETAILS || useEditMode ? this.nvmeofService.fetchHostsAndGroups() : this.nvmeofService.getAvailableHosts(this.tableContext?.toParams()); @@ -288,6 +331,8 @@ export class NvmeofGatewayNodeComponent implements OnInit, OnDestroy { next: (result: any) => { if (this.mode === NvmeofGatewayNodeMode.DETAILS) { this.processDetailsData(result.groups, result.hosts); + } else if (useEditMode) { + this.processEditModeData(result.groups, result.hosts); } else { this.hosts = result; this.count = this.hosts.length; @@ -298,6 +343,41 @@ export class NvmeofGatewayNodeComponent implements OnInit, OnDestroy { }); } + private processEditModeData(groups: any[][], hostList: Host[]) { + const groupList = groups?.[0] ?? []; + const preSelectedSet = new Set(this.preSelectedHostnames); + + const usedByOtherGroups = new Set(); + groupList.forEach((group: CephServiceSpec) => { + const hosts = group.placement?.hosts || []; + const isCurrentGroup = + hosts.every((h: string) => preSelectedSet.has(h)) && hosts.length === preSelectedSet.size; + + if (!isCurrentGroup) { + hosts.forEach((hostname: string) => usedByOtherGroups.add(hostname)); + + const label = group.placement?.label; + if (label) { + (hostList || []).forEach((host: Host) => { + if (host.labels?.includes(label as string)) { + usedByOtherGroups.add(host.hostname); + } + }); + } + } + }); + + this.hosts = (hostList || []).filter((host: Host) => { + const isPreSelected = preSelectedSet.has(host.hostname); + const isAvailable = !usedByOtherGroups.has(host.hostname); + return isPreSelected || isAvailable; + }); + + this.count = this.hosts.length; + this.hostsLoaded.emit(this.count); + this.applyPreSelection(); + } + private processDetailsData(groups: any[][], hostList: Host[]) { const groupList = groups?.[0] ?? []; diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-group-form/nvmeof-group-form.component.html b/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-group-form/nvmeof-group-form.component.html index 8cd803d79aa..7acb2ea574c 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-group-form/nvmeof-group-form.component.html +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-group-form/nvmeof-group-form.component.html @@ -50,40 +50,49 @@ - + + +
+
+ Enable encryption + +
+
+ + + @if (groupForm.controls.enableEncryption.value) {
- - - - - - - This field is required. - + [invalid]="encryptionConfigRef.isInvalid">Encryption configuration + + + This field is required.
- -
+
@@ -110,9 +119,7 @@
-
+
-
+
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-group-form/nvmeof-group-form.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-group-form/nvmeof-group-form.component.ts index f27cbec917d..ac97aa3a3b8 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-group-form/nvmeof-group-form.component.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-group-form/nvmeof-group-form.component.ts @@ -1,6 +1,5 @@ import { Component, OnInit, ViewChild } from '@angular/core'; import { UntypedFormControl, Validators } from '@angular/forms'; -import { ActivatedRoute, Router } from '@angular/router'; import { ActionLabelsI18n, URLVerbs } from '~/app/shared/constants/app.constants'; import { CdForm } from '~/app/shared/forms/cd-form'; import { CdFormGroup } from '~/app/shared/forms/cd-form-group'; @@ -11,13 +10,11 @@ import { NvmeofGatewayNodeComponent } from '../nvmeof-gateway-node/nvmeof-gatewa import { TaskWrapperService } from '~/app/shared/services/task-wrapper.service'; import { CephServiceService } from '~/app/shared/api/ceph-service.service'; import { FinishedTask } from '~/app/shared/models/finished-task'; +import { Router } from '@angular/router'; import { CdValidators } from '~/app/shared/forms/cd-validators'; import { NvmeofService } from '~/app/shared/api/nvmeof.service'; - -enum CertificateType { - internal = 'internal', - external = 'external' -} +import { Pool } from '../../pool/pool'; +import { CertificateType } from '~/app/shared/models/service.interface'; @Component({ selector: 'cd-nvmeof-group-form', @@ -28,19 +25,16 @@ enum CertificateType { export class NvmeofGroupFormComponent extends CdForm implements OnInit { @ViewChild(NvmeofGatewayNodeComponent) gatewayNodeComponent!: NvmeofGatewayNodeComponent; + readonly CertificateType = CertificateType; permission: Permission; groupForm!: CdFormGroup; action!: string; resource: string; - group: string; - pageURL: string; - hasAvailableNodes = true; - CertificateType = CertificateType; - editing = false; - gatewayGroupName = ''; - existingServiceData: any = null; - pools: Array<{ pool_name: string }> = [{ pool_name: 'rbd' }]; + group = ''; + pageURL = ''; + pools: Pool[] = []; poolsLoading = false; + hasAvailableNodes = true; constructor( private authStorageService: AuthStorageService, @@ -48,8 +42,7 @@ export class NvmeofGroupFormComponent extends CdForm implements OnInit { private taskWrapperService: TaskWrapperService, private cephServiceService: CephServiceService, private nvmeofService: NvmeofService, - private router: Router, - private route: ActivatedRoute + private router: Router ) { super(); this.permission = this.authStorageService.getPermissions().nvmeof; @@ -57,155 +50,52 @@ export class NvmeofGroupFormComponent extends CdForm implements OnInit { } ngOnInit() { - this.route.params.subscribe((params) => { - if (params['name']) { - this.editing = true; - this.gatewayGroupName = params['name']; - this.action = this.actionLabels.EDIT; - this.createForm(); - this.loadGatewayGroupData(params['name']); - } else { - this.editing = false; - this.action = this.actionLabels.CREATE; - this.gatewayGroupName = ''; - this.createForm(); - } - }); + this.action = this.actionLabels.CREATE; + this.createForm(); } createForm() { - const groupNameValidators = this.editing - ? [ - Validators.required, - (control) => { - const value = control.value; - return value && /[^a-zA-Z0-9_-]/.test(value) ? { invalidChars: true } : null; - } - ] - : [ + this.groupForm = new CdFormGroup({ + groupName: new UntypedFormControl( + null, + [ Validators.required, (control) => { const value = control.value; return value && /[^a-zA-Z0-9_-]/.test(value) ? { invalidChars: true } : null; } - ]; - - const groupNameAsyncValidators = this.editing - ? [] - : [CdValidators.unique(this.nvmeofService.exists, this.nvmeofService)]; - - this.groupForm = new CdFormGroup({ - groupName: new UntypedFormControl(null, groupNameValidators, groupNameAsyncValidators), - pool: new UntypedFormControl('rbd', { - validators: [Validators.required] - }), + ], + [CdValidators.unique(this.nvmeofService.exists, this.nvmeofService)] + ), unmanaged: new UntypedFormControl(false), enableEncryption: new UntypedFormControl(false), + encryptionConfig: new UntypedFormControl(null), + encryptionKey: new UntypedFormControl(null), enableMtls: new UntypedFormControl(false), - encryptionKey: new UntypedFormControl('', [ - CdValidators.composeIf( - { - enableEncryption: true - }, - [Validators.required] - ) - ]), certificateType: new UntypedFormControl(CertificateType.internal), + pool: new UntypedFormControl('rbd'), custom_sans: new UntypedFormControl([]), - rootCACert: new UntypedFormControl(null, [ - CdValidators.composeIf( - { - enableEncryption: true, - certificateType: 'external' - }, - [Validators.required] - ) - ]), - clientCert: new UntypedFormControl(null, [ - CdValidators.composeIf( - { - enableEncryption: true, - certificateType: 'external' - }, - [Validators.required] - ) - ]), - clientKey: new UntypedFormControl(null, [ - CdValidators.composeIf( - { - enableEncryption: true, - certificateType: 'external' - }, - [Validators.required] - ) - ]), - serverCert: new UntypedFormControl(null, [ - CdValidators.composeIf( - { - enableEncryption: true, - certificateType: 'external' - }, - [Validators.required] - ) - ]), - serverKey: new UntypedFormControl(null, [ - CdValidators.composeIf( - { - enableEncryption: true, - certificateType: CertificateType.external - }, - [Validators.required] - ) - ]) + rootCACert: new UntypedFormControl(null), + clientCert: new UntypedFormControl(null), + clientKey: new UntypedFormControl(null), + serverCert: new UntypedFormControl(null), + serverKey: new UntypedFormControl(null) }); - // Disable group name field in edit mode - if (this.editing) { - this.groupForm.get('groupName')?.disable(); - } - } - - loadGatewayGroupData(groupName: string) { - this.nvmeofService.listGatewayGroups().subscribe( - (gatewayGroups: any) => { - const groups = gatewayGroups?.[0] ?? []; - const group = groups.find((g: any) => g.spec?.group === groupName); - - if (group) { - this.existingServiceData = group; - const spec = group.spec || {}; - - // Prefill form with existing data - this.groupForm.patchValue({ - groupName: groupName, - pool: spec.pool || 'rbd', - unmanaged: spec.unmanaged || false, - enableEncryption: spec.ssl || false, - enableMtls: spec.ssl || false, - certificateType: - spec.certificate_source === 'inline' - ? CertificateType.external - : CertificateType.internal, - encryptionKey: spec.encryption_key || '', - custom_sans: spec.custom_sans || [] - }); - - // If external certificates, load them - if (spec.certificate_source === 'inline') { - this.groupForm.patchValue({ - rootCACert: spec.root_ca_cert || null, - clientCert: spec.client_cert || null, - clientKey: spec.client_key || null, - serverCert: spec.server_cert || null, - serverKey: spec.server_key || null - }); - } - } - }, - (_error) => { - // Error loading gateway group data + this.groupForm.get('enableEncryption')?.valueChanges.subscribe((enabled) => { + // Keep both legacy and new encryption fields in sync. + if (!enabled) { + return; + } + const encryptionConfigControl = this.groupForm.get('encryptionConfig'); + const encryptionKeyControl = this.groupForm.get('encryptionKey'); + if (!encryptionKeyControl?.value && encryptionConfigControl?.value) { + encryptionKeyControl.setValue(encryptionConfigControl.value, { emitEvent: false }); + } + if (!encryptionConfigControl?.value && encryptionKeyControl?.value) { + encryptionConfigControl.setValue(encryptionKeyControl.value, { emitEvent: false }); } - ); + }); } onHostsLoaded(count: number): void { @@ -249,41 +139,51 @@ export class NvmeofGroupFormComponent extends CdForm implements OnInit { return; } - const formValues = this.groupForm.getRawValue(); // Use getRawValue to get disabled fields too + const formValues = this.groupForm.value; const selectedHostnames = this.gatewayNodeComponent?.getSelectedHostnames() || []; if (selectedHostnames.length === 0) { this.groupForm.setErrors({ cdSubmitButton: true }); return; } - - const groupName = this.editing ? this.gatewayGroupName : formValues.groupName; - const pool = formValues.pool; - const serviceName = `${pool}.${groupName}`; - - let taskUrl = this.editing ? `service/${URLVerbs.EDIT}` : `service/${URLVerbs.CREATE}`; + let taskUrl = `service/${URLVerbs.CREATE}`; + const serviceName = `${formValues.groupName}`; const serviceSpec: Record = { service_type: 'nvmeof', service_id: serviceName, - pool: pool, - group: groupName, + group: formValues.groupName, placement: { hosts: selectedHostnames }, unmanaged: formValues.unmanaged }; + if (formValues.enableEncryption) { + const encryptionKey = formValues.encryptionKey || formValues.encryptionConfig; + if (encryptionKey) { + serviceSpec['encryption_key'] = encryptionKey; + } + } + if (formValues.enableMtls) { serviceSpec['ssl'] = true; serviceSpec['enable_auth'] = true; serviceSpec['certificate_source'] = formValues.certificateType === CertificateType.internal ? 'cephadm-signed' : 'inline'; - if (formValues.certificateType === CertificateType.internal) { - if (formValues.custom_sans && formValues.custom_sans.length > 0) { - serviceSpec['custom_sans'] = formValues.custom_sans; - } - } else { + if (formValues.pool) { + serviceSpec['pool'] = formValues.pool; + serviceSpec['service_id'] = `${formValues.pool}.${serviceName}`; + } + + if ( + formValues.certificateType === CertificateType.internal && + formValues.custom_sans?.length > 0 + ) { + serviceSpec['custom_sans'] = formValues.custom_sans; + } + + if (formValues.certificateType === CertificateType.external) { serviceSpec['root_ca_cert'] = formValues.rootCACert; serviceSpec['client_cert'] = formValues.clientCert; serviceSpec['client_key'] = formValues.clientKey; @@ -292,16 +192,12 @@ export class NvmeofGroupFormComponent extends CdForm implements OnInit { } } - const apiCall = this.editing - ? this.cephServiceService.update(serviceSpec) - : this.cephServiceService.create(serviceSpec); - this.taskWrapperService .wrapTaskAroundCall({ task: new FinishedTask(taskUrl, { service_name: `nvmeof.${serviceName}` }), - call: apiCall + call: this.cephServiceService.create(serviceSpec) }) .subscribe({ complete: () => { @@ -317,31 +213,20 @@ export class NvmeofGroupFormComponent extends CdForm implements OnInit { this.router.navigateByUrl('/block/nvmeof/gateways'); } - onFileUpload(event: any, controlName: string) { - const file = event.target.files[0]; - if (file) { - const reader = new FileReader(); - reader.onload = (e: any) => { - this.groupForm.get(controlName)?.setValue(e.target.result); - }; - reader.readAsText(file); - } + onCertificateTypeChange(type: CertificateType): void { + this.groupForm.get('certificateType')?.setValue(type); } - onCertificateTypeChange(type: CertificateType) { - this.groupForm.get('certificateType')?.setValue(type); - if (type === CertificateType.internal) { - this.groupForm.patchValue({ - rootCACert: null, - clientCert: null, - clientKey: null, - serverCert: null, - serverKey: null - }); - } else { - this.groupForm.patchValue({ - custom_sans: [] - }); + onFileUpload(event: Event, controlName: string): void { + const target = event.target as HTMLInputElement; + const file = target?.files?.[0]; + const control = this.groupForm.get(controlName); + if (!file || !control) { + return; } + + const reader = new FileReader(); + reader.onload = () => control.setValue(reader.result); + reader.readAsText(file, 'utf8'); } } diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-subsystems-form/nvmeof-subsystem-step-1/nvmeof-subsystem-step-1.component.html b/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-subsystems-form/nvmeof-subsystem-step-1/nvmeof-subsystem-step-1.component.html index 48b6326e937..61de7d2648d 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-subsystems-form/nvmeof-subsystem-step-1/nvmeof-subsystem-step-1.component.html +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-subsystems-form/nvmeof-subsystem-step-1/nvmeof-subsystem-step-1.component.html @@ -107,9 +107,7 @@
+ i18n-helperText> @if (realmList.length === 0) {