]> git.proxmox.com Git - ceph.git/blob - ceph/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-list/rbd-list.component.ts
import ceph 14.2.5
[ceph.git] / ceph / src / pybind / mgr / dashboard / frontend / src / app / ceph / block / rbd-list / rbd-list.component.ts
1 import { Component, OnInit, TemplateRef, ViewChild } from '@angular/core';
2
3 import { I18n } from '@ngx-translate/i18n-polyfill';
4 import * as _ from 'lodash';
5 import { BsModalRef, BsModalService } from 'ngx-bootstrap/modal';
6
7 import { RbdService } from '../../../shared/api/rbd.service';
8 import { ConfirmationModalComponent } from '../../../shared/components/confirmation-modal/confirmation-modal.component';
9 import { CriticalConfirmationModalComponent } from '../../../shared/components/critical-confirmation-modal/critical-confirmation-modal.component';
10 import { ActionLabelsI18n } from '../../../shared/constants/app.constants';
11 import { TableComponent } from '../../../shared/datatable/table/table.component';
12 import { CellTemplate } from '../../../shared/enum/cell-template.enum';
13 import { ViewCacheStatus } from '../../../shared/enum/view-cache-status.enum';
14 import { CdTableAction } from '../../../shared/models/cd-table-action';
15 import { CdTableColumn } from '../../../shared/models/cd-table-column';
16 import { CdTableSelection } from '../../../shared/models/cd-table-selection';
17 import { FinishedTask } from '../../../shared/models/finished-task';
18 import { Permission } from '../../../shared/models/permissions';
19 import { DimlessBinaryPipe } from '../../../shared/pipes/dimless-binary.pipe';
20 import { DimlessPipe } from '../../../shared/pipes/dimless.pipe';
21 import { AuthStorageService } from '../../../shared/services/auth-storage.service';
22 import { TaskListService } from '../../../shared/services/task-list.service';
23 import { TaskWrapperService } from '../../../shared/services/task-wrapper.service';
24 import { URLBuilderService } from '../../../shared/services/url-builder.service';
25 import { RbdParentModel } from '../rbd-form/rbd-parent.model';
26 import { RbdTrashMoveModalComponent } from '../rbd-trash-move-modal/rbd-trash-move-modal.component';
27 import { RbdModel } from './rbd-model';
28
29 const BASE_URL = 'block/rbd';
30
31 @Component({
32 selector: 'cd-rbd-list',
33 templateUrl: './rbd-list.component.html',
34 styleUrls: ['./rbd-list.component.scss'],
35 providers: [
36 TaskListService,
37 { provide: URLBuilderService, useValue: new URLBuilderService(BASE_URL) }
38 ]
39 })
40 export class RbdListComponent implements OnInit {
41 @ViewChild(TableComponent)
42 table: TableComponent;
43 @ViewChild('usageTpl')
44 usageTpl: TemplateRef<any>;
45 @ViewChild('parentTpl')
46 parentTpl: TemplateRef<any>;
47 @ViewChild('nameTpl')
48 nameTpl: TemplateRef<any>;
49 @ViewChild('flattenTpl')
50 flattenTpl: TemplateRef<any>;
51
52 permission: Permission;
53 tableActions: CdTableAction[];
54 images: any;
55 columns: CdTableColumn[];
56 retries: number;
57 viewCacheStatusList: any[];
58 selection = new CdTableSelection();
59
60 modalRef: BsModalRef;
61
62 builders = {
63 'rbd/create': (metadata) =>
64 this.createRbdFromTask(metadata['pool_name'], metadata['image_name']),
65 'rbd/clone': (metadata) =>
66 this.createRbdFromTask(metadata['child_pool_name'], metadata['child_image_name']),
67 'rbd/copy': (metadata) =>
68 this.createRbdFromTask(metadata['dest_pool_name'], metadata['dest_image_name'])
69 };
70
71 private createRbdFromTask(pool: string, name: string): RbdModel {
72 const model = new RbdModel();
73 model.id = '-1';
74 model.name = name;
75 model.pool_name = pool;
76 return model;
77 }
78
79 constructor(
80 private authStorageService: AuthStorageService,
81 private rbdService: RbdService,
82 private dimlessBinaryPipe: DimlessBinaryPipe,
83 private dimlessPipe: DimlessPipe,
84 private modalService: BsModalService,
85 private taskWrapper: TaskWrapperService,
86 private taskListService: TaskListService,
87 private i18n: I18n,
88 private urlBuilder: URLBuilderService,
89 public actionLabels: ActionLabelsI18n
90 ) {
91 this.permission = this.authStorageService.getPermissions().rbdImage;
92 const getImageUri = () =>
93 this.selection.first() &&
94 `${encodeURIComponent(this.selection.first().pool_name)}/${encodeURIComponent(
95 this.selection.first().name
96 )}`;
97 const addAction: CdTableAction = {
98 permission: 'create',
99 icon: 'fa-plus',
100 routerLink: () => this.urlBuilder.getCreate(),
101 canBePrimary: (selection: CdTableSelection) => !selection.hasSingleSelection,
102 name: this.actionLabels.CREATE
103 };
104 const editAction: CdTableAction = {
105 permission: 'update',
106 icon: 'fa-pencil',
107 routerLink: () => this.urlBuilder.getEdit(getImageUri()),
108 name: this.actionLabels.EDIT
109 };
110 const deleteAction: CdTableAction = {
111 permission: 'delete',
112 icon: 'fa-times',
113 click: () => this.deleteRbdModal(),
114 name: this.actionLabels.DELETE
115 };
116 const copyAction: CdTableAction = {
117 permission: 'create',
118 canBePrimary: (selection: CdTableSelection) => selection.hasSingleSelection,
119 disable: (selection: CdTableSelection) =>
120 !selection.hasSingleSelection || selection.first().cdExecuting,
121 icon: 'fa-copy',
122 routerLink: () => `/block/rbd/copy/${getImageUri()}`,
123 name: this.actionLabels.COPY
124 };
125 const flattenAction: CdTableAction = {
126 permission: 'update',
127 disable: (selection: CdTableSelection) =>
128 !selection.hasSingleSelection || selection.first().cdExecuting || !selection.first().parent,
129 icon: 'fa-chain-broken',
130 click: () => this.flattenRbdModal(),
131 name: this.actionLabels.FLATTEN
132 };
133 const moveAction: CdTableAction = {
134 permission: 'delete',
135 icon: 'fa-trash-o',
136 click: () => this.trashRbdModal(),
137 name: this.actionLabels.TRASH
138 };
139 this.tableActions = [
140 addAction,
141 editAction,
142 copyAction,
143 flattenAction,
144 deleteAction,
145 moveAction
146 ];
147 }
148
149 ngOnInit() {
150 this.columns = [
151 {
152 name: this.i18n('Name'),
153 prop: 'name',
154 flexGrow: 2,
155 cellTransformation: CellTemplate.executing
156 },
157 {
158 name: this.i18n('Pool'),
159 prop: 'pool_name',
160 flexGrow: 2
161 },
162 {
163 name: this.i18n('Size'),
164 prop: 'size',
165 flexGrow: 1,
166 cellClass: 'text-right',
167 pipe: this.dimlessBinaryPipe
168 },
169 {
170 name: this.i18n('Objects'),
171 prop: 'num_objs',
172 flexGrow: 1,
173 cellClass: 'text-right',
174 pipe: this.dimlessPipe
175 },
176 {
177 name: this.i18n('Object size'),
178 prop: 'obj_size',
179 flexGrow: 1,
180 cellClass: 'text-right',
181 pipe: this.dimlessBinaryPipe
182 },
183 {
184 name: this.i18n('Provisioned'),
185 prop: 'disk_usage',
186 cellClass: 'text-center',
187 flexGrow: 1,
188 pipe: this.dimlessBinaryPipe
189 },
190 {
191 name: this.i18n('Total provisioned'),
192 prop: 'total_disk_usage',
193 cellClass: 'text-center',
194 flexGrow: 1,
195 pipe: this.dimlessBinaryPipe
196 },
197 {
198 name: this.i18n('Parent'),
199 prop: 'parent',
200 flexGrow: 2,
201 cellTemplate: this.parentTpl
202 }
203 ];
204
205 this.taskListService.init(
206 () => this.rbdService.list(),
207 (resp) => this.prepareResponse(resp),
208 (images) => (this.images = images),
209 () => this.onFetchError(),
210 this.taskFilter,
211 this.itemFilter,
212 this.builders
213 );
214 }
215
216 onFetchError() {
217 this.table.reset(); // Disable loading indicator.
218 this.viewCacheStatusList = [{ status: ViewCacheStatus.ValueException }];
219 }
220
221 prepareResponse(resp: any[]): any[] {
222 let images = [];
223 const viewCacheStatusMap = {};
224 resp.forEach((pool) => {
225 if (_.isUndefined(viewCacheStatusMap[pool.status])) {
226 viewCacheStatusMap[pool.status] = [];
227 }
228 viewCacheStatusMap[pool.status].push(pool.pool_name);
229 images = images.concat(pool.value);
230 });
231 const viewCacheStatusList = [];
232 _.forEach(viewCacheStatusMap, (value: any, key) => {
233 viewCacheStatusList.push({
234 status: parseInt(key, 10),
235 statusFor:
236 (value.length > 1 ? 'pools ' : 'pool ') +
237 '<strong>' +
238 value.join('</strong>, <strong>') +
239 '</strong>'
240 });
241 });
242 this.viewCacheStatusList = viewCacheStatusList;
243 return images;
244 }
245
246 itemFilter(entry, task) {
247 let pool_name_k: string;
248 let image_name_k: string;
249 switch (task.name) {
250 case 'rbd/copy':
251 pool_name_k = 'dest_pool_name';
252 image_name_k = 'dest_image_name';
253 break;
254 case 'rbd/clone':
255 pool_name_k = 'child_pool_name';
256 image_name_k = 'child_image_name';
257 break;
258 default:
259 pool_name_k = 'pool_name';
260 image_name_k = 'image_name';
261 break;
262 }
263 return (
264 entry.pool_name === task.metadata[pool_name_k] && entry.name === task.metadata[image_name_k]
265 );
266 }
267
268 taskFilter(task) {
269 return [
270 'rbd/clone',
271 'rbd/copy',
272 'rbd/create',
273 'rbd/delete',
274 'rbd/edit',
275 'rbd/flatten',
276 'rbd/trash/move'
277 ].includes(task.name);
278 }
279
280 updateSelection(selection: CdTableSelection) {
281 this.selection = selection;
282 }
283
284 deleteRbdModal() {
285 const poolName = this.selection.first().pool_name;
286 const imageName = this.selection.first().name;
287
288 this.modalRef = this.modalService.show(CriticalConfirmationModalComponent, {
289 initialState: {
290 itemDescription: 'RBD',
291 itemNames: [`${poolName}/${imageName}`],
292 submitActionObservable: () =>
293 this.taskWrapper.wrapTaskAroundCall({
294 task: new FinishedTask('rbd/delete', {
295 pool_name: poolName,
296 image_name: imageName
297 }),
298 call: this.rbdService.delete(poolName, imageName)
299 })
300 }
301 });
302 }
303
304 trashRbdModal() {
305 const initialState = {
306 metaType: 'RBD',
307 poolName: this.selection.first().pool_name,
308 imageName: this.selection.first().name
309 };
310 this.modalRef = this.modalService.show(RbdTrashMoveModalComponent, { initialState });
311 }
312
313 flattenRbd(poolName, imageName) {
314 this.taskWrapper
315 .wrapTaskAroundCall({
316 task: new FinishedTask('rbd/flatten', {
317 pool_name: poolName,
318 image_name: imageName
319 }),
320 call: this.rbdService.flatten(poolName, imageName)
321 })
322 .subscribe(undefined, undefined, () => {
323 this.modalRef.hide();
324 });
325 }
326
327 flattenRbdModal() {
328 const poolName = this.selection.first().pool_name;
329 const imageName = this.selection.first().name;
330 const parent: RbdParentModel = this.selection.first().parent;
331
332 const initialState = {
333 titleText: 'RBD flatten',
334 buttonText: 'Flatten',
335 bodyTpl: this.flattenTpl,
336 bodyData: {
337 parent: `${parent.pool_name}/${parent.image_name}@${parent.snap_name}`,
338 child: `${poolName}/${imageName}`
339 },
340 onSubmit: () => {
341 this.flattenRbd(poolName, imageName);
342 }
343 };
344
345 this.modalRef = this.modalService.show(ConfirmationModalComponent, { initialState });
346 }
347 }