]> git.apps.os.sepia.ceph.com Git - ceph-ci.git/blob
05c93faf1617831466bbee17cb9bc0bbec2ebe38
[ceph-ci.git] /
1 import { Component, Input, OnChanges, OnInit, SimpleChanges } from '@angular/core';
2 import { BehaviorSubject, Observable, forkJoin, of } from 'rxjs';
3 import { catchError, shareReplay, switchMap, tap } from 'rxjs/operators';
4 import { CephfsSubvolumeGroupService } from '~/app/shared/api/cephfs-subvolume-group.service';
5 import { CephfsSubvolumeService } from '~/app/shared/api/cephfs-subvolume.service';
6 import { ActionLabelsI18n } from '~/app/shared/constants/app.constants';
7 import { CellTemplate } from '~/app/shared/enum/cell-template.enum';
8 import { Icons } from '~/app/shared/enum/icons.enum';
9 import { CdTableAction } from '~/app/shared/models/cd-table-action';
10 import { CdTableColumn } from '~/app/shared/models/cd-table-column';
11 import { CdTableFetchDataContext } from '~/app/shared/models/cd-table-fetch-data-context';
12 import { CephfsSubvolume, SubvolumeSnapshot } from '~/app/shared/models/cephfs-subvolume.model';
13 import { CephfsSubvolumeSnapshotsFormComponent } from './cephfs-subvolume-snapshots-form/cephfs-subvolume-snapshots-form.component';
14 import { ModalService } from '~/app/shared/services/modal.service';
15 import { AuthStorageService } from '~/app/shared/services/auth-storage.service';
16 import { Permissions } from '~/app/shared/models/permissions';
17 import { CdTableSelection } from '~/app/shared/models/cd-table-selection';
18 import { CdDatePipe } from '~/app/shared/pipes/cd-date.pipe';
19 import { NgbModalRef } from '@ng-bootstrap/ng-bootstrap';
20 import { CriticalConfirmationModalComponent } from '~/app/shared/components/critical-confirmation-modal/critical-confirmation-modal.component';
21 import { FinishedTask } from '~/app/shared/models/finished-task';
22 import { TaskWrapperService } from '~/app/shared/services/task-wrapper.service';
23 import { FormModalComponent } from '~/app/shared/components/form-modal/form-modal.component';
24 import { NotificationService } from '~/app/shared/services/notification.service';
25 import { NotificationType } from '~/app/shared/enum/notification-type.enum';
26 import moment from 'moment';
27 import { Validators } from '@angular/forms';
28 import { CdValidators } from '~/app/shared/forms/cd-validators';
29
30 @Component({
31   selector: 'cd-cephfs-subvolume-snapshots-list',
32   templateUrl: './cephfs-subvolume-snapshots-list.component.html',
33   styleUrls: ['./cephfs-subvolume-snapshots-list.component.scss']
34 })
35 export class CephfsSubvolumeSnapshotsListComponent implements OnInit, OnChanges {
36   @Input() fsName: string;
37
38   context: CdTableFetchDataContext;
39   columns: CdTableColumn[] = [];
40   tableActions: CdTableAction[];
41   selection = new CdTableSelection();
42   permissions: Permissions;
43   modalRef: NgbModalRef;
44
45   subVolumes$: Observable<CephfsSubvolume[]>;
46   snapshots$: Observable<any[]>;
47   snapshotSubject = new BehaviorSubject<SubvolumeSnapshot[]>([]);
48   subVolumeSubject = new BehaviorSubject<CephfsSubvolume[]>([]);
49
50   subvolumeGroupList: string[] = [];
51   subVolumesList: string[];
52
53   activeGroupName = '';
54   activeSubVolumeName = '';
55
56   isSubVolumesAvailable = false;
57   isLoading = true;
58
59   observables: any = [];
60
61   constructor(
62     private cephfsSubvolumeGroupService: CephfsSubvolumeGroupService,
63     private cephfsSubvolumeService: CephfsSubvolumeService,
64     private actionLabels: ActionLabelsI18n,
65     private modalService: ModalService,
66     private authStorageService: AuthStorageService,
67     private cdDatePipe: CdDatePipe,
68     private taskWrapper: TaskWrapperService,
69     private notificationService: NotificationService
70   ) {
71     this.permissions = this.authStorageService.getPermissions();
72   }
73
74   ngOnInit(): void {
75     this.columns = [
76       {
77         name: $localize`Name`,
78         prop: 'name',
79         flexGrow: 1
80       },
81       {
82         name: $localize`Created`,
83         prop: 'info.created_at',
84         flexGrow: 1,
85         pipe: this.cdDatePipe
86       },
87       {
88         name: $localize`Pending Clones`,
89         prop: 'info.has_pending_clones',
90         flexGrow: 0.5,
91         cellTransformation: CellTemplate.badge,
92         customTemplateConfig: {
93           map: {
94             no: { class: 'badge-success' },
95             yes: { class: 'badge-info' }
96           }
97         }
98       }
99     ];
100
101     this.tableActions = [
102       {
103         name: this.actionLabels.CREATE,
104         permission: 'create',
105         icon: Icons.add,
106         click: () => this.openModal()
107       },
108       {
109         name: this.actionLabels.CLONE,
110         permission: 'create',
111         icon: Icons.clone,
112         disable: () => !this.selection.hasSingleSelection,
113         click: () => this.cloneModal()
114       },
115       {
116         name: this.actionLabels.REMOVE,
117         permission: 'delete',
118         icon: Icons.destroy,
119         disable: () => !this.selection.hasSingleSelection,
120         click: () => this.deleteSnapshot()
121       }
122     ];
123
124     this.cephfsSubvolumeGroupService
125       .get(this.fsName)
126       .pipe(
127         switchMap((groups) => {
128           // manually adding the group '_nogroup' to the list.
129           groups.unshift({ name: '' });
130
131           const observables = groups.map((group) =>
132             this.cephfsSubvolumeService.existsInFs(this.fsName, group.name).pipe(
133               switchMap((resp) => {
134                 if (resp) {
135                   this.subvolumeGroupList.push(group.name);
136                 }
137                 return of(resp); // Emit the response
138               })
139             )
140           );
141
142           return forkJoin(observables);
143         })
144       )
145       .subscribe(() => {
146         if (this.subvolumeGroupList.length) {
147           this.isSubVolumesAvailable = true;
148         }
149         this.isLoading = false;
150       });
151   }
152
153   ngOnChanges(changes: SimpleChanges): void {
154     if (changes.fsName) {
155       this.subVolumeSubject.next([]);
156     }
157   }
158
159   selectSubVolumeGroup(subVolumeGroupName: string) {
160     this.activeGroupName = subVolumeGroupName;
161     this.getSubVolumes();
162   }
163
164   selectSubVolume(subVolumeName: string) {
165     this.activeSubVolumeName = subVolumeName;
166     this.getSubVolumesSnapshot();
167   }
168
169   getSubVolumes() {
170     this.subVolumes$ = this.subVolumeSubject.pipe(
171       switchMap(() =>
172         this.cephfsSubvolumeService.get(this.fsName, this.activeGroupName, false).pipe(
173           tap((resp) => {
174             this.subVolumesList = resp.map((subVolume) => subVolume.name);
175             this.activeSubVolumeName = resp[0].name;
176             this.getSubVolumesSnapshot();
177           })
178         )
179       )
180     );
181   }
182
183   getSubVolumesSnapshot() {
184     this.snapshots$ = this.snapshotSubject.pipe(
185       switchMap(() =>
186         this.cephfsSubvolumeService
187           .getSnapshots(this.fsName, this.activeSubVolumeName, this.activeGroupName)
188           .pipe(
189             catchError(() => {
190               this.context.error();
191               return of(null);
192             })
193           )
194       ),
195       shareReplay(1)
196     );
197   }
198
199   fetchData() {
200     this.snapshotSubject.next([]);
201   }
202
203   openModal(edit = false) {
204     this.modalService.show(
205       CephfsSubvolumeSnapshotsFormComponent,
206       {
207         fsName: this.fsName,
208         subVolumeName: this.activeSubVolumeName,
209         subVolumeGroupName: this.activeGroupName,
210         subVolumeGroups: this.subvolumeGroupList,
211         isEdit: edit
212       },
213       { size: 'lg' }
214     );
215   }
216
217   updateSelection(selection: CdTableSelection) {
218     this.selection = selection;
219   }
220
221   deleteSnapshot() {
222     const snapshotName = this.selection.first().name;
223     const subVolumeName = this.activeSubVolumeName;
224     const subVolumeGroupName = this.activeGroupName;
225     const fsName = this.fsName;
226     this.modalRef = this.modalService.show(CriticalConfirmationModalComponent, {
227       actionDescription: this.actionLabels.REMOVE,
228       itemNames: [snapshotName],
229       itemDescription: 'Snapshot',
230       submitAction: () =>
231         this.taskWrapper
232           .wrapTaskAroundCall({
233             task: new FinishedTask('cephfs/subvolume/snapshot/delete', {
234               fsName: fsName,
235               subVolumeName: subVolumeName,
236               subVolumeGroupName: subVolumeGroupName,
237               snapshotName: snapshotName
238             }),
239             call: this.cephfsSubvolumeService.deleteSnapshot(
240               fsName,
241               subVolumeName,
242               snapshotName,
243               subVolumeGroupName
244             )
245           })
246           .subscribe({
247             complete: () => this.modalRef.close(),
248             error: () => this.modalRef.componentInstance.stopLoadingSpinner()
249           })
250     });
251   }
252
253   cloneModal() {
254     const cloneName = `clone_${moment().toISOString(true)}`;
255     const allGroups = Array.from(this.subvolumeGroupList).map((group) => {
256       return { value: group, text: group === '' ? '_nogroup' : group };
257     });
258     this.modalService.show(FormModalComponent, {
259       titleText: $localize`Create clone`,
260       fields: [
261         {
262           type: 'text',
263           name: 'cloneName',
264           value: cloneName,
265           label: $localize`Name`,
266           validators: [Validators.required, Validators.pattern(/^[.A-Za-z0-9_+:-]+$/)],
267           asyncValidators: [
268             CdValidators.unique(
269               this.cephfsSubvolumeService.exists,
270               this.cephfsSubvolumeService,
271               null,
272               null,
273               this.fsName,
274               this.activeGroupName
275             )
276           ],
277           required: true,
278           errors: {
279             pattern: $localize`Allowed characters are letters, numbers, '.', '-', '+', ':' or '_'`,
280             notUnique: $localize`A subvolume or clone with this name already exists.`
281           }
282         },
283         {
284           type: 'select',
285           name: 'groupName',
286           value: this.activeGroupName,
287           label: $localize`Group name`,
288           valueChangeListener: true,
289           dependsOn: 'cloneName',
290           typeConfig: {
291             options: allGroups
292           }
293         }
294       ],
295       submitButtonText: $localize`Create Clone`,
296       updateAsyncValidators: (value: any) =>
297         CdValidators.unique(
298           this.cephfsSubvolumeService.exists,
299           this.cephfsSubvolumeService,
300           null,
301           null,
302           this.fsName,
303           value
304         ),
305       onSubmit: (value: any) => {
306         this.cephfsSubvolumeService
307           .createSnapshotClone(
308             this.fsName,
309             this.activeSubVolumeName,
310             this.selection.first().name,
311             value.cloneName,
312             this.activeGroupName,
313             value.groupName
314           )
315           .subscribe(() =>
316             this.notificationService.show(
317               NotificationType.success,
318               $localize`Created Clone "${value.cloneName}" successfully.`
319             )
320           );
321       }
322     });
323   }
324 }