]>
Commit | Line | Data |
---|---|---|
11fdf7f2 TL |
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'; | |
e306af50 | 8 | import { ListWithDetails } from '../../../shared/classes/list-with-details.class'; |
11fdf7f2 TL |
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 { TableComponent } from '../../../shared/datatable/table/table.component'; | |
13 | import { CellTemplate } from '../../../shared/enum/cell-template.enum'; | |
9f95a23c | 14 | import { Icons } from '../../../shared/enum/icons.enum'; |
11fdf7f2 TL |
15 | import { ViewCacheStatus } from '../../../shared/enum/view-cache-status.enum'; |
16 | import { CdTableAction } from '../../../shared/models/cd-table-action'; | |
17 | import { CdTableColumn } from '../../../shared/models/cd-table-column'; | |
18 | import { CdTableSelection } from '../../../shared/models/cd-table-selection'; | |
19 | import { FinishedTask } from '../../../shared/models/finished-task'; | |
9f95a23c | 20 | import { ImageSpec } from '../../../shared/models/image-spec'; |
11fdf7f2 | 21 | import { Permission } from '../../../shared/models/permissions'; |
9f95a23c | 22 | import { Task } from '../../../shared/models/task'; |
11fdf7f2 TL |
23 | import { DimlessBinaryPipe } from '../../../shared/pipes/dimless-binary.pipe'; |
24 | import { DimlessPipe } from '../../../shared/pipes/dimless.pipe'; | |
25 | import { AuthStorageService } from '../../../shared/services/auth-storage.service'; | |
26 | import { TaskListService } from '../../../shared/services/task-list.service'; | |
27 | import { TaskWrapperService } from '../../../shared/services/task-wrapper.service'; | |
28 | import { URLBuilderService } from '../../../shared/services/url-builder.service'; | |
29 | import { RbdParentModel } from '../rbd-form/rbd-parent.model'; | |
30 | import { RbdTrashMoveModalComponent } from '../rbd-trash-move-modal/rbd-trash-move-modal.component'; | |
31 | import { RbdModel } from './rbd-model'; | |
32 | ||
33 | const BASE_URL = 'block/rbd'; | |
34 | ||
35 | @Component({ | |
36 | selector: 'cd-rbd-list', | |
37 | templateUrl: './rbd-list.component.html', | |
38 | styleUrls: ['./rbd-list.component.scss'], | |
39 | providers: [ | |
40 | TaskListService, | |
41 | { provide: URLBuilderService, useValue: new URLBuilderService(BASE_URL) } | |
42 | ] | |
43 | }) | |
e306af50 | 44 | export class RbdListComponent extends ListWithDetails implements OnInit { |
9f95a23c | 45 | @ViewChild(TableComponent, { static: true }) |
11fdf7f2 | 46 | table: TableComponent; |
9f95a23c | 47 | @ViewChild('usageTpl', { static: false }) |
11fdf7f2 | 48 | usageTpl: TemplateRef<any>; |
9f95a23c | 49 | @ViewChild('parentTpl', { static: true }) |
11fdf7f2 | 50 | parentTpl: TemplateRef<any>; |
9f95a23c | 51 | @ViewChild('nameTpl', { static: false }) |
11fdf7f2 | 52 | nameTpl: TemplateRef<any>; |
9f95a23c | 53 | @ViewChild('flattenTpl', { static: true }) |
11fdf7f2 | 54 | flattenTpl: TemplateRef<any>; |
9f95a23c TL |
55 | @ViewChild('deleteTpl', { static: true }) |
56 | deleteTpl: TemplateRef<any>; | |
11fdf7f2 TL |
57 | |
58 | permission: Permission; | |
59 | tableActions: CdTableAction[]; | |
60 | images: any; | |
61 | columns: CdTableColumn[]; | |
62 | retries: number; | |
63 | viewCacheStatusList: any[]; | |
64 | selection = new CdTableSelection(); | |
65 | ||
66 | modalRef: BsModalRef; | |
67 | ||
68 | builders = { | |
9f95a23c TL |
69 | 'rbd/create': (metadata: object) => |
70 | this.createRbdFromTask(metadata['pool_name'], metadata['namespace'], metadata['image_name']), | |
71 | 'rbd/delete': (metadata: object) => this.createRbdFromTaskImageSpec(metadata['image_spec']), | |
72 | 'rbd/clone': (metadata: object) => | |
73 | this.createRbdFromTask( | |
74 | metadata['child_pool_name'], | |
75 | metadata['child_namespace'], | |
76 | metadata['child_image_name'] | |
77 | ), | |
78 | 'rbd/copy': (metadata: object) => | |
79 | this.createRbdFromTask( | |
80 | metadata['dest_pool_name'], | |
81 | metadata['dest_namespace'], | |
82 | metadata['dest_image_name'] | |
83 | ) | |
11fdf7f2 TL |
84 | }; |
85 | ||
9f95a23c TL |
86 | private createRbdFromTaskImageSpec(imageSpecStr: string): RbdModel { |
87 | const imageSpec = ImageSpec.fromString(imageSpecStr); | |
88 | return this.createRbdFromTask(imageSpec.poolName, imageSpec.namespace, imageSpec.imageName); | |
89 | } | |
90 | ||
91 | private createRbdFromTask(pool: string, namespace: string, name: string): RbdModel { | |
11fdf7f2 TL |
92 | const model = new RbdModel(); |
93 | model.id = '-1'; | |
94 | model.name = name; | |
9f95a23c | 95 | model.namespace = namespace; |
11fdf7f2 TL |
96 | model.pool_name = pool; |
97 | return model; | |
98 | } | |
99 | ||
100 | constructor( | |
101 | private authStorageService: AuthStorageService, | |
102 | private rbdService: RbdService, | |
103 | private dimlessBinaryPipe: DimlessBinaryPipe, | |
104 | private dimlessPipe: DimlessPipe, | |
105 | private modalService: BsModalService, | |
106 | private taskWrapper: TaskWrapperService, | |
107 | private taskListService: TaskListService, | |
108 | private i18n: I18n, | |
109 | private urlBuilder: URLBuilderService, | |
110 | public actionLabels: ActionLabelsI18n | |
111 | ) { | |
e306af50 | 112 | super(); |
11fdf7f2 TL |
113 | this.permission = this.authStorageService.getPermissions().rbdImage; |
114 | const getImageUri = () => | |
115 | this.selection.first() && | |
9f95a23c TL |
116 | new ImageSpec( |
117 | this.selection.first().pool_name, | |
118 | this.selection.first().namespace, | |
11fdf7f2 | 119 | this.selection.first().name |
9f95a23c | 120 | ).toStringEncoded(); |
11fdf7f2 TL |
121 | const addAction: CdTableAction = { |
122 | permission: 'create', | |
9f95a23c | 123 | icon: Icons.add, |
11fdf7f2 TL |
124 | routerLink: () => this.urlBuilder.getCreate(), |
125 | canBePrimary: (selection: CdTableSelection) => !selection.hasSingleSelection, | |
126 | name: this.actionLabels.CREATE | |
127 | }; | |
128 | const editAction: CdTableAction = { | |
129 | permission: 'update', | |
9f95a23c | 130 | icon: Icons.edit, |
11fdf7f2 TL |
131 | routerLink: () => this.urlBuilder.getEdit(getImageUri()), |
132 | name: this.actionLabels.EDIT | |
133 | }; | |
134 | const deleteAction: CdTableAction = { | |
135 | permission: 'delete', | |
9f95a23c | 136 | icon: Icons.destroy, |
11fdf7f2 | 137 | click: () => this.deleteRbdModal(), |
9f95a23c TL |
138 | name: this.actionLabels.DELETE, |
139 | disable: (selection: CdTableSelection) => | |
140 | !this.selection.first() || | |
141 | !this.selection.hasSingleSelection || | |
142 | this.hasClonedSnapshots(selection.first()), | |
143 | disableDesc: () => this.getDeleteDisableDesc() | |
11fdf7f2 TL |
144 | }; |
145 | const copyAction: CdTableAction = { | |
146 | permission: 'create', | |
147 | canBePrimary: (selection: CdTableSelection) => selection.hasSingleSelection, | |
148 | disable: (selection: CdTableSelection) => | |
149 | !selection.hasSingleSelection || selection.first().cdExecuting, | |
9f95a23c | 150 | icon: Icons.copy, |
11fdf7f2 | 151 | routerLink: () => `/block/rbd/copy/${getImageUri()}`, |
eafe8130 | 152 | name: this.actionLabels.COPY |
11fdf7f2 TL |
153 | }; |
154 | const flattenAction: CdTableAction = { | |
155 | permission: 'update', | |
156 | disable: (selection: CdTableSelection) => | |
157 | !selection.hasSingleSelection || selection.first().cdExecuting || !selection.first().parent, | |
9f95a23c | 158 | icon: Icons.flatten, |
11fdf7f2 | 159 | click: () => this.flattenRbdModal(), |
eafe8130 | 160 | name: this.actionLabels.FLATTEN |
11fdf7f2 TL |
161 | }; |
162 | const moveAction: CdTableAction = { | |
163 | permission: 'delete', | |
9f95a23c | 164 | icon: Icons.trash, |
11fdf7f2 | 165 | click: () => this.trashRbdModal(), |
eafe8130 | 166 | name: this.actionLabels.TRASH |
11fdf7f2 TL |
167 | }; |
168 | this.tableActions = [ | |
169 | addAction, | |
170 | editAction, | |
171 | copyAction, | |
172 | flattenAction, | |
173 | deleteAction, | |
174 | moveAction | |
175 | ]; | |
176 | } | |
177 | ||
178 | ngOnInit() { | |
179 | this.columns = [ | |
180 | { | |
181 | name: this.i18n('Name'), | |
182 | prop: 'name', | |
183 | flexGrow: 2, | |
184 | cellTransformation: CellTemplate.executing | |
185 | }, | |
186 | { | |
187 | name: this.i18n('Pool'), | |
188 | prop: 'pool_name', | |
189 | flexGrow: 2 | |
190 | }, | |
9f95a23c TL |
191 | { |
192 | name: this.i18n('Namespace'), | |
193 | prop: 'namespace', | |
194 | flexGrow: 2 | |
195 | }, | |
11fdf7f2 TL |
196 | { |
197 | name: this.i18n('Size'), | |
198 | prop: 'size', | |
199 | flexGrow: 1, | |
200 | cellClass: 'text-right', | |
201 | pipe: this.dimlessBinaryPipe | |
202 | }, | |
203 | { | |
204 | name: this.i18n('Objects'), | |
205 | prop: 'num_objs', | |
206 | flexGrow: 1, | |
207 | cellClass: 'text-right', | |
208 | pipe: this.dimlessPipe | |
209 | }, | |
210 | { | |
211 | name: this.i18n('Object size'), | |
212 | prop: 'obj_size', | |
213 | flexGrow: 1, | |
214 | cellClass: 'text-right', | |
215 | pipe: this.dimlessBinaryPipe | |
216 | }, | |
217 | { | |
218 | name: this.i18n('Provisioned'), | |
219 | prop: 'disk_usage', | |
220 | cellClass: 'text-center', | |
221 | flexGrow: 1, | |
222 | pipe: this.dimlessBinaryPipe | |
223 | }, | |
224 | { | |
225 | name: this.i18n('Total provisioned'), | |
226 | prop: 'total_disk_usage', | |
227 | cellClass: 'text-center', | |
228 | flexGrow: 1, | |
229 | pipe: this.dimlessBinaryPipe | |
230 | }, | |
231 | { | |
232 | name: this.i18n('Parent'), | |
233 | prop: 'parent', | |
234 | flexGrow: 2, | |
235 | cellTemplate: this.parentTpl | |
236 | } | |
237 | ]; | |
238 | ||
9f95a23c TL |
239 | const itemFilter = (entry: Record<string, any>, task: Task) => { |
240 | let taskImageSpec: string; | |
241 | switch (task.name) { | |
242 | case 'rbd/copy': | |
243 | taskImageSpec = new ImageSpec( | |
244 | task.metadata['dest_pool_name'], | |
245 | task.metadata['dest_namespace'], | |
246 | task.metadata['dest_image_name'] | |
247 | ).toString(); | |
248 | break; | |
249 | case 'rbd/clone': | |
250 | taskImageSpec = new ImageSpec( | |
251 | task.metadata['child_pool_name'], | |
252 | task.metadata['child_namespace'], | |
253 | task.metadata['child_image_name'] | |
254 | ).toString(); | |
255 | break; | |
256 | case 'rbd/create': | |
257 | taskImageSpec = new ImageSpec( | |
258 | task.metadata['pool_name'], | |
259 | task.metadata['namespace'], | |
260 | task.metadata['image_name'] | |
261 | ).toString(); | |
262 | break; | |
263 | default: | |
264 | taskImageSpec = task.metadata['image_spec']; | |
265 | break; | |
266 | } | |
267 | return ( | |
268 | taskImageSpec === new ImageSpec(entry.pool_name, entry.namespace, entry.name).toString() | |
269 | ); | |
270 | }; | |
271 | ||
272 | const taskFilter = (task: Task) => { | |
273 | return [ | |
274 | 'rbd/clone', | |
275 | 'rbd/copy', | |
276 | 'rbd/create', | |
277 | 'rbd/delete', | |
278 | 'rbd/edit', | |
279 | 'rbd/flatten', | |
280 | 'rbd/trash/move' | |
281 | ].includes(task.name); | |
282 | }; | |
283 | ||
11fdf7f2 TL |
284 | this.taskListService.init( |
285 | () => this.rbdService.list(), | |
286 | (resp) => this.prepareResponse(resp), | |
287 | (images) => (this.images = images), | |
288 | () => this.onFetchError(), | |
9f95a23c TL |
289 | taskFilter, |
290 | itemFilter, | |
11fdf7f2 TL |
291 | this.builders |
292 | ); | |
293 | } | |
294 | ||
295 | onFetchError() { | |
296 | this.table.reset(); // Disable loading indicator. | |
297 | this.viewCacheStatusList = [{ status: ViewCacheStatus.ValueException }]; | |
298 | } | |
299 | ||
300 | prepareResponse(resp: any[]): any[] { | |
9f95a23c | 301 | let images: any[] = []; |
11fdf7f2 TL |
302 | const viewCacheStatusMap = {}; |
303 | resp.forEach((pool) => { | |
304 | if (_.isUndefined(viewCacheStatusMap[pool.status])) { | |
305 | viewCacheStatusMap[pool.status] = []; | |
306 | } | |
307 | viewCacheStatusMap[pool.status].push(pool.pool_name); | |
308 | images = images.concat(pool.value); | |
309 | }); | |
9f95a23c | 310 | const viewCacheStatusList: any[] = []; |
11fdf7f2 TL |
311 | _.forEach(viewCacheStatusMap, (value: any, key) => { |
312 | viewCacheStatusList.push({ | |
313 | status: parseInt(key, 10), | |
314 | statusFor: | |
315 | (value.length > 1 ? 'pools ' : 'pool ') + | |
316 | '<strong>' + | |
317 | value.join('</strong>, <strong>') + | |
318 | '</strong>' | |
319 | }); | |
320 | }); | |
321 | this.viewCacheStatusList = viewCacheStatusList; | |
322 | return images; | |
323 | } | |
324 | ||
11fdf7f2 TL |
325 | updateSelection(selection: CdTableSelection) { |
326 | this.selection = selection; | |
327 | } | |
328 | ||
329 | deleteRbdModal() { | |
330 | const poolName = this.selection.first().pool_name; | |
9f95a23c | 331 | const namespace = this.selection.first().namespace; |
11fdf7f2 | 332 | const imageName = this.selection.first().name; |
9f95a23c | 333 | const imageSpec = new ImageSpec(poolName, namespace, imageName); |
11fdf7f2 TL |
334 | |
335 | this.modalRef = this.modalService.show(CriticalConfirmationModalComponent, { | |
336 | initialState: { | |
337 | itemDescription: 'RBD', | |
9f95a23c TL |
338 | itemNames: [imageSpec], |
339 | bodyTemplate: this.deleteTpl, | |
340 | bodyContext: { | |
341 | hasSnapshots: this.hasSnapshots(), | |
342 | snapshots: this.listProtectedSnapshots() | |
343 | }, | |
11fdf7f2 TL |
344 | submitActionObservable: () => |
345 | this.taskWrapper.wrapTaskAroundCall({ | |
346 | task: new FinishedTask('rbd/delete', { | |
9f95a23c | 347 | image_spec: imageSpec.toString() |
11fdf7f2 | 348 | }), |
9f95a23c | 349 | call: this.rbdService.delete(imageSpec) |
11fdf7f2 TL |
350 | }) |
351 | } | |
352 | }); | |
353 | } | |
354 | ||
355 | trashRbdModal() { | |
356 | const initialState = { | |
11fdf7f2 | 357 | poolName: this.selection.first().pool_name, |
9f95a23c TL |
358 | namespace: this.selection.first().namespace, |
359 | imageName: this.selection.first().name, | |
360 | hasSnapshots: this.hasSnapshots() | |
11fdf7f2 TL |
361 | }; |
362 | this.modalRef = this.modalService.show(RbdTrashMoveModalComponent, { initialState }); | |
363 | } | |
364 | ||
9f95a23c | 365 | flattenRbd(imageSpec: ImageSpec) { |
11fdf7f2 TL |
366 | this.taskWrapper |
367 | .wrapTaskAroundCall({ | |
368 | task: new FinishedTask('rbd/flatten', { | |
9f95a23c | 369 | image_spec: imageSpec.toString() |
11fdf7f2 | 370 | }), |
9f95a23c | 371 | call: this.rbdService.flatten(imageSpec) |
11fdf7f2 TL |
372 | }) |
373 | .subscribe(undefined, undefined, () => { | |
374 | this.modalRef.hide(); | |
375 | }); | |
376 | } | |
377 | ||
378 | flattenRbdModal() { | |
379 | const poolName = this.selection.first().pool_name; | |
9f95a23c | 380 | const namespace = this.selection.first().namespace; |
11fdf7f2 TL |
381 | const imageName = this.selection.first().name; |
382 | const parent: RbdParentModel = this.selection.first().parent; | |
9f95a23c TL |
383 | const parentImageSpec = new ImageSpec( |
384 | parent.pool_name, | |
385 | parent.pool_namespace, | |
386 | parent.image_name | |
387 | ); | |
388 | const childImageSpec = new ImageSpec(poolName, namespace, imageName); | |
11fdf7f2 TL |
389 | |
390 | const initialState = { | |
391 | titleText: 'RBD flatten', | |
392 | buttonText: 'Flatten', | |
393 | bodyTpl: this.flattenTpl, | |
394 | bodyData: { | |
9f95a23c TL |
395 | parent: `${parentImageSpec}@${parent.snap_name}`, |
396 | child: childImageSpec.toString() | |
11fdf7f2 TL |
397 | }, |
398 | onSubmit: () => { | |
9f95a23c | 399 | this.flattenRbd(childImageSpec); |
11fdf7f2 TL |
400 | } |
401 | }; | |
402 | ||
403 | this.modalRef = this.modalService.show(ConfirmationModalComponent, { initialState }); | |
404 | } | |
9f95a23c TL |
405 | |
406 | hasSnapshots() { | |
407 | const snapshots = this.selection.first()['snapshots'] || []; | |
408 | return snapshots.length > 0; | |
409 | } | |
410 | ||
411 | hasClonedSnapshots(image: object) { | |
412 | const snapshots = image['snapshots'] || []; | |
413 | return snapshots.some((snap: object) => snap['children'] && snap['children'].length > 0); | |
414 | } | |
415 | ||
416 | listProtectedSnapshots() { | |
417 | const first = this.selection.first(); | |
418 | const snapshots = first['snapshots']; | |
419 | return snapshots.reduce((accumulator: string[], snap: object) => { | |
420 | if (snap['is_protected']) { | |
421 | accumulator.push(snap['name']); | |
422 | } | |
423 | return accumulator; | |
424 | }, []); | |
425 | } | |
426 | ||
427 | getDeleteDisableDesc(): string { | |
428 | const first = this.selection.first(); | |
429 | if (first && this.hasClonedSnapshots(first)) { | |
430 | return this.i18n( | |
431 | 'This RBD has cloned snapshots. Please delete related RBDs before deleting this RBD.' | |
432 | ); | |
433 | } | |
434 | ||
435 | return ''; | |
436 | } | |
11fdf7f2 | 437 | } |