]> git.apps.os.sepia.ceph.com Git - ceph.git/blob
1f7169e5bcf7b55515fb3fbc0b8796e71b6ad442
[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 { 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 import { ModalCdsService } from '~/app/shared/services/modal-cds.service';
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
62   constructor(
63     private cephfsSubvolumeGroupService: CephfsSubvolumeGroupService,
64     private cephfsSubvolumeService: CephfsSubvolumeService,
65     private actionLabels: ActionLabelsI18n,
66     private modalService: ModalService,
67     private authStorageService: AuthStorageService,
68     private cdDatePipe: CdDatePipe,
69     private taskWrapper: TaskWrapperService,
70     private notificationService: NotificationService,
71     private cdsModalService: ModalCdsService
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           groups.unshift({ name: '' });
132
133           const observables = groups.map((group) =>
134             this.cephfsSubvolumeService.existsInFs(this.fsName, group.name).pipe(
135               switchMap((resp) => {
136                 if (resp) {
137                   this.subvolumeGroupList.push(group.name);
138                 }
139                 return of(resp); // Emit the response
140               })
141             )
142           );
143
144           return forkJoin(observables);
145         })
146       )
147       .subscribe(() => {
148         if (this.subvolumeGroupList.length) {
149           this.isSubVolumesAvailable = true;
150         }
151         this.isLoading = false;
152       });
153   }
154
155   ngOnChanges(changes: SimpleChanges): void {
156     if (changes.fsName) {
157       this.subVolumeSubject.next([]);
158     }
159   }
160
161   selectSubVolumeGroup(subVolumeGroupName: string) {
162     this.activeGroupName = subVolumeGroupName;
163     this.getSubVolumes();
164   }
165
166   selectSubVolume(subVolumeName: string) {
167     this.activeSubVolumeName = subVolumeName;
168     this.getSubVolumesSnapshot();
169   }
170
171   getSubVolumes() {
172     this.subVolumes$ = this.subVolumeSubject.pipe(
173       switchMap(() =>
174         this.cephfsSubvolumeService.get(this.fsName, this.activeGroupName, false).pipe(
175           tap((resp) => {
176             this.subVolumesList = resp.map((subVolume) => subVolume.name);
177             this.activeSubVolumeName = resp[0].name;
178             this.getSubVolumesSnapshot();
179           })
180         )
181       )
182     );
183   }
184
185   getSubVolumesSnapshot() {
186     this.snapshots$ = this.snapshotSubject.pipe(
187       switchMap(() =>
188         this.cephfsSubvolumeService
189           .getSnapshots(this.fsName, this.activeSubVolumeName, this.activeGroupName)
190           .pipe(
191             catchError(() => {
192               this.context.error();
193               return of(null);
194             })
195           )
196       ),
197       shareReplay(1)
198     );
199   }
200
201   fetchData() {
202     this.snapshotSubject.next([]);
203   }
204
205   openModal(edit = false) {
206     this.modalService.show(
207       CephfsSubvolumeSnapshotsFormComponent,
208       {
209         fsName: this.fsName,
210         subVolumeName: this.activeSubVolumeName,
211         subVolumeGroupName: this.activeGroupName,
212         subVolumeGroups: this.subvolumeGroupList,
213         isEdit: edit
214       },
215       { size: 'lg' }
216     );
217   }
218
219   updateSelection(selection: CdTableSelection) {
220     this.selection = selection;
221   }
222
223   deleteSnapshot() {
224     const snapshotName = this.selection.first().name;
225     const subVolumeName = this.activeSubVolumeName;
226     const subVolumeGroupName = this.activeGroupName;
227     const fsName = this.fsName;
228     this.modalRef = this.cdsModalService.show(CriticalConfirmationModalComponent, {
229       actionDescription: this.actionLabels.REMOVE,
230       itemNames: [snapshotName],
231       itemDescription: 'Snapshot',
232       submitAction: () =>
233         this.taskWrapper
234           .wrapTaskAroundCall({
235             task: new FinishedTask('cephfs/subvolume/snapshot/delete', {
236               fsName: fsName,
237               subVolumeName: subVolumeName,
238               subVolumeGroupName: subVolumeGroupName,
239               snapshotName: snapshotName
240             }),
241             call: this.cephfsSubvolumeService.deleteSnapshot(
242               fsName,
243               subVolumeName,
244               snapshotName,
245               subVolumeGroupName
246             )
247           })
248           .subscribe({
249             complete: () => this.cdsModalService.dismissAll(),
250             error: () => this.modalRef.componentInstance.stopLoadingSpinner()
251           })
252     });
253   }
254
255   cloneModal() {
256     const cloneName = `clone_${moment().toISOString(true)}`;
257     const allGroups = Array.from(this.subvolumeGroupList).map((group) => {
258       return { value: group, text: group === '' ? '_nogroup' : group };
259     });
260     this.modalService.show(FormModalComponent, {
261       titleText: $localize`Create clone`,
262       fields: [
263         {
264           type: 'text',
265           name: 'cloneName',
266           value: cloneName,
267           label: $localize`Name`,
268           validators: [Validators.required, Validators.pattern(/^[.A-Za-z0-9_+:-]+$/)],
269           asyncValidators: [
270             CdValidators.unique(
271               this.cephfsSubvolumeService.exists,
272               this.cephfsSubvolumeService,
273               null,
274               null,
275               this.fsName,
276               this.activeGroupName
277             )
278           ],
279           required: true,
280           errors: {
281             pattern: $localize`Allowed characters are letters, numbers, '.', '-', '+', ':' or '_'`,
282             notUnique: $localize`A subvolume or clone with this name already exists.`
283           }
284         },
285         {
286           type: 'select',
287           name: 'groupName',
288           value: this.activeGroupName,
289           label: $localize`Group name`,
290           valueChangeListener: true,
291           dependsOn: 'cloneName',
292           typeConfig: {
293             options: allGroups
294           }
295         }
296       ],
297       submitButtonText: $localize`Create Clone`,
298       updateAsyncValidators: (value: any) =>
299         CdValidators.unique(
300           this.cephfsSubvolumeService.exists,
301           this.cephfsSubvolumeService,
302           null,
303           null,
304           this.fsName,
305           value
306         ),
307       onSubmit: (value: any) => {
308         this.cephfsSubvolumeService
309           .createSnapshotClone(
310             this.fsName,
311             this.activeSubVolumeName,
312             this.selection.first().name,
313             value.cloneName,
314             this.activeGroupName,
315             value.groupName
316           )
317           .subscribe(() =>
318             this.notificationService.show(
319               NotificationType.success,
320               $localize`Created Clone "${value.cloneName}" successfully.`
321             )
322           );
323       }
324     });
325   }
326 }