Skip to content

Commit 1f8442e

Browse files
Extra checks in UI when deleting accounts (#10760)
Co-authored-by: Bernardo De Marco Gonçalves <[email protected]>
1 parent d697cff commit 1f8442e

File tree

4 files changed

+260
-4
lines changed

4 files changed

+260
-4
lines changed

ui/public/locales/en.json

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
{
2+
"message.delete.account.not.disabled": "Please disable the account before attempting to delete it.",
23
"alert.service.domainrouter": "Domain router",
34
"error.dedicate.cluster.failed": "Failed to dedicate cluster.",
45
"error.dedicate.host.failed": "Failed to dedicate host.",
@@ -855,6 +856,7 @@
855856
"label.endipv6": "IPv6 end IP",
856857
"label.endpoint": "Endpoint",
857858
"label.endport": "End port",
859+
"label.enter.account.name": "Enter the account name",
858860
"label.enter.code": "Enter 2FA code to verify",
859861
"label.enter.static.pin": "Enter static PIN to verify",
860862
"label.enter.token": "Enter token",
@@ -2720,7 +2722,11 @@
27202722
"message.dedicating.host": "Dedicating host...",
27212723
"message.dedicating.pod": "Dedicating pod...",
27222724
"message.dedicating.zone": "Dedicating zone...",
2723-
"message.delete.account": "Please confirm that you want to delete this Account.",
2725+
"message.delete.account.confirm": "Please confirm that you want to delete this account by entering the name of the account below.",
2726+
"message.delete.account.failed": "Delete account failed",
2727+
"message.delete.account.processing": "Deleting account",
2728+
"message.delete.account.success": "Successfully deleted account",
2729+
"message.delete.account.warning": "Deleting this account will delete all of the instances, volumes and snapshots associated with the account.",
27242730
"message.delete.acl.processing": "Removing ACL rule...",
27252731
"message.delete.acl.rule": "Remove ACL rule",
27262732
"message.delete.acl.rule.failed": "Failed to remove ACL rule.",
@@ -2807,6 +2813,7 @@
28072813
"message.enabling.security.group.provider": "Enabling security group provider",
28082814
"message.enter.valid.nic.ip": "Please enter a valid IP address for NIC",
28092815
"message.error.access.key": "Please enter access key.",
2816+
"message.error.account.delete.name.mismatch": "Name entered doesn't match the account name.",
28102817
"message.error.add.guest.network": "Either IPv4 fields or IPv6 fields need to be filled when adding a guest Network.",
28112818
"message.error.add.interface.static.route": "Adding interface Static Route failed",
28122819
"message.error.add.logical.router": "Adding Logical Router failed",

ui/src/config/section/account.js

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -225,11 +225,10 @@ export default {
225225
message: 'message.delete.account',
226226
dataView: true,
227227
disabled: (record, store) => {
228-
return record.id !== 'undefined' && store.userInfo.accountid === record.id
228+
return store.userInfo.accountid === record?.id
229229
},
230-
groupAction: true,
231230
popup: true,
232-
groupMap: (selection) => { return selection.map(x => { return { id: x } }) }
231+
component: shallowRef(defineAsyncComponent(() => import('@/views/iam/DeleteAccountWrapper.vue')))
233232
}
234233
]
235234
}

