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