feat(clients): selective bulk attach + new bulk detach

Inbounds page:
- AttachClientsModal now shows a per-client selection table (email,
  comment, enabled tag) with search and a live "selected of total"
  counter; all clients are pre-selected so the old "attach all"
  workflow stays a single OK click.
- New DetachClientsModal on the inbound row menu lets you pick which
  clients to remove from that inbound (records are kept so they can be
  re-attached later; for full removal use Delete).

Clients page:
- New "Attach (N)" bulk-action button + BulkAttachInboundsModal that
  attaches selected clients to one or more multi-user inbounds.
- New "Detach (N)" bulk-action button + BulkDetachInboundsModal that
  removes selected clients from chosen inbounds; (email, inbound) pairs
  where the client isn't attached are silently skipped.

Backend adds POST /panel/api/clients/bulkDetach, wrapping the existing
Detach service for each email and reporting per-email
detached/skipped/errors. ClientRecord rows are kept on detach to match
the single-client endpoint; bulkDel remains the path for full removal.
This commit is contained in:
MHSanaei
2026-05-28 11:08:52 +02:00
parent a07b68894c
commit 72b68cce22
15 changed files with 809 additions and 13 deletions
+61
View File
@@ -2968,6 +2968,67 @@
}
}
},
"/panel/api/clients/bulkDetach": {
"post": {
"tags": [
"Clients"
],
"summary": "Mirror of bulkAttach: detach many existing clients from many inbounds in one call. For each email, intersects the client's current inbounds with the requested set and detaches from those only; (email, inbound) pairs where the client is not currently attached are silently no-ops. Emails not attached to any of the requested inbounds are reported under skipped. Client records are kept even if they become orphaned — use bulkDel for full removal. Returns per-email detached/skipped/errors lists and triggers a single Xray restart if any target inbound was running.",
"operationId": "post_panel_api_clients_bulkDetach",
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object"
},
"example": {
"emails": [
"alice",
"bob"
],
"inboundIds": [
7,
9
]
}
}
}
},
"responses": {
"200": {
"description": "Successful response",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"success": {
"type": "boolean"
},
"msg": {
"type": "string"
},
"obj": {}
}
},
"example": {
"success": true,
"obj": {
"detached": [
"alice",
"bob"
],
"skipped": [],
"errors": []
}
}
}
}
}
}
}
},
"/panel/api/clients/bulkResetTraffic": {
"post": {
"tags": [