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