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