This is an automated email from the ASF dual-hosted git repository.

harikrishna-patnala pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/cloudstack.git


The following commit(s) were added to refs/heads/main by this push:
     new 72b99a3f8ce Make resource deletion safer with name confirmation 
(#13104)
72b99a3f8ce is described below

commit 72b99a3f8ce2265d51dd42526afaf1b399f378f3
Author: Manoj Kumar <[email protected]>
AuthorDate: Fri May 8 10:56:50 2026 +0530

    Make resource deletion safer with name confirmation (#13104)
    
    * enable double confirmation in delete flow for resource
    
    * address copilot comments
---
 ui/public/locales/en.json        |  1 +
 ui/src/config/section/project.js |  1 +
 ui/src/views/AutogenView.vue     | 45 +++++++++++++++++++++++++---------------
 3 files changed, 30 insertions(+), 17 deletions(-)

diff --git a/ui/public/locales/en.json b/ui/public/locales/en.json
index f0a6ad3c79b..1187b3e62b4 100644
--- a/ui/public/locales/en.json
+++ b/ui/public/locales/en.json
@@ -795,6 +795,7 @@
 "label.delete.ciscoasa1000v": "Delete CiscoASA1000v",
 "label.delete.ciscovnmc.resource": "Delete CiscoVNMC resource",
 "label.delete.condition": "Delete condition",
+"label.delete.confirmation": "Enter the exact resource name to proceed with 
deletion",
 "label.delete.custom.action": "Delete Custom Action",
 "label.delete.dedicated.vlan.range": "Deleted dedicated VLAN/VNI range.",
 "label.delete.domain": "Delete domain",
diff --git a/ui/src/config/section/project.js b/ui/src/config/section/project.js
index 5a1f5f71c81..c08cb8dce4d 100644
--- a/ui/src/config/section/project.js
+++ b/ui/src/config/section/project.js
@@ -162,6 +162,7 @@ export default {
       },
       groupAction: true,
       popup: true,
+      requireNameConfirmation: true,
       groupMap: (selection, values) => { return selection.map(x => { return { 
id: x, cleanup: values.cleanup || null } }) },
       args: (record, store) => {
         const fields = []
diff --git a/ui/src/views/AutogenView.vue b/ui/src/views/AutogenView.vue
index 066226980c5..985d12bd718 100644
--- a/ui/src/views/AutogenView.vue
+++ b/ui/src/views/AutogenView.vue
@@ -195,8 +195,6 @@
         :footer="null"
         style="top: 20px;"
         :width="modalWidth"
-        :ok-button-props="getOkProps()"
-        :cancel-button-props="getCancelProps()"
         :confirmLoading="actionLoading"
         @cancel="cancelAction"
         centered
@@ -270,8 +268,18 @@
               </a-table>
             </div>
             <br v-if="currentAction.paramFields.length > 0" />
-          </span>
-          <a-form
+             </span>
+           <div v-if="currentAction.requireNameConfirmation && 
!(currentAction.groupAction && selectedRowKeys.length > 0)" 
style="margin-bottom: 5px">
+            <a-form-item>
+                <a-input v-model:value="actionConfirmText" 
:placeholder="resource.name" />
+            </a-form-item>
+            <a-alert type="info">
+              <template #message>
+                <div v-html="$t('label.delete.confirmation')"></div>
+              </template>
+            </a-alert>
+           </div>
+           <a-form
             :ref="formRef"
             :model="form"
             :rules="rules"
@@ -531,6 +539,7 @@
                 type="primary"
                 @click="handleSubmit"
                 ref="submit"
+                :disabled="isSubmitDisabled"
               >{{ $t('label.ok') }}</a-button>
             </div>
           </a-form>
@@ -691,6 +700,7 @@ export default {
       confirmDirty: false,
       firstIndex: 0,
       modalWidth: '30vw',
+      actionConfirmText: '',
       promises: []
     }
   },
@@ -898,6 +908,12 @@ export default {
         return 'active'
       }
       return 'self'
+    },
+    isSubmitDisabled () {
+      if (this.currentAction?.requireNameConfirmation && 
!(this.currentAction.groupAction && this.selectedRowKeys.length > 0)) {
+        return this.actionConfirmText.trim() !== this.resource?.name?.trim()
+      }
+      return false
     }
   },
   methods: {
@@ -907,19 +923,6 @@ export default {
       }
       return 'inline-flex'
     },
-    getOkProps () {
-      if (this.selectedRowKeys.length > 0 && this.currentAction?.groupAction) {
-      } else {
-        return { props: { type: 'primary' } }
-      }
-    },
-    getCancelProps () {
-      if (this.selectedRowKeys.length > 0 && this.currentAction?.groupAction) {
-        return { props: { type: 'primary' } }
-      } else {
-        return { props: { type: 'default' } }
-      }
-    },
     switchProject (projectId) {
       if (!projectId || !projectId.length || projectId.length !== 36) {
         return
@@ -1308,6 +1311,7 @@ export default {
       this.actionLoading = false
       this.showAction = false
       this.currentAction = {}
+      this.actionConfirmText = ''
     },
     cancelAction () {
       eventBus.emit('action-closing', { action: this.currentAction })
@@ -1365,6 +1369,7 @@ export default {
       this.currentAction = action
       this.currentAction.params = 
store.getters.apis[this.currentAction.api].params
       this.resource = action.resource
+      this.actionConfirmText = ''
       this.$emit('change-resource', this.resource)
       var paramFields = this.currentAction.params
       paramFields.sort(function (a, b) {
@@ -1647,6 +1652,12 @@ export default {
     },
     handleSubmit (e) {
       if (this.actionLoading) return
+
+      if (this.currentAction?.requireNameConfirmation && 
!(this.currentAction.groupAction && this.selectedRowKeys.length > 0)) {
+        if (this.actionConfirmText.trim() !== this.resource?.name?.trim()) {
+          return
+        }
+      }
       this.promises = []
       if (!this.dataView && this.currentAction.groupAction && 
this.selectedRowKeys.length > 0) {
         if (this.selectedRowKeys.length > 0) {

Reply via email to