]> git.apps.os.sepia.ceph.com Git - ceph-ci.git/blob
df66b0e8842ae01dd96e61d657e635cbbc8626c4
[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 { 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
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   rbdName: string;
62   @ViewChild('nameTpl')
63   nameTpl: TemplateRef<any>;
64   @ViewChild('rollbackTpl', { static: true })
65   rollbackTpl: TemplateRef<any>;
66
67   permission: Permission;
68   selection = new CdTableSelection();
69   tableActions: CdTableAction[];
70   rbdTableActions: RbdSnapshotActionsModel;
71   imageSpec: ImageSpec;
72
73   data: RbdSnapshotModel[];
74
75   columns: CdTableColumn[];
76
77   modalRef: NgbModalRef;
78
79   builders = {
80     'rbd/snap/create': (metadata: any) => {
81       const model = new RbdSnapshotModel();
82       model.name = metadata['snapshot_name'];
83       return model;
84     }
85   };
86
87   constructor(
88     private authStorageService: AuthStorageService,
89     private modalService: ModalService,
90     private dimlessBinaryPipe: DimlessBinaryPipe,
91     private cdDatePipe: CdDatePipe,
92     private rbdService: RbdService,
93     private taskManagerService: TaskManagerService,
94     private notificationService: NotificationService,
95     private summaryService: SummaryService,
96     private taskListService: TaskListService,
97     private actionLabels: ActionLabelsI18n,
98     private cdr: ChangeDetectorRef
99   ) {
100     this.permission = this.authStorageService.getPermissions().rbdImage;
101   }
102
103   ngOnInit() {
104     this.columns = [
105       {
106         name: $localize`Name`,
107         prop: 'name',
108         cellTransformation: CellTemplate.executing,
109         flexGrow: 2
110       },
111       {
112         name: $localize`Size`,
113         prop: 'size',
114         flexGrow: 1,
115         cellClass: 'text-right',
116         pipe: this.dimlessBinaryPipe
117       },
118       {
119         name: $localize`Provisioned`,
120         prop: 'disk_usage',
121         flexGrow: 1,
122         cellClass: 'text-right',
123         pipe: this.dimlessBinaryPipe
124       },
125       {
126         name: $localize`State`,
127         prop: 'is_protected',
128         flexGrow: 1,
129         cellTransformation: CellTemplate.badge,
130         customTemplateConfig: {
131           map: {
132             true: { value: $localize`PROTECTED`, class: 'badge-success' },
133             false: { value: $localize`UNPROTECTED`, class: 'badge-info' }
134           }
135         }
136       },
137       {
138         name: $localize`Created`,
139         prop: 'timestamp',
140         flexGrow: 1,
141         pipe: this.cdDatePipe
142       }
143     ];
144
145     this.imageSpec = new ImageSpec(this.poolName, this.namespace, this.rbdName);
146     this.rbdTableActions = new RbdSnapshotActionsModel(
147       this.actionLabels,
148       this.featuresName,
149       this.rbdService
150     );
151     this.rbdTableActions.create.click = () => this.openCreateSnapshotModal();
152     this.rbdTableActions.rename.click = () => this.openEditSnapshotModal();
153     this.rbdTableActions.protect.click = () => this.toggleProtection();
154     this.rbdTableActions.unprotect.click = () => this.toggleProtection();
155     const getImageUri = () =>
156       this.selection.first() &&
157       `${this.imageSpec.toStringEncoded()}/${encodeURIComponent(this.selection.first().name)}`;
158     this.rbdTableActions.clone.routerLink = () => `/block/rbd/clone/${getImageUri()}`;
159     this.rbdTableActions.copy.routerLink = () => `/block/rbd/copy/${getImageUri()}`;
160     this.rbdTableActions.rollback.click = () => this.rollbackModal();
161     this.rbdTableActions.deleteSnap.click = () => this.deleteSnapshotModal();
162
163     this.tableActions = this.rbdTableActions.ordering;
164
165     const itemFilter = (entry: any, task: Task) => {
166       return entry.name === task.metadata['snapshot_name'];
167     };
168
169     const taskFilter = (task: Task) => {
170       return (
171         ['rbd/snap/create', 'rbd/snap/delete', 'rbd/snap/edit', 'rbd/snap/rollback'].includes(
172           task.name
173         ) && this.imageSpec.toString() === task.metadata['image_spec']
174       );
175     };
176
177     this.taskListService.init(
178       () => of(this.snapshots),
179       null,
180       (items) => {
181         const hasChanges = CdHelperClass.updateChanged(this, {
182           data: items
183         });
184         if (hasChanges) {
185           this.cdr.detectChanges();
186           this.data = [...this.data];
187         }
188       },
189       () => {
190         const hasChanges = CdHelperClass.updateChanged(this, {
191           data: this.snapshots
192         });
193         if (hasChanges) {
194           this.cdr.detectChanges();
195           this.data = [...this.data];
196         }
197       },
198       taskFilter,
199       itemFilter,
200       this.builders
201     );
202   }
203
204   ngOnChanges() {
205     if (this.columns) {
206       this.imageSpec = new ImageSpec(this.poolName, this.namespace, this.rbdName);
207       if (this.rbdTableActions) {
208         this.rbdTableActions.featuresName = this.featuresName;
209       }
210       this.taskListService.fetch();
211     }
212   }
213
214   private openSnapshotModal(taskName: string, snapName: string = null) {
215     const modalVariables = {
216       mirroring: this.mirroring
217     };
218     this.modalRef = this.modalService.show(RbdSnapshotFormModalComponent, modalVariables);
219     this.modalRef.componentInstance.poolName = this.poolName;
220     this.modalRef.componentInstance.imageName = this.rbdName;
221     this.modalRef.componentInstance.namespace = this.namespace;
222     if (snapName) {
223       this.modalRef.componentInstance.setEditing();
224     } else {
225       // Auto-create a name for the snapshot: <image_name>_<timestamp_ISO_8601>
226       // https://en.wikipedia.org/wiki/ISO_8601
227       snapName = `${this.rbdName}_${moment().toISOString(true)}`;
228     }
229     this.modalRef.componentInstance.setSnapName(snapName);
230     this.modalRef.componentInstance.onSubmit.subscribe((snapshotName: string) => {
231       const executingTask = new ExecutingTask();
232       executingTask.name = taskName;
233       executingTask.metadata = {
234         image_spec: this.imageSpec.toString(),
235         snapshot_name: snapshotName
236       };
237       this.summaryService.addRunningTask(executingTask);
238     });
239   }
240
241   openCreateSnapshotModal() {
242     this.openSnapshotModal('rbd/snap/create');
243   }
244
245   openEditSnapshotModal() {
246     this.openSnapshotModal('rbd/snap/edit', this.selection.first().name);
247   }
248
249   toggleProtection() {
250     const snapshotName = this.selection.first().name;
251     const isProtected = this.selection.first().is_protected;
252     const finishedTask = new FinishedTask();
253     finishedTask.name = 'rbd/snap/edit';
254     const imageSpec = new ImageSpec(this.poolName, this.namespace, this.rbdName);
255     finishedTask.metadata = {
256       image_spec: imageSpec.toString(),
257       snapshot_name: snapshotName
258     };
259     this.rbdService
260       .protectSnapshot(imageSpec, snapshotName, !isProtected)
261       .toPromise()
262       .then(() => {
263         const executingTask = new ExecutingTask();
264         executingTask.name = finishedTask.name;
265         executingTask.metadata = finishedTask.metadata;
266         this.summaryService.addRunningTask(executingTask);
267         this.taskManagerService.subscribe(
268           finishedTask.name,
269           finishedTask.metadata,
270           (asyncFinishedTask: FinishedTask) => {
271             this.notificationService.notifyTask(asyncFinishedTask);
272           }
273         );
274       });
275   }
276
277   _asyncTask(task: string, taskName: string, snapshotName: string) {
278     const finishedTask = new FinishedTask();
279     finishedTask.name = taskName;
280     finishedTask.metadata = {
281       image_spec: new ImageSpec(this.poolName, this.namespace, this.rbdName).toString(),
282       snapshot_name: snapshotName
283     };
284     const imageSpec = new ImageSpec(this.poolName, this.namespace, this.rbdName);
285     this.rbdService[task](imageSpec, snapshotName)
286       .toPromise()
287       .then(() => {
288         const executingTask = new ExecutingTask();
289         executingTask.name = finishedTask.name;
290         executingTask.metadata = finishedTask.metadata;
291         this.summaryService.addRunningTask(executingTask);
292         this.modalRef.close();
293         this.taskManagerService.subscribe(
294           executingTask.name,
295           executingTask.metadata,
296           (asyncFinishedTask: FinishedTask) => {
297             this.notificationService.notifyTask(asyncFinishedTask);
298           }
299         );
300       })
301       .catch(() => {
302         this.modalRef.componentInstance.stopLoadingSpinner();
303       });
304   }
305
306   rollbackModal() {
307     const snapshotName = this.selection.selected[0].name;
308     const imageSpec = new ImageSpec(this.poolName, this.namespace, this.rbdName).toString();
309     const initialState = {
310       titleText: $localize`RBD snapshot rollback`,
311       buttonText: $localize`Rollback`,
312       bodyTpl: this.rollbackTpl,
313       bodyData: {
314         snapName: `${imageSpec}@${snapshotName}`
315       },
316       onSubmit: () => {
317         this._asyncTask('rollbackSnapshot', 'rbd/snap/rollback', snapshotName);
318       }
319     };
320
321     this.modalRef = this.modalService.show(ConfirmationModalComponent, initialState);
322   }
323
324   deleteSnapshotModal() {
325     const snapshotName = this.selection.selected[0].name;
326     this.modalRef = this.modalService.show(CriticalConfirmationModalComponent, {
327       itemDescription: $localize`RBD snapshot`,
328       itemNames: [snapshotName],
329       submitAction: () => this._asyncTask('deleteSnapshot', 'rbd/snap/delete', snapshotName)
330     });
331   }
332
333   updateSelection(selection: CdTableSelection) {
334     this.selection = selection;
335   }
336 }