]> git.apps.os.sepia.ceph.com Git - ceph.git/blob
5d3fe6b2ddad809b00e4aa175dda5e29ed81e649
[ceph.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 { AuthStorageService } from '~/app/shared/services/auth-storage.service';
15 import { Permissions } from '~/app/shared/models/permissions';
16 import { CdTableSelection } from '~/app/shared/models/cd-table-selection';
17 import { CdDatePipe } from '~/app/shared/pipes/cd-date.pipe';
18 import { NgbModalRef } from '@ng-bootstrap/ng-bootstrap';
19 import { CriticalConfirmationModalComponent } from '~/app/shared/components/critical-confirmation-modal/critical-confirmation-modal.component';
20 import { FinishedTask } from '~/app/shared/models/finished-task';
21 import { TaskWrapperService } from '~/app/shared/services/task-wrapper.service';
22 import { FormModalComponent } from '~/app/shared/components/form-modal/form-modal.component';
23 import { NotificationService } from '~/app/shared/services/notification.service';
24 import { NotificationType } from '~/app/shared/enum/notification-type.enum';
25 import moment from 'moment';
26 import { Validators } from '@angular/forms';
27 import { CdValidators } from '~/app/shared/forms/cd-validators';
28 import { ModalCdsService } from '~/app/shared/services/modal-cds.service';
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: ModalCdsService,
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(CephfsSubvolumeSnapshotsFormComponent, {
205       fsName: this.fsName,
206       subVolumeName: this.activeSubVolumeName,
207       subVolumeGroupName: this.activeGroupName,
208       subVolumeGroups: this.subvolumeGroupList,
209       isEdit: edit
210     });
211   }
212
213   updateSelection(selection: CdTableSelection) {
214     this.selection = selection;
215   }
216
217   deleteSnapshot() {
218     const snapshotName = this.selection.first().name;
219     const subVolumeName = this.activeSubVolumeName;
220     const subVolumeGroupName = this.activeGroupName;
221     const fsName = this.fsName;
222     this.modalRef = this.modalService.show(CriticalConfirmationModalComponent, {
223       actionDescription: this.actionLabels.REMOVE,
224       itemNames: [snapshotName],
225       itemDescription: 'Snapshot',
226       submitAction: () =>
227         this.taskWrapper
228           .wrapTaskAroundCall({
229             task: new FinishedTask('cephfs/subvolume/snapshot/delete', {
230               fsName: fsName,
231               subVolumeName: subVolumeName,
232               subVolumeGroupName: subVolumeGroupName,
233               snapshotName: snapshotName
234             }),
235             call: this.cephfsSubvolumeService.deleteSnapshot(
236               fsName,
237               subVolumeName,
238               snapshotName,
239               subVolumeGroupName
240             )
241           })
242           .subscribe({
243             complete: () => this.modalService.dismissAll(),
244             error: () => this.modalRef.componentInstance.stopLoadingSpinner()
245           })
246     });
247   }
248
249   cloneModal() {
250     const cloneName = `clone_${moment().toISOString(true)}`;
251     const allGroups = Array.from(this.subvolumeGroupList).map((group) => {
252       return { value: group, text: group === '' ? '_nogroup' : group };
253     });
254     this.modalService.show(FormModalComponent, {
255       titleText: $localize`Create clone`,
256       fields: [
257         {
258           type: 'text',
259           name: 'cloneName',
260           value: cloneName,
261           label: $localize`Name`,
262           validators: [Validators.required, Validators.pattern(/^[.A-Za-z0-9_+:-]+$/)],
263           asyncValidators: [
264             CdValidators.unique(
265               this.cephfsSubvolumeService.exists,
266               this.cephfsSubvolumeService,
267               null,
268               null,
269               this.fsName,
270               this.activeGroupName
271             )
272           ],
273           required: true,
274           errors: {
275             pattern: $localize`Allowed characters are letters, numbers, '.', '-', '+', ':' or '_'`,
276             notUnique: $localize`A subvolume or clone with this name already exists.`
277           }
278         },
279         {
280           type: 'select',
281           name: 'groupName',
282           value: this.activeGroupName,
283           label: $localize`Group name`,
284           valueChangeListener: true,
285           dependsOn: 'cloneName',
286           typeConfig: {
287             options: allGroups
288           }
289         }
290       ],
291       submitButtonText: $localize`Create Clone`,
292       updateAsyncValidators: (value: any) =>
293         CdValidators.unique(
294           this.cephfsSubvolumeService.exists,
295           this.cephfsSubvolumeService,
296           null,
297           null,
298           this.fsName,
299           value
300         ),
301       onSubmit: (value: any) => {
302         this.cephfsSubvolumeService
303           .createSnapshotClone(
304             this.fsName,
305             this.activeSubVolumeName,
306             this.selection.first().name,
307             value.cloneName,
308             this.activeGroupName,
309             value.groupName
310           )
311           .subscribe(() =>
312             this.notificationService.show(
313               NotificationType.success,
314               $localize`Created Clone "${value.cloneName}" successfully.`
315             )
316           );
317       }
318     });
319   }
320 }