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