From: Sagar Gopale Date: Mon, 6 Apr 2026 13:16:27 +0000 (+0530) Subject: mgr/dashboard: NVMe-oF Initiator Hostname Pre-validation X-Git-Url: http://git-server-git.apps.pok.os.sepia.ceph.com/?a=commitdiff_plain;h=ca99febcefb341a14ee3e077841ecdcbfc5c246b;p=ceph.git mgr/dashboard: NVMe-oF Initiator Hostname Pre-validation Fixes: https://tracker.ceph.com/issues/75923 Signed-off-by: Sagar Gopale --- diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-initiators-form/nvmeof-initiators-form.component.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-initiators-form/nvmeof-initiators-form.component.spec.ts index 4ef4f03929b3..8ec7fbb023f8 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-initiators-form/nvmeof-initiators-form.component.spec.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-initiators-form/nvmeof-initiators-form.component.spec.ts @@ -78,7 +78,7 @@ describe('NvmeofInitiatorsFormComponent', () => { describe('should test form', () => { beforeEach(() => { nvmeofService = TestBed.inject(NvmeofService); - spyOn(nvmeofService, 'addSubsystemInitiators').and.stub(); + spyOn(nvmeofService, 'addSubsystemInitiators').and.returnValue(of({})); }); it('should be creating request correctly', () => { @@ -118,5 +118,21 @@ describe('NvmeofInitiatorsFormComponent', () => { hosts: [{ dhchap_key: '', host_nqn: 'host2' }] }); }); + + it('should not submit when hostType is SPECIFIC and no host is provided', () => { + const subsystemNQN = 'nqn.test'; + component.subsystemNQN = subsystemNQN; + component.group = 'test-group'; + + const payload: any = { + hostType: HOST_TYPE.SPECIFIC, + addedHosts: [] + }; + + component.onSubmit(payload); + + expect(nvmeofService.addSubsystemInitiators).not.toHaveBeenCalled(); + expect(component.isSubmitLoading).toBe(false); + }); }); }); diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-initiators-form/nvmeof-initiators-form.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-initiators-form/nvmeof-initiators-form.component.ts index cbb5513c6ecd..d25ffa1368a6 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-initiators-form/nvmeof-initiators-form.component.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-initiators-form/nvmeof-initiators-form.component.ts @@ -109,10 +109,9 @@ export class NvmeofInitiatorsFormComponent implements OnInit { } onSubmit(payload: InitiatorsFormPayload) { - this.isSubmitLoading = true; const taskUrl = `nvmeof/initiator/add`; - const hostKeyList = payload.hostDchapKeyList || []; - const addedHosts = payload.addedHosts || []; + const hostKeyList = (payload.hostDchapKeyList || []).filter((host) => !!host?.host_nqn?.trim()); + const addedHosts = (payload.addedHosts || []).filter((host) => !!host?.trim()); const hosts = payload.hostType === HOST_TYPE.SPECIFIC ? hostKeyList.length @@ -120,6 +119,25 @@ export class NvmeofInitiatorsFormComponent implements OnInit { : addedHosts.map((host_nqn: string) => ({ host_nqn, dhchap_key: '' })) : []; + if (payload.hostType === HOST_TYPE.SPECIFIC && hosts.length === 0) { + const hostStepIndex = Math.max( + this.tearsheet?.getStepIndexByLabel(STEP_LABELS.HOSTS) ?? 0, + 0 + ); + const hostStepForm = this.tearsheet?.stepContents?.toArray()?.[hostStepIndex]?.stepComponent + ?.formGroup; + + hostStepForm?.markAllAsTouched(); + hostStepForm?.get('hostname')?.markAsTouched(); + hostStepForm?.get('hostname')?.updateValueAndValidity({ emitEvent: true }); + if (this.tearsheet) { + this.tearsheet.currentStep = hostStepIndex; + } + return; + } + + this.isSubmitLoading = true; + const request: SubsystemInitiatorRequest = { allow_all: payload.hostType === HOST_TYPE.ALL, hosts, diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/components/tearsheet/tearsheet.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/components/tearsheet/tearsheet.component.ts index e3c5fa46d428..39c7cc304a0c 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/shared/components/tearsheet/tearsheet.component.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/components/tearsheet/tearsheet.component.ts @@ -177,6 +177,15 @@ export class TearsheetComponent implements OnInit, AfterViewInit, OnDestroy { } handleSubmit() { + this.stepContents?.forEach((wrapper, index) => { + const form = wrapper.stepComponent?.formGroup; + if (!form) return; + + form.markAllAsTouched(); + form.updateValueAndValidity({ emitEvent: true }); + this._updateStepInvalid(index, form.invalid); + }); + if (this.steps.some((step) => step?.invalid)) return; const mergedPayloads = this.getMergedPayload();