From: Naman Munet Date: Wed, 22 Jan 2025 10:59:20 +0000 (+0530) Subject: mgr/dashboard: Add confirmation textbox for resource name on delete action X-Git-Tag: v19.2.3~343^2~1 X-Git-Url: http://git-server-git.apps.pok.os.sepia.ceph.com/?a=commitdiff_plain;h=0ec1d36a6bdd374386ea0013badb0964327c8d70;p=ceph.git mgr/dashboard: Add confirmation textbox for resource name on delete action Before: ===== User was able to delete a single or multiple critical resources like ( images, snapshots, subvolumes, subvolume-groups, pools, hosts , OSDs, buckets, file system, services ) by just clicking on a checkbox. After: ===== User now has to type the resource name that they are deleting in the textbox on the delete modal, and then only they will be able to delete the critical resource. Also from now onwards multiple selection for deletions of critical resources is not possible. Hence, user can delete only single resource at a time. On the other side, non-critical resources can be deleted in one go. fixes: https://tracker.ceph.com/issues/69628 Signed-off-by: Naman Munet (cherry picked from commit ed222df10900198707d7708518e02af5c06c912b) --- diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/mirroring/pool-list/pool-list.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/mirroring/pool-list/pool-list.component.ts index 61f81217756..a51f983340a 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/mirroring/pool-list/pool-list.component.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/mirroring/pool-list/pool-list.component.ts @@ -17,6 +17,7 @@ import { AuthStorageService } from '~/app/shared/services/auth-storage.service'; import { ModalService } from '~/app/shared/services/modal.service'; import { TaskWrapperService } from '~/app/shared/services/task-wrapper.service'; import { PoolEditPeerModalComponent } from '../pool-edit-peer-modal/pool-edit-peer-modal.component'; +import { DeletionImpact } from '~/app/shared/enum/critical-confirmation-modal-impact.enum'; const BASE_URL = '/block/mirroring'; @Component({ @@ -149,7 +150,8 @@ export class PoolListComponent implements OnInit, OnDestroy { const poolName = this.selection.first().name; const peerUUID = this.getPeerUUID(); - this.modalRef = this.modalService.show(CriticalConfirmationModalComponent, { + this.modalService.show(CriticalConfirmationModalComponent, { + impact: DeletionImpact.high, itemDescription: $localize`mirror peer`, itemNames: [`${poolName} (${peerUUID})`], submitActionObservable: () => diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-list/rbd-list.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-list/rbd-list.component.ts index 8fc36a4cb47..bfa053149d8 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-list/rbd-list.component.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-list/rbd-list.component.ts @@ -33,6 +33,9 @@ import { RbdParentModel } from '../rbd-form/rbd-parent.model'; import { RbdTrashMoveModalComponent } from '../rbd-trash-move-modal/rbd-trash-move-modal.component'; import { RBDImageFormat, RbdModel } from './rbd-model'; +import { ModalCdsService } from '~/app/shared/services/modal-cds.service'; +import { RBDActionHelpers } from '../rbd-contants'; +import { DeletionImpact } from '~/app/shared/enum/critical-confirmation-modal-impact.enum'; const BASE_URL = 'block/rbd'; @Component({ @@ -428,8 +431,9 @@ export class RbdListComponent extends ListWithDetails implements OnInit { const imageSpec = new ImageSpec(poolName, namespace, imageName); this.modalRef = this.modalService.show(CriticalConfirmationModalComponent, { + impact: DeletionImpact.high, itemDescription: 'RBD', - itemNames: [imageSpec], + itemNames: [imageSpec.imageName], bodyTemplate: this.deleteTpl, bodyContext: { hasSnapshots: this.hasSnapshots(), diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-snapshot-list/rbd-snapshot-list.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-snapshot-list/rbd-snapshot-list.component.ts index da8a185ea1c..5f12821aaa5 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-snapshot-list/rbd-snapshot-list.component.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-snapshot-list/rbd-snapshot-list.component.ts @@ -38,6 +38,7 @@ import { TaskManagerService } from '~/app/shared/services/task-manager.service'; import { RbdSnapshotFormModalComponent } from '../rbd-snapshot-form/rbd-snapshot-form-modal.component'; import { RbdSnapshotActionsModel } from './rbd-snapshot-actions.model'; import { RbdSnapshotModel } from './rbd-snapshot.model'; +import { DeletionImpact } from '~/app/shared/enum/critical-confirmation-modal-impact.enum'; @Component({ selector: 'cd-rbd-snapshot-list', @@ -326,6 +327,7 @@ export class RbdSnapshotListComponent implements OnInit, OnChanges { deleteSnapshotModal() { const snapshotName = this.selection.selected[0].name; this.modalRef = this.modalService.show(CriticalConfirmationModalComponent, { + impact: DeletionImpact.high, itemDescription: $localize`RBD snapshot`, itemNames: [snapshotName], submitAction: () => this._asyncTask('deleteSnapshot', 'rbd/snap/delete', snapshotName) diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs-directories/cephfs-directories.component.html b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs-directories/cephfs-directories.component.html index c4c4728bab5..91518ed55fa 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs-directories/cephfs-directories.component.html +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs-directories/cephfs-directories.component.html @@ -54,7 +54,7 @@ [columns]="snapshot.columns" identifier="name" forceIdentifier="true" - selectionType="multiClick" + selectionType="single" (updateSelection)="snapshot.updateSelection($event)"> this.deleteSnapshotModal(), - canBePrimary: (selection) => selection.hasSelection, - disable: (selection) => !selection.hasSelection + click: () => this.deleteSnapshotModal() } ] }; @@ -692,6 +691,7 @@ export class CephfsDirectoriesComponent implements OnInit, OnChanges { deleteSnapshotModal() { this.modalRef = this.modalService.show(CriticalConfirmationModalComponent, { + impact: DeletionImpact.high, itemDescription: $localize`CephFs Snapshot`, itemNames: this.snapshot.selection.selected.map((snapshot: CephfsSnapshot) => snapshot.name), submitAction: () => this.deleteSnapshot() diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs-list/cephfs-list.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs-list/cephfs-list.component.ts index 748eeee0ee4..7b6ee3aac9c 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs-list/cephfs-list.component.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs-list/cephfs-list.component.ts @@ -26,6 +26,7 @@ import { CephfsMountDetailsComponent } from '../cephfs-mount-details/cephfs-moun import { map, switchMap } from 'rxjs/operators'; import { HealthService } from '~/app/shared/api/health.service'; import { CephfsAuthModalComponent } from '~/app/ceph/cephfs/cephfs-auth-modal/cephfs-auth-modal.component'; +import { DeletionImpact } from '~/app/shared/enum/critical-confirmation-modal-impact.enum'; const BASE_URL = 'cephfs/fs'; @@ -172,6 +173,7 @@ export class CephfsListComponent extends ListWithDetails implements OnInit { removeVolumeModal() { const volName = this.selection.first().mdsmap['fs_name']; this.modalService.show(CriticalConfirmationModalComponent, { + impact: DeletionImpact.high, itemDescription: 'File System', itemNames: [volName], actionDescription: 'remove', diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs-subvolume-group/cephfs-subvolume-group.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs-subvolume-group/cephfs-subvolume-group.component.ts index a8b62556f28..10d155fab02 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs-subvolume-group/cephfs-subvolume-group.component.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs-subvolume-group/cephfs-subvolume-group.component.ts @@ -20,6 +20,7 @@ import { TaskWrapperService } from '~/app/shared/services/task-wrapper.service'; import { CephfsSubvolumeGroup } from '~/app/shared/models/cephfs-subvolume-group.model'; import { NgbModalRef } from '@ng-bootstrap/ng-bootstrap'; import _ from 'lodash'; +import { DeletionImpact } from '~/app/shared/enum/critical-confirmation-modal-impact.enum'; @Component({ selector: 'cd-cephfs-subvolume-group', @@ -178,6 +179,7 @@ export class CephfsSubvolumeGroupComponent implements OnInit, OnChanges { removeSubVolumeModal() { const name = this.selection.first().name; this.modalService.show(CriticalConfirmationModalComponent, { + impact: DeletionImpact.high, itemDescription: 'subvolume group', itemNames: [name], actionDescription: 'remove', diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs-subvolume-list/cephfs-subvolume-list.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs-subvolume-list/cephfs-subvolume-list.component.ts index 2d646f968dc..f1815b0dff9 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs-subvolume-list/cephfs-subvolume-list.component.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs-subvolume-list/cephfs-subvolume-list.component.ts @@ -36,6 +36,7 @@ import { HealthService } from '~/app/shared/api/health.service'; import _ from 'lodash'; const DEFAULT_SUBVOLUME_GROUP = '_nogroup'; +import { DeletionImpact } from '~/app/shared/enum/critical-confirmation-modal-impact.enum'; @Component({ selector: 'cd-cephfs-subvolume-list', @@ -252,6 +253,7 @@ export class CephfsSubvolumeListComponent extends CdForm implements OnInit, OnCh this.selectedName = this.selection.first().name; this.modalRef = this.modalService.show(CriticalConfirmationModalComponent, { actionDescription: 'Remove', + impact: DeletionImpact.high, itemNames: [this.selectedName], itemDescription: 'Subvolume', childFormGroup: this.removeForm, diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs-subvolume-snapshots-list/cephfs-subvolume-snapshots-list.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs-subvolume-snapshots-list/cephfs-subvolume-snapshots-list.component.ts index d0bf2aaa18a..cc6e6e0df5b 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs-subvolume-snapshots-list/cephfs-subvolume-snapshots-list.component.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs-subvolume-snapshots-list/cephfs-subvolume-snapshots-list.component.ts @@ -27,6 +27,7 @@ import moment from 'moment'; import { Validators } from '@angular/forms'; import { CdValidators } from '~/app/shared/forms/cd-validators'; import { DEFAULT_SUBVOLUME_GROUP } from '~/app/shared/constants/cephfs'; +import { DeletionImpact } from '~/app/shared/enum/critical-confirmation-modal-impact.enum'; @Component({ selector: 'cd-cephfs-subvolume-snapshots-list', @@ -232,7 +233,8 @@ export class CephfsSubvolumeSnapshotsListComponent implements OnInit, OnChanges const subVolumeGroupName = this.activeGroupName; const fsName = this.fsName; this.modalRef = this.modalService.show(CriticalConfirmationModalComponent, { - actionDescription: this.actionLabels.REMOVE, + impact: DeletionImpact.high, + actionDescription: 'remove', itemNames: [snapshotName], itemDescription: 'Snapshot', submitAction: () => diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/hosts/hosts.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/hosts/hosts.component.ts index fef729de58a..2945a79cd1c 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/hosts/hosts.component.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/hosts/hosts.component.ts @@ -34,6 +34,7 @@ import { NotificationService } from '~/app/shared/services/notification.service' import { TaskWrapperService } from '~/app/shared/services/task-wrapper.service'; import { URLBuilderService } from '~/app/shared/services/url-builder.service'; import { HostFormComponent } from './host-form/host-form.component'; +import { DeletionImpact } from '~/app/shared/enum/critical-confirmation-modal-impact.enum'; const BASE_URL = 'hosts'; @@ -468,6 +469,7 @@ export class HostsComponent extends ListWithDetails implements OnDestroy, OnInit deleteAction() { const hostname = this.selection.first().hostname; this.modalRef = this.modalService.show(CriticalConfirmationModalComponent, { + impact: DeletionImpact.high, itemDescription: 'Host', itemNames: [hostname], actionDescription: 'remove', diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/osd/osd-list/osd-list.component.html b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/osd/osd-list/osd-list.component.html index b32e78c56a8..0c64cdbfc30 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/osd/osd-list/osd-list.component.html +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/osd/osd-list/osd-list.component.html @@ -8,7 +8,7 @@ ): void { check(this.getSelectedOsdIds()).subscribe((result) => { + const osdIds = this.getSelectedOsdIds(); const modalRef = this.modalService.show(CriticalConfirmationModalComponent, { + impact: DeletionImpact.high, + itemNames: osdIds, actionDescription: actionDescription, itemDescription: itemDescription, bodyTemplate: this.criticalConfirmationTpl, @@ -590,7 +594,7 @@ export class OsdListComponent extends ListWithDetails implements OnInit { missingStats: result.missing_stats, storedPgs: result.stored_pgs, actionDescription: templateItemDescription, - osdIds: this.getSelectedOsdIds() + osdIds }, childFormGroup: childFormGroup, childFormGroupTemplate: childFormGroupTemplate, diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/services/services.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/services/services.component.ts index 82a975c9df4..a3f6ef90907 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/services/services.component.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/services/services.component.ts @@ -27,6 +27,7 @@ import { TaskWrapperService } from '~/app/shared/services/task-wrapper.service'; import { URLBuilderService } from '~/app/shared/services/url-builder.service'; import { PlacementPipe } from './placement.pipe'; import { ServiceFormComponent } from './service-form/service-form.component'; +import { DeletionImpact } from '~/app/shared/enum/critical-confirmation-modal-impact.enum'; const BASE_URL = 'services'; @@ -238,6 +239,7 @@ export class ServicesComponent extends ListWithDetails implements OnChanges, OnI deleteAction() { const service = this.selection.first(); this.modalService.show(CriticalConfirmationModalComponent, { + impact: DeletionImpact.high, itemDescription: $localize`Service`, itemNames: [service.service_name], actionDescription: 'delete', diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/nfs/nfs-list/nfs-list.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/nfs/nfs-list/nfs-list.component.ts index 8be95c6febe..fe53b38786b 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/nfs/nfs-list/nfs-list.component.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/nfs/nfs-list/nfs-list.component.ts @@ -25,6 +25,12 @@ import { TaskListService } from '~/app/shared/services/task-list.service'; import { TaskWrapperService } from '~/app/shared/services/task-wrapper.service'; import { getFsalFromRoute, getPathfromFsal } from '../utils'; import { SUPPORTED_FSAL } from '../models/nfs.fsal'; +import { DeletionImpact } from '~/app/shared/enum/critical-confirmation-modal-impact.enum'; + +export enum RgwExportType { + BUCKET = 'bucket', + USER = 'user' +} @Component({ selector: 'cd-nfs-list', @@ -191,6 +197,7 @@ export class NfsListComponent extends ListWithDetails implements OnInit, OnDestr const export_id = this.selection.first().export_id; this.modalRef = this.modalService.show(CriticalConfirmationModalComponent, { + impact: DeletionImpact.high, itemDescription: $localize`NFS export`, itemNames: [`${cluster_id}:${export_id}`], submitActionObservable: () => diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/pool/pool-list/pool-list.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/pool/pool-list/pool-list.component.ts index ba2d9cbe521..2696a9d5e68 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/pool/pool-list/pool-list.component.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/pool/pool-list/pool-list.component.ts @@ -30,6 +30,7 @@ import { TaskWrapperService } from '~/app/shared/services/task-wrapper.service'; import { URLBuilderService } from '~/app/shared/services/url-builder.service'; import { Pool } from '../pool'; import { PoolStat, PoolStats } from '../pool-stat'; +import { DeletionImpact } from '~/app/shared/enum/critical-confirmation-modal-impact.enum'; const BASE_URL = 'pool'; @@ -224,6 +225,7 @@ export class PoolListComponent extends ListWithDetails implements OnInit { deletePoolModal() { const name = this.selection.first().pool_name; this.modalService.show(CriticalConfirmationModalComponent, { + impact: DeletionImpact.high, itemDescription: 'Pool', itemNames: [name], submitActionObservable: () => diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-bucket-list/rgw-bucket-list.component.html b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-bucket-list/rgw-bucket-list.component.html index b5e75841afe..2d6e7252a4d 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-bucket-list/rgw-bucket-list.component.html +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-bucket-list/rgw-bucket-list.component.html @@ -3,7 +3,7 @@ [data]="buckets" [columns]="columns" columnMode="flex" - selectionType="multiClick" + selectionType="single" [hasDetails]="true" (setExpandedRow)="setExpandedRow($event)" (updateSelection)="updateSelection($event)" diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-bucket-list/rgw-bucket-list.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-bucket-list/rgw-bucket-list.component.ts index fdb6217fd44..e00895433d8 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-bucket-list/rgw-bucket-list.component.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-bucket-list/rgw-bucket-list.component.ts @@ -21,6 +21,7 @@ import { AuthStorageService } from '~/app/shared/services/auth-storage.service'; import { ModalService } from '~/app/shared/services/modal.service'; import { URLBuilderService } from '~/app/shared/services/url-builder.service'; import { Bucket } from '../models/rgw-bucket'; +import { DeletionImpact } from '~/app/shared/enum/critical-confirmation-modal-impact.enum'; const BASE_URL = 'rgw/bucket'; @@ -116,9 +117,7 @@ export class RgwBucketListComponent extends ListWithDetails implements OnInit, O permission: 'delete', icon: Icons.destroy, click: () => this.deleteAction(), - disable: () => !this.selection.hasSelection, - name: this.actionLabels.DELETE, - canBePrimary: (selection: CdTableSelection) => selection.hasMultiSelection + name: this.actionLabels.DELETE }; this.tableActions = [addAction, editAction, deleteAction]; this.setTableRefreshTimeout(); @@ -144,9 +143,11 @@ export class RgwBucketListComponent extends ListWithDetails implements OnInit, O } deleteAction() { + const itemNames = this.selection.selected.map((bucket: any) => bucket['bid']); this.modalService.show(CriticalConfirmationModalComponent, { - itemDescription: this.selection.hasSingleSelection ? $localize`bucket` : $localize`buckets`, - itemNames: this.selection.selected.map((bucket: any) => bucket['bid']), + itemDescription: $localize`bucket`, + impact: DeletionImpact.high, + itemNames: itemNames, submitActionObservable: () => { return new Observable((observer: Subscriber) => { // Delete all selected data table rows. diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-user-list/rgw-user-list.component.html b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-user-list/rgw-user-list.component.html index 8f50e4abcb2..4413afe28a8 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-user-list/rgw-user-list.component.html +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-user-list/rgw-user-list.component.html @@ -5,7 +5,7 @@ [data]="users" [columns]="columns" columnMode="flex" - selectionType="multiClick" + selectionType="single" [hasDetails]="true" (setExpandedRow)="setExpandedRow($event)" (updateSelection)="updateSelection($event)" diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-user-list/rgw-user-list.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-user-list/rgw-user-list.component.ts index 3c0f9264d80..4f7caedcea9 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-user-list/rgw-user-list.component.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-user-list/rgw-user-list.component.ts @@ -8,6 +8,7 @@ import { CriticalConfirmationModalComponent } from '~/app/shared/components/crit import { ActionLabelsI18n } from '~/app/shared/constants/app.constants'; import { TableComponent } from '~/app/shared/datatable/table/table.component'; import { CellTemplate } from '~/app/shared/enum/cell-template.enum'; +import { DeletionImpact } from '~/app/shared/enum/critical-confirmation-modal-impact.enum'; import { Icons } from '~/app/shared/enum/icons.enum'; import { CdTableAction } from '~/app/shared/models/cd-table-action'; import { CdTableColumn } from '~/app/shared/models/cd-table-column'; @@ -124,8 +125,7 @@ export class RgwUserListComponent extends ListWithDetails implements OnInit { icon: Icons.destroy, click: () => this.deleteAction(), disable: () => !this.selection.hasSelection, - name: this.actionLabels.DELETE, - canBePrimary: (selection: CdTableSelection) => selection.hasMultiSelection + name: this.actionLabels.DELETE }; this.tableActions = [addAction, editAction, deleteAction]; this.setTableRefreshTimeout(); @@ -149,6 +149,7 @@ export class RgwUserListComponent extends ListWithDetails implements OnInit { deleteAction() { this.modalService.show(CriticalConfirmationModalComponent, { + impact: DeletionImpact.high, itemDescription: this.selection.hasSingleSelection ? $localize`user` : $localize`users`, itemNames: this.selection.selected.map((user: any) => user['uid']), submitActionObservable: (): Observable => { diff --git a/src/pybind/mgr/dashboard/frontend/src/app/core/auth/user-list/user-list.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/core/auth/user-list/user-list.component.ts index 3a16fdce610..1941cf3566d 100755 --- a/src/pybind/mgr/dashboard/frontend/src/app/core/auth/user-list/user-list.component.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/core/auth/user-list/user-list.component.ts @@ -7,6 +7,7 @@ import { UserService } from '~/app/shared/api/user.service'; import { CriticalConfirmationModalComponent } from '~/app/shared/components/critical-confirmation-modal/critical-confirmation-modal.component'; import { ActionLabelsI18n } from '~/app/shared/constants/app.constants'; import { CellTemplate } from '~/app/shared/enum/cell-template.enum'; +import { DeletionImpact } from '~/app/shared/enum/critical-confirmation-modal-impact.enum'; import { Icons } from '~/app/shared/enum/icons.enum'; import { NotificationType } from '~/app/shared/enum/notification-type.enum'; import { CdTableAction } from '~/app/shared/models/cd-table-action'; @@ -174,6 +175,7 @@ export class UserListComponent implements OnInit { } this.modalRef = this.modalService.show(CriticalConfirmationModalComponent, { + impact: DeletionImpact.high, itemDescription: 'User', itemNames: [username], submitAction: () => this.deleteUser(username) diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/components/critical-confirmation-modal/critical-confirmation-modal.component.html b/src/pybind/mgr/dashboard/frontend/src/app/shared/components/critical-confirmation-modal/critical-confirmation-modal.component.html index cc2eded0e3b..cd0bc956c7c 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/shared/components/critical-confirmation-modal/critical-confirmation-modal.component.html +++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/components/critical-confirmation-modal/critical-confirmation-modal.component.html @@ -27,17 +27,39 @@
-
- - -
+ + Yes, I am sure. + + + Resource Name + + + + + This field is required. + + + Enter the correct resource name. + + +
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/components/critical-confirmation-modal/critical-confirmation-modal.component.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/components/critical-confirmation-modal/critical-confirmation-modal.component.spec.ts index e501d9f329a..4210fc777ef 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/shared/components/critical-confirmation-modal/critical-confirmation-modal.component.spec.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/components/critical-confirmation-modal/critical-confirmation-modal.component.spec.ts @@ -11,6 +11,7 @@ import { configureTestBed, modalServiceShow } from '~/testing/unit-test-helper'; import { AlertPanelComponent } from '../alert-panel/alert-panel.component'; import { LoadingPanelComponent } from '../loading-panel/loading-panel.component'; import { CriticalConfirmationModalComponent } from './critical-confirmation-modal.component'; +import { DeletionImpact } from '../../enum/critical-confirmation-modal-impact.enum'; @NgModule({}) export class MockModule {} @@ -141,8 +142,8 @@ describe('CriticalConfirmationModalComponent', () => { }); describe('component functions', () => { - const changeValue = (value: boolean) => { - const ctrl = component.deletionForm.get('confirmation'); + const changeValue = (formControl: string, value: any) => { + const ctrl = component.deletionForm.get(formControl); ctrl.setValue(value); ctrl.markAsDirty(); ctrl.updateValueAndValidity(); @@ -167,17 +168,57 @@ describe('CriticalConfirmationModalComponent', () => { beforeEach(() => { component.deletionForm.reset(); + const ctrl = component.deletionForm.get('impact'); + ctrl.setValue(DeletionImpact.normal); + ctrl.markAsDirty(); + ctrl.updateValueAndValidity(); + component.deletionForm.get('confirmation').updateValueAndValidity(); }); it('should test empty values', () => { component.deletionForm.reset(); testValidation(false, undefined, false); testValidation(true, 'required', true); + changeValue('confirmation', true); + changeValue('confirmation', false); + testValidation(true, 'required', true); + }); + }); + + describe('validate confirmInput', () => { + const testValidation = (submitted: boolean, error: string, expected: boolean) => { + expect( + component.deletionForm.showError('confirmInput', { submitted: submitted }, error) + ).toBe(expected); + }; + + beforeEach(() => { component.deletionForm.reset(); - changeValue(true); - changeValue(false); + const ctrl = component.deletionForm.get('impact'); + ctrl.setValue(DeletionImpact.high); + ctrl.markAsDirty(); + ctrl.updateValueAndValidity(); + component.deletionForm.get('confirmInput').updateValueAndValidity(); + }); + + it('should test empty values', () => { + testValidation(true, 'required', true); + changeValue('confirmInput', 'dummytext'); + changeValue('confirmInput', ''); testValidation(true, 'required', true); }); + + it('should give error, if entered resource name is wrong', () => { + component.itemNames = ['resource1']; + changeValue('confirmInput', 'dummytext'); + testValidation(true, 'matchResource', true); + }); + + it('should give error, if entered resource name is correct', () => { + component.itemNames = ['resource1']; + changeValue('confirmInput', 'resource1'); + testValidation(true, 'matchResource', false); + }); }); describe('deletion call', () => { diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/components/critical-confirmation-modal/critical-confirmation-modal.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/components/critical-confirmation-modal/critical-confirmation-modal.component.ts index 406f992a9df..ca7b42358dc 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/shared/components/critical-confirmation-modal/critical-confirmation-modal.component.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/components/critical-confirmation-modal/critical-confirmation-modal.component.ts @@ -6,6 +6,9 @@ import { Observable } from 'rxjs'; import { CdFormGroup } from '~/app/shared/forms/cd-form-group'; import { SubmitButtonComponent } from '../submit-button/submit-button.component'; +import { BaseModal } from 'carbon-components-angular'; +import { CdValidators } from '../../forms/cd-validators'; +import { DeletionImpact } from '../../enum/critical-confirmation-modal-impact.enum'; @Component({ selector: 'cd-deletion-modal', @@ -25,15 +28,33 @@ export class CriticalConfirmationModalComponent implements OnInit { itemDescription: 'entry'; itemNames: string[]; actionDescription = 'delete'; - + impactEnum = DeletionImpact; childFormGroup: CdFormGroup; childFormGroupTemplate: TemplateRef; - - constructor(public activeModal: NgbActiveModal) {} + impact: DeletionImpact; + constructor(public activeModal: NgbActiveModal) { + this.impact = this.impact || DeletionImpact.normal; + } ngOnInit() { const controls = { - confirmation: new UntypedFormControl(false, [Validators.requiredTrue]) + impact: new UntypedFormControl(this.impact), + confirmation: new UntypedFormControl(false, { + validators: [ + CdValidators.composeIf( + { + impact: DeletionImpact.normal + }, + [Validators.requiredTrue] + ) + ] + }), + confirmInput: new UntypedFormControl('', [ + CdValidators.composeIf({ impact: this.impactEnum.high }, [ + this.matchResourceName.bind(this), + Validators.required + ]) + ]) }; if (this.childFormGroup) { controls['child'] = this.childFormGroup; @@ -44,6 +65,13 @@ export class CriticalConfirmationModalComponent implements OnInit { } } + matchResourceName(control: AbstractControl): ValidationErrors | null { + if (this.itemNames && control.value != this.itemNames[0]) { + return { matchResource: true }; + } + return null; + } + callSubmitAction() { if (this.submitActionObservable) { this.submitActionObservable().subscribe({ diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/enum/critical-confirmation-modal-impact.enum.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/enum/critical-confirmation-modal-impact.enum.ts new file mode 100644 index 00000000000..045d6f52ae6 --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/enum/critical-confirmation-modal-impact.enum.ts @@ -0,0 +1,4 @@ +export enum DeletionImpact { + normal = 'normal', + high = 'high' // NOTE: User should be able to select only single resource while deleting +}