]> git-server-git.apps.pok.os.sepia.ceph.com Git - ceph.git/blob
32097cd044a0ab1777c4b4c65f6bbd6d38e3f735
[ceph.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 @Component({
29   selector: 'cd-nvmeof-subsystems-form',
30   templateUrl: './nvmeof-subsystems-form.component.html',
31   styleUrls: ['./nvmeof-subsystems-form.component.scss'],
32   standalone: false
33 })
34 export class NvmeofSubsystemsFormComponent implements OnInit {
35   subsystemForm: CdFormGroup;
36   action: string;
37   group: string;
38   steps: Step[] = [
39     {
40       label: $localize`Subsystem details`,
41       complete: false,
42       invalid: false
43     },
44     {
45       label: $localize`Host access control`,
46       invalid: false
47     },
48     {
49       label: $localize`Authentication`,
50       complete: false
51     }
52   ];
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;
57
58   @ViewChild(TearsheetComponent) tearsheet!: TearsheetComponent;
59
60   constructor(
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
69   ) {}
70
71   ngOnInit() {
72     this.route.queryParams.pipe(takeUntilDestroyed(this.destroyRef)).subscribe((params) => {
73       this.group = params?.['group'];
74     });
75   }
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(','),
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.addSubsystemInitiators(
101                     `${payload.nqn}.${this.group}`,
102                     initiatorRequest
103                   )
104               }
105             ],
106             stepResults
107           ).subscribe({
108             complete: () => this.showFinalNotification(stepResults)
109           });
110         },
111         error: (err) => {
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`,
117             errorMsg
118           );
119           this.isSubmitLoading = false;
120           this.router.navigate(['block/nvmeof/gateways'], {
121             queryParams: { group: this.group, tab: 'subsystem' }
122           });
123         }
124       });
125   }
126
127   private runSequentialSteps(
128     steps: { step: string; call: () => Observable<any> }[],
129     stepResults: StepResult[]
130   ): Observable<void> {
131     return from(steps).pipe(
132       concatMap((step) =>
133         step.call().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 });
139             return of(null);
140           })
141         )
142       ),
143       map(() => void 0)
144     );
145   }
146
147   private showFinalNotification(stepResults: StepResult[]) {
148     this.isSubmitLoading = false;
149
150     const messageLines = stepResults.map((stepResult) =>
151       stepResult.success
152         ? $localize`<div>${stepResult.step} step created successfully</div><br/>`
153         : $localize`<div>${stepResult.step} step failed: <code>${stepResult.error}</code></div><br/>`
154     );
155
156     const rawHtml = messageLines.join('<br/>');
157     const sanitizedHtml = this.sanitizer.sanitize(SecurityContext.HTML, rawHtml) ?? '';
158
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`;
164
165     this.notificationService.show(type, title, sanitizedHtml);
166     this.router.navigate(['block/nvmeof/gateways'], {
167       queryParams: {
168         group: this.group,
169         tab: 'subsystem',
170         nqn: stepResults[0]?.success ? this.lastCreatedNqn : null
171       }
172     });
173   }
174 }