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