]> git-server-git.apps.pok.os.sepia.ceph.com Git - ceph.git/commitdiff
mgr/dashboard: Add confirmation textbox for resource name on delete action
authorNaman Munet <naman.munet@ibm.com>
Wed, 22 Jan 2025 10:59:20 +0000 (16:29 +0530)
committerNaman Munet <naman.munet@ibm.com>
Tue, 18 Feb 2025 10:34:18 +0000 (16:04 +0530)
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 <naman.munet@ibm.com>
25 files changed:
src/pybind/mgr/dashboard/frontend/src/app/ceph/block/mirroring/pool-list/pool-list.component.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-list/rbd-list.component.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-snapshot-list/rbd-snapshot-list.component.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs-directories/cephfs-directories.component.html
src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs-directories/cephfs-directories.component.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs-list/cephfs-list.component.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs-subvolume-group/cephfs-subvolume-group.component.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs-subvolume-list/cephfs-subvolume-list.component.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs-subvolume-snapshots-list/cephfs-subvolume-snapshots-list.component.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/hosts/hosts.component.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/osd/osd-list/osd-list.component.html
src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/osd/osd-list/osd-list.component.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/services/services.component.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/nfs/nfs-list/nfs-list.component.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/pool/pool-list/pool-list.component.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-bucket-list/rgw-bucket-list.component.html
src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-bucket-list/rgw-bucket-list.component.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-user-list/rgw-user-list.component.html
src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-user-list/rgw-user-list.component.ts
src/pybind/mgr/dashboard/frontend/src/app/core/auth/user-list/user-list.component.ts
src/pybind/mgr/dashboard/frontend/src/app/shared/components/critical-confirmation-modal/critical-confirmation-modal.component.html
src/pybind/mgr/dashboard/frontend/src/app/shared/components/critical-confirmation-modal/critical-confirmation-modal.component.spec.ts
src/pybind/mgr/dashboard/frontend/src/app/shared/components/critical-confirmation-modal/critical-confirmation-modal.component.ts
src/pybind/mgr/dashboard/frontend/src/app/shared/enum/critical-confirmation-modal-impact.enum.ts [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/src/app/shared/services/task-message.service.ts

index 847a75b630a685c1b13927540ce62f79495c6e95..ef075f0f4b629409c90e02243bdd4e284dfc283f 100644 (file)
@@ -16,6 +16,7 @@ import { AuthStorageService } from '~/app/shared/services/auth-storage.service';
 import { TaskWrapperService } from '~/app/shared/services/task-wrapper.service';
 import { PoolEditPeerModalComponent } from '../pool-edit-peer-modal/pool-edit-peer-modal.component';
 import { ModalCdsService } from '~/app/shared/services/modal-cds.service';
+import { DeletionImpact } from '~/app/shared/enum/critical-confirmation-modal-impact.enum';
 
 const BASE_URL = '/block/mirroring';
 @Component({
@@ -147,6 +148,7 @@ export class PoolListComponent implements OnInit, OnDestroy {
     const peerUUID = this.getPeerUUID();
 
     this.modalService.show(CriticalConfirmationModalComponent, {
+      impact: DeletionImpact.high,
       itemDescription: $localize`mirror peer`,
       itemNames: [`${poolName} (${peerUUID})`],
       submitActionObservable: () =>
index 52d9ff819e2fb605c4946f1e31d6f80d492d4e57..9e5c4fc9e3f3dea530a42a192816aa0889775400 100644 (file)
@@ -32,6 +32,7 @@ import { RbdTrashMoveModalComponent } from '../rbd-trash-move-modal/rbd-trash-mo
 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({
@@ -426,8 +427,9 @@ export class RbdListComponent extends ListWithDetails implements OnInit {
     const imageSpec = new ImageSpec(poolName, namespace, imageName);
 
     this.cdsModalService.show(CriticalConfirmationModalComponent, {
+      impact: DeletionImpact.high,
       itemDescription: 'RBD',
-      itemNames: [imageSpec],
+      itemNames: [imageSpec.imageName],
       bodyTemplate: this.deleteTpl,
       bodyContext: {
         hasSnapshots: this.hasSnapshots(),
index d014598214defcc4f47c2968da47374b99b47703..ac20dc76fa310f018c02004931049e9264e19286 100644 (file)
@@ -37,6 +37,7 @@ import { RbdSnapshotFormModalComponent } from '../rbd-snapshot-form/rbd-snapshot
 import { RbdSnapshotActionsModel } from './rbd-snapshot-actions.model';
 import { RbdSnapshotModel } from './rbd-snapshot.model';
 import { ModalCdsService } from '~/app/shared/services/modal-cds.service';
+import { DeletionImpact } from '~/app/shared/enum/critical-confirmation-modal-impact.enum';
 
 @Component({
   selector: 'cd-rbd-snapshot-list',
@@ -325,6 +326,7 @@ export class RbdSnapshotListComponent implements OnInit, OnChanges {
   deleteSnapshotModal() {
     const snapshotName = this.selection.selected[0].name;
     this.modalRef = this.cdsModalService.show(CriticalConfirmationModalComponent, {
+      impact: DeletionImpact.high,
       itemDescription: $localize`RBD snapshot`,
       itemNames: [snapshotName],
       submitAction: () => this._asyncTask('deleteSnapshot', 'rbd/snap/delete', snapshotName)
index a6a64bf27346ae7b4f2a3aeb3a8e69296203c4f6..a1a74471b37caa1e49fa2529f247dec4804dc884 100644 (file)
@@ -53,7 +53,7 @@
                   [columns]="snapshot.columns"
                   identifier="name"
                   forceIdentifier="true"
-                  selectionType="multiClick"
+                  selectionType="single"
                   (updateSelection)="snapshot.updateSelection($event)">
           <cd-table-actions class="table-actions"
                             [permission]="permission"
index 3add42ae238c021e6031ada862d5bf751e889b49..f51290e31ec2626e8ccccc4888dd040bdb2a443b 100644 (file)
@@ -12,6 +12,7 @@ import { CriticalConfirmationModalComponent } from '~/app/shared/components/crit
 import { FormModalComponent } from '~/app/shared/components/form-modal/form-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 { CdValidators } from '~/app/shared/forms/cd-validators';
@@ -208,9 +209,7 @@ export class CephfsDirectoriesComponent implements OnInit, OnChanges {
           name: this.actionLabels.DELETE,
           icon: Icons.destroy,
           permission: 'delete',
-          click: () => this.deleteSnapshotModal(),
-          canBePrimary: (selection) => selection.hasSelection,
-          disable: (selection) => !selection.hasSelection
+          click: () => this.deleteSnapshotModal()
         }
       ]
     };
@@ -684,6 +683,7 @@ export class CephfsDirectoriesComponent implements OnInit, OnChanges {
 
   deleteSnapshotModal() {
     this.modalService.show(CriticalConfirmationModalComponent, {
+      impact: DeletionImpact.high,
       itemDescription: $localize`CephFs Snapshot`,
       itemNames: this.snapshot.selection.selected.map((snapshot: CephfsSnapshot) => snapshot.name),
       submitAction: () => this.deleteSnapshot()
index 41c1ff76fe41655894cd8be85252d4c556fbaae6..cda087b0a33cab8b062dc3ab9f6e300a17950fd8 100644 (file)
@@ -26,6 +26,7 @@ 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 { ModalCdsService } from '~/app/shared/services/modal-cds.service';
+import { DeletionImpact } from '~/app/shared/enum/critical-confirmation-modal-impact.enum';
 
 const BASE_URL = 'cephfs/fs';
 
@@ -173,6 +174,7 @@ export class CephfsListComponent extends ListWithDetails implements OnInit {
   removeVolumeModal() {
     const volName = this.selection.first().mdsmap['fs_name'];
     this.cdsModalService.show(CriticalConfirmationModalComponent, {
+      impact: DeletionImpact.high,
       itemDescription: 'File System',
       itemNames: [volName],
       actionDescription: 'remove',
index ea3a18a50163bdfd9361bd14f66b3fd07229f64b..167ea4b8c5cedde0519c42d63ec6456fd718f70c 100644 (file)
@@ -20,6 +20,7 @@ import { CephfsSubvolumeGroup } from '~/app/shared/models/cephfs-subvolume-group
 import { NgbModalRef } from '@ng-bootstrap/ng-bootstrap';
 import _ from 'lodash';
 import { ModalCdsService } from '~/app/shared/services/modal-cds.service';
+import { DeletionImpact } from '~/app/shared/enum/critical-confirmation-modal-impact.enum';
 
 @Component({
   selector: 'cd-cephfs-subvolume-group',
@@ -175,6 +176,7 @@ export class CephfsSubvolumeGroupComponent implements OnInit, OnChanges {
   removeSubVolumeModal() {
     const name = this.selection.first().name;
     this.cdsModalService.show(CriticalConfirmationModalComponent, {
+      impact: DeletionImpact.high,
       itemDescription: 'subvolume group',
       itemNames: [name],
       actionDescription: 'remove',
index be7b81940dffd5cb430f847db87c609f8f460106..013288fc36b342238cdd9f1157f80d3c08b9f0d0 100644 (file)
@@ -35,6 +35,7 @@ import { HealthService } from '~/app/shared/api/health.service';
 import _ from 'lodash';
 import { ModalCdsService } from '~/app/shared/services/modal-cds.service';
 import { DEFAULT_SUBVOLUME_GROUP } from '~/app/shared/constants/cephfs.constant';
+import { DeletionImpact } from '~/app/shared/enum/critical-confirmation-modal-impact.enum';
 
 @Component({
   selector: 'cd-cephfs-subvolume-list',
@@ -246,7 +247,8 @@ export class CephfsSubvolumeListComponent extends CdForm implements OnInit, OnCh
     this.errorMessage = '';
     this.selectedName = this.selection.first().name;
     this.modalService.show(CriticalConfirmationModalComponent, {
-      actionDescription: 'Remove',
+      impact: DeletionImpact.high,
+      actionDescription: 'remove',
       itemNames: [this.selectedName],
       itemDescription: 'Subvolume',
       childFormGroup: this.removeForm,
index 0087ffd66cd5ef875ae3bb8d7b3d05c063d3071a..770fbf2fe5225daebe5814544428e9d19d6e5f23 100644 (file)
@@ -27,6 +27,7 @@ import { Validators } from '@angular/forms';
 import { CdValidators } from '~/app/shared/forms/cd-validators';
 import { ModalCdsService } from '~/app/shared/services/modal-cds.service';
 import { DEFAULT_SUBVOLUME_GROUP } from '~/app/shared/constants/cephfs.constant';
+import { DeletionImpact } from '~/app/shared/enum/critical-confirmation-modal-impact.enum';
 
 @Component({
   selector: 'cd-cephfs-subvolume-snapshots-list',
@@ -228,7 +229,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: () =>
index c26d24177fd1bbe93ba0a431c4b38ed490a27f67..702c90e3c5c877bebf97f8d1aafbfded1490b762 100644 (file)
@@ -34,6 +34,7 @@ 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 { ModalCdsService } from '~/app/shared/services/modal-cds.service';
+import { DeletionImpact } from '~/app/shared/enum/critical-confirmation-modal-impact.enum';
 
 const BASE_URL = 'hosts';
 
@@ -470,6 +471,7 @@ export class HostsComponent extends ListWithDetails implements OnDestroy, OnInit
   deleteAction() {
     const hostname = this.selection.first().hostname;
     this.modalRef = this.cdsModalService.show(CriticalConfirmationModalComponent, {
+      impact: DeletionImpact.high,
       itemDescription: 'Host',
       itemNames: [hostname],
       actionDescription: 'remove',
index a56877512f99ae90b6db44b006e22a845b99c8df..122084410f3017ededca78aa86cb0bc71345dd11 100644 (file)
@@ -8,7 +8,7 @@
       <cd-table [data]="osds"
                 (fetchData)="getOsdList($event)"
                 [columns]="columns"
-                selectionType="multiClick"
+                selectionType="single"
                 [hasDetails]="true"
                 (setExpandedRow)="setExpandedRow($event)"
                 (updateSelection)="updateSelection($event)"
index 91cb0193f3cced88899675ae31e34aa60318443c..19e3a6d2aba1201284d43c18ccf98bc67c9c87f2 100644 (file)
@@ -41,6 +41,7 @@ import { OsdScrubModalComponent } from '../osd-scrub-modal/osd-scrub-modal.compo
 import { ModalCdsService } from '~/app/shared/services/modal-cds.service';
 import { CdTableFetchDataContext } from '~/app/shared/models/cd-table-fetch-data-context';
 import { Osd } from '~/app/shared/models/osd.model';
+import { DeletionImpact } from '~/app/shared/enum/critical-confirmation-modal-impact.enum';
 
 const BASE_URL = 'osd';
 
@@ -581,7 +582,10 @@ export class OsdListComponent extends ListWithDetails implements OnInit {
     childFormGroupTemplate?: TemplateRef<any>
   ): void {
     check(this.getSelectedOsdIds()).subscribe((result) => {
+      const osdIds = this.getSelectedOsdIds();
       this.cdsModalService.show(CriticalConfirmationModalComponent, {
+        impact: DeletionImpact.high,
+        itemNames: osdIds,
         actionDescription: actionDescription,
         itemDescription: itemDescription,
         bodyTemplate: this.criticalConfirmationTpl,
@@ -592,7 +596,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,
index a07dcfcdd35345285d6b802e514f1b8a10d46fec..000cda887ef2967c0039686183aefe1ebfa3f4ad 100644 (file)
@@ -30,6 +30,7 @@ import { ServiceFormComponent } from './service-form/service-form.component';
 import { SettingsService } from '~/app/shared/api/settings.service';
 import { ModalCdsService } from '~/app/shared/services/modal-cds.service';
 import { CellTemplate } from '~/app/shared/enum/cell-template.enum';
+import { DeletionImpact } from '~/app/shared/enum/critical-confirmation-modal-impact.enum';
 
 const BASE_URL = 'services';
 
@@ -276,6 +277,7 @@ export class ServicesComponent extends ListWithDetails implements OnChanges, OnI
   deleteAction() {
     const service = this.selection.first();
     this.cdsModalService.show(CriticalConfirmationModalComponent, {
+      impact: DeletionImpact.high,
       itemDescription: $localize`Service`,
       itemNames: [service.service_name],
       actionDescription: 'delete',
index 29488ed8613d75002d0709c90e83d8e7e419176f..d6dc5007e21e7be9262c7fd1c5e2ea50e20ff067 100644 (file)
@@ -25,6 +25,7 @@ import { TaskWrapperService } from '~/app/shared/services/task-wrapper.service';
 import { getFsalFromRoute, getPathfromFsal } from '../utils';
 import { SUPPORTED_FSAL } from '../models/nfs.fsal';
 import { ModalCdsService } from '~/app/shared/services/modal-cds.service';
+import { DeletionImpact } from '~/app/shared/enum/critical-confirmation-modal-impact.enum';
 
 export enum RgwExportType {
   BUCKET = 'bucket',
@@ -212,6 +213,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: () =>
index 19e875f5998e3d6c5264fddb8af7768854e44fef..dddfbdd6385fb927dbc38bad4845f32adec9e9fd 100644 (file)
@@ -30,6 +30,7 @@ import { URLBuilderService } from '~/app/shared/services/url-builder.service';
 import { Pool } from '../pool';
 import { PoolStat, PoolStats } from '../pool-stat';
 import { ModalCdsService } from '~/app/shared/services/modal-cds.service';
+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: () =>
index 8012547a7819ecff31ae34b875fe22cb64ae2ccf..07feaca26245ce0373f8bc630e80a482ca04bc86 100644 (file)
@@ -3,7 +3,7 @@
           [data]="buckets"
           [columns]="columns"
           columnMode="flex"
-          selectionType="multiClick"
+          selectionType="single"
           [hasDetails]="true"
           (setExpandedRow)="setExpandedRow($event)"
           (updateSelection)="updateSelection($event)"
index b74002191206736fcf78c187bdc7b4bd72e3670f..b2fa1b4a5e520f5e36b53d1dd477ec464111ee30 100644 (file)
@@ -23,6 +23,7 @@ import { ModalCdsService } from '~/app/shared/services/modal-cds.service';
 import { TaskWrapperService } from '~/app/shared/services/task-wrapper.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';
 
@@ -126,9 +127,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();
@@ -156,7 +155,8 @@ 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`,
+      itemDescription: $localize`bucket`,
+      impact: DeletionImpact.high,
       itemNames: itemNames,
       bodyTemplate: this.deleteTpl,
       submitActionObservable: () => {
index 8c1954a37ba35f7c0088565e2309bf72d8c5045d..0bb5726726242df57a002e7e349b505c56aac379 100644 (file)
@@ -5,7 +5,7 @@
           [data]="users"
           [columns]="columns"
           columnMode="flex"
-          selectionType="multiClick"
+          selectionType="single"
           [hasDetails]="true"
           (setExpandedRow)="setExpandedRow($event)"
           (updateSelection)="updateSelection($event)"
index 43704df333c85a1f4c3c297e0b796c0f8348325f..d7d74695864ab9678ba26b2fd175a2b08daecb02 100644 (file)
@@ -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<any> => {
index 7d5c6a5ee77b841f010e04d18f11f72a6a0512bf..a414b6a154a03df5681658c120ec7e0cb1274075 100755 (executable)
@@ -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)
index 90e3a1d2be8b20fdac20adfa0e4564b652728b24..3ae4b339da3b1b50279db8ae71770c66bbc50edf 100644 (file)
@@ -5,11 +5,11 @@
     <ng-container *ngTemplateOutlet="deletionHeading"></ng-container>
   </cds-modal-header>
 
-    <section cdsModalContent>
-      <form name="deletionForm"
-            #formDir="ngForm"
-            [formGroup]="deletionForm"
-            novalidate>
+  <section cdsModalContent>
+    <form name="deletionForm"
+          #formDir="ngForm"
+          [formGroup]="deletionForm"
+          novalidate>
       <cd-alert-panel *ngIf="infoMessage"
                       type="info"
                       spacingClass="mb-3"
         </ng-template>
         <ng-container *ngTemplateOutlet="childFormGroupTemplate; context:{form:deletionForm}"></ng-container>
         <div class="form-item">
-          <cds-checkbox id="confirmation"
-                        name="confirmation"
-                        formControlName="confirmation"
-                        autofocus
-                        [required]="true"
-                        modal-primary-focus
-                        i18n>Yes, I am sure.</cds-checkbox>
+          <ng-container *ngIf="impact == impactEnum.normal; else highImpactDeletion">
+            <cds-checkbox id="confirmation"
+                          name="confirmation"
+                          formControlName="confirmation"
+                          autofocus
+                          [required]="true"
+                          modal-primary-focus
+                          i18n>Yes, I am sure.</cds-checkbox>
+          </ng-container>
+          <ng-template #highImpactDeletion>
+            <cds-text-label label="Resource Name"
+                            for="resource_name"
+                            cdRequiredField="Resource Name"
+                            [invalid]="!deletionForm.controls.confirmInput.valid && deletionForm.controls.confirmInput.dirty"
+                            [invalidText]="ResourceError"
+                            i18n>Resource Name
+              <input cdsText
+                     type="text"
+                     placeholder="Enter resource name to delete"
+                     id="resource_name"
+                     formControlName="confirmInput"/>
+            </cds-text-label>
+            <ng-template #ResourceError>
+              <span *ngIf="deletionForm.showError('confirmInput', formDir, 'required')"
+                    class="invalid-feedback">
+                <ng-container i18n>This field is required.</ng-container>
+              </span>
+              <span *ngIf="deletionForm.showError('confirmInput', formDir, 'matchResource')"
+                    class="invalid-feedback">
+                <ng-container i18n>Enter the correct resource name.</ng-container>
+              </span>
+            </ng-template>
+          </ng-template>
         </div>
       </div>
     </form>
index 2e0706786e38f446c7ce377c80401155ac933f32..814f18a5dd254624cee7ad82f86c3ef290ffdf82 100644 (file)
@@ -11,6 +11,7 @@ import { LoadingPanelComponent } from '../loading-panel/loading-panel.component'
 import { CriticalConfirmationModalComponent } from './critical-confirmation-modal.component';
 import { CheckboxModule, ModalService, PlaceholderService } from 'carbon-components-angular';
 import { ModalCdsService } from '../../services/modal-cds.service';
+import { DeletionImpact } from '../../enum/critical-confirmation-modal-impact.enum';
 
 @NgModule({})
 export class MockModule {}
@@ -143,8 +144,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,16 +168,55 @@ 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(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', <NgForm>{ 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', () => {
index 7230c7707bd300f164fb6490533c8363fe045c22..5dc45f7fc8b369753b0899545e43a4714adb48db 100644 (file)
@@ -1,11 +1,12 @@
 import { Component, Inject, OnInit, Optional, TemplateRef, ViewChild } from '@angular/core';
-import { UntypedFormControl, Validators } from '@angular/forms';
-
+import { UntypedFormControl, AbstractControl, ValidationErrors, Validators } from '@angular/forms';
 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',
@@ -16,11 +17,12 @@ export class CriticalConfirmationModalComponent extends BaseModal implements OnI
   @ViewChild(SubmitButtonComponent, { static: true })
   submitButton: SubmitButtonComponent;
   deletionForm: CdFormGroup;
-
+  impactEnum = DeletionImpact;
   childFormGroup: CdFormGroup;
   childFormGroupTemplate: TemplateRef<any>;
 
   constructor(
+    @Optional() @Inject('impact') public impact: DeletionImpact,
     @Optional() @Inject('itemDescription') public itemDescription: 'entry',
     @Optional() @Inject('itemNames') public itemNames: string[],
     @Optional() @Inject('actionDescription') public actionDescription = 'delete',
@@ -38,11 +40,28 @@ export class CriticalConfirmationModalComponent extends BaseModal implements OnI
   ) {
     super();
     this.actionDescription = actionDescription || 'delete';
+    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;
@@ -53,6 +72,13 @@ export class CriticalConfirmationModalComponent extends BaseModal implements OnI
     }
   }
 
+  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 (file)
index 0000000..045d6f5
--- /dev/null
@@ -0,0 +1,4 @@
+export enum DeletionImpact {
+  normal = 'normal',
+  high = 'high' // NOTE: User should be able to select only single resource while deleting
+}
index 14b1df5b33474a384b6c85835764acdb420c575b..05f7c460712ab98647c8bab350e42a62ba790f6d 100644 (file)
@@ -323,9 +323,7 @@ export class TaskMessageService {
     ),
     // RGW operations
     'rgw/bucket/delete': this.newTaskMessage(this.commonOperations.delete, (metadata) => {
-      return $localize`${
-        metadata.bucket_names.length > 1 ? 'selected buckets' : metadata.bucket_names[0]
-      }`;
+      return $localize`${metadata.bucket_names[0]}`;
     }),
     'rgw/accounts': this.newTaskMessage(this.commonOperations.delete, (metadata) => {
       return $localize`${`account '${metadata.account_names[0]}'`}`;