1 import { Component, DestroyRef, OnInit, SecurityContext, ViewChild } from '@angular/core';
2 import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
3 import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap';
5 import { ActionLabelsI18n } from '~/app/shared/constants/app.constants';
6 import { CdFormGroup } from '~/app/shared/forms/cd-form-group';
7 import { ActivatedRoute, Router } from '@angular/router';
8 import { Step } from 'carbon-components-angular';
9 import { InitiatorRequest, NvmeofService } from '~/app/shared/api/nvmeof.service';
10 import { TearsheetComponent } from '~/app/shared/components/tearsheet/tearsheet.component';
11 import { HOST_TYPE } from '~/app/shared/models/nvmeof';
12 import { from, Observable, of } from 'rxjs';
13 import { NotificationService } from '~/app/shared/services/notification.service';
14 import { NotificationType } from '~/app/shared/enum/notification-type.enum';
15 import { catchError, concatMap, map, tap } from 'rxjs/operators';
16 import { DomSanitizer } from '@angular/platform-browser';
18 export type SubsystemPayload = {
21 subsystemDchapKey: string;
26 type StepResult = { step: string; success: boolean; error?: string };
28 const PAGE_URL = 'block/nvmeof/subsystems';
31 selector: 'cd-nvmeof-subsystems-form',
32 templateUrl: './nvmeof-subsystems-form.component.html',
33 styleUrls: ['./nvmeof-subsystems-form.component.scss'],
36 export class NvmeofSubsystemsFormComponent implements OnInit {
37 subsystemForm: CdFormGroup;
42 label: $localize`Subsystem details`,
47 label: $localize`Host access control`,
51 label: $localize`Authentication`,
55 title: string = $localize`Create Subsystem`;
56 description: string = $localize`Subsytems define how hosts connect to NVMe namespaces and ensure secure access to storage.`;
57 isSubmitLoading: boolean = false;
59 @ViewChild(TearsheetComponent) tearsheet!: TearsheetComponent;
62 public actionLabels: ActionLabelsI18n,
63 public activeModal: NgbActiveModal,
64 private route: ActivatedRoute,
65 private destroyRef: DestroyRef,
66 private nvmeofService: NvmeofService,
67 private notificationService: NotificationService,
68 private router: Router,
69 private sanitizer: DomSanitizer
73 this.route.queryParams.pipe(takeUntilDestroyed(this.destroyRef)).subscribe((params) => {
74 this.group = params?.['group'];
77 onSubmit(payload: SubsystemPayload) {
78 this.isSubmitLoading = true;
79 const stepResults: StepResult[] = [];
80 const initiatorRequest: InitiatorRequest = {
81 host_nqn: payload.hostType === HOST_TYPE.ALL ? '*' : payload.addedHosts.join(','),
90 dhchap_key: payload.subsystemDchapKey
94 stepResults.push({ step: this.steps[0].label, success: true });
95 this.runSequentialSteps(
98 step: this.steps[1].label,
100 this.nvmeofService.addInitiators(`${payload.nqn}.${this.group}`, initiatorRequest)
105 complete: () => this.showFinalNotification(stepResults)
109 err.preventDefault();
110 const errorMsg = err?.error?.detail || $localize`Subsystem creation failed`;
111 this.notificationService.show(
112 NotificationType.error,
113 $localize`Subsystem creation failed`,
116 this.isSubmitLoading = false;
117 this.router.navigate([PAGE_URL, { outlets: { modal: null } }]);
122 private runSequentialSteps(
123 steps: { step: string; call: () => Observable<any> }[],
124 stepResults: StepResult[]
125 ): Observable<void> {
126 return from(steps).pipe(
129 tap(() => stepResults.push({ step: step.step, success: true })),
130 catchError((err) => {
131 err.preventDefault();
132 const errorMsg = err?.error?.detail || '';
133 stepResults.push({ step: step.step, success: false, error: errorMsg });
142 private showFinalNotification(stepResults: StepResult[]) {
143 this.isSubmitLoading = false;
145 const messageLines = stepResults.map((stepResult) =>
147 ? $localize`<div>${stepResult.step} step created successfully</div><br/>`
148 : $localize`<div>${stepResult.step} step failed: <code>${stepResult.error}</code></div><br/>`
151 const rawHtml = messageLines.join('<br/>');
152 const sanitizedHtml = this.sanitizer.sanitize(SecurityContext.HTML, rawHtml) ?? '';
154 const hasFailure = stepResults.some((r) => !r.success);
155 const type = hasFailure ? NotificationType.error : NotificationType.success;
156 const title = hasFailure
157 ? $localize`Subsystem created (with errors)`
158 : $localize`Subsystem created`;
160 this.notificationService.show(type, title, sanitizedHtml);
161 this.router.navigate([PAGE_URL, { outlets: { modal: null } }]);