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) {