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