]> git.proxmox.com Git - ceph.git/blame - ceph/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-list/rbd-list.component.ts
bump version to 17.2.5-pve1
[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>;
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}