]> git-server-git.apps.pok.os.sepia.ceph.com Git - ceph-ci.git/blob
468cf64de52df47374f1952cb0c6e9d5be5711a1
[ceph-ci.git] /
1 import { Component, OnDestroy, OnInit, TemplateRef, ViewChild } from '@angular/core';
2 import { ActivatedRoute, Router } from '@angular/router';
3 import { ActionLabelsI18n, URLVerbs } from '~/app/shared/constants/app.constants';
4 import { CdTableSelection } from '~/app/shared/models/cd-table-selection';
5 import {
6   NvmeofSubsystem,
7   NvmeofSubsystemInitiator,
8   getSubsystemAuthStatus
9 } from '~/app/shared/models/nvmeof';
10 import { Permissions } from '~/app/shared/models/permissions';
11 import { AuthStorageService } from '~/app/shared/services/auth-storage.service';
12 import { ListWithDetails } from '~/app/shared/classes/list-with-details.class';
13 import { CdTableFetchDataContext } from '~/app/shared/models/cd-table-fetch-data-context';
14 import { CdTableAction } from '~/app/shared/models/cd-table-action';
15
16 import { Icons } from '~/app/shared/enum/icons.enum';
17 import { NvmeofSubsystemAuthType } from '~/app/shared/enum/nvmeof.enum';
18 import { DeleteConfirmationModalComponent } from '~/app/shared/components/delete-confirmation-modal/delete-confirmation-modal.component';
19 import { FinishedTask } from '~/app/shared/models/finished-task';
20 import { TaskWrapperService } from '~/app/shared/services/task-wrapper.service';
21 import { NvmeofService, GroupsComboboxItem } from '~/app/shared/api/nvmeof.service';
22 import { NotificationService } from '~/app/shared/services/notification.service';
23 import { NotificationType } from '~/app/shared/enum/notification-type.enum';
24 import { ModalCdsService } from '~/app/shared/services/modal-cds.service';
25 import { CephServiceSpec } from '~/app/shared/models/service.interface';
26 import { BehaviorSubject, forkJoin, Observable, of, Subject } from 'rxjs';
27 import { catchError, map, switchMap, takeUntil } from 'rxjs/operators';
28 import { DeletionImpact } from '~/app/shared/enum/delete-confirmation-modal-impact.enum';
29
30 const BASE_URL = 'block/nvmeof/subsystems';
31 const DEFAULT_PLACEHOLDER = $localize`Enter group name`;
32
33 @Component({
34   selector: 'cd-nvmeof-subsystems',
35   templateUrl: './nvmeof-subsystems.component.html',
36   styleUrls: ['./nvmeof-subsystems.component.scss'],
37   standalone: false
38 })
39 export class NvmeofSubsystemsComponent extends ListWithDetails implements OnInit, OnDestroy {
40   @ViewChild('authenticationTpl', { static: true })
41   authenticationTpl: TemplateRef<any>;
42
43   @ViewChild('encryptionTpl', { static: true })
44   encryptionTpl: TemplateRef<any>;
45
46   @ViewChild('deleteTpl', { static: true })
47   deleteTpl: TemplateRef<any>;
48
49   subsystemsColumns: any;
50   permissions: Permissions;
51   selection = new CdTableSelection();
52   tableActions: CdTableAction[];
53   subsystemDetails: any[];
54   context: CdTableFetchDataContext;
55   gwGroups: GroupsComboboxItem[] = [];
56   group: string = null;
57   gwGroupsEmpty: boolean = false;
58   gwGroupPlaceholder: string = DEFAULT_PLACEHOLDER;
59   authType = NvmeofSubsystemAuthType;
60   subsystems$: Observable<(NvmeofSubsystem & { gw_group?: string; initiator_count?: number })[]>;
61   private subsystemSubject = new BehaviorSubject<void>(undefined);
62
63   private destroy$ = new Subject<void>();
64
65   constructor(
66     private nvmeofService: NvmeofService,
67     private authStorageService: AuthStorageService,
68     public actionLabels: ActionLabelsI18n,
69     private router: Router,
70     private route: ActivatedRoute,
71     private modalService: ModalCdsService,
72     private taskWrapper: TaskWrapperService,
73     private notificationService: NotificationService
74   ) {
75     super();
76     this.permissions = this.authStorageService.getPermissions();
77   }
78
79   ngOnInit() {
80     this.route.queryParams.pipe(takeUntil(this.destroy$)).subscribe((params) => {
81       if (params?.['group']) this.onGroupSelection({ content: params?.['group'] });
82     });
83     this.setGatewayGroups();
84     this.subsystemsColumns = [
85       {
86         name: $localize`Subsystem NQN`,
87         prop: 'nqn',
88         flexGrow: 2
89       },
90       {
91         name: $localize`Gateway group`,
92         prop: 'gw_group'
93       },
94       {
95         name: $localize`Initiators`,
96         prop: 'initiator_count'
97       },
98
99       {
100         name: $localize`Namespaces`,
101         prop: 'namespace_count'
102       },
103       {
104         name: $localize`Authentication`,
105         prop: 'authentication',
106         cellTemplate: this.authenticationTpl
107       },
108       {
109         name: $localize`Traffic encryption`,
110         prop: 'encryption',
111         cellTemplate: this.encryptionTpl
112       }
113     ];
114
115     this.tableActions = [
116       {
117         name: this.actionLabels.CREATE,
118         permission: 'create',
119         icon: Icons.add,
120         click: () =>
121           this.router.navigate([BASE_URL, { outlets: { modal: [URLVerbs.CREATE] } }], {
122             queryParams: { group: this.group }
123           }),
124         canBePrimary: (selection: CdTableSelection) => !selection.hasSelection,
125         disable: () => !this.group
126       },
127       {
128         name: this.actionLabels.DELETE,
129         permission: 'delete',
130         icon: Icons.destroy,
131         click: () => this.deleteSubsystemModal()
132       }
133     ];
134
135     this.subsystems$ = this.subsystemSubject.pipe(
136       switchMap(() => {
137         if (!this.group) {
138           return of([]);
139         }
140         return this.nvmeofService.listSubsystems(this.group).pipe(
141           switchMap((subsystems: NvmeofSubsystem[] | NvmeofSubsystem) => {
142             const subs = Array.isArray(subsystems) ? subsystems : [subsystems];
143             if (subs.length === 0) return of([]);
144             return forkJoin(subs.map((sub) => this.enrichSubsystemWithInitiators(sub)));
145           }),
146           catchError((error) => {
147             this.notificationService.show(
148               NotificationType.error,
149               $localize`Unable to fetch Gateway group`,
150               $localize`Gateway group does not exist`
151             );
152             this.handleError(error);
153             return of([]);
154           })
155         );
156       }),
157       takeUntil(this.destroy$)
158     );
159   }
160
161   updateSelection(selection: CdTableSelection) {
162     this.selection = selection;
163   }
164
165   getSubsystems() {
166     this.subsystemSubject.next();
167   }
168
169   fetchData() {
170     this.subsystemSubject.next();
171   }
172
173   deleteSubsystemModal() {
174     const subsystem = this.selection.first();
175     this.modalService.show(DeleteConfirmationModalComponent, {
176       itemDescription: $localize`Subsystem`,
177       impact: DeletionImpact.high,
178       bodyTemplate: this.deleteTpl,
179       itemNames: [subsystem.nqn],
180       actionDescription: 'delete',
181       bodyContext: {
182         deletionMessage: $localize`Deleting <strong>${subsystem.nqn}</strong> will remove all associated configurations and resources. Dependent services may stop working. This action cannot be undone.`
183       },
184       submitActionObservable: () =>
185         this.taskWrapper.wrapTaskAroundCall({
186           task: new FinishedTask('nvmeof/subsystem/delete', { nqn: subsystem.nqn }),
187           call: this.nvmeofService.deleteSubsystem(subsystem.nqn, this.group)
188         })
189     });
190   }
191
192   onGroupSelection(selected: GroupsComboboxItem) {
193     selected.selected = true;
194     this.group = selected.content;
195     this.getSubsystems();
196   }
197
198   onGroupClear() {
199     this.group = null;
200     this.getSubsystems();
201   }
202
203   setGatewayGroups() {
204     this.nvmeofService
205       .listGatewayGroups()
206       .pipe(takeUntil(this.destroy$))
207       .subscribe({
208         next: (response: CephServiceSpec[][]) => this.handleGatewayGroupsSuccess(response),
209         error: (error) => this.handleGatewayGroupsError(error)
210       });
211   }
212
213   handleGatewayGroupsSuccess(response: CephServiceSpec[][]) {
214     if (response?.[0]?.length) {
215       this.gwGroups = this.nvmeofService.formatGwGroupsList(response);
216     } else {
217       this.gwGroups = [];
218     }
219     this.updateGroupSelectionState();
220   }
221
222   updateGroupSelectionState() {
223     if (!this.group && this.gwGroups.length) {
224       this.onGroupSelection(this.gwGroups[0]);
225       this.gwGroupsEmpty = false;
226       this.gwGroupPlaceholder = DEFAULT_PLACEHOLDER;
227     } else {
228       this.gwGroupsEmpty = true;
229       this.gwGroupPlaceholder = $localize`No groups available`;
230     }
231   }
232
233   handleGatewayGroupsError(error: any) {
234     this.gwGroups = [];
235     this.gwGroupsEmpty = true;
236     this.gwGroupPlaceholder = $localize`Unable to fetch Gateway groups`;
237     this.handleError(error);
238   }
239
240   private handleError(error: any): void {
241     if (error?.preventDefault) {
242       error?.preventDefault?.();
243     }
244     this.context?.error?.(error);
245   }
246
247   private enrichSubsystemWithInitiators(sub: NvmeofSubsystem) {
248     return this.nvmeofService.getInitiators(sub.nqn, this.group).pipe(
249       catchError(() => of([])),
250       map((initiators: NvmeofSubsystemInitiator[] | { hosts?: NvmeofSubsystemInitiator[] }) => {
251         let count = 0;
252         if (Array.isArray(initiators)) count = initiators.length;
253         else if (initiators?.hosts && Array.isArray(initiators.hosts)) {
254           count = initiators.hosts.length;
255         }
256
257         return {
258           ...sub,
259           gw_group: this.group,
260           initiator_count: count,
261           auth: getSubsystemAuthStatus(sub, initiators)
262         } as NvmeofSubsystem & { initiator_count?: number; auth?: string };
263       })
264     );
265   }
266
267   ngOnDestroy() {
268     this.destroy$.next();
269     this.destroy$.complete();
270   }
271 }