]> git.apps.os.sepia.ceph.com Git - ceph-ci.git/blob
1559a9854f9e1f2f8724b27100b6f2febefca972
[ceph-ci.git] /
1 import { Component, Input, OnChanges, OnInit, TemplateRef, ViewChild } from '@angular/core';
2
3 import { NgbModalRef } from '@ng-bootstrap/ng-bootstrap';
4 import * as moment from 'moment';
5 import { of } from 'rxjs';
6
7 import { RbdService } from '../../../shared/api/rbd.service';
8 import { ConfirmationModalComponent } from '../../../shared/components/confirmation-modal/confirmation-modal.component';
9 import { CriticalConfirmationModalComponent } from '../../../shared/components/critical-confirmation-modal/critical-confirmation-modal.component';
10 import { ActionLabelsI18n } from '../../../shared/constants/app.constants';
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 { ImageSpec } from '../../../shared/models/image-spec';
18 import { Permission } from '../../../shared/models/permissions';
19 import { Task } from '../../../shared/models/task';
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 { ModalService } from '../../../shared/services/modal.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 { RbdSnapshotFormModalComponent } from '../rbd-snapshot-form/rbd-snapshot-form-modal.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')
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: NgbModalRef;
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: ModalService,
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 actionLabels: ActionLabelsI18n
83   ) {
84     this.permission = this.authStorageService.getPermissions().rbdImage;
85   }
86
87   ngOnInit() {
88     this.columns = [
89       {
90         name: $localize`Name`,
91         prop: 'name',
92         cellTransformation: CellTemplate.executing,
93         flexGrow: 2
94       },
95       {
96         name: $localize`Size`,
97         prop: 'size',
98         flexGrow: 1,
99         cellClass: 'text-right',
100         pipe: this.dimlessBinaryPipe
101       },
102       {
103         name: $localize`Provisioned`,
104         prop: 'disk_usage',
105         flexGrow: 1,
106         cellClass: 'text-right',
107         pipe: this.dimlessBinaryPipe
108       },
109       {
110         name: $localize`State`,
111         prop: 'is_protected',
112         flexGrow: 1,
113         cellTransformation: CellTemplate.badge,
114         customTemplateConfig: {
115           map: {
116             true: { value: $localize`PROTECTED`, class: 'badge-success' },
117             false: { value: $localize`UNPROTECTED`, class: 'badge-info' }
118           }
119         }
120       },
121       {
122         name: $localize`Created`,
123         prop: 'timestamp',
124         flexGrow: 1,
125         pipe: this.cdDatePipe
126       }
127     ];
128   }
129
130   ngOnChanges() {
131     const imageSpec = new ImageSpec(this.poolName, this.namespace, this.rbdName);
132
133     const actions = new RbdSnapshotActionsModel(this.actionLabels, this.featuresName);
134     actions.create.click = () => this.openCreateSnapshotModal();
135     actions.rename.click = () => this.openEditSnapshotModal();
136     actions.protect.click = () => this.toggleProtection();
137     actions.unprotect.click = () => this.toggleProtection();
138     const getImageUri = () =>
139       this.selection.first() &&
140       `${imageSpec.toStringEncoded()}/${encodeURIComponent(this.selection.first().name)}`;
141     actions.clone.routerLink = () => `/block/rbd/clone/${getImageUri()}`;
142     actions.copy.routerLink = () => `/block/rbd/copy/${getImageUri()}`;
143     actions.rollback.click = () => this.rollbackModal();
144     actions.deleteSnap.click = () => this.deleteSnapshotModal();
145     this.tableActions = actions.ordering;
146
147     const itemFilter = (entry: any, task: Task) => {
148       return entry.name === task.metadata['snapshot_name'];
149     };
150
151     const taskFilter = (task: Task) => {
152       return (
153         ['rbd/snap/create', 'rbd/snap/delete', 'rbd/snap/edit', 'rbd/snap/rollback'].includes(
154           task.name
155         ) && imageSpec.toString() === task.metadata['image_spec']
156       );
157     };
158
159     this.taskListService.init(
160       () => of(this.snapshots),
161       null,
162       (items) => (this.data = items),
163       () => (this.data = this.snapshots),
164       taskFilter,
165       itemFilter,
166       this.builders
167     );
168   }
169
170   private openSnapshotModal(taskName: string, snapName: string = null) {
171     this.modalRef = this.modalService.show(RbdSnapshotFormModalComponent);
172     this.modalRef.componentInstance.poolName = this.poolName;
173     this.modalRef.componentInstance.imageName = this.rbdName;
174     this.modalRef.componentInstance.namespace = this.namespace;
175     if (snapName) {
176       this.modalRef.componentInstance.setEditing();
177     } else {
178       // Auto-create a name for the snapshot: <image_name>_<timestamp_ISO_8601>
179       // https://en.wikipedia.org/wiki/ISO_8601
180       snapName = `${this.rbdName}_${moment().toISOString(true)}`;
181     }
182     this.modalRef.componentInstance.setSnapName(snapName);
183     this.modalRef.componentInstance.onSubmit.subscribe((snapshotName: string) => {
184       const executingTask = new ExecutingTask();
185       executingTask.name = taskName;
186       executingTask.metadata = {
187         image_name: this.rbdName,
188         pool_name: this.poolName,
189         snapshot_name: snapshotName
190       };
191       this.summaryService.addRunningTask(executingTask);
192       this.ngOnChanges();
193     });
194   }
195
196   openCreateSnapshotModal() {
197     this.openSnapshotModal('rbd/snap/create');
198   }
199
200   openEditSnapshotModal() {
201     this.openSnapshotModal('rbd/snap/edit', this.selection.first().name);
202   }
203
204   toggleProtection() {
205     const snapshotName = this.selection.first().name;
206     const isProtected = this.selection.first().is_protected;
207     const finishedTask = new FinishedTask();
208     finishedTask.name = 'rbd/snap/edit';
209     const imageSpec = new ImageSpec(this.poolName, this.namespace, this.rbdName);
210     finishedTask.metadata = {
211       image_spec: imageSpec.toString(),
212       snapshot_name: snapshotName
213     };
214     this.rbdService
215       .protectSnapshot(imageSpec, snapshotName, !isProtected)
216       .toPromise()
217       .then(() => {
218         const executingTask = new ExecutingTask();
219         executingTask.name = finishedTask.name;
220         executingTask.metadata = finishedTask.metadata;
221         this.summaryService.addRunningTask(executingTask);
222         this.ngOnChanges();
223         this.taskManagerService.subscribe(
224           finishedTask.name,
225           finishedTask.metadata,
226           (asyncFinishedTask: FinishedTask) => {
227             this.notificationService.notifyTask(asyncFinishedTask);
228           }
229         );
230       });
231   }
232
233   _asyncTask(task: string, taskName: string, snapshotName: string) {
234     const finishedTask = new FinishedTask();
235     finishedTask.name = taskName;
236     finishedTask.metadata = {
237       image_spec: new ImageSpec(this.poolName, this.namespace, this.rbdName).toString(),
238       snapshot_name: snapshotName
239     };
240     const imageSpec = new ImageSpec(this.poolName, this.namespace, this.rbdName);
241     this.rbdService[task](imageSpec, snapshotName)
242       .toPromise()
243       .then(() => {
244         const executingTask = new ExecutingTask();
245         executingTask.name = finishedTask.name;
246         executingTask.metadata = finishedTask.metadata;
247         this.summaryService.addRunningTask(executingTask);
248         this.modalRef.close();
249         this.ngOnChanges();
250         this.taskManagerService.subscribe(
251           executingTask.name,
252           executingTask.metadata,
253           (asyncFinishedTask: FinishedTask) => {
254             this.notificationService.notifyTask(asyncFinishedTask);
255           }
256         );
257       })
258       .catch(() => {
259         this.modalRef.componentInstance.stopLoadingSpinner();
260       });
261   }
262
263   rollbackModal() {
264     const snapshotName = this.selection.selected[0].name;
265     const imageSpec = new ImageSpec(this.poolName, this.namespace, this.rbdName).toString();
266     const initialState = {
267       titleText: $localize`RBD snapshot rollback`,
268       buttonText: $localize`Rollback`,
269       bodyTpl: this.rollbackTpl,
270       bodyData: {
271         snapName: `${imageSpec}@${snapshotName}`
272       },
273       onSubmit: () => {
274         this._asyncTask('rollbackSnapshot', 'rbd/snap/rollback', snapshotName);
275       }
276     };
277
278     this.modalRef = this.modalService.show(ConfirmationModalComponent, initialState);
279   }
280
281   deleteSnapshotModal() {
282     const snapshotName = this.selection.selected[0].name;
283     this.modalRef = this.modalService.show(CriticalConfirmationModalComponent, {
284       itemDescription: $localize`RBD snapshot`,
285       itemNames: [snapshotName],
286       submitAction: () => this._asyncTask('deleteSnapshot', 'rbd/snap/delete', snapshotName)
287     });
288   }
289
290   updateSelection(selection: CdTableSelection) {
291     this.selection = selection;
292   }
293 }