]> git-server-git.apps.pok.os.sepia.ceph.com Git - ceph.git/blob
d2207c2f57750131e67fbb197feb698ccd535684
[ceph.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 { forkJoin, 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   subsystems: (NvmeofSubsystem & { gw_group?: string; initiator_count?: number })[] = [];
50   subsystemsColumns: any;
51   permissions: Permissions;
52   selection = new CdTableSelection();
53   tableActions: CdTableAction[];
54   subsystemDetails: any[];
55   context: CdTableFetchDataContext;
56   gwGroups: GroupsComboboxItem[] = [];
57   group: string = null;
58   gwGroupsEmpty: boolean = false;
59   gwGroupPlaceholder: string = DEFAULT_PLACEHOLDER;
60   authType = NvmeofSubsystemAuthType;
61
62   private destroy$ = new Subject<void>();
63
64   constructor(
65     private nvmeofService: NvmeofService,
66     private authStorageService: AuthStorageService,
67     public actionLabels: ActionLabelsI18n,
68     private router: Router,
69     private route: ActivatedRoute,
70     private modalService: ModalCdsService,
71     private taskWrapper: TaskWrapperService,
72     private notificationService: NotificationService
73   ) {
74     super();
75     this.permissions = this.authStorageService.getPermissions();
76   }
77
78   ngOnInit() {
79     this.route.queryParams.pipe(takeUntil(this.destroy$)).subscribe((params) => {
80       if (params?.['group']) this.onGroupSelection({ content: params?.['group'] });
81     });
82     this.setGatewayGroups();
83     this.subsystemsColumns = [
84       {
85         name: $localize`Subsystem NQN`,
86         prop: 'nqn',
87         flexGrow: 2
88       },
89       {
90         name: $localize`Gateway group`,
91         prop: 'gw_group'
92       },
93       {
94         name: $localize`Initiators`,
95         prop: 'initiator_count'
96       },
97
98       {
99         name: $localize`Namespaces`,
100         prop: 'namespace_count'
101       },
102       {
103         name: $localize`Authentication`,
104         prop: 'authentication',
105         cellTemplate: this.authenticationTpl
106       },
107       {
108         name: $localize`Traffic encryption`,
109         prop: 'encryption',
110         cellTemplate: this.encryptionTpl
111       }
112     ];
113
114     this.tableActions = [
115       {
116         name: this.actionLabels.CREATE,
117         permission: 'create',
118         icon: Icons.add,
119         click: () =>
120           this.router.navigate([BASE_URL, { outlets: { modal: [URLVerbs.CREATE] } }], {
121             queryParams: { group: this.group }
122           }),
123         canBePrimary: (selection: CdTableSelection) => !selection.hasSelection,
124         disable: () => !this.group
125       },
126       {
127         name: this.actionLabels.DELETE,
128         permission: 'delete',
129         icon: Icons.destroy,
130         click: () => this.deleteSubsystemModal()
131       }
132     ];
133   }
134
135   updateSelection(selection: CdTableSelection) {
136     this.selection = selection;
137   }
138
139   getSubsystems() {
140     if (this.group) {
141       this.nvmeofService
142         .listSubsystems(this.group)
143         .pipe(
144           switchMap((subsystems: NvmeofSubsystem[] | NvmeofSubsystem) => {
145             const subs = Array.isArray(subsystems) ? subsystems : [subsystems];
146             if (subs.length === 0) return of([]);
147
148             return forkJoin(subs.map((sub) => this.enrichSubsystemWithInitiators(sub)));
149           })
150         )
151         .pipe(takeUntil(this.destroy$))
152         .subscribe({
153           next: (subsystems: NvmeofSubsystem[]) => {
154             this.subsystems = subsystems;
155           },
156           error: (error) => {
157             this.subsystems = [];
158             this.notificationService.show(
159               NotificationType.error,
160               $localize`Unable to fetch Gateway group`,
161               $localize`Gateway group does not exist`
162             );
163             this.handleError(error);
164           }
165         });
166     } else {
167       this.subsystems = [];
168     }
169   }
170
171   deleteSubsystemModal() {
172     const subsystem = this.selection.first();
173     this.modalService.show(DeleteConfirmationModalComponent, {
174       itemDescription: $localize`Subsystem`,
175       impact: DeletionImpact.high,
176       bodyTemplate: this.deleteTpl,
177       itemNames: [subsystem.nqn],
178       actionDescription: 'delete',
179       bodyContext: {
180         deletionMessage: $localize`Deleting <strong>${subsystem.nqn}</strong> will remove all associated configurations and resources. Dependent services may stop working. This action cannot be undone.`
181       },
182       submitActionObservable: () =>
183         this.taskWrapper.wrapTaskAroundCall({
184           task: new FinishedTask('nvmeof/subsystem/delete', { nqn: subsystem.nqn }),
185           call: this.nvmeofService.deleteSubsystem(subsystem.nqn, this.group)
186         })
187     });
188   }
189
190   onGroupSelection(selected: GroupsComboboxItem) {
191     selected.selected = true;
192     this.group = selected.content;
193     this.getSubsystems();
194   }
195
196   onGroupClear() {
197     this.group = null;
198     this.getSubsystems();
199   }
200
201   setGatewayGroups() {
202     this.nvmeofService
203       .listGatewayGroups()
204       .pipe(takeUntil(this.destroy$))
205       .subscribe({
206         next: (response: CephServiceSpec[][]) => this.handleGatewayGroupsSuccess(response),
207         error: (error) => this.handleGatewayGroupsError(error)
208       });
209   }
210
211   handleGatewayGroupsSuccess(response: CephServiceSpec[][]) {
212     if (response?.[0]?.length) {
213       this.gwGroups = this.nvmeofService.formatGwGroupsList(response);
214     } else {
215       this.gwGroups = [];
216     }
217     this.updateGroupSelectionState();
218   }
219
220   updateGroupSelectionState() {
221     if (!this.group && this.gwGroups.length) {
222       this.onGroupSelection(this.gwGroups[0]);
223       this.gwGroupsEmpty = false;
224       this.gwGroupPlaceholder = DEFAULT_PLACEHOLDER;
225     } else {
226       this.gwGroupsEmpty = true;
227       this.gwGroupPlaceholder = $localize`No groups available`;
228     }
229   }
230
231   handleGatewayGroupsError(error: any) {
232     this.gwGroups = [];
233     this.gwGroupsEmpty = true;
234     this.gwGroupPlaceholder = $localize`Unable to fetch Gateway groups`;
235     this.handleError(error);
236   }
237
238   private handleError(error: any): void {
239     if (error?.preventDefault) {
240       error?.preventDefault?.();
241     }
242     this.context?.error?.(error);
243   }
244
245   private enrichSubsystemWithInitiators(sub: NvmeofSubsystem) {
246     return this.nvmeofService.getInitiators(sub.nqn, this.group).pipe(
247       catchError(() => of([])),
248       map((initiators: NvmeofSubsystemInitiator[] | { hosts?: NvmeofSubsystemInitiator[] }) => {
249         let count = 0;
250         if (Array.isArray(initiators)) count = initiators.length;
251         else if (initiators?.hosts && Array.isArray(initiators.hosts)) {
252           count = initiators.hosts.length;
253         }
254
255         return {
256           ...sub,
257           gw_group: this.group,
258           initiator_count: count,
259           auth: getSubsystemAuthStatus(sub, initiators)
260         } as NvmeofSubsystem & { initiator_count?: number; auth?: string };
261       })
262     );
263   }
264
265   ngOnDestroy() {
266     this.destroy$.next();
267     this.destroy$.complete();
268   }
269 }