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