]> 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 15.2.0 Octopus source
[ceph.git] / ceph / src / pybind / mgr / dashboard / frontend / src / app / ceph / block / rbd-snapshot-list / rbd-snapshot-list.component.ts
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 { 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', { 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(RbdSnapshotFormModalComponent);
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 }