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