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