From d8aedcdde42926d0b4f341ddb21ac62aeef8d6cc Mon Sep 17 00:00:00 2001 From: MHSanaei Date: Mon, 11 May 2026 10:22:52 +0200 Subject: [PATCH] fix(inbounds): bulk-delete keeps last client to satisfy backend constraint MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit DelClient rejects the removal that would leave an inbound with zero clients (the constraint exists because Xray protocols need at least one client to keep the inbound functional). The bulk-delete flow fired one DelClient call per picked client in a loop, so picking every client meant the final iteration always errored out with "no client remained in Inbound" and surfaced as a red toast even though N-1 deletions had already gone through. Now confirmBulkDelete detects the "all selected" case up front, drops the last client from the request, and surfaces the partial operation in the confirm dialog ("N-1 / N — last selected will remain. Delete the inbound to remove all."). The pre-existing single-row delete path and partial-selection bulk delete paths are untouched. If the only client in the inbound is selected, a Modal.warning explains the constraint instead of asking for confirm. --- .../src/pages/inbounds/ClientRowTable.vue | 22 ++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/frontend/src/pages/inbounds/ClientRowTable.vue b/frontend/src/pages/inbounds/ClientRowTable.vue index 67b19966..f6119323 100644 --- a/frontend/src/pages/inbounds/ClientRowTable.vue +++ b/frontend/src/pages/inbounds/ClientRowTable.vue @@ -219,14 +219,30 @@ watch(clients, (list) => { function confirmBulkDelete() { const picked = clients.value.filter((c) => selected.value.has(rowKey(c))); if (picked.length === 0) return; + + const total = clients.value.length; + const keepLast = picked.length === total; + const toDelete = keepLast ? picked.slice(0, -1) : picked; + + if (toDelete.length === 0) { + Modal.warning({ + title: t('pages.inbounds.deleteClient'), + content: 'Inbound must keep at least one client — delete the inbound to remove all.', + okText: t('confirm'), + }); + return; + } + Modal.confirm({ - title: t('pages.inbounds.deleteClient') + ` — ${picked.length}`, - content: t('pages.inbounds.deleteClientContent'), + title: `${t('pages.inbounds.deleteClient')} — ${toDelete.length}${keepLast ? ` / ${total}` : ''}`, + content: keepLast + ? 'Inbound must keep at least one client — the last selected will remain. Delete the inbound to remove all.' + : t('pages.inbounds.deleteClientContent'), okText: t('delete'), okType: 'danger', cancelText: t('cancel'), onOk: () => { - emit('delete-clients', { dbInbound: props.dbInbound, clients: picked }); + emit('delete-clients', { dbInbound: props.dbInbound, clients: toDelete }); clearSelection(); }, });