]> git.proxmox.com Git - ceph.git/blob - ceph/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-snapshot-list/rbd-snapshot-list.component.ts
import ceph quincy 17.2.6
[ceph.git] / ceph / src / pybind / mgr / dashboard / frontend / src / app / ceph / block / rbd-snapshot-list / rbd-snapshot-list.component.ts
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 { RbdMirroringService } from '~/app/shared/api/rbd-mirroring.service';
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 }