feat(05-11): add adminDeleteUser API function + inline delete confirmation panel
- Export adminDeleteUser(id, adminPassword) from client.js — sends JSON body to DELETE /api/admin/users/{id}
- AdminUsersTab: add confirmDelete, deletePassword, deleteError state refs
- AdminUsersTab: add startDelete, cancelDelete, confirmDoDelete functions (mutually exclusive with deactivate panel)
- AdminUsersTab: Delete button added to active and deactivated user rows
- AdminUsersTab: inline password confirmation panel with Argon2 verification via backend
This commit is contained in:
@@ -269,6 +269,14 @@ export function adminUpdateAiConfig(id, provider, model) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function adminDeleteUser(id, adminPassword) {
|
||||||
|
return request(`/api/admin/users/${id}`, {
|
||||||
|
method: 'DELETE',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({ admin_password: adminPassword }),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// ── Folders ───────────────────────────────────────────────────────────────────
|
// ── Folders ───────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
export function listFolders(parentId = null) {
|
export function listFolders(parentId = null) {
|
||||||
|
|||||||
@@ -171,6 +171,43 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Inline delete confirmation panel -->
|
||||||
|
<div v-else-if="confirmDelete === user.id" class="space-y-2">
|
||||||
|
<p class="text-xs text-red-700 font-semibold">
|
||||||
|
Permanently delete <span class="font-bold">{{ user.email }}</span>?
|
||||||
|
This will erase all their documents, cloud connections, and quota data. This cannot be undone.
|
||||||
|
</p>
|
||||||
|
<div>
|
||||||
|
<label class="block text-xs text-gray-700 mb-1 font-semibold">Your admin password to confirm</label>
|
||||||
|
<input
|
||||||
|
v-model="deletePassword"
|
||||||
|
type="password"
|
||||||
|
autocomplete="current-password"
|
||||||
|
placeholder="Admin password"
|
||||||
|
class="block w-full rounded-lg px-2 py-1.5 text-xs border border-red-300 focus:outline-none focus:ring-2 focus:ring-red-500 focus:border-red-500 transition-colors"
|
||||||
|
@keydown.enter.prevent="confirmDoDelete(user.id)"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<p v-if="deleteError" class="text-xs text-red-600">{{ deleteError }}</p>
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<button
|
||||||
|
@click="confirmDoDelete(user.id)"
|
||||||
|
:disabled="pendingAction[user.id] || !deletePassword"
|
||||||
|
class="text-red-700 hover:text-red-800 text-sm font-semibold disabled:opacity-50"
|
||||||
|
>
|
||||||
|
<span v-if="pendingAction[user.id]" class="flex items-center gap-1">
|
||||||
|
<span class="animate-spin rounded-full border-2 border-current border-t-transparent w-3 h-3"></span>
|
||||||
|
Deleting…
|
||||||
|
</span>
|
||||||
|
<span v-else>Delete permanently</span>
|
||||||
|
</button>
|
||||||
|
<span class="text-gray-300">·</span>
|
||||||
|
<button @click="cancelDelete" class="text-gray-500 hover:text-gray-700 text-sm">
|
||||||
|
Cancel
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Normal actions -->
|
<!-- Normal actions -->
|
||||||
<div v-else class="flex items-center gap-2">
|
<div v-else class="flex items-center gap-2">
|
||||||
<span v-if="pendingAction[user.id]" class="flex items-center gap-1 text-gray-400 text-sm">
|
<span v-if="pendingAction[user.id]" class="flex items-center gap-1 text-gray-400 text-sm">
|
||||||
@@ -191,6 +228,13 @@
|
|||||||
>
|
>
|
||||||
Deactivate
|
Deactivate
|
||||||
</button>
|
</button>
|
||||||
|
<span class="text-gray-300">·</span>
|
||||||
|
<button
|
||||||
|
@click="startDelete(user.id)"
|
||||||
|
class="text-red-800 hover:text-red-900 text-sm font-semibold"
|
||||||
|
>
|
||||||
|
Delete
|
||||||
|
</button>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template v-else>
|
<template v-else>
|
||||||
@@ -200,6 +244,13 @@
|
|||||||
>
|
>
|
||||||
Reactivate
|
Reactivate
|
||||||
</button>
|
</button>
|
||||||
|
<span class="text-gray-300">·</span>
|
||||||
|
<button
|
||||||
|
@click="startDelete(user.id)"
|
||||||
|
class="text-red-800 hover:text-red-900 text-sm font-semibold"
|
||||||
|
>
|
||||||
|
Delete
|
||||||
|
</button>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
@@ -221,6 +272,9 @@ const users = ref([])
|
|||||||
const loading = ref(false)
|
const loading = ref(false)
|
||||||
const showCreateForm = ref(false)
|
const showCreateForm = ref(false)
|
||||||
const confirmDeactivate = ref(null)
|
const confirmDeactivate = ref(null)
|
||||||
|
const confirmDelete = ref(null)
|
||||||
|
const deletePassword = ref('')
|
||||||
|
const deleteError = ref(null)
|
||||||
const pendingAction = reactive({})
|
const pendingAction = reactive({})
|
||||||
const actionError = ref(null)
|
const actionError = ref(null)
|
||||||
const creating = ref(false)
|
const creating = ref(false)
|
||||||
@@ -308,6 +362,36 @@ async function submitCreate() {
|
|||||||
|
|
||||||
function startDeactivate(id) {
|
function startDeactivate(id) {
|
||||||
confirmDeactivate.value = id
|
confirmDeactivate.value = id
|
||||||
|
confirmDelete.value = null
|
||||||
|
deletePassword.value = ''
|
||||||
|
deleteError.value = null
|
||||||
|
}
|
||||||
|
|
||||||
|
function startDelete(id) {
|
||||||
|
confirmDelete.value = id
|
||||||
|
deletePassword.value = ''
|
||||||
|
deleteError.value = null
|
||||||
|
confirmDeactivate.value = null
|
||||||
|
}
|
||||||
|
|
||||||
|
function cancelDelete() {
|
||||||
|
confirmDelete.value = null
|
||||||
|
deletePassword.value = ''
|
||||||
|
deleteError.value = null
|
||||||
|
}
|
||||||
|
|
||||||
|
async function confirmDoDelete(id) {
|
||||||
|
pendingAction[id] = true
|
||||||
|
deleteError.value = null
|
||||||
|
try {
|
||||||
|
await api.adminDeleteUser(id, deletePassword.value)
|
||||||
|
users.value = users.value.filter(u => u.id !== id)
|
||||||
|
cancelDelete()
|
||||||
|
} catch (e) {
|
||||||
|
deleteError.value = e.message
|
||||||
|
} finally {
|
||||||
|
delete pendingAction[id]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function confirmDoDeactivate(id) {
|
async function confirmDoDeactivate(id) {
|
||||||
|
|||||||
Reference in New Issue
Block a user