]>
Commit | Line | Data |
---|---|---|
11fdf7f2 TL |
1 | import { Component, OnInit, TemplateRef, ViewChild } from '@angular/core'; |
2 | ||
f67539c2 TL |
3 | import { NgbModalRef } from '@ng-bootstrap/ng-bootstrap'; |
4 | import _ from 'lodash'; | |
2a845540 | 5 | import { Observable, Subscriber } from 'rxjs'; |
f67539c2 TL |
6 | |
7 | import { RbdService } from '~/app/shared/api/rbd.service'; | |
8 | import { ListWithDetails } from '~/app/shared/classes/list-with-details.class'; | |
2a845540 | 9 | import { TableStatus } from '~/app/shared/classes/table-status'; |
f67539c2 TL |
10 | import { ConfirmationModalComponent } from '~/app/shared/components/confirmation-modal/confirmation-modal.component'; |
11 | import { CriticalConfirmationModalComponent } from '~/app/shared/components/critical-confirmation-modal/critical-confirmation-modal.component'; | |
12 | import { ActionLabelsI18n } from '~/app/shared/constants/app.constants'; | |
13 | import { TableComponent } from '~/app/shared/datatable/table/table.component'; | |
f67539c2 | 14 | import { Icons } from '~/app/shared/enum/icons.enum'; |
f67539c2 TL |
15 | import { CdTableAction } from '~/app/shared/models/cd-table-action'; |
16 | import { CdTableColumn } from '~/app/shared/models/cd-table-column'; | |
2a845540 | 17 | import { CdTableFetchDataContext } from '~/app/shared/models/cd-table-fetch-data-context'; |
f67539c2 TL |
18 | import { CdTableSelection } from '~/app/shared/models/cd-table-selection'; |
19 | import { FinishedTask } from '~/app/shared/models/finished-task'; | |
20 | import { ImageSpec } from '~/app/shared/models/image-spec'; | |
21 | import { Permission } from '~/app/shared/models/permissions'; | |
22 | import { Task } from '~/app/shared/models/task'; | |
23 | import { DimlessBinaryPipe } from '~/app/shared/pipes/dimless-binary.pipe'; | |
24 | import { DimlessPipe } from '~/app/shared/pipes/dimless.pipe'; | |
25 | import { AuthStorageService } from '~/app/shared/services/auth-storage.service'; | |
2a845540 | 26 | import { CdTableServerSideService } from '~/app/shared/services/cd-table-server-side.service'; |
f67539c2 TL |
27 | import { ModalService } from '~/app/shared/services/modal.service'; |
28 | import { TaskListService } from '~/app/shared/services/task-list.service'; | |
29 | import { TaskWrapperService } from '~/app/shared/services/task-wrapper.service'; | |
30 | import { URLBuilderService } from '~/app/shared/services/url-builder.service'; | |
2a845540 | 31 | import { RbdFormEditRequestModel } from '../rbd-form/rbd-form-edit-request.model'; |
11fdf7f2 TL |
32 | import { RbdParentModel } from '../rbd-form/rbd-parent.model'; |
33 | import { RbdTrashMoveModalComponent } from '../rbd-trash-move-modal/rbd-trash-move-modal.component'; | |
f6b5b4d7 | 34 | import { RBDImageFormat, RbdModel } from './rbd-model'; |
11fdf7f2 TL |
35 | |
36 | const BASE_URL = 'block/rbd'; | |
37 | ||
38 | @Component({ | |
39 | selector: 'cd-rbd-list', | |
40 | templateUrl: './rbd-list.component.html', | |
41 | styleUrls: ['./rbd-list.component.scss'], | |
42 | providers: [ | |
43 | TaskListService, | |
44 | { provide: URLBuilderService, useValue: new URLBuilderService(BASE_URL) } | |
45 | ] | |
46 | }) | |
e306af50 | 47 | export class RbdListComponent extends ListWithDetails implements OnInit { |
9f95a23c | 48 | @ViewChild(TableComponent, { static: true }) |
11fdf7f2 | 49 | table: TableComponent; |
f67539c2 | 50 | @ViewChild('usageTpl') |
11fdf7f2 | 51 | usageTpl: TemplateRef<any>; |
9f95a23c | 52 | @ViewChild('parentTpl', { static: true }) |
11fdf7f2 | 53 | parentTpl: TemplateRef<any>; |
f67539c2 | 54 | @ViewChild('nameTpl') |
11fdf7f2 | 55 | nameTpl: TemplateRef<any>; |
2a845540 TL |
56 | @ViewChild('mirroringTpl', { static: true }) |
57 | mirroringTpl: TemplateRef<any>; | |
9f95a23c | 58 | @ViewChild('flattenTpl', { static: true }) |
11fdf7f2 | 59 | flattenTpl: TemplateRef<any>; |
9f95a23c TL |
60 | @ViewChild('deleteTpl', { static: true }) |
61 | deleteTpl: TemplateRef<any>; | |
b3b6e05e TL |
62 | @ViewChild('removingStatTpl', { static: true }) |
63 | removingStatTpl: TemplateRef<any>; | |
a4b75251 TL |
64 | @ViewChild('provisionedNotAvailableTooltipTpl', { static: true }) |
65 | provisionedNotAvailableTooltipTpl: TemplateRef<any>; | |
66 | @ViewChild('totalProvisionedNotAvailableTooltipTpl', { static: true }) | |
67 | totalProvisionedNotAvailableTooltipTpl: TemplateRef<any>; | |
11fdf7f2 TL |
68 | |
69 | permission: Permission; | |
70 | tableActions: CdTableAction[]; | |
71 | images: any; | |
72 | columns: CdTableColumn[]; | |
73 | retries: number; | |
2a845540 | 74 | tableStatus = new TableStatus('light'); |
11fdf7f2 | 75 | selection = new CdTableSelection(); |
b3b6e05e | 76 | icons = Icons; |
2a845540 TL |
77 | count = 0; |
78 | private tableContext: CdTableFetchDataContext = null; | |
f67539c2 | 79 | modalRef: NgbModalRef; |
11fdf7f2 TL |
80 | |
81 | builders = { | |
9f95a23c TL |
82 | 'rbd/create': (metadata: object) => |
83 | this.createRbdFromTask(metadata['pool_name'], metadata['namespace'], metadata['image_name']), | |
84 | 'rbd/delete': (metadata: object) => this.createRbdFromTaskImageSpec(metadata['image_spec']), | |
85 | 'rbd/clone': (metadata: object) => | |
86 | this.createRbdFromTask( | |
87 | metadata['child_pool_name'], | |
88 | metadata['child_namespace'], | |
89 | metadata['child_image_name'] | |
90 | ), | |
91 | 'rbd/copy': (metadata: object) => | |
92 | this.createRbdFromTask( | |
93 | metadata['dest_pool_name'], | |
94 | metadata['dest_namespace'], | |
95 | metadata['dest_image_name'] | |
96 | ) | |
11fdf7f2 | 97 | }; |
2a845540 | 98 | remove_scheduling: boolean; |
11fdf7f2 | 99 | |
9f95a23c TL |
100 | private createRbdFromTaskImageSpec(imageSpecStr: string): RbdModel { |
101 | const imageSpec = ImageSpec.fromString(imageSpecStr); | |
102 | return this.createRbdFromTask(imageSpec.poolName, imageSpec.namespace, imageSpec.imageName); | |
103 | } | |
104 | ||
105 | private createRbdFromTask(pool: string, namespace: string, name: string): RbdModel { | |
11fdf7f2 TL |
106 | const model = new RbdModel(); |
107 | model.id = '-1'; | |
f6b5b4d7 | 108 | model.unique_id = '-1'; |
11fdf7f2 | 109 | model.name = name; |
9f95a23c | 110 | model.namespace = namespace; |
11fdf7f2 | 111 | model.pool_name = pool; |
f6b5b4d7 | 112 | model.image_format = RBDImageFormat.V2; |
11fdf7f2 TL |
113 | return model; |
114 | } | |
115 | ||
116 | constructor( | |
117 | private authStorageService: AuthStorageService, | |
118 | private rbdService: RbdService, | |
119 | private dimlessBinaryPipe: DimlessBinaryPipe, | |
120 | private dimlessPipe: DimlessPipe, | |
f67539c2 | 121 | private modalService: ModalService, |
11fdf7f2 | 122 | private taskWrapper: TaskWrapperService, |
f67539c2 | 123 | public taskListService: TaskListService, |
11fdf7f2 TL |
124 | private urlBuilder: URLBuilderService, |
125 | public actionLabels: ActionLabelsI18n | |
126 | ) { | |
e306af50 | 127 | super(); |
11fdf7f2 TL |
128 | this.permission = this.authStorageService.getPermissions().rbdImage; |
129 | const getImageUri = () => | |
130 | this.selection.first() && | |
9f95a23c TL |
131 | new ImageSpec( |
132 | this.selection.first().pool_name, | |
133 | this.selection.first().namespace, | |
11fdf7f2 | 134 | this.selection.first().name |
9f95a23c | 135 | ).toStringEncoded(); |
11fdf7f2 TL |
136 | const addAction: CdTableAction = { |
137 | permission: 'create', | |
9f95a23c | 138 | icon: Icons.add, |
11fdf7f2 TL |
139 | routerLink: () => this.urlBuilder.getCreate(), |
140 | canBePrimary: (selection: CdTableSelection) => !selection.hasSingleSelection, | |
141 | name: this.actionLabels.CREATE | |
142 | }; | |
143 | const editAction: CdTableAction = { | |
144 | permission: 'update', | |
9f95a23c | 145 | icon: Icons.edit, |
11fdf7f2 | 146 | routerLink: () => this.urlBuilder.getEdit(getImageUri()), |
f67539c2 | 147 | name: this.actionLabels.EDIT, |
b3b6e05e TL |
148 | disable: (selection: CdTableSelection) => |
149 | this.getRemovingStatusDesc(selection) || this.getInvalidNameDisable(selection) | |
11fdf7f2 TL |
150 | }; |
151 | const deleteAction: CdTableAction = { | |
152 | permission: 'delete', | |
9f95a23c | 153 | icon: Icons.destroy, |
11fdf7f2 | 154 | click: () => this.deleteRbdModal(), |
9f95a23c | 155 | name: this.actionLabels.DELETE, |
f91f0fd5 | 156 | disable: (selection: CdTableSelection) => this.getDeleteDisableDesc(selection) |
11fdf7f2 | 157 | }; |
2a845540 TL |
158 | const resyncAction: CdTableAction = { |
159 | permission: 'update', | |
160 | icon: Icons.refresh, | |
161 | click: () => this.resyncRbdModal(), | |
162 | name: this.actionLabels.RESYNC, | |
163 | disable: (selection: CdTableSelection) => this.getResyncDisableDesc(selection) | |
164 | }; | |
11fdf7f2 TL |
165 | const copyAction: CdTableAction = { |
166 | permission: 'create', | |
167 | canBePrimary: (selection: CdTableSelection) => selection.hasSingleSelection, | |
168 | disable: (selection: CdTableSelection) => | |
b3b6e05e TL |
169 | this.getRemovingStatusDesc(selection) || |
170 | this.getInvalidNameDisable(selection) || | |
171 | !!selection.first().cdExecuting, | |
9f95a23c | 172 | icon: Icons.copy, |
11fdf7f2 | 173 | routerLink: () => `/block/rbd/copy/${getImageUri()}`, |
eafe8130 | 174 | name: this.actionLabels.COPY |
11fdf7f2 TL |
175 | }; |
176 | const flattenAction: CdTableAction = { | |
177 | permission: 'update', | |
178 | disable: (selection: CdTableSelection) => | |
b3b6e05e | 179 | this.getRemovingStatusDesc(selection) || |
f67539c2 TL |
180 | this.getInvalidNameDisable(selection) || |
181 | selection.first().cdExecuting || | |
182 | !selection.first().parent, | |
9f95a23c | 183 | icon: Icons.flatten, |
11fdf7f2 | 184 | click: () => this.flattenRbdModal(), |
eafe8130 | 185 | name: this.actionLabels.FLATTEN |
11fdf7f2 TL |
186 | }; |
187 | const moveAction: CdTableAction = { | |
188 | permission: 'delete', | |
9f95a23c | 189 | icon: Icons.trash, |
11fdf7f2 | 190 | click: () => this.trashRbdModal(), |
f6b5b4d7 TL |
191 | name: this.actionLabels.TRASH, |
192 | disable: (selection: CdTableSelection) => | |
b3b6e05e | 193 | this.getRemovingStatusDesc(selection) || |
f67539c2 | 194 | this.getInvalidNameDisable(selection) || |
f6b5b4d7 | 195 | selection.first().image_format === RBDImageFormat.V1 |
11fdf7f2 | 196 | }; |
2a845540 TL |
197 | const removeSchedulingAction: CdTableAction = { |
198 | permission: 'update', | |
199 | icon: Icons.edit, | |
200 | click: () => this.removeSchedulingModal(), | |
201 | name: this.actionLabels.REMOVE_SCHEDULING, | |
202 | disable: (selection: CdTableSelection) => | |
203 | this.getRemovingStatusDesc(selection) || | |
204 | this.getInvalidNameDisable(selection) || | |
205 | selection.first().schedule_info === undefined | |
206 | }; | |
207 | const promoteAction: CdTableAction = { | |
208 | permission: 'update', | |
209 | icon: Icons.edit, | |
210 | click: () => this.actionPrimary(true), | |
211 | name: this.actionLabels.PROMOTE, | |
212 | visible: () => this.selection.first() != null && !this.selection.first().primary | |
213 | }; | |
214 | const demoteAction: CdTableAction = { | |
215 | permission: 'update', | |
216 | icon: Icons.edit, | |
217 | click: () => this.actionPrimary(false), | |
218 | name: this.actionLabels.DEMOTE, | |
219 | visible: () => this.selection.first() != null && this.selection.first().primary | |
220 | }; | |
11fdf7f2 TL |
221 | this.tableActions = [ |
222 | addAction, | |
223 | editAction, | |
224 | copyAction, | |
225 | flattenAction, | |
2a845540 | 226 | resyncAction, |
11fdf7f2 | 227 | deleteAction, |
2a845540 TL |
228 | moveAction, |
229 | removeSchedulingAction, | |
230 | promoteAction, | |
231 | demoteAction | |
11fdf7f2 TL |
232 | ]; |
233 | } | |
234 | ||
235 | ngOnInit() { | |
236 | this.columns = [ | |
237 | { | |
f67539c2 | 238 | name: $localize`Name`, |
11fdf7f2 TL |
239 | prop: 'name', |
240 | flexGrow: 2, | |
b3b6e05e | 241 | cellTemplate: this.removingStatTpl |
11fdf7f2 TL |
242 | }, |
243 | { | |
f67539c2 | 244 | name: $localize`Pool`, |
11fdf7f2 TL |
245 | prop: 'pool_name', |
246 | flexGrow: 2 | |
247 | }, | |
9f95a23c | 248 | { |
f67539c2 | 249 | name: $localize`Namespace`, |
9f95a23c TL |
250 | prop: 'namespace', |
251 | flexGrow: 2 | |
252 | }, | |
11fdf7f2 | 253 | { |
f67539c2 | 254 | name: $localize`Size`, |
11fdf7f2 TL |
255 | prop: 'size', |
256 | flexGrow: 1, | |
257 | cellClass: 'text-right', | |
2a845540 | 258 | sortable: false, |
11fdf7f2 TL |
259 | pipe: this.dimlessBinaryPipe |
260 | }, | |
261 | { | |
f67539c2 | 262 | name: $localize`Objects`, |
11fdf7f2 TL |
263 | prop: 'num_objs', |
264 | flexGrow: 1, | |
265 | cellClass: 'text-right', | |
2a845540 | 266 | sortable: false, |
11fdf7f2 TL |
267 | pipe: this.dimlessPipe |
268 | }, | |
269 | { | |
f67539c2 | 270 | name: $localize`Object size`, |
11fdf7f2 TL |
271 | prop: 'obj_size', |
272 | flexGrow: 1, | |
273 | cellClass: 'text-right', | |
2a845540 | 274 | sortable: false, |
11fdf7f2 TL |
275 | pipe: this.dimlessBinaryPipe |
276 | }, | |
277 | { | |
f67539c2 | 278 | name: $localize`Provisioned`, |
11fdf7f2 TL |
279 | prop: 'disk_usage', |
280 | cellClass: 'text-center', | |
281 | flexGrow: 1, | |
a4b75251 | 282 | pipe: this.dimlessBinaryPipe, |
2a845540 | 283 | sortable: false, |
a4b75251 | 284 | cellTemplate: this.provisionedNotAvailableTooltipTpl |
11fdf7f2 TL |
285 | }, |
286 | { | |
f67539c2 | 287 | name: $localize`Total provisioned`, |
11fdf7f2 TL |
288 | prop: 'total_disk_usage', |
289 | cellClass: 'text-center', | |
290 | flexGrow: 1, | |
a4b75251 | 291 | pipe: this.dimlessBinaryPipe, |
2a845540 | 292 | sortable: false, |
a4b75251 | 293 | cellTemplate: this.totalProvisionedNotAvailableTooltipTpl |
11fdf7f2 TL |
294 | }, |
295 | { | |
f67539c2 | 296 | name: $localize`Parent`, |
11fdf7f2 TL |
297 | prop: 'parent', |
298 | flexGrow: 2, | |
2a845540 | 299 | sortable: false, |
11fdf7f2 | 300 | cellTemplate: this.parentTpl |
2a845540 TL |
301 | }, |
302 | { | |
303 | name: $localize`Mirroring`, | |
304 | prop: 'mirror_mode', | |
305 | flexGrow: 3, | |
306 | sortable: false, | |
307 | cellTemplate: this.mirroringTpl | |
11fdf7f2 TL |
308 | } |
309 | ]; | |
310 | ||
9f95a23c TL |
311 | const itemFilter = (entry: Record<string, any>, task: Task) => { |
312 | let taskImageSpec: string; | |
313 | switch (task.name) { | |
314 | case 'rbd/copy': | |
315 | taskImageSpec = new ImageSpec( | |
316 | task.metadata['dest_pool_name'], | |
317 | task.metadata['dest_namespace'], | |
318 | task.metadata['dest_image_name'] | |
319 | ).toString(); | |
320 | break; | |
321 | case 'rbd/clone': | |
322 | taskImageSpec = new ImageSpec( | |
323 | task.metadata['child_pool_name'], | |
324 | task.metadata['child_namespace'], | |
325 | task.metadata['child_image_name'] | |
326 | ).toString(); | |
327 | break; | |
328 | case 'rbd/create': | |
329 | taskImageSpec = new ImageSpec( | |
330 | task.metadata['pool_name'], | |
331 | task.metadata['namespace'], | |
332 | task.metadata['image_name'] | |
333 | ).toString(); | |
334 | break; | |
335 | default: | |
336 | taskImageSpec = task.metadata['image_spec']; | |
337 | break; | |
338 | } | |
339 | return ( | |
340 | taskImageSpec === new ImageSpec(entry.pool_name, entry.namespace, entry.name).toString() | |
341 | ); | |
342 | }; | |
343 | ||
344 | const taskFilter = (task: Task) => { | |
345 | return [ | |
346 | 'rbd/clone', | |
347 | 'rbd/copy', | |
348 | 'rbd/create', | |
349 | 'rbd/delete', | |
350 | 'rbd/edit', | |
351 | 'rbd/flatten', | |
352 | 'rbd/trash/move' | |
353 | ].includes(task.name); | |
354 | }; | |
355 | ||
11fdf7f2 | 356 | this.taskListService.init( |
2a845540 | 357 | (context) => this.getRbdImages(context), |
11fdf7f2 TL |
358 | (resp) => this.prepareResponse(resp), |
359 | (images) => (this.images = images), | |
360 | () => this.onFetchError(), | |
9f95a23c TL |
361 | taskFilter, |
362 | itemFilter, | |
11fdf7f2 TL |
363 | this.builders |
364 | ); | |
365 | } | |
366 | ||
367 | onFetchError() { | |
368 | this.table.reset(); // Disable loading indicator. | |
2a845540 TL |
369 | this.tableStatus = new TableStatus('danger'); |
370 | } | |
371 | ||
372 | getRbdImages(context: CdTableFetchDataContext) { | |
373 | if (context !== null) { | |
374 | this.tableContext = context; | |
375 | } | |
376 | if (this.tableContext == null) { | |
377 | this.tableContext = new CdTableFetchDataContext(() => undefined); | |
378 | } | |
379 | return this.rbdService.list(this.tableContext?.toParams()); | |
11fdf7f2 TL |
380 | } |
381 | ||
382 | prepareResponse(resp: any[]): any[] { | |
9f95a23c | 383 | let images: any[] = []; |
f67539c2 | 384 | |
11fdf7f2 | 385 | resp.forEach((pool) => { |
11fdf7f2 TL |
386 | images = images.concat(pool.value); |
387 | }); | |
f67539c2 | 388 | |
2a845540 TL |
389 | images.forEach((image) => { |
390 | if (image.schedule_info !== undefined) { | |
391 | let scheduling: any[] = []; | |
392 | const scheduleStatus = 'scheduled'; | |
393 | let nextSnapshotDate = +new Date(image.schedule_info.schedule_time); | |
394 | const offset = new Date().getTimezoneOffset(); | |
395 | nextSnapshotDate = nextSnapshotDate + Math.abs(offset) * 60000; | |
396 | scheduling.push(image.mirror_mode, scheduleStatus, nextSnapshotDate); | |
397 | image.mirror_mode = scheduling; | |
398 | scheduling = []; | |
399 | } | |
400 | }); | |
f67539c2 | 401 | |
2a845540 TL |
402 | if (images.length > 0) { |
403 | this.count = CdTableServerSideService.getCount(resp[0]); | |
f67539c2 | 404 | } else { |
2a845540 | 405 | this.count = 0; |
f67539c2 | 406 | } |
11fdf7f2 TL |
407 | return images; |
408 | } | |
409 | ||
11fdf7f2 TL |
410 | updateSelection(selection: CdTableSelection) { |
411 | this.selection = selection; | |
412 | } | |
413 | ||
414 | deleteRbdModal() { | |
415 | const poolName = this.selection.first().pool_name; | |
9f95a23c | 416 | const namespace = this.selection.first().namespace; |
11fdf7f2 | 417 | const imageName = this.selection.first().name; |
9f95a23c | 418 | const imageSpec = new ImageSpec(poolName, namespace, imageName); |
11fdf7f2 TL |
419 | |
420 | this.modalRef = this.modalService.show(CriticalConfirmationModalComponent, { | |
f67539c2 TL |
421 | itemDescription: 'RBD', |
422 | itemNames: [imageSpec], | |
423 | bodyTemplate: this.deleteTpl, | |
424 | bodyContext: { | |
425 | hasSnapshots: this.hasSnapshots(), | |
426 | snapshots: this.listProtectedSnapshots() | |
427 | }, | |
428 | submitActionObservable: () => | |
429 | this.taskWrapper.wrapTaskAroundCall({ | |
430 | task: new FinishedTask('rbd/delete', { | |
431 | image_spec: imageSpec.toString() | |
432 | }), | |
433 | call: this.rbdService.delete(imageSpec) | |
434 | }) | |
11fdf7f2 TL |
435 | }); |
436 | } | |
437 | ||
2a845540 TL |
438 | resyncRbdModal() { |
439 | const poolName = this.selection.first().pool_name; | |
440 | const namespace = this.selection.first().namespace; | |
441 | const imageName = this.selection.first().name; | |
442 | const imageSpec = new ImageSpec(poolName, namespace, imageName); | |
443 | ||
444 | this.modalRef = this.modalService.show(CriticalConfirmationModalComponent, { | |
445 | itemDescription: 'RBD', | |
446 | itemNames: [imageSpec], | |
447 | actionDescription: 'resync', | |
448 | submitActionObservable: () => | |
449 | this.taskWrapper.wrapTaskAroundCall({ | |
450 | task: new FinishedTask('rbd/edit', { | |
451 | image_spec: imageSpec.toString() | |
452 | }), | |
453 | call: this.rbdService.update(imageSpec, { resync: true }) | |
454 | }) | |
455 | }); | |
456 | } | |
457 | ||
11fdf7f2 TL |
458 | trashRbdModal() { |
459 | const initialState = { | |
11fdf7f2 | 460 | poolName: this.selection.first().pool_name, |
9f95a23c TL |
461 | namespace: this.selection.first().namespace, |
462 | imageName: this.selection.first().name, | |
463 | hasSnapshots: this.hasSnapshots() | |
11fdf7f2 | 464 | }; |
f67539c2 | 465 | this.modalRef = this.modalService.show(RbdTrashMoveModalComponent, initialState); |
11fdf7f2 TL |
466 | } |
467 | ||
9f95a23c | 468 | flattenRbd(imageSpec: ImageSpec) { |
11fdf7f2 TL |
469 | this.taskWrapper |
470 | .wrapTaskAroundCall({ | |
471 | task: new FinishedTask('rbd/flatten', { | |
9f95a23c | 472 | image_spec: imageSpec.toString() |
11fdf7f2 | 473 | }), |
9f95a23c | 474 | call: this.rbdService.flatten(imageSpec) |
11fdf7f2 | 475 | }) |
f67539c2 TL |
476 | .subscribe({ |
477 | complete: () => { | |
478 | this.modalRef.close(); | |
479 | } | |
11fdf7f2 TL |
480 | }); |
481 | } | |
482 | ||
483 | flattenRbdModal() { | |
484 | const poolName = this.selection.first().pool_name; | |
9f95a23c | 485 | const namespace = this.selection.first().namespace; |
11fdf7f2 TL |
486 | const imageName = this.selection.first().name; |
487 | const parent: RbdParentModel = this.selection.first().parent; | |
9f95a23c TL |
488 | const parentImageSpec = new ImageSpec( |
489 | parent.pool_name, | |
490 | parent.pool_namespace, | |
491 | parent.image_name | |
492 | ); | |
493 | const childImageSpec = new ImageSpec(poolName, namespace, imageName); | |
11fdf7f2 TL |
494 | |
495 | const initialState = { | |
496 | titleText: 'RBD flatten', | |
497 | buttonText: 'Flatten', | |
498 | bodyTpl: this.flattenTpl, | |
499 | bodyData: { | |
9f95a23c TL |
500 | parent: `${parentImageSpec}@${parent.snap_name}`, |
501 | child: childImageSpec.toString() | |
11fdf7f2 TL |
502 | }, |
503 | onSubmit: () => { | |
9f95a23c | 504 | this.flattenRbd(childImageSpec); |
11fdf7f2 TL |
505 | } |
506 | }; | |
507 | ||
f67539c2 | 508 | this.modalRef = this.modalService.show(ConfirmationModalComponent, initialState); |
11fdf7f2 | 509 | } |
9f95a23c | 510 | |
2a845540 TL |
511 | editRequest() { |
512 | const request = new RbdFormEditRequestModel(); | |
513 | request.remove_scheduling = !request.remove_scheduling; | |
514 | return request; | |
515 | } | |
516 | ||
517 | removeSchedulingModal() { | |
518 | const imageName = this.selection.first().name; | |
519 | ||
520 | const imageSpec = new ImageSpec( | |
521 | this.selection.first().pool_name, | |
522 | this.selection.first().namespace, | |
523 | this.selection.first().name | |
524 | ); | |
525 | ||
526 | this.modalRef = this.modalService.show(CriticalConfirmationModalComponent, { | |
527 | actionDescription: 'remove scheduling on', | |
528 | itemDescription: $localize`image`, | |
529 | itemNames: [`${imageName}`], | |
530 | submitActionObservable: () => | |
531 | new Observable((observer: Subscriber<any>) => { | |
532 | this.taskWrapper | |
533 | .wrapTaskAroundCall({ | |
534 | task: new FinishedTask('rbd/edit', { | |
535 | image_spec: imageSpec.toString() | |
536 | }), | |
537 | call: this.rbdService.update(imageSpec, this.editRequest()) | |
538 | }) | |
539 | .subscribe({ | |
540 | error: (resp) => observer.error(resp), | |
541 | complete: () => { | |
542 | this.modalRef.close(); | |
543 | } | |
544 | }); | |
545 | }) | |
546 | }); | |
547 | } | |
548 | ||
549 | actionPrimary(primary: boolean) { | |
550 | const request = new RbdFormEditRequestModel(); | |
551 | request.primary = primary; | |
552 | const imageSpec = new ImageSpec( | |
553 | this.selection.first().pool_name, | |
554 | this.selection.first().namespace, | |
555 | this.selection.first().name | |
556 | ); | |
557 | this.taskWrapper | |
558 | .wrapTaskAroundCall({ | |
559 | task: new FinishedTask('rbd/edit', { | |
560 | image_spec: imageSpec.toString() | |
561 | }), | |
562 | call: this.rbdService.update(imageSpec, request) | |
563 | }) | |
564 | .subscribe(); | |
565 | } | |
566 | ||
9f95a23c TL |
567 | hasSnapshots() { |
568 | const snapshots = this.selection.first()['snapshots'] || []; | |
569 | return snapshots.length > 0; | |
570 | } | |
571 | ||
572 | hasClonedSnapshots(image: object) { | |
573 | const snapshots = image['snapshots'] || []; | |
574 | return snapshots.some((snap: object) => snap['children'] && snap['children'].length > 0); | |
575 | } | |
576 | ||
577 | listProtectedSnapshots() { | |
578 | const first = this.selection.first(); | |
579 | const snapshots = first['snapshots']; | |
580 | return snapshots.reduce((accumulator: string[], snap: object) => { | |
581 | if (snap['is_protected']) { | |
582 | accumulator.push(snap['name']); | |
583 | } | |
584 | return accumulator; | |
585 | }, []); | |
586 | } | |
587 | ||
f91f0fd5 TL |
588 | getDeleteDisableDesc(selection: CdTableSelection): string | boolean { |
589 | const first = selection.first(); | |
590 | ||
9f95a23c | 591 | if (first && this.hasClonedSnapshots(first)) { |
f67539c2 | 592 | return $localize`This RBD has cloned snapshots. Please delete related RBDs before deleting this RBD.`; |
9f95a23c TL |
593 | } |
594 | ||
f67539c2 TL |
595 | return this.getInvalidNameDisable(selection) || this.hasClonedSnapshots(selection.first()); |
596 | } | |
597 | ||
2a845540 TL |
598 | getResyncDisableDesc(selection: CdTableSelection): string | boolean { |
599 | const first = selection.first(); | |
600 | ||
601 | if (first && this.imageIsPrimary(first)) { | |
602 | return $localize`Primary RBD images cannot be resynced`; | |
603 | } | |
604 | ||
605 | return this.getInvalidNameDisable(selection); | |
606 | } | |
607 | ||
608 | imageIsPrimary(image: object) { | |
609 | return image['primary']; | |
610 | } | |
f67539c2 TL |
611 | getInvalidNameDisable(selection: CdTableSelection): string | boolean { |
612 | const first = selection.first(); | |
613 | ||
614 | if (first?.name?.match(/[@/]/)) { | |
615 | return $localize`This RBD image has an invalid name and can't be managed by ceph.`; | |
616 | } | |
617 | ||
618 | return !selection.first() || !selection.hasSingleSelection; | |
9f95a23c | 619 | } |
b3b6e05e TL |
620 | |
621 | getRemovingStatusDesc(selection: CdTableSelection): string | boolean { | |
622 | const first = selection.first(); | |
623 | if (first?.source === 'REMOVING') { | |
624 | return $localize`Action not possible for an RBD in status 'Removing'`; | |
625 | } | |
626 | return false; | |
627 | } | |
11fdf7f2 | 628 | } |