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 61f812177561..a51f983340ab 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 8fc36a4cb479..bfa053149d87 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 da8a185ea1cb..5f12821aaa59 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 c4c4728bab5d..91518ed55fa7 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 748eeee0ee4c..7b6ee3aac9ce 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 a8b62556f28b..10d155fab023 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 2d646f968dc5..f1815b0dff95 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 d0bf2aaa18a8..cc6e6e0df5be 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 fef729de58a0..2945a79cd1cf 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 b32e78c56a8a..0c64cdbfc301 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 82a975c9df47..a3f6ef909078 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 8be95c6febe7..fe53b38786be 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 ba2d9cbe521b..2696a9d5e681 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 b5e75841afe6..2d6e7252a4dd 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 fdb6217fd446..e00895433d8d 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 8f50e4abcb24..4413afe28a86 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 3c0f9264d801..4f7caedcea9a 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 3a16fdce610d..1941cf3566dd 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 cc2eded0e3b8..cd0bc956c7cd 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 e501d9f329ad..4210fc777eff 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 406f992a9df2..ca7b42358dc5 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 000000000000..045d6f52ae6c --- /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 +}