]> git.apps.os.sepia.ceph.com Git - ceph.git/blob
5f12821aaa59c5e9ac521796ce3da9df16fa3b94
[ceph.git] /
1 import {
2   ChangeDetectionStrategy,
3   ChangeDetectorRef,
4   Component,
5   Input,
6   OnChanges,
7   OnInit,
8   TemplateRef,
9   ViewChild
10 } from '@angular/core';
11
12 import { NgbModalRef } from '@ng-bootstrap/ng-bootstrap';
13 import moment from 'moment';
14 import { of } from 'rxjs';
15
16 import { RbdService } from '~/app/shared/api/rbd.service';
17 import { CdHelperClass } from '~/app/shared/classes/cd-helper.class';
18 import { ConfirmationModalComponent } from '~/app/shared/components/confirmation-modal/confirmation-modal.component';
19 import { CriticalConfirmationModalComponent } from '~/app/shared/components/critical-confirmation-modal/critical-confirmation-modal.component';
20 import { ActionLabelsI18n } from '~/app/shared/constants/app.constants';
21 import { CellTemplate } from '~/app/shared/enum/cell-template.enum';
22 import { CdTableAction } from '~/app/shared/models/cd-table-action';
23 import { CdTableColumn } from '~/app/shared/models/cd-table-column';
24 import { CdTableSelection } from '~/app/shared/models/cd-table-selection';
25 import { ExecutingTask } from '~/app/shared/models/executing-task';
26 import { FinishedTask } from '~/app/shared/models/finished-task';
27 import { ImageSpec } from '~/app/shared/models/image-spec';
28 import { Permission } from '~/app/shared/models/permissions';
29 import { Task } from '~/app/shared/models/task';
30 import { CdDatePipe } from '~/app/shared/pipes/cd-date.pipe';
31 import { DimlessBinaryPipe } from '~/app/shared/pipes/dimless-binary.pipe';
32 import { AuthStorageService } from '~/app/shared/services/auth-storage.service';
33 import { ModalService } from '~/app/shared/services/modal.service';
34 import { NotificationService } from '~/app/shared/services/notification.service';
35 import { SummaryService } from '~/app/shared/services/summary.service';
36 import { TaskListService } from '~/app/shared/services/task-list.service';
37 import { TaskManagerService } from '~/app/shared/services/task-manager.service';
38 import { RbdSnapshotFormModalComponent } from '../rbd-snapshot-form/rbd-snapshot-form-modal.component';
39 import { RbdSnapshotActionsModel } from './rbd-snapshot-actions.model';
40 import { RbdSnapshotModel } from './rbd-snapshot.model';
41 import { DeletionImpact } from '~/app/shared/enum/critical-confirmation-modal-impact.enum';
42
43 @Component({
44   selector: 'cd-rbd-snapshot-list',
45   templateUrl: './rbd-snapshot-list.component.html',
46   styleUrls: ['./rbd-snapshot-list.component.scss'],
47   providers: [TaskListService],
48   changeDetection: ChangeDetectionStrategy.OnPush
49 })
50 export class RbdSnapshotListComponent implements OnInit, OnChanges {
51   @Input()
52   snapshots: RbdSnapshotModel[] = [];
53   @Input()
54   featuresName: string[];
55   @Input()
56   poolName: string;
57   @Input()
58   namespace: string;
59   @Input()
60   mirroring: string;
61   @Input()
62   primary: boolean;
63   @Input()
64   rbdName: string;
65   @ViewChild('nameTpl')
66   nameTpl: TemplateRef<any>;
67   @ViewChild('rollbackTpl', { static: true })
68   rollbackTpl: TemplateRef<any>;
69
70   permission: Permission;
71   selection = new CdTableSelection();
72   tableActions: CdTableAction[];
73   rbdTableActions: RbdSnapshotActionsModel;
74   imageSpec: ImageSpec;
75
76   data: RbdSnapshotModel[];
77
78   columns: CdTableColumn[];
79
80   modalRef: NgbModalRef;
81
82   builders = {
83     'rbd/snap/create': (metadata: any) => {
84       const model = new RbdSnapshotModel();
85       model.name = metadata['snapshot_name'];
86       return model;
87     }
88   };
89
90   constructor(
91     private authStorageService: AuthStorageService,
92     private modalService: ModalService,
93     private dimlessBinaryPipe: DimlessBinaryPipe,
94     private cdDatePipe: CdDatePipe,
95     private rbdService: RbdService,
96     private taskManagerService: TaskManagerService,
97     private notificationService: NotificationService,
98     private summaryService: SummaryService,
99     private taskListService: TaskListService,
100     private actionLabels: ActionLabelsI18n,
101     private cdr: ChangeDetectorRef
102   ) {
103     this.permission = this.authStorageService.getPermissions().rbdImage;
104   }
105
106   ngOnInit() {
107     this.columns = [
108       {
109         name: $localize`Name`,
110         prop: 'name',
111         cellTransformation: CellTemplate.executing,
112         flexGrow: 2
113       },
114       {
115         name: $localize`Size`,
116         prop: 'size',
117         flexGrow: 1,
118         cellClass: 'text-right',
119         pipe: this.dimlessBinaryPipe
120       },
121       {
122         name: $localize`Used`,
123         prop: 'disk_usage',
124         flexGrow: 1,
125         cellClass: 'text-right',
126         pipe: this.dimlessBinaryPipe
127       },
128       {
129         name: $localize`State`,
130         prop: 'is_protected',
131         flexGrow: 1,
132         cellTransformation: CellTemplate.badge,
133         customTemplateConfig: {
134           map: {
135             true: { value: $localize`PROTECTED`, class: 'badge-success' },
136             false: { value: $localize`UNPROTECTED`, class: 'badge-info' }
137           }
138         }
139       },
140       {
141         name: $localize`Created`,
142         prop: 'timestamp',
143         flexGrow: 1,
144         pipe: this.cdDatePipe
145       }
146     ];
147
148     this.imageSpec = new ImageSpec(this.poolName, this.namespace, this.rbdName);
149     this.rbdTableActions = new RbdSnapshotActionsModel(
150       this.actionLabels,
151       this.featuresName,
152       this.rbdService
153     );
154     this.rbdTableActions.create.click = () => this.openCreateSnapshotModal();
155     this.rbdTableActions.rename.click = () => this.openEditSnapshotModal();
156     this.rbdTableActions.protect.click = () => this.toggleProtection();
157     this.rbdTableActions.unprotect.click = () => this.toggleProtection();
158     const getImageUri = () =>
159       this.selection.first() &&
160       `${this.imageSpec.toStringEncoded()}/${encodeURIComponent(this.selection.first().name)}`;
161     this.rbdTableActions.clone.routerLink = () => `/block/rbd/clone/${getImageUri()}`;
162     this.rbdTableActions.copy.routerLink = () => `/block/rbd/copy/${getImageUri()}`;
163     this.rbdTableActions.rollback.click = () => this.rollbackModal();
164     this.rbdTableActions.deleteSnap.click = () => this.deleteSnapshotModal();
165
166     this.tableActions = this.rbdTableActions.ordering;
167
168     const itemFilter = (entry: any, task: Task) => {
169       return entry.name === task.metadata['snapshot_name'];
170     };
171
172     const taskFilter = (task: Task) => {
173       return (
174         ['rbd/snap/create', 'rbd/snap/delete', 'rbd/snap/edit', 'rbd/snap/rollback'].includes(
175           task.name
176         ) && this.imageSpec.toString() === task.metadata['image_spec']
177       );
178     };
179
180     this.taskListService.init(
181       () => of(this.snapshots),
182       null,
183       (items) => {
184         const hasChanges = CdHelperClass.updateChanged(this, {
185           data: items
186         });
187         if (hasChanges) {
188           this.cdr.detectChanges();
189           this.data = [...this.data];
190         }
191       },
192       () => {
193         const hasChanges = CdHelperClass.updateChanged(this, {
194           data: this.snapshots
195         });
196         if (hasChanges) {
197           this.cdr.detectChanges();
198           this.data = [...this.data];
199         }
200       },
201       taskFilter,
202       itemFilter,
203       this.builders
204     );
205   }
206
207   ngOnChanges() {
208     if (this.columns) {
209       this.imageSpec = new ImageSpec(this.poolName, this.namespace, this.rbdName);
210       if (this.rbdTableActions) {
211         this.rbdTableActions.featuresName = this.featuresName;
212       }
213       this.taskListService.fetch();
214     }
215   }
216
217   private openSnapshotModal(taskName: string, snapName: string = null) {
218     const modalVariables = {
219       mirroring: this.mirroring
220     };
221     this.modalRef = this.modalService.show(RbdSnapshotFormModalComponent, modalVariables);
222     this.modalRef.componentInstance.poolName = this.poolName;
223     this.modalRef.componentInstance.imageName = this.rbdName;
224     this.modalRef.componentInstance.namespace = this.namespace;
225     if (snapName) {
226       this.modalRef.componentInstance.setEditing();
227     } else {
228       // Auto-create a name for the snapshot: <image_name>_<timestamp_ISO_8601>
229       // https://en.wikipedia.org/wiki/ISO_8601
230       snapName = `${this.rbdName}_${moment().toISOString(true)}`;
231     }
232     this.modalRef.componentInstance.setSnapName(snapName);
233     this.modalRef.componentInstance.onSubmit.subscribe((snapshotName: string) => {
234       const executingTask = new ExecutingTask();
235       executingTask.name = taskName;
236       executingTask.metadata = {
237         image_spec: this.imageSpec.toString(),
238         snapshot_name: snapshotName
239       };
240       this.summaryService.addRunningTask(executingTask);
241     });
242   }
243
244   openCreateSnapshotModal() {
245     this.openSnapshotModal('rbd/snap/create');
246   }
247
248   openEditSnapshotModal() {
249     this.openSnapshotModal('rbd/snap/edit', this.selection.first().name);
250   }
251
252   toggleProtection() {
253     const snapshotName = this.selection.first().name;
254     const isProtected = this.selection.first().is_protected;
255     const finishedTask = new FinishedTask();
256     finishedTask.name = 'rbd/snap/edit';
257     const imageSpec = new ImageSpec(this.poolName, this.namespace, this.rbdName);
258     finishedTask.metadata = {
259       image_spec: imageSpec.toString(),
260       snapshot_name: snapshotName
261     };
262     this.rbdService
263       .protectSnapshot(imageSpec, snapshotName, !isProtected)
264       .toPromise()
265       .then(() => {
266         const executingTask = new ExecutingTask();
267         executingTask.name = finishedTask.name;
268         executingTask.metadata = finishedTask.metadata;
269         this.summaryService.addRunningTask(executingTask);
270         this.taskManagerService.subscribe(
271           finishedTask.name,
272           finishedTask.metadata,
273           (asyncFinishedTask: FinishedTask) => {
274             this.notificationService.notifyTask(asyncFinishedTask);
275           }
276         );
277       });
278   }
279
280   _asyncTask(task: string, taskName: string, snapshotName: string) {
281     const finishedTask = new FinishedTask();
282     finishedTask.name = taskName;
283     finishedTask.metadata = {
284       image_spec: new ImageSpec(this.poolName, this.namespace, this.rbdName).toString(),
285       snapshot_name: snapshotName
286     };
287     const imageSpec = new ImageSpec(this.poolName, this.namespace, this.rbdName);
288     this.rbdService[task](imageSpec, snapshotName)
289       .toPromise()
290       .then(() => {
291         const executingTask = new ExecutingTask();
292         executingTask.name = finishedTask.name;
293         executingTask.metadata = finishedTask.metadata;
294         this.summaryService.addRunningTask(executingTask);
295         this.modalRef.close();
296         this.taskManagerService.subscribe(
297           executingTask.name,
298           executingTask.metadata,
299           (asyncFinishedTask: FinishedTask) => {
300             this.notificationService.notifyTask(asyncFinishedTask);
301           }
302         );
303       })
304       .catch(() => {
305         this.modalRef.componentInstance.stopLoadingSpinner();
306       });
307   }
308
309   rollbackModal() {
310     const snapshotName = this.selection.selected[0].name;
311     const imageSpec = new ImageSpec(this.poolName, this.namespace, this.rbdName).toString();
312     const initialState = {
313       titleText: $localize`RBD snapshot rollback`,
314       buttonText: $localize`Rollback`,
315       bodyTpl: this.rollbackTpl,
316       bodyData: {
317         snapName: `${imageSpec}@${snapshotName}`
318       },
319       onSubmit: () => {
320         this._asyncTask('rollbackSnapshot', 'rbd/snap/rollback', snapshotName);
321       }
322     };
323
324     this.modalRef = this.modalService.show(ConfirmationModalComponent, initialState);
325   }
326
327   deleteSnapshotModal() {
328     const snapshotName = this.selection.selected[0].name;
329     this.modalRef = this.modalService.show(CriticalConfirmationModalComponent, {
330       impact: DeletionImpact.high,
331       itemDescription: $localize`RBD snapshot`,
332       itemNames: [snapshotName],
333       submitAction: () => this._asyncTask('deleteSnapshot', 'rbd/snap/delete', snapshotName)
334     });
335   }
336
337   updateSelection(selection: CdTableSelection) {
338     this.selection = selection;
339   }
340 }