From: Afreen Misbah Date: Wed, 28 Jan 2026 13:18:52 +0000 (+0530) Subject: mgr/dashboard: Add step 1 for subsystem form X-Git-Url: http://git-server-git.apps.pok.os.sepia.ceph.com/?a=commitdiff_plain;h=d93bcb4923cfd5a97e78548951164da0d6b35e4e;p=ceph.git mgr/dashboard: Add step 1 for subsystem form Fixes https://tracker.ceph.com/issues/74093 Fixes https://tracker.ceph.com/issues/74094 - updates tearsheet component css to match with carbon component - adds laoding state to submit button - adds support for step validation when angualr component are use for steps rather than plain html templates - adds step one of nvmeof Signed-off-by: Afreen Misbah --- 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 9725b2ab6bd4..f09a5fbf1a12 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 @@ -47,6 +47,10 @@ import { NvmeofNamespacesListComponent } from './nvmeof-namespaces-list/nvmeof-n import { NvmeofNamespacesFormComponent } from './nvmeof-namespaces-form/nvmeof-namespaces-form.component'; import { NvmeofInitiatorsListComponent } from './nvmeof-initiators-list/nvmeof-initiators-list.component'; import { NvmeofInitiatorsFormComponent } from './nvmeof-initiators-form/nvmeof-initiators-form.component'; +import { NvmeofGatewayGroupComponent } from './nvmeof-gateway-group/nvmeof-gateway-group.component'; +import { NvmeofSubsystemsStepOneComponent } from './nvmeof-subsystems-form/nvmeof-subsystem-step-1/nvmeof-subsystem-step-1.component'; +import { NvmeofGatewayNodeComponent } from './nvmeof-gateway-node/nvmeof-gateway-node.component'; +import { NvmeofGroupFormComponent } from './nvmeof-group-form /nvmeof-group-form.component'; import { ButtonModule, @@ -75,9 +79,6 @@ import SubtractFilled from '@carbon/icons/es/subtract--filled/32'; import Reset from '@carbon/icons/es/reset/32'; import SubtractAlt from '@carbon/icons/es/subtract--alt/20'; import ProgressBarRound from '@carbon/icons/es/progress-bar--round/32'; -import { NvmeofGatewayGroupComponent } from './nvmeof-gateway-group/nvmeof-gateway-group.component'; -import { NvmeofGroupFormComponent } from './nvmeof-group-form /nvmeof-group-form.component'; -import { NvmeofGatewayNodeComponent } from './nvmeof-gateway-node/nvmeof-gateway-node.component'; @NgModule({ imports: [ @@ -145,7 +146,8 @@ import { NvmeofGatewayNodeComponent } from './nvmeof-gateway-node/nvmeof-gateway NvmeofInitiatorsListComponent, NvmeofInitiatorsFormComponent, NvmeofGatewayNodeComponent, - NvmeofGroupFormComponent + NvmeofGroupFormComponent, + NvmeofSubsystemsStepOneComponent ], exports: [RbdConfigurationListComponent, RbdConfigurationFormComponent] }) 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 new file mode 100644 index 000000000000..a8c179ef9492 --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-subsystems-form/nvmeof-subsystem-step-1/nvmeof-subsystem-step-1.component.html @@ -0,0 +1,58 @@ + +
+
+
+
+

Subsystem details

+

Enter identifying and network details for this subsystem.

