]> git-server-git.apps.pok.os.sepia.ceph.com Git - ceph-ci.git/blob
fb25ba1f815d2825517db21e07d787fd7a19ad9e
[ceph-ci.git] /
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';
4
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';
17
18 export type SubsystemPayload = {
19   nqn: string;
20   gw_group: string;
21   subsystemDchapKey: string;
22   addedHosts: string[];
23   hostType: string;
24 };
25
26 type StepResult = { step: string; success: boolean; error?: string };
27
28 const PAGE_URL = 'block/nvmeof/subsystems';
29
30 @Component({
31   selector: 'cd-nvmeof-subsystems-form',
32   templateUrl: './nvmeof-subsystems-form.component.html',
33   styleUrls: ['./nvmeof-subsystems-form.component.scss'],
34   standalone: false
35 })
36 export class NvmeofSubsystemsFormComponent implements OnInit {
37   subsystemForm: CdFormGroup;
38   action: string;
39   group: string;
40   steps: Step[] = [
41     {
42       label: $localize`Subsystem details`,
43       complete: false,
44       invalid: false
45     },
46     {
47       label: $localize`Host access control`,
48       invalid: false
49     },
50     {
51       label: $localize`Authentication`,
52       complete: false
53     }
54   ];
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;
58
59   @ViewChild(TearsheetComponent) tearsheet!: TearsheetComponent;
60
61   constructor(
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
70   ) {}
71
72   ngOnInit() {
73     this.route.queryParams.pipe(takeUntilDestroyed(this.destroyRef)).subscribe((params) => {
74       this.group = params?.['group'];
75     });
76   }
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(','),
82       gw_group: this.group
83     };
84
85     this.nvmeofService
86       .createSubsystem({
87         nqn: payload.nqn,
88         gw_group: this.group,
89         enable_ha: true,
90         dhchap_key: payload.subsystemDchapKey
91       })
92       .subscribe({
93         next: () => {
94           stepResults.push({ step: this.steps[0].label, success: true });
95           this.runSequentialSteps(
96             [
97               {
98                 step: this.steps[1].label,
99                 call: () =>
100                   this.nvmeofService.addInitiators(`${payload.nqn}.${this.group}`, initiatorRequest)
101               }
102             ],
103             stepResults
104           ).subscribe({
105             complete: () => this.showFinalNotification(stepResults)
106           });
107         },
108         error: (err) => {
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`,
114             errorMsg
115           );
116           this.isSubmitLoading = false;
117           this.router.navigate([PAGE_URL, { outlets: { modal: null } }]);
118         }
119       });
120   }
121
122   private runSequentialSteps(
123     steps: { step: string; call: () => Observable<any> }[],
124     stepResults: StepResult[]
125   ): Observable<void> {
126     return from(steps).pipe(
127       concatMap((step) =>
128         step.call().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 });
134             return of(null);
135           })
136         )
137       ),
138       map(() => void 0)
139     );
140   }
141
142   private showFinalNotification(stepResults: StepResult[]) {
143     this.isSubmitLoading = false;
144
145     const messageLines = stepResults.map((stepResult) =>
146       stepResult.success
147         ? $localize`<div>${stepResult.step} step created successfully</div><br/>`
148         : $localize`<div>${stepResult.step} step failed: <code>${stepResult.error}</code></div><br/>`
149     );
150
151     const rawHtml = messageLines.join('<br/>');
152     const sanitizedHtml = this.sanitizer.sanitize(SecurityContext.HTML, rawHtml) ?? '';
153
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`;
159
160     this.notificationService.show(type, title, sanitizedHtml);
161     this.router.navigate([PAGE_URL, { outlets: { modal: null } }]);
162   }
163 }