ui/src/views/iam/DeleteAccount.vue

Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
// Licensed to the Apache Software Foundation (ASF) under one
2+
// or more contributor license agreements. See the NOTICE file
3+
// distributed with this work for additional information
4+
// regarding copyright ownership. The ASF licenses this file
5+
// to you under the Apache License, Version 2.0 (the
6+
// "License"); you may not use this file except in compliance
7+
// with the License. You may obtain a copy of the License at
8+
//
9+
// http://www.apache.org/licenses/LICENSE-2.0
10+
//
11+
// Unless required by applicable law or agreed to in writing,
12+
// software distributed under the License is distributed on an
13+
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
// KIND, either express or implied. See the License for the
15+
// specific language governing permissions and limitations
16+
// under the License.
17+
18+
<template>
19+
<a-form
20+
class="form"
21+
:ref="formRef"
22+
:model="form"
23+
:rules="rules"
24+
layout="vertical"
25+
@finish="handleSubmit"
26+
v-ctrl-enter="handleSubmit"
27+
>
28+
<div style="margin-bottom: 10px">
29+
<a-alert type="warning">
30+
<template #message>
31+
<div v-html="$t('message.delete.account.warning')"></div>
32+
</template>
33+
</a-alert>
34+
</div>
35+
<div style="margin-bottom: 10px">
36+
<a-alert>
37+
<template #message>
38+
<div v-html="$t('message.delete.account.confirm')"></div>
39+
</template>
40+
</a-alert>
41+
</div>
42+
<a-form-item name="name" ref="name">
43+
<a-input
44+
v-model:value="form.name"
45+
:placeholder="$t('label.enter.account.name')"
46+
style="width: 100%"/>
47+
</a-form-item>
48+
<p v-if="error" class="error">{{ error }}</p>
49+
<div :span="24" class="actions">
50+
<a-button @click="closeModal">{{ $t('label.cancel') }}</a-button>
51+
<a-button type="primary" ref="submit" @click="handleSubmit">{{ $t('label.ok') }}</a-button>
52+
</div>
53+
</a-form>
54+
</template>
55+
56+
<script>
57+
import { ref, reactive } from 'vue'
58+
import { api } from '@/api'
59+
60+
export default {
61+
name: 'DeleteAccount',
62+
props: {
63+
resource: {
64+
type: Object,
65+
required: true
66+
}
67+
},
68+
data () {
69+
return {
70+
error: '',
71+
isDeleting: false
72+
}
73+
},
74+
created () {
75+
this.initForm()
76+
},
77+
methods: {
78+
initForm () {
79+
this.formRef = ref()
80+
this.form = reactive({})
81+
this.rules = reactive({
82+
name: [{ required: true, message: this.$t('label.required') }]
83+
})
84+
},
85+
closeModal () {
86+
this.$emit('close-action')
87+
},
88+
handleSubmit (e) {
89+
e.preventDefault()
90+
if (this.isDeleting) return // Prevent double submission
91+
this.formRef.value.validate().then(async () => {
92+
if (this.form.name !== this.resource.name) {
93+
this.error = `${this.$t('message.error.account.delete.name.mismatch')}`
94+
return
95+
}
96+
if (this.hasActiveResources) {
97+
return
98+
}
99+
this.isDeleting = true
100+
// Store the account ID and name before we close the modal
101+
const accountId = this.resource.id
102+
const accountName = this.resource.name
103+
// Close the modal first
104+
this.closeModal()
105+
// Immediately navigate to the accounts page to avoid "unable to find account" errors
106+
this.$router.push({ path: '/account' })
107+
.then(() => {
108+
// After successful navigation, start the deletion job
109+
api('deleteAccount', {
110+
id: accountId
111+
}).then(response => {
112+
this.$pollJob({
113+
jobId: response.deleteaccountresponse.jobid,
114+
title: this.$t('label.action.delete.account'),
115+
description: accountId,
116+
successMessage: `${this.$t('message.delete.account.success')} - ${accountName}`,
117+
errorMessage: `${this.$t('message.delete.account.failed')} - ${accountName}`,
118+
loadingMessage: `${this.$t('message.delete.account.processing')} - ${accountName}`,
119+
catchMessage: this.$t('error.fetching.async.job.result')
120+
})
121+
}).catch(error => {
122+
this.$notifyError(error)
123+
this.isDeleting = false
124+
})
125+
})
126+
.catch(err => {
127+
console.error('Navigation failed:', err)
128+
this.isDeleting = false
129+
// If navigation fails, still try to delete the account
130+
// but don't navigate afterwards
131+
api('deleteAccount', {
132+
id: accountId
133+
}).then(response => {
134+
this.$pollJob({
135+
jobId: response.deleteaccountresponse.jobid,
136+
title: this.$t('label.action.delete.account'),
137+
description: accountId,
138+
successMessage: `${this.$t('message.delete.account.success')} - ${accountName}`,
139+
errorMessage: `${this.$t('message.delete.account.failed')} - ${accountName}`,
140+
loadingMessage: `${this.$t('message.delete.account.processing')} - ${accountName}`,
141+
catchMessage: this.$t('error.fetching.async.job.result')
142+
})
143+
}).catch(error => {
144+
this.$notifyError(error)
145+
})
146+
})
147+
}).catch((error) => {
148+
this.formRef.value.scrollToField(error.errorFields[0].name)
149+
})
150+
}
151+
}
152+
}
153+
</script>
154+
155+
<style lang="scss" scoped>
156+
.form {
157+
width: 80vw;
158+
@media (min-width: 500px) {
159+
width: 400px;
160+
}
161+
}
162+
.actions {
163+
display: flex;
164+
justify-content: flex-end;
165+
margin-top: 20px;
166+
button {
167+
&:not(:last-child) {
168+
margin-right: 10px;
169+
}
170+
}
171+
}
172+
.error {
173+
color: red;
174+
margin-top: 10px;
175+
}
176+
</style>
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
// Licensed to the Apache Software Foundation (ASF) under one
2+
// or more contributor license agreements. See the NOTICE file
3+
// distributed with this work for additional information
4+
// regarding copyright ownership. The ASF licenses this file
5+
// to you under the Apache License, Version 2.0 (the
6+
// "License"); you may not use this file except in compliance
7+
// with the License. You may obtain a copy of the License at
8+
//
9+
// http://www.apache.org/licenses/LICENSE-2.0
10+
//
11+
// Unless required by applicable law or agreed to in writing,
12+
// software distributed under the License is distributed on an
13+
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
// KIND, either express or implied. See the License for the
15+
// specific language governing permissions and limitations
16+
// under the License.
17+
18+
<template>
19+
<div>
20+
<!-- Show error message if account is not disabled -->
21+
<template v-if="resource.state !== 'disabled'">
22+
<div style="margin-bottom: 10px">
23+
<a-alert type="error">
24+
<template #message>
25+
<div>{{ $t('message.delete.account.not.disabled') }}</div>
26+
</template>
27+
</a-alert>
28+
</div>
29+
<div :span="24" class="actions">
30+
<a-button type="primary" @click="closeModal">{{ $t('label.ok') }}</a-button>
31+
</div>
32+
</template>
33+
<!-- Show delete form if account is disabled -->
34+
<delete-account
35+
v-else
36+
:resource="resource"
37+
@close-action="closeModal" />
38+
</div>
39+
</template>
40+
41+
<script>
42+
import DeleteAccount from './DeleteAccount.vue'
43+
44+
export default {
45+
name: 'DeleteAccountWrapper',
46+
components: {
47+
DeleteAccount
48+
},
49+
props: {
50+
resource: {
51+
type: Object,
52+
required: true
53+
}
54+
},
55+
methods: {
56+
closeModal () {
57+
this.$emit('close-action')
58+
}
59+
}
60+
}
61+
</script>
62+
63+
<style lang="scss" scoped>
64+
.actions {
65+
display: flex;
66+
justify-content: flex-end;
67+
margin-top: 20px;
68+
button {
69+
&:not(:last-child) {
70+
margin-right: 10px;
71+
}
72+
}
73+
}
74+
</style>

0 commit comments

Comments
 (0)