]> git.proxmox.com Git - ceph.git/blame - ceph/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-list/rbd-list.component.ts
import ceph quincy 17.2.6
[ceph.git] / ceph / src / pybind / mgr / dashboard / frontend / src / app / ceph / block / rbd-list / rbd-list.component.ts
CommitLineData
11fdf7f2
TL
1import { Component, OnInit, TemplateRef, ViewChild } from '@angular/core';
2
f67539c2
TL
3import { NgbModalRef } from '@ng-bootstrap/ng-bootstrap';
4import _ from 'lodash';
2a845540 5import { Observable, Subscriber } from 'rxjs';
f67539c2
TL
6
7import { RbdService } from '~/app/shared/api/rbd.service';
8import { ListWithDetails } from '~/app/shared/classes/list-with-details.class';
2a845540 9import { TableStatus } from '~/app/shared/classes/table-status';
f67539c2
TL
10import { ConfirmationModalComponent } from '~/app/shared/components/confirmation-modal/confirmation-modal.component';
11import { CriticalConfirmationModalComponent } from '~/app/shared/components/critical-confirmation-modal/critical-confirmation-modal.component';
12import { ActionLabelsI18n } from '~/app/shared/constants/app.constants';
13import { TableComponent } from '~/app/shared/datatable/table/table.component';
f67539c2 14import { Icons } from '~/app/shared/enum/icons.enum';
f67539c2
TL
15import { CdTableAction } from '~/app/shared/models/cd-table-action';
16import { CdTableColumn } from '~/app/shared/models/cd-table-column';
2a845540 17import { CdTableFetchDataContext } from '~/app/shared/models/cd-table-fetch-data-context';
f67539c2
TL
18import { CdTableSelection } from '~/app/shared/models/cd-table-selection';
19import { FinishedTask } from '~/app/shared/models/finished-task';
20import { ImageSpec } from '~/app/shared/models/image-spec';
21import { Permission } from '~/app/shared/models/permissions';
22import { Task } from '~/app/shared/models/task';
23import { DimlessBinaryPipe } from '~/app/shared/pipes/dimless-binary.pipe';
24import { DimlessPipe } from '~/app/shared/pipes/dimless.pipe';
25import { AuthStorageService } from '~/app/shared/services/auth-storage.service';
2a845540 26import { CdTableServerSideService } from '~/app/shared/services/cd-table-server-side.service';
f67539c2
TL
27import { ModalService } from '~/app/shared/services/modal.service';
28import { TaskListService } from '~/app/shared/services/task-list.service';
29import { TaskWrapperService } from '~/app/shared/services/task-wrapper.service';
30import { URLBuilderService } from '~/app/shared/services/url-builder.service';
2a845540 31import { RbdFormEditRequestModel } from '../rbd-form/rbd-form-edit-request.model';
11fdf7f2
TL
32import { RbdParentModel } from '../rbd-form/rbd-parent.model';
33import { RbdTrashMoveModalComponent } from '../rbd-trash-move-modal/rbd-trash-move-modal.component';
f6b5b4d7 34import { RBDImageFormat, RbdModel } from './rbd-model';
11fdf7f2
TL
35
36const 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 47export 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}