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