]> git-server-git.apps.pok.os.sepia.ceph.com Git - ceph-ci.git/blob
58853ad8cd5429c853899a8cb5928fbe722a7e9f
[ceph-ci.git] /
1 import { Component, Input, OnDestroy, OnInit, TemplateRef, ViewChild } from '@angular/core';
2 import { ActivatedRoute, Router } from '@angular/router';
3 import { NvmeofService, GroupsComboboxItem } from '~/app/shared/api/nvmeof.service';
4 import { DeleteConfirmationModalComponent } from '~/app/shared/components/delete-confirmation-modal/delete-confirmation-modal.component';
5 import { ActionLabelsI18n, URLVerbs } from '~/app/shared/constants/app.constants';
6 import { DeletionImpact } from '~/app/shared/enum/delete-confirmation-modal-impact.enum';
7 import { Icons } from '~/app/shared/enum/icons.enum';
8 import { CdTableAction } from '~/app/shared/models/cd-table-action';
9 import { CdTableSelection } from '~/app/shared/models/cd-table-selection';
10 import { FinishedTask } from '~/app/shared/models/finished-task';
11 import { NvmeofSubsystemNamespace } from '~/app/shared/models/nvmeof';
12 import { Permission } from '~/app/shared/models/permissions';
13 import { CephServiceSpec } from '~/app/shared/models/service.interface';
14 import { DimlessBinaryPipe } from '~/app/shared/pipes/dimless-binary.pipe';
15 import { AuthStorageService } from '~/app/shared/services/auth-storage.service';
16 import { ModalCdsService } from '~/app/shared/services/modal-cds.service';
17 import { TaskWrapperService } from '~/app/shared/services/task-wrapper.service';
18 import { BehaviorSubject, Observable, of, Subject } from 'rxjs';
19 import { catchError, map, switchMap, takeUntil } from 'rxjs/operators';
20
21 const BASE_URL = 'block/nvmeof/subsystems';
22 const DEFAULT_PLACEHOLDER = $localize`Enter group name`;
23
24 @Component({
25   selector: 'cd-nvmeof-namespaces-list',
26   templateUrl: './nvmeof-namespaces-list.component.html',
27   styleUrls: ['./nvmeof-namespaces-list.component.scss'],
28   standalone: false
29 })
30 export class NvmeofNamespacesListComponent implements OnInit, OnDestroy {
31   @Input()
32   subsystemNQN: string;
33   @Input()
34   group: string;
35   @ViewChild('deleteTpl', { static: true })
36   deleteTpl: TemplateRef<any>;
37   namespacesColumns: any;
38   tableActions: CdTableAction[];
39   selection = new CdTableSelection();
40   permission: Permission;
41   namespaces$: Observable<NvmeofSubsystemNamespace[]>;
42   private namespaceSubject = new BehaviorSubject<void>(undefined);
43
44   // Gateway group dropdown properties
45   gwGroups: GroupsComboboxItem[] = [];
46   gwGroupsEmpty: boolean = false;
47   gwGroupPlaceholder: string = DEFAULT_PLACEHOLDER;
48
49   private destroy$ = new Subject<void>();
50
51   constructor(
52     public actionLabels: ActionLabelsI18n,
53     private router: Router,
54     private route: ActivatedRoute,
55     private modalService: ModalCdsService,
56     private authStorageService: AuthStorageService,
57     private taskWrapper: TaskWrapperService,
58     private nvmeofService: NvmeofService,
59     private dimlessBinaryPipe: DimlessBinaryPipe
60   ) {
61     this.permission = this.authStorageService.getPermissions().nvmeof;
62   }
63
64   ngOnInit() {
65     this.route.queryParams.pipe(takeUntil(this.destroy$)).subscribe((params) => {
66       if (params?.['group']) this.onGroupSelection({ content: params?.['group'] });
67     });
68     this.setGatewayGroups();
69     this.namespacesColumns = [
70       {
71         name: $localize`Namespace ID`,
72         prop: 'nsid'
73       },
74       {
75         name: $localize`Size`,
76         prop: 'rbd_image_size',
77         pipe: this.dimlessBinaryPipe
78       },
79       {
80         name: $localize`Pool`,
81         prop: 'rbd_pool_name'
82       },
83       {
84         name: $localize`Image`,
85         prop: 'rbd_image_name'
86       },
87       {
88         name: $localize`Subsystem`,
89         prop: 'ns_subsystem_nqn'
90       }
91     ];
92     this.tableActions = [
93       {
94         name: this.actionLabels.CREATE,
95         permission: 'create',
96         icon: Icons.add,
97         click: () =>
98           this.router.navigate(
99             [BASE_URL, { outlets: { modal: [URLVerbs.CREATE, this.subsystemNQN, 'namespace'] } }],
100             { queryParams: { group: this.group } }
101           ),
102         canBePrimary: (selection: CdTableSelection) => !selection.hasSelection,
103         disable: () => !this.group
104       },
105       {
106         name: this.actionLabels.EDIT,
107         permission: 'update',
108         icon: Icons.edit,
109         click: () =>
110           this.router.navigate(
111             [
112               BASE_URL,
113               {
114                 outlets: {
115                   modal: [
116                     URLVerbs.EDIT,
117                     this.subsystemNQN,
118                     'namespace',
119                     this.selection.first().nsid
120                   ]
121                 }
122               }
123             ],
124             { queryParams: { group: this.group } }
125           )
126       },
127       {
128         name: this.actionLabels.DELETE,
129         permission: 'delete',
130         icon: Icons.destroy,
131         click: () => this.deleteNamespaceModal()
132       }
133     ];
134
135     this.namespaces$ = this.namespaceSubject.pipe(
136       switchMap(() => {
137         if (!this.group) {
138           return of([]);
139         }
140         return this.nvmeofService.listNamespaces(this.group).pipe(
141           map((res: NvmeofSubsystemNamespace[] | { namespaces: NvmeofSubsystemNamespace[] }) => {
142             return Array.isArray(res) ? res : res.namespaces || [];
143           }),
144           catchError(() => of([]))
145         );
146       }),
147       takeUntil(this.destroy$)
148     );
149   }
150
151   updateSelection(selection: CdTableSelection) {
152     this.selection = selection;
153   }
154
155   listNamespaces() {
156     this.namespaceSubject.next();
157   }
158
159   fetchData() {
160     this.namespaceSubject.next();
161   }
162
163   // Gateway groups methods
164   onGroupSelection(selected: GroupsComboboxItem) {
165     selected.selected = true;
166     this.group = selected.content;
167     this.listNamespaces();
168   }
169
170   onGroupClear() {
171     this.group = null;
172     this.listNamespaces();
173   }
174
175   setGatewayGroups() {
176     this.nvmeofService
177       .listGatewayGroups()
178       .pipe(takeUntil(this.destroy$))
179       .subscribe({
180         next: (response: CephServiceSpec[][]) => this.handleGatewayGroupsSuccess(response),
181         error: (error) => this.handleGatewayGroupsError(error)
182       });
183   }
184
185   handleGatewayGroupsSuccess(response: CephServiceSpec[][]) {
186     if (response?.[0]?.length) {
187       this.gwGroups = this.nvmeofService.formatGwGroupsList(response);
188     } else {
189       this.gwGroups = [];
190     }
191     this.updateGroupSelectionState();
192   }
193
194   updateGroupSelectionState() {
195     if (!this.group && this.gwGroups.length) {
196       this.onGroupSelection(this.gwGroups[0]);
197       this.gwGroupsEmpty = false;
198       this.gwGroupPlaceholder = DEFAULT_PLACEHOLDER;
199     } else if (!this.gwGroups.length) {
200       this.gwGroupsEmpty = true;
201       this.gwGroupPlaceholder = $localize`No groups available`;
202     }
203   }
204
205   handleGatewayGroupsError(error: any) {
206     this.gwGroups = [];
207     this.gwGroupsEmpty = true;
208     this.gwGroupPlaceholder = $localize`Unable to fetch Gateway groups`;
209     if (error?.preventDefault) {
210       error?.preventDefault?.();
211     }
212   }
213
214   deleteNamespaceModal() {
215     const namespace = this.selection.first();
216     const subsystemNqn = namespace.ns_subsystem_nqn;
217     this.modalService.show(DeleteConfirmationModalComponent, {
218       itemDescription: $localize`Namespace`,
219       impact: DeletionImpact.high,
220       bodyTemplate: this.deleteTpl,
221       itemNames: [namespace.nsid],
222       actionDescription: 'delete',
223       bodyContext: {
224         deletionMessage: $localize`Deleting the namespace <strong>${namespace.nsid}</strong> will permanently remove all resources, services, and configurations within it. This action cannot be undone.`
225       },
226       submitActionObservable: () =>
227         this.taskWrapper.wrapTaskAroundCall({
228           task: new FinishedTask('nvmeof/namespace/delete', {
229             nqn: subsystemNqn,
230             nsid: namespace.nsid
231           }),
232           call: this.nvmeofService.deleteNamespace(subsystemNqn, namespace.nsid, this.group)
233         })
234     });
235   }
236
237   ngOnDestroy() {
238     this.destroy$.next();
239     this.destroy$.complete();
240   }
241 }