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 };
29 selector: 'cd-nvmeof-subsystems-form',
30 templateUrl: './nvmeof-subsystems-form.component.html',
31 styleUrls: ['./nvmeof-subsystems-form.component.scss'],
34 export class NvmeofSubsystemsFormComponent implements OnInit {
35 subsystemForm: CdFormGroup;
40 label: $localize`Subsystem details`,
45 label: $localize`Host access control`,
49 label: $localize`Authentication`,
53 title: string = $localize`Create Subsystem`;
54 description: string = $localize`Subsytems define how hosts connect to NVMe namespaces and ensure secure access to storage.`;
55 isSubmitLoading: boolean = false;
56 private lastCreatedNqn: string;
58 @ViewChild(TearsheetComponent) tearsheet!: TearsheetComponent;
61 public actionLabels: ActionLabelsI18n,
62 public activeModal: NgbActiveModal,
63 private route: ActivatedRoute,
64 private destroyRef: DestroyRef,
65 private nvmeofService: NvmeofService,
66 private notificationService: NotificationService,
67 private router: Router,
68 private sanitizer: DomSanitizer
72 this.route.queryParams.pipe(takeUntilDestroyed(this.destroyRef)).subscribe((params) => {
73 this.group = params?.['group'];
76 onSubmit(payload: SubsystemPayload) {
77 this.isSubmitLoading = true;
78 this.lastCreatedNqn = payload.nqn;
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.addSubsystemInitiators(
101 `${payload.nqn}.${this.group}`,
108 complete: () => this.showFinalNotification(stepResults)
112 err.preventDefault();
113 const errorMsg = err?.error?.detail || $localize`Subsystem creation failed`;
114 this.notificationService.show(
115 NotificationType.error,
116 $localize`Subsystem creation failed`,
119 this.isSubmitLoading = false;
120 this.router.navigate(['block/nvmeof/gateways'], {
121 queryParams: { group: this.group, tab: 'subsystem' }
127 private runSequentialSteps(
128 steps: { step: string; call: () => Observable<any> }[],
129 stepResults: StepResult[]
130 ): Observable<void> {
131 return from(steps).pipe(
134 tap(() => stepResults.push({ step: step.step, success: true })),
135 catchError((err) => {
136 err.preventDefault();
137 const errorMsg = err?.error?.detail || '';
138 stepResults.push({ step: step.step, success: false, error: errorMsg });
147 private showFinalNotification(stepResults: StepResult[]) {
148 this.isSubmitLoading = false;
150 const messageLines = stepResults.map((stepResult) =>
152 ? $localize`<div>${stepResult.step} step created successfully</div><br/>`
153 : $localize`<div>${stepResult.step} step failed: <code>${stepResult.error}</code></div><br/>`
156 const rawHtml = messageLines.join('<br/>');
157 const sanitizedHtml = this.sanitizer.sanitize(SecurityContext.HTML, rawHtml) ?? '';
159 const hasFailure = stepResults.some((r) => !r.success);
160 const type = hasFailure ? NotificationType.error : NotificationType.success;
161 const title = hasFailure
162 ? $localize`Subsystem created (with errors)`
163 : $localize`Subsystem created`;
165 this.notificationService.show(type, title, sanitizedHtml);
166 this.router.navigate(['block/nvmeof/gateways'], {
170 nqn: stepResults[0]?.success ? this.lastCreatedNqn : null