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