+
+
+ + Subsystem NQN (NVMe Qualified Name) + + +
+
+ + Gateway group + + +
+
+
+
+ + +@for (err of formGroup.get('nqn').errors | keyvalue; track err.key) { +{{ INVALID_TEXTS[err.key] }} +} + diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-subsystems-form/nvmeof-subsystem-step-1/nvmeof-subsystem-step-1.component.scss b/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-subsystems-form/nvmeof-subsystem-step-1/nvmeof-subsystem-step-1.component.scss new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-subsystems-form/nvmeof-subsystem-step-1/nvmeof-subsystem-step-1.component.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-subsystems-form/nvmeof-subsystem-step-1/nvmeof-subsystem-step-1.component.spec.ts new file mode 100644 index 000000000000..5e4c78405de0 --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-subsystems-form/nvmeof-subsystem-step-1/nvmeof-subsystem-step-1.component.spec.ts @@ -0,0 +1,65 @@ +import { HttpClientTestingModule } from '@angular/common/http/testing'; +import { ReactiveFormsModule } from '@angular/forms'; +import { RouterTestingModule } from '@angular/router/testing'; +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { ToastrModule } from 'ngx-toastr'; + +import { NgbActiveModal, NgbTypeaheadModule } from '@ng-bootstrap/ng-bootstrap'; + +import { CdFormGroup } from '~/app/shared/forms/cd-form-group'; +import { SharedModule } from '~/app/shared/shared.module'; +import { NvmeofSubsystemsStepOneComponent } from './nvmeof-subsystem-step-1.component'; +import { FormHelper } from '~/testing/unit-test-helper'; +import { NvmeofService } from '~/app/shared/api/nvmeof.service'; +import { GridModule, InputModule } from 'carbon-components-angular'; + +describe('NvmeofSubsystemsStepOneComponent', () => { + let component: NvmeofSubsystemsStepOneComponent; + let fixture: ComponentFixture; + let nvmeofService: NvmeofService; + let form: CdFormGroup; + let formHelper: FormHelper; + const mockGroupName = 'default'; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [NvmeofSubsystemsStepOneComponent], + providers: [NgbActiveModal], + imports: [ + HttpClientTestingModule, + NgbTypeaheadModule, + ReactiveFormsModule, + RouterTestingModule, + SharedModule, + InputModule, + GridModule, + ToastrModule.forRoot() + ] + }).compileComponents(); + + fixture = TestBed.createComponent(NvmeofSubsystemsStepOneComponent); + component = fixture.componentInstance; + component.ngOnInit(); + form = component.formGroup; + formHelper = new FormHelper(form); + fixture.detectChanges(); + component.group = mockGroupName; + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); + + describe('should test form', () => { + beforeEach(() => { + nvmeofService = TestBed.inject(NvmeofService); + spyOn(nvmeofService, 'createSubsystem').and.stub(); + }); + + it('should give error on invalid nqn', () => { + formHelper.setValue('nqn', 'nqn:2001-07.com.ceph:'); + formHelper.expectError('nqn', 'nqnPattern'); + }); + }); +}); diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-subsystems-form/nvmeof-subsystem-step-1/nvmeof-subsystem-step-1.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-subsystems-form/nvmeof-subsystem-step-1/nvmeof-subsystem-step-1.component.ts new file mode 100644 index 000000000000..94cd4442ec1d --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-subsystems-form/nvmeof-subsystem-step-1/nvmeof-subsystem-step-1.component.ts @@ -0,0 +1,73 @@ +import { Component, Input, OnInit } from '@angular/core'; +import { UntypedFormControl, Validators } from '@angular/forms'; +import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'; + +import { ActionLabelsI18n } from '~/app/shared/constants/app.constants'; +import { CdFormGroup } from '~/app/shared/forms/cd-form-group'; +import { CdValidators } from '~/app/shared/forms/cd-validators'; + +import { NvmeofService } from '~/app/shared/api/nvmeof.service'; +import { TearsheetStep } from '~/app/shared/models/tearsheet-step'; + +@Component({ + selector: 'cd-nvmeof-subsystem-step-one', + templateUrl: './nvmeof-subsystem-step-1.component.html', + styleUrls: ['./nvmeof-subsystem-step-1.component.scss'], + standalone: false +}) +export class NvmeofSubsystemsStepOneComponent implements OnInit, TearsheetStep { + @Input() group!: string; + formGroup: CdFormGroup; + action: string; + pageURL: string; + INVALID_TEXTS = { + required: $localize`This field is required`, + nqnPattern: $localize`Expected NQN format is "nqn.$year-$month.$reverseDomainName:$utf8-string" or "nqn.2014-08.org.nvmexpress:uuid:$UUID-string"`, + notUnique: $localize`This NQN is already in use`, + maxLength: $localize`An NQN may not be more than 223 bytes in length.` + }; + + constructor( + public actionLabels: ActionLabelsI18n, + public activeModal: NgbActiveModal, + private nvmeofService: NvmeofService + ) {} + + DEFAULT_NQN = 'nqn.2001-07.com.ceph:' + Date.now(); + NQN_REGEX = /^nqn\.(19|20)\d\d-(0[1-9]|1[0-2])\.\D{2,3}(\.[A-Za-z0-9-]+)+(:[A-Za-z0-9-\.]+(:[A-Za-z0-9-\.]+)*)$/; + NQN_REGEX_UUID = /^nqn\.2014-08\.org\.nvmexpress:uuid:[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/; + + customNQNValidator = CdValidators.custom( + 'nqnPattern', + (nqnInput: string) => + !!nqnInput && !(this.NQN_REGEX.test(nqnInput) || this.NQN_REGEX_UUID.test(nqnInput)) + ); + + ngOnInit() { + this.createForm(); + } + + createForm() { + this.formGroup = new CdFormGroup({ + nqn: new UntypedFormControl(this.DEFAULT_NQN, { + validators: [ + this.customNQNValidator, + Validators.required, + CdValidators.custom( + 'maxLength', + (nqnInput: string) => new TextEncoder().encode(nqnInput).length > 223 + ) + ], + asyncValidators: [ + CdValidators.unique( + this.nvmeofService.isSubsystemPresent, + this.nvmeofService, + null, + null, + this.group + ) + ] + }) + }); + } +} diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-subsystems-form/nvmeof-subsystems-form.component.html b/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-subsystems-form/nvmeof-subsystems-form.component.html index ad17ad83deb8..04e7e38bbf36 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-subsystems-form/nvmeof-subsystems-form.component.html +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-subsystems-form/nvmeof-subsystems-form.component.html @@ -2,63 +2,12 @@ [steps]="steps" [title]="title" [description]="description" - (submitRequested)="onSubmit()" + [isSubmitLoading]="isSubmitLoading" + (submitRequested)="onSubmit($event)" > -
- -
- -
- - - A unique and permanent name for the lifetime of the subsystem. - - This field is required. - This NQN is already in use. - Expected NQN format
<nqn.$year-$month.$reverseDomainName:$utf8-string".> or
<nqn.2014-08.org.nvmexpress:uuid:$UUID-string".>
- An NQN may not be more than 223 bytes in length. -
-
- -
- -
- - The maximum namespaces per subsystem. Default is {{defaultMaxNamespace}} - The value must be at least 1. - The value must be a positive integer. -
-
-
+
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-subsystems-form/nvmeof-subsystems-form.component.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-subsystems-form/nvmeof-subsystems-form.component.spec.ts index 45e5a59b9d70..79cc234c4d4f 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-subsystems-form/nvmeof-subsystems-form.component.spec.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-subsystems-form/nvmeof-subsystems-form.component.spec.ts @@ -7,28 +7,30 @@ import { ToastrModule } from 'ngx-toastr'; import { NgbActiveModal, NgbTypeaheadModule } from '@ng-bootstrap/ng-bootstrap'; -import { CdFormGroup } from '~/app/shared/forms/cd-form-group'; import { SharedModule } from '~/app/shared/shared.module'; -import { NvmeofSubsystemsFormComponent } from './nvmeof-subsystems-form.component'; -import { FormHelper } from '~/testing/unit-test-helper'; import { - DEFAULT_MAX_NAMESPACE_PER_SUBSYSTEM, - NvmeofService -} from '~/app/shared/api/nvmeof.service'; + NvmeofSubsystemsFormComponent, + SubsystemPayload +} from './nvmeof-subsystems-form.component'; +import { NvmeofService } from '~/app/shared/api/nvmeof.service'; +import { NvmeofSubsystemsStepOneComponent } from './nvmeof-subsystem-step-1/nvmeof-subsystem-step-1.component'; +import { GridModule, InputModule } from 'carbon-components-angular'; describe('NvmeofSubsystemsFormComponent', () => { let component: NvmeofSubsystemsFormComponent; let fixture: ComponentFixture; let nvmeofService: NvmeofService; - let form: CdFormGroup; - let formHelper: FormHelper; const mockTimestamp = 1720693470789; const mockGroupName = 'default'; + const mockPayload: SubsystemPayload = { + nqn: '', + gw_group: mockGroupName + }; beforeEach(async () => { spyOn(Date, 'now').and.returnValue(mockTimestamp); await TestBed.configureTestingModule({ - declarations: [NvmeofSubsystemsFormComponent], + declarations: [NvmeofSubsystemsFormComponent, NvmeofSubsystemsStepOneComponent], providers: [NgbActiveModal], imports: [ HttpClientTestingModule, @@ -36,6 +38,8 @@ describe('NvmeofSubsystemsFormComponent', () => { ReactiveFormsModule, RouterTestingModule, SharedModule, + InputModule, + GridModule, ToastrModule.forRoot() ] }).compileComponents(); @@ -43,8 +47,6 @@ describe('NvmeofSubsystemsFormComponent', () => { fixture = TestBed.createComponent(NvmeofSubsystemsFormComponent); component = fixture.componentInstance; component.ngOnInit(); - form = component.subsystemForm; - formHelper = new FormHelper(form); fixture.detectChanges(); component.group = mockGroupName; }); @@ -61,43 +63,13 @@ describe('NvmeofSubsystemsFormComponent', () => { it('should be creating request correctly', () => { const expectedNqn = 'nqn.2001-07.com.ceph:' + mockTimestamp; - component.onSubmit(); + mockPayload['nqn'] = expectedNqn; + component.onSubmit(mockPayload); expect(nvmeofService.createSubsystem).toHaveBeenCalledWith({ nqn: expectedNqn, - max_namespaces: DEFAULT_MAX_NAMESPACE_PER_SUBSYSTEM, - enable_ha: true, - gw_group: mockGroupName + gw_group: mockGroupName, + enable_ha: true }); }); - - it('should give error on invalid nqn', () => { - formHelper.setValue('nqn', 'nqn:2001-07.com.ceph:'); - component.onSubmit(); - formHelper.expectError('nqn', 'pattern'); - }); - - it('should give error on invalid max_namespaces', () => { - formHelper.setValue('max_namespaces', -56); - component.onSubmit(); - formHelper.expectError('max_namespaces', 'pattern'); - }); - - it(`should not give error on max_namespaces greater than ${DEFAULT_MAX_NAMESPACE_PER_SUBSYSTEM}`, () => { - const expectedNqn = 'nqn.2001-07.com.ceph:' + mockTimestamp; - formHelper.setValue('max_namespaces', 600); - component.onSubmit(); - expect(nvmeofService.createSubsystem).toHaveBeenCalledWith({ - nqn: expectedNqn, - max_namespaces: DEFAULT_MAX_NAMESPACE_PER_SUBSYSTEM, - enable_ha: true, - gw_group: mockGroupName - }); - }); - - it('should give error on max_namespaces lesser than 1', () => { - formHelper.setValue('max_namespaces', 0); - component.onSubmit(); - formHelper.expectError('max_namespaces', 'min'); - }); }); }); diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-subsystems-form/nvmeof-subsystems-form.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-subsystems-form/nvmeof-subsystems-form.component.ts index b89e823b32e4..7f17d7da1c4e 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-subsystems-form/nvmeof-subsystems-form.component.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-subsystems-form/nvmeof-subsystems-form.component.ts @@ -1,22 +1,20 @@ -import { Component, DestroyRef, OnInit } from '@angular/core'; -import { FormControlStatus, UntypedFormControl, Validators } from '@angular/forms'; +import { Component, DestroyRef, OnInit, ViewChild } from '@angular/core'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'; import { ActionLabelsI18n, URLVerbs } from '~/app/shared/constants/app.constants'; import { CdFormGroup } from '~/app/shared/forms/cd-form-group'; -import { CdValidators } from '~/app/shared/forms/cd-validators'; -import { Permission } from '~/app/shared/models/permissions'; -import { AuthStorageService } from '~/app/shared/services/auth-storage.service'; -import { TaskWrapperService } from '~/app/shared/services/task-wrapper.service'; -import { FinishedTask } from '~/app/shared/models/finished-task'; import { ActivatedRoute, Router } from '@angular/router'; -import { - DEFAULT_MAX_NAMESPACE_PER_SUBSYSTEM, - NvmeofService -} from '~/app/shared/api/nvmeof.service'; import { Step } from 'carbon-components-angular'; -import { startWith } from 'rxjs/operators'; +import { FinishedTask } from '~/app/shared/models/finished-task'; +import { NvmeofService } from '~/app/shared/api/nvmeof.service'; +import { TaskWrapperService } from '~/app/shared/services/task-wrapper.service'; +import { TearsheetComponent } from '~/app/shared/components/tearsheet/tearsheet.component'; + +export type SubsystemPayload = { + nqn: string; + gw_group: string; +}; @Component({ selector: 'cd-nvmeof-subsystems-form', @@ -25,16 +23,12 @@ import { startWith } from 'rxjs/operators'; standalone: false }) export class NvmeofSubsystemsFormComponent implements OnInit { - permission: Permission; subsystemForm: CdFormGroup; action: string; - resource: string; - pageURL: string; - defaultMaxNamespace: number = DEFAULT_MAX_NAMESPACE_PER_SUBSYSTEM; group: string; steps: Step[] = [ { - label: $localize`Subsystem Details`, + label: $localize`Subsystem details`, complete: false, invalid: false }, @@ -47,114 +41,52 @@ export class NvmeofSubsystemsFormComponent implements OnInit { complete: false }, { - label: $localize`Advanced Options`, + label: $localize`Advanced options`, complete: false, secondaryLabel: $localize`Advanced` } ]; title: string = $localize`Create Subsystem`; description: string = $localize`Subsytems define how hosts connect to NVMe namespaces and ensure secure access to storage.`; + isSubmitLoading: boolean = false; + + @ViewChild(TearsheetComponent) tearsheet!: TearsheetComponent; constructor( - private authStorageService: AuthStorageService, public actionLabels: ActionLabelsI18n, public activeModal: NgbActiveModal, + private route: ActivatedRoute, + private destroyRef: DestroyRef, private nvmeofService: NvmeofService, private taskWrapperService: TaskWrapperService, - private router: Router, - private route: ActivatedRoute, - private destroyRef: DestroyRef - ) { - this.permission = this.authStorageService.getPermissions().nvmeof; - this.resource = $localize`Subsystem`; - this.pageURL = 'block/nvmeof/subsystems'; - } - - DEFAULT_NQN = 'nqn.2001-07.com.ceph:' + Date.now(); - NQN_REGEX = /^nqn\.(19|20)\d\d-(0[1-9]|1[0-2])\.\D{2,3}(\.[A-Za-z0-9-]+)+(:[A-Za-z0-9-\.]+(:[A-Za-z0-9-\.]+)*)$/; - NQN_REGEX_UUID = /^nqn\.2014-08\.org\.nvmexpress:uuid:[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/; - - customNQNValidator = CdValidators.custom( - 'pattern', - (nqnInput: string) => - !!nqnInput && !(this.NQN_REGEX.test(nqnInput) || this.NQN_REGEX_UUID.test(nqnInput)) - ); + private router: Router + ) {} ngOnInit() { this.route.queryParams.pipe(takeUntilDestroyed(this.destroyRef)).subscribe((params) => { this.group = params?.['group']; }); - - this.createForm(); - this.action = this.actionLabels.CREATE; - - this.subsystemForm.statusChanges - .pipe(startWith(this.subsystemForm.status), takeUntilDestroyed(this.destroyRef)) - .subscribe((status: FormControlStatus) => { - const step = this.steps[0]; - step.invalid = status === 'INVALID'; - }); - } - - createForm() { - this.subsystemForm = new CdFormGroup({ - nqn: new UntypedFormControl(this.DEFAULT_NQN, { - validators: [ - this.customNQNValidator, - Validators.required, - this.customNQNValidator, - CdValidators.custom( - 'maxLength', - (nqnInput: string) => new TextEncoder().encode(nqnInput).length > 223 - ) - ], - asyncValidators: [ - CdValidators.unique( - this.nvmeofService.isSubsystemPresent, - this.nvmeofService, - null, - null, - this.group - ) - ] - }), - max_namespaces: new UntypedFormControl(this.defaultMaxNamespace, { - validators: [CdValidators.number(false), Validators.min(1)] - }) - }); } - onSubmit() { + onSubmit(payload: SubsystemPayload) { const component = this; - const nqn: string = this.subsystemForm.getValue('nqn'); - const max_namespaces: number = Number(this.subsystemForm.getValue('max_namespaces')); + const pageURL = 'block/nvmeof/subsystems'; let taskUrl = `nvmeof/subsystem/${URLVerbs.CREATE}`; - - const request = { - nqn, - enable_ha: true, - gw_group: this.group, - max_namespaces - }; - - if (!max_namespaces) { - delete request.max_namespaces; - } + this.isSubmitLoading = true; this.taskWrapperService .wrapTaskAroundCall({ task: new FinishedTask(taskUrl, { - nqn: nqn + nqn: payload.nqn }), - call: this.nvmeofService.createSubsystem(request) + call: this.nvmeofService.createSubsystem({ ...payload, enable_ha: true }) }) .subscribe({ error() { - // instead have error message set, not setting form status INVALID - // which will show input as false - component.subsystemForm.setErrors({ cdSubmitButton: true }); + component.isSubmitLoading = false; }, complete: () => { - this.router.navigate([this.pageURL, { outlets: { modal: null } }]); + component.isSubmitLoading = false; + this.router.navigate([pageURL, { outlets: { modal: null } }]); } }); } diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/api/nvmeof.service.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/api/nvmeof.service.ts index e8721188dabe..cac534f42754 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/shared/api/nvmeof.service.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/api/nvmeof.service.ts @@ -91,12 +91,7 @@ export class NvmeofService { return this.http.get(`${API_PATH}/subsystem/${subsystemNQN}?gw_group=${group}`); } - createSubsystem(request: { - nqn: string; - enable_ha: boolean; - gw_group: string; - max_namespaces?: number; - }) { + createSubsystem(request: { nqn: string; enable_ha: boolean; gw_group: string }) { return this.http.post(`${API_PATH}/subsystem`, request, { observe: 'response' }); } diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/components/tearsheet-step/tearsheet-step.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/components/tearsheet-step/tearsheet-step.component.ts index 243ba899463f..946040a30569 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/shared/components/tearsheet-step/tearsheet-step.component.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/components/tearsheet-step/tearsheet-step.component.ts @@ -1,4 +1,5 @@ -import { Component, TemplateRef, ViewChild } from '@angular/core'; +import { Component, ContentChild, TemplateRef, ViewChild } from '@angular/core'; +import { TearsheetStep } from '../../models/tearsheet-step'; @Component({ selector: 'cd-tearsheet-step', @@ -9,4 +10,7 @@ import { Component, TemplateRef, ViewChild } from '@angular/core'; export class TearsheetStepComponent { @ViewChild(TemplateRef, { static: true }) template!: TemplateRef; + + @ContentChild('tearsheetStep') + stepComponent!: TearsheetStep; } diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/components/tearsheet/tearsheet.component.html b/src/pybind/mgr/dashboard/frontend/src/app/shared/components/tearsheet/tearsheet.component.html index 4a917b3229cc..19bff6c52f27 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/shared/components/tearsheet/tearsheet.component.html +++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/components/tearsheet/tearsheet.component.html @@ -130,8 +130,22 @@ @if (currentStep === lastStep) { + class="tearsheet-footer-submit" + [disabled]="isSubmitLoading" + (click)="handleSubmit()" + i18n> + @if (isSubmitLoading) { + + + {{submitButtonLoadingLabel}}... + } + @else { + {{submitButtonLabel}} + } + } @else {