feat(inbounds): add multi-select and bulk delete

Mirror the clients page: checkbox selection on the desktop table and on
mobile cards, with a danger Delete button in the toolbar that removes all
selected inbounds in one call.

Backend adds POST /panel/api/inbounds/bulkDel, which loops the existing
DelInbound per id (xray restarts at most once) and returns {deleted,
skipped}. Frontend shows a confirm modal plus a result toast, clears the
selection on success, adds bulk-delete i18n keys across all 13 languages,
and documents the endpoint in the in-panel API docs.
This commit is contained in:
MHSanaei
2026-05-31 00:29:24 +02:00
parent 6bb5a3b56b
commit cf50952921
21 changed files with 315 additions and 4 deletions
@@ -357,6 +357,36 @@ export default function InboundsPage() {
});
}, [modal, refresh, t]);
const confirmBulkDelete = useCallback((ids: number[]) => new Promise<boolean>((resolve) => {
if (ids.length === 0) {
resolve(false);
return;
}
modal.confirm({
title: t('pages.inbounds.bulkDeleteConfirmTitle', { count: ids.length }),
content: t('pages.inbounds.bulkDeleteConfirmContent'),
okText: t('delete'),
okType: 'danger',
cancelText: t('cancel'),
onOk: async () => {
const msg = await HttpUtil.post('/panel/api/inbounds/bulkDel', { ids }, { headers: { 'Content-Type': 'application/json' } });
const obj = (msg?.obj ?? {}) as { deleted?: number; skipped?: { id: number; reason: string }[] };
const ok = obj.deleted ?? 0;
const skipped = obj.skipped ?? [];
if (msg?.success && skipped.length === 0) {
messageApi.success(t('pages.inbounds.toasts.bulkDeleted', { count: ok }));
} else {
const firstError = skipped[0]?.reason ?? msg?.msg ?? '';
const base = t('pages.inbounds.toasts.bulkDeletedMixed', { ok, failed: skipped.length });
messageApi.warning(firstError ? `${base}${firstError}` : base);
}
await refresh();
resolve(true);
},
onCancel: () => resolve(false),
});
}), [modal, refresh, t, messageApi]);
const confirmResetTraffic = useCallback((dbInbound: DBInbound) => {
modal.confirm({
title: t('pages.inbounds.resetConfirmTitle', { remark: dbInbound.remark }),
@@ -567,6 +597,7 @@ export default function InboundsPage() {
onAddInbound={onAddInbound}
onGeneralAction={onGeneralAction}
onRowAction={({ key, dbInbound }) => onRowAction({ key, dbInbound: dbInbound as unknown as DBInbound })}
onBulkDelete={confirmBulkDelete}
/>
</Col>
</Row>