feat(inbounds): bulk-select clients + UX polish

- ClientBulkModal: add `comment` and VLESS `reverseTag` fields so the
  bulk-add modal can set them on every generated client (matching the
  single-client form)
- ClientRowTable: add multi-select checkboxes (desktop + mobile) with a
  tri-state select-all and a sticky bulk-action bar; emits a new
  `delete-clients` event so the parent can wipe the picked clients in
  one go. Hidden entirely when the inbound has only one client (the
  last one must stay)
- ClientRowTable: new "Remained" column shows live remaining quota
  per client (∞ for unlimited, red when depleted)
- InboundInfoModal: Remained cell now shows the ∞ tag when the client
  has no totalGB limit, matching how Total Usage already renders it
- InboundsPage: add Online tag (+ per-bucket popovers listing client
  emails) to the summary card so it mirrors the per-inbound row, and
  wire an `onDeleteClients` handler that loops the existing single-
  delete endpoint then refreshes once
- InboundList: forward the `delete-clients` event; hide empty remarks
  on both the desktop table (custom #bodyCell) and the mobile card
- useInbounds: aggregate an `online` email list across all inbounds
  so the summary popover has data to render
This commit is contained in:
MHSanaei
2026-05-11 03:50:28 +02:00
parent e4900f1bd4
commit 6d732d8d32
6 changed files with 231 additions and 14 deletions
@@ -53,6 +53,7 @@ const form = reactive({
flow: '',
subId: '',
tgId: 0,
comment: '',
limitIp: 0,
totalGB: 0,
expiryTime: 0, // ms epoch; negative => delayed start days
@@ -85,6 +86,7 @@ watch(() => props.open, (next) => {
form.flow = '';
form.subId = '';
form.tgId = 0;
form.comment = '';
form.limitIp = 0;
form.totalGB = 0;
form.expiryTime = 0;
@@ -135,6 +137,7 @@ function buildClients() {
if (form.subId.length > 0) c.subId = form.subId;
c.tgId = form.tgId;
if (form.comment.length > 0) c.comment = form.comment;
c.security = form.security;
c.limitIp = form.limitIp;
// Use the clien's totalGB setter (ms epoch and bytes already handled
@@ -227,6 +230,10 @@ async function submit() {
<a-input-number v-model:value="form.tgId" :min="0" :style="{ width: '50%' }" />
</a-form-item>
<a-form-item :label="t('comment')">
<a-input v-model:value="form.comment" />
</a-form-item>
<a-form-item v-if="ipLimitEnable" :label="t('pages.inbounds.IPLimit')">
<a-input-number v-model:value="form.limitIp" :min="0" />
</a-form-item>